Search code examples
xsl-foapache-fop

XSL-FO: Absolute positioning of allocation rectangle rather than content rectangle


I'm implementing PDF export of 'dashboards' using XSL-FO (with Apache FOP as the rendering engine). These dashboards are defined as a set of rectangular elements rendered within an area of a particular size, whose positions are defined by percentage top/left offsets and percentage widths/heights. Rendering this using XSL-FO absolute positioning seemed like the obvious solution, and for the most part works well.

The following XSL-FO:

<fo:root xmlns:fo="http://www.w3.org/1999/XSL/Format" font-size="10pt" id="sequence-id">
<fo:layout-master-set>
<fo:simple-page-master master-name="page-layout" page-width=" 4in " page-height=" 4in ">
    <fo:region-body region-name="document-content" margin="0.5in"/>
</fo:simple-page-master>
</fo:layout-master-set>
<fo:page-sequence master-reference="page-layout" initial-page-number="1">
<fo:flow flow-name="document-content">
<fo:block-container border="1px solid green" height="3in">
<fo:block-container absolute-position="absolute" width="50%" left="0" top="0" height="40%" border="1px solid red">
    <fo:block border="1px solid blue" background-color="green">I am some content. It is possible for me to occupy the entire width of my allocated area.</fo:block>
</fo:block-container>
<fo:block-container absolute-position="absolute" width="50%" left="50%" top="0" height="50%" border="1px solid red">
    <fo:block border="1px solid blue" background-color="green">I am some content</fo:block>
</fo:block-container>
<fo:block-container absolute-position="absolute" width="50%" left="0" top="40%" height="60%" border="1px solid red">
    <fo:block border="1px solid blue" background-color="green">I am some content</fo:block>
</fo:block-container>
<fo:block-container position="absolute" width="50%" left="50%" top="50%" height="50%" border="1px solid red">
    <fo:block border="1px solid blue" background-color="green">I am some content</fo:block>
</fo:block-container>
</fo:block-container>
</fo:flow>
</fo:page-sequence>
</fo:root>

...results in the following output:

Example with no padding

The green areas, representing the content of a dashboard element, butt up against each other, which is ugly. So, I want to add some padding around the content. If I add padding to the block-container representing the first dashboard element:

<fo:block-container absolute-position="absolute" padding="5px" width="50%" left="0" top="0" height="40%" border="1px solid red">

...the (red) border of the dashboard element expands outward rather than adding padding inward:

Example with padding naively added

This is because, in contrast to CSS, absolute positioning offsets in XSL-FO address the content rectangle (i.e., inside the padding) rather than a rectangle including padding and borders. Per the spec:

These properties set the position of the content-rectangle of the associated area.

I want some way to add padding/a border inside the area I allocate to the dashboard element via absolute positioning attributes. I've tried various things around putting the content inside another nested block-container or block, but nothing I've tried works properly. As a last resort, I could compute the offsets/dimensions to effectively leave some blank space between elements, but this feels like something that should be handled automatically by the rendering engine. Any suggestions?


Solution

  • Easiest way for all dimensions ... add a block inside the block container in which you add all your content inside. This block should have your desired margin. So:

        <fo:flow flow-name="document-content">
            <fo:block-container border="1px solid green" height="3in">
                <fo:block-container absolute-position="absolute" width="50%" left="0" top="0" height="40%" border="1px solid red">
                    <fo:block margin="5px">
                        <fo:block border="1px solid blue" background-color="green">I am some content. It is possible for me to occupy the entire width of my allocated area.</fo:block>
                    </fo:block>
                </fo:block-container>
                <fo:block-container absolute-position="absolute" width="50%" left="50%" top="0" height="50%" border="1px solid red">
                    <fo:block margin="5px">
                    <fo:block border="1px solid blue" background-color="green">I am some content</fo:block>
                    </fo:block>
                </fo:block-container>
                <fo:block-container absolute-position="absolute" width="50%" left="0" top="40%" height="60%" border="1px solid red">
                    <fo:block margin="5px">
                    <fo:block border="1px solid blue" background-color="green">I am some content</fo:block>
                    </fo:block>
                </fo:block-container>
                <fo:block-container position="absolute" width="50%" left="50%" top="50%" height="50%" border="1px solid red">
                    <fo:block margin="5px">
                    <fo:block border="1px solid blue" background-color="green">I am some content</fo:block>
                    </fo:block>
                </fo:block-container>
            </fo:block-container>
        </fo:flow>
    

    Yields this:

    enter image description here