You are viewing a plain text version of this content. The canonical link for it is here.
Posted to fop-users@xmlgraphics.apache.org by Craig Ringer <cr...@postnewspapers.com.au> on 2010/06/21 18:47:51 UTC

Distributing vertical space in a column while repeating column headings

Hi all

I'm new to Apache FOP, and XSL-FO in general, so if this is a stupid
question please accept my apologies in advance. I've read the FAQ,
endless XSL-FO docs (including the ... dense ... spec), and searched the
list archives, so I'm not popping up without doing some research.

The short version is that I can't figure out how to distribute vertical
space to avoid ragged column bottoms in multi-column pages when the flow
contains several long-ish one-column tables. Why one-column tables?
Because I have sections with headings that must be repeated at the top
of a column if split across columns.

I'm writing a (to be open source) app to automatically paginate
classified ads to PDF, as I've been unable to find an acceptable
commercial solution at a reasonable price or locate any decent OSS
offering. I'm trying to use XSL-FO for layout, and need to produce
multi-column output, with column content flowing from bottom-left to
top-right along the columns. I'm using <fo:simple-page-master
column-count="7" ... /> to achieve that with no fuss.

The content is blocks of text and graphics that either have large
widow/orphan limits or cannot be split across columns at all, so elastic
spacing is required to avoid huge ragged blobs of whitespace at the
bottom of columns. This is easy enough using elastic space-before[1] if
each ad is in its own <fo:block> that's directly flowed into the column.

The tricky bit is that these collections of text blocks and images are
grouped into classifications, each of which must have a heading. If the
classification spans more than one column, **the heading must be
repeated at the top of the column**. I'm satisfying this requirement by
putting the ads in each classification into a one-column table and using
<fo:table-header/> to provide a repeating header. It's ugly, but it works.

However, I can't figure out how to specify elastic spacing to distribute
space between rows in the table and between tables. Table rows don't
have a space-before attribute. Adding elastic space-before to the
<fo:block/> within each table cell appears to have no effect. And
there's no display-align="justify" that would spread spacing evenly
line-by-line within all objects [2], as would be ideal. I can specify
elastic space-before for each table as a whole, but to have much effect
I need so much space that large holes open up between tables, while the
rows within the tables remain cramped.

(a) I need elastic spacing to prevent big, uneven chunks of whitespace
at the bottom of columns; but

(b) I need to repeat classification headings at the top of columns, and
can only seem to do that with 1-column tables, the use of which seems to
prevent effective elastic spacing.


I've been trying to figure out a way to use the
block-progression-dimension attribute to achieve this, as it seems to be
a more generalized form of elastic sizing. However, I just can't figure
out how to use it to express a flexible "expansion" amount. To use it, I
seem to already have to know how big the block needs to be to hold its
contents, so I can set the minimum, optimum, and maximum appropriately.

Is there any way to do this? Or am I barking up the wrong tree entirely?

Is there a way to achieve repeating column headings that'll let me avoid
wrapping sections (classifications) up in one-column tables, so I can
use the usual elastic spacing mechanism?

Is there any notional equivalent of display-align="justify" ? And if so,
any way to limit that justification or set a threshold of whitespace
percentage over which it won't try to do it?


Since I know how frustrating it is to see such questions without
concrete code: here's a sample XSL-FO document, generated from last
weeks' newspapers published classified ads:

  http://www.postnewspapers.com.au/~craig/fo_question/cl_pdf.fo

and the PDF fop produces for it:

  http://www.postnewspapers.com.au/~craig/fo_question/cl_pdf.pdf

The FO and PDF for another advertising section is also present, but is
less useful because it requires a huge bunch of referenced images to
paginate:

  http://www.postnewspapers.com.au/~craig/fo_question/tr_pdf.fo
  http://www.postnewspapers.com.au/~craig/fo_question/tr_pdf.pdf

( In that directory there's also an XSLT transform (ads_to_fo.xml) and
the source xml (cl.xml and tr.xml), not that it's really relevant to the
question. )


[1]
http://old.nabble.com/display-align%3D%22justify%22-ts8772141.html#a8772141

[2]http://article.gmane.org/gmane.text.xml.fop.user/25707

--
Craig Ringer

---------------------------------------------------------------------
To unsubscribe, e-mail: fop-users-unsubscribe@xmlgraphics.apache.org
For additional commands, e-mail: fop-users-help@xmlgraphics.apache.org


Re: Distributing vertical space in a column while repeating column headings

Posted by Craig Ringer <cr...@postnewspapers.com.au>.
> The short version is that I can't figure out how to distribute vertical
> space to avoid ragged column bottoms in multi-column pages when the flow
> contains several long-ish one-column tables. Why one-column tables?
> Because I have sections with headings that must be repeated at the top
> of a column if split across columns.
>   


A quick follow-up on this: I ended up solving it using my existing
one-column table approach to repeat headings, then post-processing the
area tree to re-distribute the space. I'm most of the way through
implementing insertion of house ads as whitepace filler as part of area
tree post-processing, too.

I've posted the core of the space redistribution code below in case it
helps anyone else. The rest of the code (not shown) is just the usual
stuff to embed fop, generate the area tree to a tempfile, and then
render from a dom to pdf after reprocessing.

Sorry for the mixed HTML/plain post, but in this case it's the best way
to get Thunderbird to maintain my code formatting. Grr stupid mail clients.

I may post the whole lot later if I get permission from the boss to open
source this whole classified/pagination system, which is likely. For
now, just the bits to reprocess the area tree follow, along with a
cut-down version of the PaginatorConfiguration class that provides the
required factories.

This code finds all blocks with a prod-id starting with "ad_" or
"heading_", determines how much free space is in the column, and
distributes that free space evenly among those blocks, adding to any
existing space-before if found. If it adds space to a block, it adds the
same amount of space to the block progression dimension of all
containing parent elements up to and including the <flow> that contains
the column. To work, it requires that blocks that should receive
distributed space be labeled with a suitable "id" attribute like
"ad_bobsmowing" or "heading_forsale" in the XSL-FO.

My app produces the XSL-FO with some XSLT, from simple input in a format
like the following:


      sample_ad_input.xml



<ads>
  <section class_no="1700">
    <heading>FOR SALE HOUSEHOLD</heading>
    <ad adname="BBQ 4BURNER GAS"><adbody><b>BBQ</b> 4-burner gas, good
cond $80 ONO. 9999 9999.</adbody></ad>
  </section>
  <section class_no="1725">
    <heading>DANCE</heading>
    <ad adname="LATIN AMERICAN ">
      <adbody><b>LATIN</b> American and Social Dancing. Learn all the
popular dances, Cha Cha, Jive, Rumba, Waltz, Quickstep, foxtrot ....
Private and Wedding lessons available.</adbody>
    </ad>
  </section>
</ads>

... but of course your needs would differ. I'm just showing how the
space is redistributed in case others have this problem.

In reality the XML is generated on demand by queries against a
PostgreSQL database containing the ads, but that doesn't matter much for
this purpose.

A cut-down version of the XSLT to transform the above into FO is:


      ads_to_fo.xsl


<?xml version="1.0"?>

<!--
REQUIREMENTS:
 - An XSLST processor
 - Apache FOP
 - Hyphenation files from http://offo.sourceforge.net/hyphenation/index.html
-->

<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:fo="http://www.w3.org/1999/XSL/Format">

<!-- File extension image files will have. -->
<xsl:param name="imgext"/>

<!--
  The root template produces the XSL-FO
  document structure, including page templates etc.
  It then calls the processor to loop through
  the <ad/> elements and generate content for them.
-->
<xsl:template match="/">

<!-- XSL-FO document structure-->
<fo:root xmlns:fo="http://www.w3.org/1999/XSL/Format" xml:lang="en">
  <!-- Define master pages -->
  <fo:layout-master-set>
    <!-- TODO: separate masters for first page, left pages, right pages -->
    <!-- see http://xmlgraphics.apache.org/fop/fo.html#fo-oddeven -->
    <fo:simple-page-master master-name="page" page-height="400mm"
page-width="290mm" margin="0mm">
      <!-- Body, with columns -->
      <fo:region-body column-count="7" column-gap="0" margin-top="12mm"/>
      <!-- masthead -->
      <fo:region-before extent="10mm"/>
    </fo:simple-page-master>
    <fo:page-sequence-master master-name="pagesequence">
      <!-- If you want a different first page, use
fo:single-page-master-reference here -->
      <fo:repeatable-page-master-reference master-reference="page"/>
    </fo:page-sequence-master>

  </fo:layout-master-set>

  <!-- Define page contents -->
  <fo:page-sequence master-reference="pagesequence" language="en">

    <!-- Ad text -->
    <fo:flow flow-name="xsl-region-body">
        <!-- This should really be a fo:wrapper, but fop isn't bright
enough to cope
             with that right now and will complain about inappropriate
inline areas.
             Use a fo:block container instead until fop svn (which fixes
this)
             is released to replace 0.95 -->
        <fo:block border-left-style="solid" border-right-style="solid"
                border-left-width="0.5pt" border-right-width="0.5pt"
                border-left-color="black" border-right-color="black"
                margin-left="-0.5pt" padding-left="2pt" margin-right="0pt"
                padding-right="2pt">
                <xsl:apply-templates/>
        </fo:block>
    </fo:flow>
  </fo:page-sequence>

</fo:root>
<!-- End XSL-FO document structure-->
</xsl:template>

<!--
Process a classification section, producing a one-column table so we can
ensure
the heading repeats on column breaks.

The table header will be provided by the <heading> element, which must
be the
first element of a <section>. Subsequent <ad> elements will go in the body.
-->
<xsl:template match="section">
    <fo:table table-layout="fixed" width="100%" space-before="4pt"
id="section_{@class_no}">
    <xsl:apply-templates select="heading"/>
    <fo:table-body>
      <fo:table-row>
        <fo:table-cell>
          <xsl:apply-templates select="ad"/>
        </fo:table-cell>
      </fo:table-row>
    </fo:table-body>
  </fo:table>
</xsl:template>

<!--
  Process a heading
-->
<xsl:template match="heading">
  <fo:table-header>
    <fo:table-cell>
            <fo:block hyphenate="false" text-align="center"
background-color="black" color="white" font-family="Helvetica"
font-size="10pt" font-weight="bold" padding-before="3pt"
padding-after="1.5pt" margin-top="0" margin-bottom="2pt"
id="heading_{../@class_no}">
        <xsl:apply-templates/>
      </fo:block>
    </fo:table-cell>
  </fo:table-header>
</xsl:template>

<!--
  Process an ad.
  Additional top-level templates are used to handle formatting,
  so this just encloses it in a block and calls the processor.
-->
<xsl:template match="ad">
      <fo:block hyphenate="true" text-align="justify"
text-align-last="left" widows="4" orphans="4"
                border-top-width="0.2pt" border-top-style="solid"
border-top-color="black"
                padding-after="0.2pt" padding-before="0.3pt"
                font-family="Helvetica" font-weight="regular"
font-size="6.3pt"
                id="ad_{@adname}_class_{../@class_no}"
                >
      <xsl:apply-templates/>
      </fo:block>
</xsl:template>

<xsl:template match="adbody">
    <xsl:apply-templates/>
</xsl:template>

<!-- handle an external ad reference, for when an ad is an image -->
<!-- They need to be centered -->
<xsl:template match="external">
        <!-- Fop takes some persuasion to scale the pics to the right
width. The explicit "width=100%"
             appears to be necessary to get it to scale - scale-to-fit
alone won't do it. -->
        <fo:external-graphic content-width="scale-to-fit" width="100%"
content-height="auto"
                scaling="non-uniform" src="url('pics/{.}{$imgext}')" />
</xsl:template>

<!--
  Convert bold tag to XSL-FO bold inline style block
-->
<xsl:template match="b">
  <fo:inline font-weight="bold">
  <xsl:apply-templates/>
  </fo:inline>
</xsl:template>

</xsl:stylesheet>






Given xml produced by that XSLT, converted to area tree XML by fop and
loaded into a W3C DOM (Document) using the usual Java tools, the
following code will redistribute space in the columns so that the
Document may be passed back into FOP via a DOMSource to be rendered to PDF.


      *AreaTreeTransformer.java:*


import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpression;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

/**
 * AreaTreeTransformer is responsible for manipulating a loaded area tree
 * XML DOM to redistribute space, insert house ads, etc.
 *
 * @author Craig Ringer <cr...@postnewspapers.com.au>
 */
public class AreaTreeTransformer {

    private final PaginatorConfiguration conf;
    private final Document areaTree;

    private final XPathFactory xpathFactory;
    private final XPathExpression findColumnsInDocument;
    private final XPathExpression findAdsInColumn;

    /**
     * Prepare a new transformer to operate on the passed area tree XML.
     *
     * @param conf PaginatorConfiguration to provide factories required
     * @param areaTree W3C DOM containing area tree XML to process
     * @throws XPathExpressionException
     */
    public AreaTreeTransformer(PaginatorConfiguration conf, Document
areaTree) throws XPathExpressionException {
        this.conf = conf;
        this.areaTree = areaTree;
        this.xpathFactory = conf.getXPathFactory();

        // This expression locates all columns in the document. It'll be
called
        // with the document root node as an argument.
        findColumnsInDocument =
xpathFactory.newXPath().compile("//span/flow");

        // This expression locates all ad and heading nodes within a column.
        // It'll be called with a column node, as returned by
findColumnsInDocument,
        // as an argument.
        findAdsInColumn =
xpathFactory.newXPath().compile(".//block[starts-with(@prod-id,'ad_') or
starts-with(@prod-id,'heading_')]");
    }

    /**
     * Transformation of the document is done on a column-by-column basis.
     * First, we must find all the columns and iterate over them. Then
within
     * each column, we must find the amount of white space that must be
consumed.
     *
     * Once the white space is known, the decision of what to do with it
must be
     * made. Should it just be re-distributed? Or should a house ad be
inserted?
     * Or (for the final column) should it be left empty for other
content to be
     * put in?
     *
     * Once any house ads are inserted, the remaining white space must
be distributed
     * between all the ads.
     *
     * For some basic info on the xpath api see
     * http://www.ibm.com/developerworks/library/x-javaxpathapi.html
     */
    public void doTransform() throws XPathExpressionException {
        // First we must find the columns. Each column in the area tree
is identifed
        // by a flow element under a span element, so it's easy to find
them.
        NodeList columnList =
(NodeList)findColumnsInDocument.evaluate(areaTree, XPathConstants.NODESET);
        for ( int i = 0; i < columnList.getLength(); i++ ) {
            Element flowNode = (Element)columnList.item(i);
            // For each column, we must determine how much free space is
in the column.
            // This is the difference between the block progression
dimension of the
            // span (ie the max col height) and the block progression
dimension of the
            // flow containing the column its self.
            final int spanBpd =
Integer.parseInt(((Element)flowNode.getParentNode()).getAttribute("bpd"));
            final int flowBpd =
Integer.parseInt(flowNode.getAttribute("bpd"));
            if (flowBpd == 0) {
                // Empty column.
                // TODO: The last column BEFORE the empty column may
need special
                // treatment, so we might need to add lookahead. OTOH,
there's no
                // guarantee there will be any empty cols - the last col
might be
                // on the end of a page.
                continue;
            }
            final double spaceToFill = (double)spanBpd - (double)flowBpd;
           
            // TODO: determine optimal house ad(s) to consume this space
            // and append them to the column, increasing the flow b-p-d as
            // necessary.
            final double spaceToDistribute = addHouseAds(flowNode,
spaceToFill);
            // Now redistribute space within the column so that ads use up
            // all the space. To do this, we find all ads (and headings)
in the
            // column, and then divide the space evenly between all except
            // the first block in the column. We then distribute that
space among
            // all the nodes we found by adding it to each node's
space-before.
            //
            // For each node to which space is added, we must update the
b-p-d
            // of all parent blocks up to the flow level, so that everything
            // starts in the right places and all the children fit
inside their
            // containing parents. The easiest way to do that is walk up the
            // ancestor tree adding to the b-p-d of each node along the way.
            NodeList adsAndHeads = (NodeList)
findAdsInColumn.evaluate(flowNode, XPathConstants.NODESET);
            // Distribute space among all blocks EXCEPT first, which
shouldn't get any
            // because we want it flush with the top margin.
            final int numBlocksToPad = adsAndHeads.getLength() - 1;
            if (numBlocksToPad == 0) {
                // Only one block in this column!
                System.err.println("Cannot distribute space in column:
only one ad block in column");
                continue;
            }
            final double extraSpacePerBlock = spaceToDistribute /
numBlocksToPad;

            // Start padding AFTER first block
            for ( int j = 1 ; j < adsAndHeads.getLength(); j++ ) {
                Element block = (Element) adsAndHeads.item(j);
                padBlock( block, flowNode, extraSpacePerBlock );
            }
        }
    }

    private double addHouseAds(Element columnElement, double spaceToFill) {
        // TODO: use conf object to obtain house ad dimensions,
determine best fit,
        // and insert ads into area tree.
        //
        // Currently no ads added, return original space
        return spaceToFill;
    }

    /*
     * Add `extraSpacePerBlock' to space-before on block, adding the
attribute if
     * it is missing and otherwise increasing its value by the specified
amount.
     *
     * Then scan up the ancestor tree, and for each ancestor with a bpd
attribute
     * (block progression dimension) between the block and the
surrounding flow
     * element, inclusive, increase the bpd of that element by
extraSpacePerBlock.
     */
    private void padBlock(Element block, Element flowNode, double
extraSpacePerBlock) {
        double newSpaceBefore = extraSpacePerBlock;
        if (block.hasAttribute("space-before")) {
            newSpaceBefore +=
Integer.parseInt(block.getAttribute("space-before"));
        }
        String roundedSpaceBefore =
Long.toString(Math.round(newSpaceBefore));
        block.setAttribute("space-before", roundedSpaceBefore);

        Element parent = (Element)block.getParentNode();
        do {
            if (parent.hasAttribute("bpd")) {
                long newBpd = Math.round(extraSpacePerBlock +
Integer.parseInt(parent.getAttribute("bpd")));
                parent.setAttribute("bpd", Long.toString(newBpd));
            }
            if (flowNode.isSameNode(parent))
                break;
        } while ( (parent = (Element)parent.getParentNode()) != null );
    }

}


      PaginatorConfiguration.java


import java.io.File;
import java.net.MalformedURLException;
import java.nio.file.Path;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.xpath.XPathFactory;
import org.apache.fop.apps.FopFactory;

/**
 * PaginatorConfiguration tracks instances of configured factories required
 * for parsing, formatting, etc.
 *
 * @author Craig Ringer <cr...@postnewspapers.com.au>
 */
public class PaginatorConfiguration {

    private final FopFactory fopFactory = FopFactory.newInstance();
    private final TransformerFactory xsltFactory =
TransformerFactory.newInstance();
    private final DocumentBuilderFactory documentBuilderFactory =
DocumentBuilderFactory.newInstance();
    private final XPathFactory xpathFactory = XPathFactory.newInstance();
   
    // TODO: load from resource
    private final File adsToFoXSLTFile = new File("ads_to_fo.xml");

    public PaginatorConfiguration() throws MalformedURLException {
        // Namespace awareness is required if feeding the dom back into fop.
        documentBuilderFactory.setNamespaceAware(true);
        // TODO: configure font base, image base, etc here.
        File cwd = new File( System.getProperty("user.dir") );
        fopFactory.setBaseURL( cwd.toURI().toString() );
        fopFactory.setFontBaseURL( (new
File(cwd,"fonts")).toURI().toString() );
        fopFactory.setSourceResolution(200);
        fopFactory.setTargetResolution(200);
        // TODO: download and paginate house ads
    }

    public FopFactory getFopFactory() {
        return fopFactory;
    }

    public TransformerFactory getTransformerFactory() {
        return xsltFactory;
    }

    public DocumentBuilderFactory getDocBuilderFactory() {
        return documentBuilderFactory;
    }

    public XPathFactory getXPathFactory() {
        return xpathFactory;
    }

    public File getAdsToFoXSLTFile() {
        return adsToFoXSLTFile;
    }

}

-- 
Craig Ringer

Tech-related writing: http://soapyfrogs.blogspot.com/


Re: Distributing vertical space in a column while repeating column headings

Posted by Jeremias Maerki <de...@jeremias-maerki.ch>.
I've responded to Craig off-list with some information and alternatives
that probably don't belong on this list. At any rate, I can't help Craig
with a fix in the short term and Georg has provided some interesting
ideas. So if anyone else has time, please get in contact with Craig.

On 23.06.2010 10:30:48 Craig Ringer wrote:
<snip/>
> Alas, I don't think I'll have any better luck doing this with TeX(ML)
> either, nor generating InDesign documents as XML, so FOP + area trees
> might be my best bet. Thanks for the pointer, I'll have a play.
> Meanwhile I'd be interested in your thoughts on the above, with a
> potential view to paid extension work on FOP - if you see anything that
> might be worthwhile and if I can swing the cost with my boss.
> 
> --
> Craig Ringer


Jeremias Maerki


---------------------------------------------------------------------
To unsubscribe, e-mail: fop-users-unsubscribe@xmlgraphics.apache.org
For additional commands, e-mail: fop-users-help@xmlgraphics.apache.org


AW: AW: Distributing vertical space in a column while repeating column headings

Posted by Georg Datterl <ge...@geneon.de>.
Hi Craig,

> I see. So the only way they could ever make sense was if, rather than
> columns, the document used named regions (as per XSL-FO 1.1) with a flow
> map to control flow into them. Then static content could be aware of
> what "column" it was coming from.

That would not work, because the content would spill from flow1 on page 1 to flow1 on page 2, not flow2 on page 1.

Regards,

Georg Datterl

------ Kontakt ------

Georg Datterl

Geneon media solutions gmbh
Gutenstetter Straße 8a
90449 Nürnberg

HRB Nürnberg: 17193
Geschäftsführer: Yong-Harry Steiert

Tel.: 0911/36 78 88 - 26
Fax: 0911/36 78 88 - 20

www.geneon.de

Weitere Mitglieder der Willmy MediaGroup:

IRS Integrated Realization Services GmbH:    www.irs-nbg.de
Willmy PrintMedia GmbH:                            www.willmy.de
Willmy Consult & Content GmbH:                 www.willmycc.de


---------------------------------------------------------------------
To unsubscribe, e-mail: fop-users-unsubscribe@xmlgraphics.apache.org
For additional commands, e-mail: fop-users-help@xmlgraphics.apache.org


Re: AW: Distributing vertical space in a column while repeating column headings

Posted by Craig Ringer <cr...@postnewspapers.com.au>.
On 23/06/10 19:39, Georg Datterl wrote:
> Hi Craig,
>
> Give the first add in the classification an empty marker and each
> other add the correct marker. Then grab the first marker. If a new
> classification started on the page, you get the header, but grab the
> empty marker. In each other case you have no header, but a correct
> marker.

Really close, but still no cookie. Alas. Thanks so much for your efforts
to help me out - I try to do the same on other lists (like PostgreSQL's)
but here it's me who's the helpless newbie out of their depth.

Your suggestion as I've implemented it mostly - but not quite - works
out. Unfortunately the display of marked content must be in
static-content in region-before, so headings placed by markers appear
higher up the column than in-line headings that happen to be first on
the page. This could be cropped away, but would result in different
column lengths and therefore ragged bottoms - so back to square 1, really.

If XSL-FO allowed static content and flowed content to be combined in
one region, such that the flowed content flowed into space the static
content hadn't used, this would be possible. However, because of markers
etc the static content can change size & shape based on the flowed
content, so it's really impossible to do and not surprising that the
spec doesn't permit it.

I could overlap the region-before and region-body, but because FO
doesn't allow the region-before's content to "push" the region-body's
out of the way, that'd just print the marker-added heading with the body
content overtyped on it. Not a solution.

Again, it seems to come down to the need for blocks that can be
conditionally displayed depending on whether they're just after (or
before?) a break.

I am increasingly getting the feeling that XSL-FO just isn't the right
tool for this job, and I'll have to try to bodge it with area tree
output unless some way to achieve conditional blocks presents its self.

Well, that or try to implement this in TeX(ML), since that offers a much
more complete (and complex) set of tools for conditional formatting.

--
Craig Ringer

---------------------------------------------------------------------
To unsubscribe, e-mail: fop-users-unsubscribe@xmlgraphics.apache.org
For additional commands, e-mail: fop-users-help@xmlgraphics.apache.org


Re: AW: Distributing vertical space in a column while repeating column headings

Posted by Craig Ringer <cr...@postnewspapers.com.au>.
> It's ... gruesome ... but an awfully good idea.

Unfortunately a quick test suggests it may also not work.

Classifications don't always begin in the middle of a column (now page).
Sometimes they happen to being at the top of a column, simply because
the previous classification fit neatly into the previous column. As a
heading is always output where a new classification begins, use of
markers would cause *two* headings to appear - first the marker-provided
heading, then the heading that appears in the flow where the new
classification begins.

I don't see any way to suppress the double heading in that situation,
and it's *ugly*.

Sigh. I keep on coming back to the need for some kind of conditionally
rendered block, like the following imaginary syntax:

<fo:block displayed-if="is-first-after-break">
  heading content here
</fo:block>

so I can repeat the block before *every* ad, using a keep to make sure
that if the heading is displayed, the following ad appears on the same page.

If there doesn't turn out to be a smarter way of doing this, I'd be
interested in discussing costs to contract someone to implement this as
a fop extension to fo - if anyone's interested, it'd be acceptable for
fop, and it's a reasonably sized project.

( BTW, I've been trying to check out fop svn trunk, but keep on getting
a connection refused from Apache's svn servers. I got it after several
tries over half an hour, but ... load issues? Scheduled maintenance? )

--
Craig Ringer

---------------------------------------------------------------------
To unsubscribe, e-mail: fop-users-unsubscribe@xmlgraphics.apache.org
For additional commands, e-mail: fop-users-help@xmlgraphics.apache.org


Re: AW: Distributing vertical space in a column while repeating column headings

Posted by Craig Ringer <cr...@postnewspapers.com.au>.
On 23/06/10 17:55, Georg Datterl wrote:

> Table markers are not yet implemented and most likely wouldn't help
> you anyway. The problem with ordinary marker is, the header is not
> aware of the columns, since they are defined in the flow. So markers
> in the header can't find, like, the first marker in a column. That's
> why I suggested a seven-column table to fake the seven columns of the
> flow.

I see. So the only way they could ever make sense was if, rather than
columns, the document used named regions (as per XSL-FO 1.1) with a flow
map to control flow into them. Then static content could be aware of
what "column" it was coming from.

That's a whole lot more complicated, though, and doesn't seem to be even
considered in the spec. Most use cases would probably be better handled
by smarter conditional content (as per the "future work" section of the
spec) anyway.

> Another idea I just had: What would happen, if you defined one
> pagewidth as 1/7th of a real page, spread the page header and footer
> over seven pages and just PRINT seven pages on one sheet of paper?
> (or combine them later, if possible?)

Yuck!

That's fantastically ugly, and would actually work pretty decently. It
also makes house ad placement easier because it's way easier to
manipulate the size of a region on a page master rather than try to
block out a piece of one column.

It's ... gruesome ... but an awfully good idea. It wouldn't work if I
was trying to generate press-ready pages, since I'd need to include a
proper folio with page numbers etc, but in my case all I'm trying to do
is generate the "content" PDFs that'll then be placed onto InDesign
pages, possibly with other content, for layout and print.

If I did need to assemble the paginated document into a single PDF for
final output, I imagine some area-tree post processing could probably
manage that, and failing that there's always PDF imposition.

I've been staring at the area tree output and going increasingly
cross-eyed thinking about how I can possibly manipulate it to achieve
what I need, so your suggestion gives me a great workaround and buys me
some time to figure out the area tree approach.

Thanks!

--
Craig Ringer

---------------------------------------------------------------------
To unsubscribe, e-mail: fop-users-unsubscribe@xmlgraphics.apache.org
For additional commands, e-mail: fop-users-help@xmlgraphics.apache.org


AW: Distributing vertical space in a column while repeating column headings

Posted by Georg Datterl <ge...@geneon.de>.
Hi Craig,

Table markers are not yet implemented and most likely wouldn't help you anyway. The problem with ordinary marker is, the header is not aware of the columns, since they are defined in the flow. So markers in the header can't find, like, the first marker in a column. That's why I suggested a seven-column table to fake the seven columns of the flow.

Another idea I just had: What would happen, if you defined one pagewidth as 1/7th of a real page, spread the page header and footer over seven pages and just PRINT seven pages on one sheet of paper? (or combine them later, if possible?)

Regards,

Georg Datterl

------ Kontakt ------

Georg Datterl

Geneon media solutions gmbh
Gutenstetter Straße 8a
90449 Nürnberg

HRB Nürnberg: 17193
Geschäftsführer: Yong-Harry Steiert

Tel.: 0911/36 78 88 - 26
Fax: 0911/36 78 88 - 20

www.geneon.de

Weitere Mitglieder der Willmy MediaGroup:

IRS Integrated Realization Services GmbH:    www.irs-nbg.de
Willmy PrintMedia GmbH:                            www.willmy.de
Willmy Consult & Content GmbH:                 www.willmycc.de

-----Ursprüngliche Nachricht-----
Von: Craig Ringer [mailto:craig@postnewspapers.com.au]
Gesendet: Mittwoch, 23. Juni 2010 10:31
An: fop-users@xmlgraphics.apache.org
Cc: Jeremias Maerki
Betreff: Re: Distributing vertical space in a column while repeating column headings

On 22/06/10 15:06, Jeremias Maerki wrote:

> I remember from back when I was deep in the table layout code that
> elastic space in tables is extremely tricky (with our layout approach)
> which is why it's not available in tables today. In your case, it would
> actually be relatively easy since there is only one column, but it's
> still not available. So what you've done between tables is about as much
> as you can get right now.

Thanks very much for spending the time to look at and respond to this,
and for confirming I'm not missing the obvious.

> The only work-around that I can come up with right now is not to put the
> ads in a table and generate only the initial header for each section.
> And then to try some post-processing magic with the area tree XML. Ugly
> but I don't see any other possibility right now.

I'll have to experiment with that. It'll potentially be a way to solve
the other issue I have (how to insert "house ads" where holes in columns
are too big to neatly justify out) without requiring user intervention.
Thanks for the pointer.

It's a pity that it'll mean I can't use just standard XSL-FO, but the
spec doesn't appear to be up to this particular task yet.

If this doesn't work, given that you said adding support for expanding
one-column tables would be "relatively easy", is there any chance you'd
be willing to quote on that as a contract job to extend fop? Assuming
that it'd be within the standard to support elastic spacing in table
cells, of course. If it's the difference between being able to use
XSL-FO + fop, and having to buy an insanely overpriced classified
pagination/booking/automation "system", paying for some small
enhancements to fop would be a no brainer.

Georg Datterl mentioned markers as a possibility if I could use
page-break rather than column-break as the repetition boundary.
Unfortunately I can't, but this makes me wonder if there's any concept
of a more granular "break marker" that could be used at column
boundaries too. (I suspect I'm displaying my relative ignorance of FO
here). It seems like a pity that markers were limited to page-level
operation in the spec, especially since the 1.1 spec extends and
generalizes regions in ways that'd make "region markers" very useful.

It looks like FO also has "table markers" ... which would be great,
except that tables already solve the issue markers are needed for but
introduce issues with vertical space justification. Argh.

The other thing I've been wondering about is the possibility of
conditional content. Much as space-before can be conditional, so that it
doesn't display if the block is the first element after a break, I
wonder about having conditional block display where an object only gets
laid out if it's the first item after a break, or *not* the first item
after a break. That'd be incredibly handy, and I suspect not just for my
weird little use case. Leaders would be an obvious use for this. This
isn't something you can really do at the XSLT stage (unlike most
variable/conditional content) since it requires layout awareness. I
can't find any consideration of this in the FO spec except as applies to
markers, which I'm rather surprised by.

Of course, even with a solution to this issue I'll still probably need
to use the area tree output to determine where I need to insert "house
ads" to consume any column whitespace holes that are too large to just
spread out between objects in the column. There doesn't seem to be even
the remotest concept in FO of marker-like content that's triggered by
"amount of free space in the region", which is what I'd need to do this
automatically.

Alas, I don't think I'll have any better luck doing this with TeX(ML)
either, nor generating InDesign documents as XML, so FOP + area trees
might be my best bet. Thanks for the pointer, I'll have a play.
Meanwhile I'd be interested in your thoughts on the above, with a
potential view to paid extension work on FOP - if you see anything that
might be worthwhile and if I can swing the cost with my boss.

--
Craig Ringer

---------------------------------------------------------------------
To unsubscribe, e-mail: fop-users-unsubscribe@xmlgraphics.apache.org
For additional commands, e-mail: fop-users-help@xmlgraphics.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: fop-users-unsubscribe@xmlgraphics.apache.org
For additional commands, e-mail: fop-users-help@xmlgraphics.apache.org


Re: Distributing vertical space in a column while repeating column headings

Posted by Craig Ringer <cr...@postnewspapers.com.au>.
On 22/06/10 15:06, Jeremias Maerki wrote:

> I remember from back when I was deep in the table layout code that
> elastic space in tables is extremely tricky (with our layout approach)
> which is why it's not available in tables today. In your case, it would
> actually be relatively easy since there is only one column, but it's
> still not available. So what you've done between tables is about as much
> as you can get right now.

Thanks very much for spending the time to look at and respond to this,
and for confirming I'm not missing the obvious.

> The only work-around that I can come up with right now is not to put the
> ads in a table and generate only the initial header for each section.
> And then to try some post-processing magic with the area tree XML. Ugly
> but I don't see any other possibility right now.

I'll have to experiment with that. It'll potentially be a way to solve
the other issue I have (how to insert "house ads" where holes in columns
are too big to neatly justify out) without requiring user intervention.
Thanks for the pointer.

It's a pity that it'll mean I can't use just standard XSL-FO, but the
spec doesn't appear to be up to this particular task yet.

If this doesn't work, given that you said adding support for expanding
one-column tables would be "relatively easy", is there any chance you'd
be willing to quote on that as a contract job to extend fop? Assuming
that it'd be within the standard to support elastic spacing in table
cells, of course. If it's the difference between being able to use
XSL-FO + fop, and having to buy an insanely overpriced classified
pagination/booking/automation "system", paying for some small
enhancements to fop would be a no brainer.

Georg Datterl mentioned markers as a possibility if I could use
page-break rather than column-break as the repetition boundary.
Unfortunately I can't, but this makes me wonder if there's any concept
of a more granular "break marker" that could be used at column
boundaries too. (I suspect I'm displaying my relative ignorance of FO
here). It seems like a pity that markers were limited to page-level
operation in the spec, especially since the 1.1 spec extends and
generalizes regions in ways that'd make "region markers" very useful.

It looks like FO also has "table markers" ... which would be great,
except that tables already solve the issue markers are needed for but
introduce issues with vertical space justification. Argh.

The other thing I've been wondering about is the possibility of
conditional content. Much as space-before can be conditional, so that it
doesn't display if the block is the first element after a break, I
wonder about having conditional block display where an object only gets
laid out if it's the first item after a break, or *not* the first item
after a break. That'd be incredibly handy, and I suspect not just for my
weird little use case. Leaders would be an obvious use for this. This
isn't something you can really do at the XSLT stage (unlike most
variable/conditional content) since it requires layout awareness. I
can't find any consideration of this in the FO spec except as applies to
markers, which I'm rather surprised by.

Of course, even with a solution to this issue I'll still probably need
to use the area tree output to determine where I need to insert "house
ads" to consume any column whitespace holes that are too large to just
spread out between objects in the column. There doesn't seem to be even
the remotest concept in FO of marker-like content that's triggered by
"amount of free space in the region", which is what I'd need to do this
automatically.

Alas, I don't think I'll have any better luck doing this with TeX(ML)
either, nor generating InDesign documents as XML, so FOP + area trees
might be my best bet. Thanks for the pointer, I'll have a play.
Meanwhile I'd be interested in your thoughts on the above, with a
potential view to paid extension work on FOP - if you see anything that
might be worthwhile and if I can swing the cost with my boss.

--
Craig Ringer

---------------------------------------------------------------------
To unsubscribe, e-mail: fop-users-unsubscribe@xmlgraphics.apache.org
For additional commands, e-mail: fop-users-help@xmlgraphics.apache.org


Re: Distributing vertical space in a column while repeating column headings

Posted by Jeremias Maerki <de...@jeremias-maerki.ch>.
Craig,
I remember from back when I was deep in the table layout code that
elastic space in tables is extremely tricky (with our layout approach)
which is why it's not available in tables today. In your case, it would
actually be relatively easy since there is only one column, but it's
still not available. So what you've done between tables is about as much
as you can get right now.

The only work-around that I can come up with right now is not to put the
ads in a table and generate only the initial header for each section.
And then to try some post-processing magic with the area tree XML. Ugly
but I don't see any other possibility right now.

I've also tried inserting empty rows with elastic b-p-d but that didn't
have any effect. At least that would provide a possibility of a
reasonably easy fix for your situation because in rows like that,
there's no "element list combination" (but only if there are no spans,
i.e. the spacer table-row is a separate "row-group"). I guess it would
still require quite an effort.

Maybe someone else has a better idea.

On 21.06.2010 18:47:51 Craig Ringer wrote:
> Hi all
> 
> I'm new to Apache FOP, and XSL-FO in general, so if this is a stupid
> question please accept my apologies in advance. I've read the FAQ,
> endless XSL-FO docs (including the ... dense ... spec), and searched the
> list archives, so I'm not popping up without doing some research.
> 
> The short version is that I can't figure out how to distribute vertical
> space to avoid ragged column bottoms in multi-column pages when the flow
> contains several long-ish one-column tables. Why one-column tables?
> Because I have sections with headings that must be repeated at the top
> of a column if split across columns.
> 
> I'm writing a (to be open source) app to automatically paginate
> classified ads to PDF, as I've been unable to find an acceptable
> commercial solution at a reasonable price or locate any decent OSS
> offering. I'm trying to use XSL-FO for layout, and need to produce
> multi-column output, with column content flowing from bottom-left to
> top-right along the columns. I'm using <fo:simple-page-master
> column-count="7" ... /> to achieve that with no fuss.
> 
> The content is blocks of text and graphics that either have large
> widow/orphan limits or cannot be split across columns at all, so elastic
> spacing is required to avoid huge ragged blobs of whitespace at the
> bottom of columns. This is easy enough using elastic space-before[1] if
> each ad is in its own <fo:block> that's directly flowed into the column.
> 
> The tricky bit is that these collections of text blocks and images are
> grouped into classifications, each of which must have a heading. If the
> classification spans more than one column, **the heading must be
> repeated at the top of the column**. I'm satisfying this requirement by
> putting the ads in each classification into a one-column table and using
> <fo:table-header/> to provide a repeating header. It's ugly, but it works.
> 
> However, I can't figure out how to specify elastic spacing to distribute
> space between rows in the table and between tables. Table rows don't
> have a space-before attribute. Adding elastic space-before to the
> <fo:block/> within each table cell appears to have no effect. And
> there's no display-align="justify" that would spread spacing evenly
> line-by-line within all objects [2], as would be ideal. I can specify
> elastic space-before for each table as a whole, but to have much effect
> I need so much space that large holes open up between tables, while the
> rows within the tables remain cramped.
> 
> (a) I need elastic spacing to prevent big, uneven chunks of whitespace
> at the bottom of columns; but
> 
> (b) I need to repeat classification headings at the top of columns, and
> can only seem to do that with 1-column tables, the use of which seems to
> prevent effective elastic spacing.
> 
> 
> I've been trying to figure out a way to use the
> block-progression-dimension attribute to achieve this, as it seems to be
> a more generalized form of elastic sizing. However, I just can't figure
> out how to use it to express a flexible "expansion" amount. To use it, I
> seem to already have to know how big the block needs to be to hold its
> contents, so I can set the minimum, optimum, and maximum appropriately.
> 
> Is there any way to do this? Or am I barking up the wrong tree entirely?
> 
> Is there a way to achieve repeating column headings that'll let me avoid
> wrapping sections (classifications) up in one-column tables, so I can
> use the usual elastic spacing mechanism?
> 
> Is there any notional equivalent of display-align="justify" ? And if so,
> any way to limit that justification or set a threshold of whitespace
> percentage over which it won't try to do it?
> 
> 
> Since I know how frustrating it is to see such questions without
> concrete code: here's a sample XSL-FO document, generated from last
> weeks' newspapers published classified ads:
> 
>   http://www.postnewspapers.com.au/~craig/fo_question/cl_pdf.fo
> 
> and the PDF fop produces for it:
> 
>   http://www.postnewspapers.com.au/~craig/fo_question/cl_pdf.pdf
> 
> The FO and PDF for another advertising section is also present, but is
> less useful because it requires a huge bunch of referenced images to
> paginate:
> 
>   http://www.postnewspapers.com.au/~craig/fo_question/tr_pdf.fo
>   http://www.postnewspapers.com.au/~craig/fo_question/tr_pdf.pdf
> 
> ( In that directory there's also an XSLT transform (ads_to_fo.xml) and
> the source xml (cl.xml and tr.xml), not that it's really relevant to the
> question. )
> 
> 
> [1]
> http://old.nabble.com/display-align%3D%22justify%22-ts8772141.html#a8772141
> 
> [2]http://article.gmane.org/gmane.text.xml.fop.user/25707
> 
> --
> Craig Ringer


Jeremias Maerki


---------------------------------------------------------------------
To unsubscribe, e-mail: fop-users-unsubscribe@xmlgraphics.apache.org
For additional commands, e-mail: fop-users-help@xmlgraphics.apache.org


AW: Distributing vertical space in a column while repeating column headings

Posted by Georg Datterl <ge...@geneon.de>.
Hi Craig,

I'm sorry I don't have a solution for your problem, but if you can change your requirements from "header repeated in each colum" to "header repeated on each new page", then markers would be your way to go, I guess.

Or, as Jeremias suggested, some ugly coding. I did not try that, just thinking aloud:
Since each column needs a header anyway, the first headers might be relocated to the page header section. There you have a 7-cell table spanning the whole page width. For layouting purposes all cells would contain the longest possible classification header or a fixed height. Now generate the area tree of the page and find out, which classification is the first in each column. Write this header into the table and generate the page again. This approach breaks as soon as the table height is different between the two runs. It's ugly, but it may work.

Regards,

Georg Datterl

------ Kontakt ------

Georg Datterl

Geneon media solutions gmbh
Gutenstetter Straße 8a
90449 Nürnberg

HRB Nürnberg: 17193
Geschäftsführer: Yong-Harry Steiert

Tel.: 0911/36 78 88 - 26
Fax: 0911/36 78 88 - 20

www.geneon.de

Weitere Mitglieder der Willmy MediaGroup:

IRS Integrated Realization Services GmbH:    www.irs-nbg.de
Willmy PrintMedia GmbH:                            www.willmy.de
Willmy Consult & Content GmbH:                 www.willmycc.de

-----Ursprüngliche Nachricht-----
Von: Craig Ringer [mailto:craig@postnewspapers.com.au]
Gesendet: Montag, 21. Juni 2010 18:48
An: fop-users@xmlgraphics.apache.org
Betreff: Distributing vertical space in a column while repeating column headings

Hi all

I'm new to Apache FOP, and XSL-FO in general, so if this is a stupid
question please accept my apologies in advance. I've read the FAQ,
endless XSL-FO docs (including the ... dense ... spec), and searched the
list archives, so I'm not popping up without doing some research.

The short version is that I can't figure out how to distribute vertical
space to avoid ragged column bottoms in multi-column pages when the flow
contains several long-ish one-column tables. Why one-column tables?
Because I have sections with headings that must be repeated at the top
of a column if split across columns.

I'm writing a (to be open source) app to automatically paginate
classified ads to PDF, as I've been unable to find an acceptable
commercial solution at a reasonable price or locate any decent OSS
offering. I'm trying to use XSL-FO for layout, and need to produce
multi-column output, with column content flowing from bottom-left to
top-right along the columns. I'm using <fo:simple-page-master
column-count="7" ... /> to achieve that with no fuss.

The content is blocks of text and graphics that either have large
widow/orphan limits or cannot be split across columns at all, so elastic
spacing is required to avoid huge ragged blobs of whitespace at the
bottom of columns. This is easy enough using elastic space-before[1] if
each ad is in its own <fo:block> that's directly flowed into the column.

The tricky bit is that these collections of text blocks and images are
grouped into classifications, each of which must have a heading. If the
classification spans more than one column, **the heading must be
repeated at the top of the column**. I'm satisfying this requirement by
putting the ads in each classification into a one-column table and using
<fo:table-header/> to provide a repeating header. It's ugly, but it works.

However, I can't figure out how to specify elastic spacing to distribute
space between rows in the table and between tables. Table rows don't
have a space-before attribute. Adding elastic space-before to the
<fo:block/> within each table cell appears to have no effect. And
there's no display-align="justify" that would spread spacing evenly
line-by-line within all objects [2], as would be ideal. I can specify
elastic space-before for each table as a whole, but to have much effect
I need so much space that large holes open up between tables, while the
rows within the tables remain cramped.

(a) I need elastic spacing to prevent big, uneven chunks of whitespace
at the bottom of columns; but

(b) I need to repeat classification headings at the top of columns, and
can only seem to do that with 1-column tables, the use of which seems to
prevent effective elastic spacing.


I've been trying to figure out a way to use the
block-progression-dimension attribute to achieve this, as it seems to be
a more generalized form of elastic sizing. However, I just can't figure
out how to use it to express a flexible "expansion" amount. To use it, I
seem to already have to know how big the block needs to be to hold its
contents, so I can set the minimum, optimum, and maximum appropriately.

Is there any way to do this? Or am I barking up the wrong tree entirely?

Is there a way to achieve repeating column headings that'll let me avoid
wrapping sections (classifications) up in one-column tables, so I can
use the usual elastic spacing mechanism?

Is there any notional equivalent of display-align="justify" ? And if so,
any way to limit that justification or set a threshold of whitespace
percentage over which it won't try to do it?


Since I know how frustrating it is to see such questions without
concrete code: here's a sample XSL-FO document, generated from last
weeks' newspapers published classified ads:

  http://www.postnewspapers.com.au/~craig/fo_question/cl_pdf.fo

and the PDF fop produces for it:

  http://www.postnewspapers.com.au/~craig/fo_question/cl_pdf.pdf

The FO and PDF for another advertising section is also present, but is
less useful because it requires a huge bunch of referenced images to
paginate:

  http://www.postnewspapers.com.au/~craig/fo_question/tr_pdf.fo
  http://www.postnewspapers.com.au/~craig/fo_question/tr_pdf.pdf

( In that directory there's also an XSLT transform (ads_to_fo.xml) and
the source xml (cl.xml and tr.xml), not that it's really relevant to the
question. )


[1]
http://old.nabble.com/display-align%3D%22justify%22-ts8772141.html#a8772141

[2]http://article.gmane.org/gmane.text.xml.fop.user/25707

--
Craig Ringer

---------------------------------------------------------------------
To unsubscribe, e-mail: fop-users-unsubscribe@xmlgraphics.apache.org
For additional commands, e-mail: fop-users-help@xmlgraphics.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: fop-users-unsubscribe@xmlgraphics.apache.org
For additional commands, e-mail: fop-users-help@xmlgraphics.apache.org