This is the mail archive of the docbook-apps@lists.oasis-open.org mailing list .


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]
Other format: [Raw text]

[docbook-apps] soft page break


[FOP users can skip this message since this
doesn't work in FOP]

I've been experimenting with a soft page break feature for
DocBook FO output.  Anyone who has implemented hard page
breaks (usually with processing  instructions) knows that
they can be an immediate fix, but  they can also create
maintenance headaches when you edit the document and change
the pagination.   Then you end up with page breaks where
they are not needed.

I borrowed the idea of a soft page break from the troff
typesetting system,  which uses the term "need". You put in
a processing instruction in your document that effectively
says "I need at least 2 inches left on the current page
to fit  the following material. If that much space is not
available on the page, then  break to the next page at
this point.  If there is enough space, don't break."

This kind of conditional page break is handy when the
normal "keeps" used in the  stylesheet are not sufficient,
either for technical reasons or for aesthetic  reasons.
For example, you may want to make sure a short introductory
paragraph  that precedes a code listing has at least a
few lines of code with it on the  page.  The para and the
programlisting are separate elements that normally would
not have a "keep".

This kind of page breaking is not perfect, because you need
to estimate how much  physical space is needed for the
content you want to keep together.  I typically  use it
after the first printout so I can take a ruler to it. But
since it isn't  wrapping elements, it can create keeps
of arbitrary size.  

I've implemented this feature using a block-container
of the desired height, followed by an empty block with
a negative space-before property that brings it back to
the top of the block-container.   If the block-container
doesn't fit on the page, it is forced to the next page,
and the empty block and the text that follows appear
on the next page.  If the block-container fits, then
the block-container is simply overwritten by the text
that follows the empty block, starting at the top of the
block-container. If done correctly, a block-container that
fits should have no net effect, but a block-container that
doesn't fit should force the page break.

In a text picture, it looks like this (this won't
look right if viewed with a proportional font):
 
+===================================+ <-- top of container 
|First line of text after the need  |     and empty block
|and some more text after the need  |   
+-----------------------------------+ 
 and even more text after the need
 that does not fit in the container.

It sounds easy, but there are several issues with how the
space-before and space-after properties combine in adjacent
blocks. Getting the "need" to not add or reduce spacing
between the visible blocks makes it more complicated.

I'd like to let people try this out before I commit to
the stylesheet.  Unfortunately, it does not work with
the current FOP processor, because FOP does not fully
implement block-container. I tried using an fo:table with
a specified row height, but FOP doesn't seem to break
the page properly for an empty row container.  But anyone
using XEP or Antenna House is welcome to try this out.

I've included a customization below that you can
xsl:include in your customization layer. Then you should
be able to add something like this to your document:

<?dbfo-need height="2in"?> 

You can't put the processing instruction inline. It must be
between elements that generate blocks of text, otherwise
you may get invalid XSL-FO. Here is an example of how to
use it:

<para>blah blah</para> 
<?dbfo-need  height="0.5in" ?> 
<para>The following code snippet illustrates 
the technique:</para> 
<programlisting>blah blah more 
and more etc. 

Note that the processing instruction name is "dbfo-need",
not "dbfo".  The default template for dbfo PIs is a
no-op. The processing of all dbfo PIs is triggered by the
template handling the PI's container element. This PI must
be processed on its own, so it has a different name.

The dbfo-need PI also accepts a second optional pseudo
attribute named 'space-before'. This is useful to manually
adjust the spacing when the stylesheet can't quite resolve
the spacing the way it was without the PI.  For example:

<?dbfo-need  height="0.5in"  space-before="3em" ?> 

It also could be used to add extra vertical space wherever
you need it.  If you leave out the height pseudo attribute,
then you will just get the extra spacing.

Here is the code:
--------------------------- snip ---------------------------------
<?xml version="1.0"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform";
xmlns:fo="http://www.w3.org/1999/XSL/Format";
                version='1.0'>

<!-- need PI -->
<xsl:template match="processing-instruction('dbfo-need')">

  <xsl:variable name="pi-height">
    <xsl:call-template name="dbfo-attribute">
      <xsl:with-param name="pis" select="."/>
      <xsl:with-param name="attribute" select="'height'"/>
    </xsl:call-template>
  </xsl:variable>

  <xsl:variable name="height">
    <xsl:choose>
      <xsl:when test="$pi-height != ''">
        <xsl:value-of select="$pi-height"/>
      </xsl:when>
      <xsl:otherwise>0pt</xsl:otherwise>
    </xsl:choose>
  </xsl:variable>

  <xsl:variable name="pi-before">
    <xsl:call-template name="dbfo-attribute">
      <xsl:with-param name="pis" select="."/>
      <xsl:with-param name="attribute" select="'space-before'"/>
    </xsl:call-template>
  </xsl:variable>

  <xsl:variable name="spacer">
    <fo:block-container width="100%" height="{$height}">
      <fo:block/>
    </fo:block-container>
  </xsl:variable>

  <xsl:choose>
    <xsl:when test="$pi-before != ''">
      <fo:block space-after="0pt" space-before="{$pi-before}">
        <xsl:copy-of select="$spacer"/>
      </fo:block>
    </xsl:when>
    <xsl:when test="following-sibling::para">
      <fo:block space-after="0pt" 
                xsl:use-attribute-sets="normal.para.spacing">
        <xsl:copy-of select="$spacer"/>
      </fo:block>
    </xsl:when>
    <xsl:when test="following-sibling::table or
                    following-sibling::figure or
                    following-sibling::example or
                    following-sibling::equation">
      <fo:block space-after="0pt" 
                xsl:use-attribute-sets="formal.object.properties">
        <xsl:copy-of select="$spacer"/>
      </fo:block>
    </xsl:when>
    <xsl:when test="following-sibling::informaltable or
                    following-sibling::informalfigure or
                    following-sibling::informalexample or
                    following-sibling::informalequation">
      <fo:block space-after="0pt" 
                xsl:use-attribute-sets="informal.object.properties">
        <xsl:copy-of select="$spacer"/>
      </fo:block>
    </xsl:when>
    <xsl:when test="following-sibling::itemizedlist or
                    following-sibling::orderedlist or
                    following-sibling::variablelist or
                    following-sibling::simplelist">
      <fo:block space-after="0pt" 
                xsl:use-attribute-sets="informal.object.properties">
        <xsl:copy-of select="$spacer"/>
      </fo:block>
    </xsl:when>
    <xsl:when test="following-sibling::listitem or
                    following-sibling::step">
      <fo:list-item space-after="0pt" 
                xsl:use-attribute-sets="informal.object.properties">
        <fo:list-item-label/>
        <fo:list-item-body start-indent="0pt" end-indent="0pt">
          <xsl:copy-of select="$spacer"/>
        </fo:list-item-body>
      </fo:list-item>
    </xsl:when>
    <xsl:when test="following-sibling::sect1 or
                    following-sibling::sect2 or
                    following-sibling::sect3 or
                    following-sibling::sect4 or
                    following-sibling::sect5 or
                    following-sibling::section">
      <fo:block space-after="0pt" 
                xsl:use-attribute-sets="section.title.properties">
        <xsl:copy-of select="$spacer"/>
      </fo:block>
    </xsl:when>
    <xsl:otherwise>
      <fo:block space-after="0pt" space-before="0em">
        <xsl:copy-of select="$spacer"/>
      </fo:block>
    </xsl:otherwise>
  </xsl:choose>

  <xsl:choose>
    <xsl:when test="following-sibling::listitem or
                    following-sibling::step">
      <fo:list-item space-before.precedence="force"
                space-before="-{$height}"
                space-after="0pt"
                space-after.precedence="force">
        <fo:list-item-label/>
        <fo:list-item-body start-indent="0pt" end-indent="0pt"/>
      </fo:list-item>
    </xsl:when>
    <xsl:otherwise>
      <fo:block space-before.precedence="force"
                space-before="-{$height}"
                space-after="0pt"
                space-after.precedence="force">
      </fo:block>
    </xsl:otherwise>
  </xsl:choose>
</xsl:template>

</xsl:stylesheet>
--------------------------- snip ---------------------------------

I'd be interested in getting feedback on situations where this
doesn't work properly.

Bob Stayton
Sagehill Enterprises
DocBook Consulting
bobs@sagehill.net




To unsubscribe from this list, send a post to docbook-apps-unsubscribe@lists.oasis-open.org, or visit http://www.oasis-open.org/mlmanage/.


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]