我的多页迭代 PDF 转换器应该使用啥 XSLT:FO 布局?

Posted

技术标签:

【中文标题】我的多页迭代 PDF 转换器应该使用啥 XSLT:FO 布局?【英文标题】:What XSLT:FO layout should I use for my multi-page iterative PDF converter?我的多页迭代 PDF 转换器应该使用什么 XSLT:FO 布局? 【发布时间】:2021-12-18 15:13:11 【问题描述】:

我正在使用 Apache 的 XML Graphics FOP 2.6 创建一个至少包含 2 页的 PDF 文档。没有最大页数。

每个页面都使用完全相同的标题。

第 1 页(见附件)

需要包含带有总框数、总框数中包含的项目总数以及 1、2 或 3 行的表格的文本。在每一行,都有一张盒子的照片,它的名字和它包含的物品数量。至少会有一个盒子包含至少一件物品。

第 2 页

仅当有 4 个或更多框且包含第 1 页表格的第 4、5、6、7、8 和 9 行(共 6 行)时才存在。如果超过 9 个(= 3 + 6 X 1) 框,需要有一个新页面,其中包含第 2 页的布局,但包含框 10、...、15 的行。将遵循此模式,直到最后一个框。

第 3 页

将包含第一个盒子的照片、名称和物品数量。下面会有一个表格,其中一行包含列名,最多 4 行,对应于第一个框中的前 4 个项目。

第 4 页

如果第一个框的项目超过 4 个,则会出现新页面,称为第 4 页,仅包含表格的延续,包括具有列名的行。它将总共有 7 个项目,如果第一个框有超过 11 个(= 4 + 7 X 1)项目,则后面是另一页。以此类推,直到所有项目结束。

第5页(不附上以免重复)

将遵循与第 3 页相同的逻辑,但在这种情况下,对于第二个框,如果有第二个框。以此类推,直到所有盒子结束。

任何人都可以帮助我了解如何根据我的要求在 XSL 文件中构建 layout-master-set 吗?我不是在寻找完整的解决方案。仅适用于一般布局结构。答案不必太详细或太定制以满足我的需求。如果需要,我可以调整它。

<xsl:template match="/doc">
    <xsl:variable name="Logo"><xsl:value-of select="Logo"/></xsl:variable>
            ...

    <fo:root xmlns:fo="http://www.w3.org/1999/XSL/Format" xmlns:fox="http://xmlgraphics.apache.org/fop/extensions"
             font-family="Nexus Sans Pro" font-weight="normal">
        <fo:layout-master-set>
            <fo:simple-page-master master-name="Boxes-A4" page- page-
                                   margin-top="0mm" margin-bottom="0mm" margin-left="0mm" margin-right="0mm">
                <fo:region-body region-name="xsl-region-body"/>
                <fo:region-before region-name="xsl-region-before" extent="70mm"/>
            </fo:simple-page-master>
            <fo:simple-page-master master-name="Items-A4" page- page-
                                   margin-top="0mm" margin-bottom="0mm" margin-left="0mm" margin-right="0mm">
                <fo:region-body region-name="xsl-region-body"/>
                <fo:region-before region-name="xsl-region-before" extent="70mm"/>
            </fo:simple-page-master>
        </fo:layout-master-set>

我已经尝试了上面的代码(使用 xsl:stylesheet version="1.0"),但未能正确布局。即使我用 break-before="page" aka page-break-before="always" 标记了适当的行,表格中不适合页面的行也不会显示在下一页上。 作为辅助,我可以将后端数据结构中的索引插入到库使用的参数中,以便知道每个元素的索引。

非常感谢。

Page 1

Page 2

Page 3

Page 4

<xsl:stylesheet version="1.0"
                xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
                xmlns:fo="http://www.w3.org/1999/XSL/Format"
                xmlns:fox="http://xmlgraphics.apache.org/fop/extensions">
    <xsl:output indent="yes"/>
    <xsl:strip-space elements="*"/>

    <xsl:template match="/doc">
        <fo:root font-family="Nexus Sans Pro" font-weight="normal">
            <fo:layout-master-set>
                <fo:simple-page-master master-name="Boxes-A4" page- page-
                                       margin-top="0mm" margin-bottom="0mm" margin-left="0mm" margin-right="0mm">
                    <fo:region-body region-name="xsl-region-body"/>
                    <fo:region-before region-name="xsl-region-before" extent="70mm"/>
                </fo:simple-page-master>
                <fo:simple-page-master master-name="Items-A4" page- page-
                                       margin-top="0mm" margin-bottom="0mm" margin-left="0mm" margin-right="0mm">
                    <fo:region-body region-name="xsl-region-body"/>
                    <fo:region-before region-name="xsl-region-before" extent="70mm"/>
                </fo:simple-page-master>
            </fo:layout-master-set>
            <xsl:call-template name="boxes-template"/>
            <xsl:call-template name="items-template"/>
        </fo:root>
    </xsl:template>

    <xsl:template name="boxes-template">
        <fo:page-sequence master-reference="Boxes-A4" font-family="Nexus Sans Pro" font-weight="normal">
            <fo:static-content flow-name="xsl-region-before">
                <fo:block-container border-bottom-
                                    border-bottom-style="solid"
                                    border-bottom-color="rgb(220,220,220)"
                                    position="absolute" top="1mm" left="5mm" right="5mm" >
                    <xsl:variable name="Logo" select="Logo"/>
                    <fo:block-container
                            background-image="url($Logo)"
                            top="5mm" left="5mm"  
                            background-repeat="no-repeat"
                            fox:background-image- fox:background-image-
                            absolute-position="absolute">
                        <fo:block/>
                    </fo:block-container>
                    <xsl:variable name="Stamp" select="Stamp"/>
                    <fo:block-container
                            background-image="url($Stamp)"
                            top="5mm" right="5mm"  
                            background-repeat="no-repeat"
                            fox:background-image- fox:background-image-
                            absolute-position="absolute">
                        <fo:block/>
                    </fo:block-container>
                    <fo:block-container position="absolute" top="8mm" left="80mm"  >
                        <fo:table table-layout="fixed" >
                            <fo:table-column column-/>
                            <fo:table-body>
                                <fo:table-row>
                                    <fo:table-cell >
                                        <fo:block font-family="Nexus Sans Pro Bold"
                                                  text-align="center" font-size="32pt"> Header Text
                                        </fo:block>
                                    </fo:table-cell>
                                </fo:table-row>
                                <fo:table-row>
                                    <fo:table-cell display-align="after" >
                                        <fo:block font-family="Nexus Sans Pro"
                                                  display-align="after" text-align="center" font-size="32pt"
                                                  color="rgb(233,113,28)">
                                            <xsl:value-of select="user"/>
                                        </fo:block>
                                    </fo:table-cell>
                                </fo:table-row>
                            </fo:table-body>
                        </fo:table>
                    </fo:block-container>
                    <fo:block-container position="absolute" top="48mm" left="77mm"  >
                        <fo:table table-layout="fixed" >
                            <fo:table-body>
                                <fo:table-row>
                                    <fo:table-cell >
                                        <fo:block font-family="Nexus Sans Pro" font-weight="normal" font-size="15pt"
                                                  color="rgb(128,128,128)">
                                            From:
                                            <xsl:value-of select="fromDate"/>
                                        </fo:block>
                                    </fo:table-cell>
                                    <fo:table-cell >
                                        <fo:block font-family="Nexus Sans Pro" font-weight="normal" font-size="15pt"
                                                  color="rgb(128,128,128)">
                                            To:
                                            <xsl:value-of select="toDate"/>
                                        </fo:block>
                                    </fo:table-cell>
                                </fo:table-row>
                            </fo:table-body>
                        </fo:table>
                    </fo:block-container>
                </fo:block-container>
            </fo:static-content>

            <fo:flow flow-name="xsl-region-body">
                <fo:block-container position="absolute" top="75mm" left="110mm"  >
                    <fo:table table-layout="fixed" >
                        <fo:table-column column-number="1" column-/>
                        <fo:table-column column-number="2" column-/>
                        <fo:table-body>
                            <fo:table-row>
                                <fo:table-cell >
                                    <fo:block
                                            font-family="Nexus Sans Pro Bold"
                                            font-size="15pt" color="rgb(35,31,32)">
                                        Total number of boxes:
                                    </fo:block>
                                </fo:table-cell>
                                <fo:table-cell >
                                    <fo:block font-family="Nexus Sans Pro Bold"
                                              font-size="15pt" color="rgb(35,31,32)">
                                        <xsl:value-of select="totalNumberOfBoxes"/>
                                    </fo:block>
                                </fo:table-cell>
                            </fo:table-row>
                            <fo:table-row>
                                <fo:table-cell >
                                    <fo:block font-family="Nexus Sans Pro Bold"
                                              font-size="15pt" color="rgb(35,31,32)">
                                        Total number of items:
                                    </fo:block>
                                </fo:table-cell>
                                <fo:table-cell >
                                    <fo:block font-family="Nexus Sans Pro Bold"
                                              font-size="15pt" color="rgb(35,31,32)">
                                        <xsl:value-of select="totalNumberOfItems"/>
                                    </fo:block>
                                </fo:table-cell>
                            </fo:table-row>
                        </fo:table-body>
                    </fo:table>
                </fo:block-container>

                <xsl:for-each select="/doc/box">
                    <xsl:variable name="boxImageURL" select="boxImageURL"/>
                    <fo:block-container position="absolute" top="105mm" left="30mm"  >
                        <fo:table table-layout="fixed" >
                            <fo:table-column column-number="1" column-/>
                            <fo:table-column column-number="2" column-/>
                            <fo:table-column column-number="3" column-/>
                            <fo:table-body>
                                <fo:table-row page-break-inside="auto"
                                              border-top-
                                              border-top-style="solid"
                                              border-top-color="rgb(220,220,220)"
                                              margin-bottom="2mm"
                                              >
                                    <fo:table-cell>
                                        <xsl:if test="$boxImageURL != 'null'">
                                            <fo:block-container
                                                    background-image="url($boxImageURL)"
                                                    top="110mm" right="15mm"  
                                                    background-repeat="no-repeat" margin-top="2mm"
                                                    fox:background-image- fox:background-image->
                                                <fo:block/>
                                            </fo:block-container>
                                        </xsl:if>
                                    </fo:table-cell>
                                    <fo:table-cell display-align="center" >
                                        <fo:block text-align="left"  font-size="16pt" color="rgb(35,31,32)">
                                            <xsl:value-of select="boxTitle"/>
                                        </fo:block>
                                    </fo:table-cell>
                                    <fo:table-cell display-align="center" >
                                        <fo:block text-align="left" font-size="16pt" color="rgb(35,31,32)">
                                            <xsl:value-of select="numberOfItems"/>
                                        </fo:block>
                                    </fo:table-cell>
                                </fo:table-row>
                            </fo:table-body>
                        </fo:table>
                    </fo:block-container>
                </xsl:for-each>
            </fo:flow>
        </fo:page-sequence>
    </xsl:template>

    <xsl:template name="items-template">
        <xsl:variable name="Logo"><xsl:value-of select="Logo"/></xsl:variable>
        <xsl:variable name="Stamp"><xsl:value-of select="Stamp"/></xsl:variable>
        <xsl:variable name="user"><xsl:value-of select="user"/></xsl:variable>
        <xsl:variable name="fromDate"><xsl:value-of select="fromDate"/></xsl:variable>
        <xsl:variable name="toDate"><xsl:value-of select="toDate"/></xsl:variable>
        <xsl:for-each select="/doc/box">
            <xsl:variable name="boxImageURL" select="boxImageURL"/>
            <fo:page-sequence master-reference="Items-A4" font-family="Nexus Sans Pro" font-weight="normal">
                <fo:static-content flow-name="xsl-region-before">
                ... Same header as on the Box-A4
                </fo:static-content>

                <fo:flow flow-name="xsl-region-body">
                    <fo:block-container page-break-before="always"
                                        position="absolute" top="70mm" left="30mm"  >
                        <fo:table page-break-before="always" table-layout="fixed" >
                            <fo:table-column column-number="1" column-/>
                            <fo:table-column column-number="2" column-/>
                            <fo:table-body>
                                <fo:table-row margin-bottom="2mm"
                                              >
                                    <fo:table-cell>
                                        <xsl:if test="$boxImageURL != 'null'">
                                            <fo:block-container
                                                    background-image="url($boxImageURL)"
                                                    top="110mm" right="15mm"  
                                                    background-repeat="no-repeat" margin-top="2mm"
                                                    fox:background-image- fox:background-image->
                                                <fo:block/>
                                            </fo:block-container>
                                        </xsl:if>
                                    </fo:table-cell>
                                    <fo:table-cell display-align="center" >
                                        <fo:block  margin-top="4mm" text-align="left"  font-size="16pt" color="rgb(35,31,32)">
                                            <xsl:value-of select="boxTitle"/>
                                            <fo:block margin-top="4mm" text-align="left" font-size="16pt" color="rgb(35,31,32)">
                                                <xsl:value-of select="numberOfThings"/> things
                                            </fo:block>
                                        </fo:block>
                                    </fo:table-cell>
                                </fo:table-row>
                            </fo:table-body>
                        </fo:table>
                    </fo:block-container>

                    <fo:block-container position="absolute" top="100mm" left="30mm"  >
                        <fo:table page-break-before="always" table-layout="fixed" >
                            <fo:table-column column-number="1" column-/>
                            <fo:table-column column-number="2" column-/>
                            <fo:table-column column-number="3" column-/>
                            <fo:table-body>
                                <fo:table-row border-top-
                                              border-top-style="solid"
                                              border-top-color="rgb(220,220,220)"
                                              margin-bottom="2mm"
                                              >
                                    <fo:table-cell display-align="center" >
                                        <fo:block text-align="left"  font-size="16pt" color="rgb(35,31,32)">
                                            Item title
                                        </fo:block>
                                    </fo:table-cell>
                                    <fo:table-cell display-align="center" >
                                        <fo:block text-align="left"  font-size="16pt" color="rgb(35,31,32)">
                                            Revision
                                        </fo:block>
                                    </fo:table-cell>
                                    <fo:table-cell display-align="center" >
                                        <fo:block text-align="left" font-size="16pt" color="rgb(35,31,32)">
                                            Date completed
                                        </fo:block>
                                    </fo:table-cell>
                                </fo:table-row>
                            </fo:table-body>
                        </fo:table>
                    </fo:block-container>

                    <fo:block-container position="absolute" top="125mm" left="30mm"  >
                        <fo:table page-break-before="always" table-layout="fixed" >
                            <fo:table-column column-number="1" column-/>
                            <fo:table-column column-number="2" column-/>
                            <fo:table-column column-number="3" column-/>
                            <fo:table-body>
                                <xsl:for-each select="items/item">
                                    <fo:table-row keep-together.within-page="always"
                                                  break-after="page"
                                                  border-top-
                                                  border-top-style="solid"
                                                  border-top-color="rgb(220,220,220)"
                                                  margin-bottom="2mm"
                                                  >
                                        <fo:table-cell display-align="center" >
                                            <fo:block text-align="left"  font-size="16pt" color="rgb(35,31,32)">
                                                <xsl:value-of select="itemTitle"/>
                                            </fo:block>
                                        </fo:table-cell>
                                        <fo:table-cell display-align="center" >
                                            <fo:block text-align="left" font-size="16pt" color="rgb(35,31,32)">
                                                <xsl:value-of select="revision"/>
                                            </fo:block>
                                        </fo:table-cell>
                                        <fo:table-cell display-align="center" >
                                            <fo:block text-align="left"  font-size="16pt" color="rgb(35,31,32)">
                                                <xsl:value-of select="dateCompleted"/>
                                            </fo:block>
                                        </fo:table-cell>
                                    </fo:table-row>
                                </xsl:for-each>
                            </fo:table-body>
                        </fo:table>
                    </fo:block-container>
                </fo:flow>
            </fo:page-sequence>
        </xsl:for-each>
    </xsl:template>
</xsl:stylesheet>

我的简化parameters.xml(少了几行):

<doc>
    <Logo>ELogo.jpg</Logo>
    <Stamp>Stamp.jpg</Stamp>
    <backgroundImageURL>bkg.jpg</backgroundImageURL>
    <user>John Richard Edgar Bowens-Robins III</user>
    <fromDate>1st March 2021</fromDate>
    <toDate>31 November 2021</toDate>
    <totalNumberOfBoxes>5</totalNumberOfBoxes>
    <totalNumberOfItems>19</totalNumberOfItems>
    <box>
        <boxImageURL>box1.jpg</boxImageURL>
        <boxTitle>JREBR's Box 1</boxTitle>
        <items>
            <item>
                <itemTitle>The Nice Item 11</itemTitle>
            </item>
            <item>
                <itemTitle>The Nice Item 12</itemTitle>
            </item>
            <item>
                <itemTitle>The Nice Item 13</itemTitle>
            </item>
        </items>
    </box>
    <box>
        <boxImageURL>box2.jpg</boxImageURL>
        <boxTitle>JREBR's Box 2</boxTitle>
        <items>
            <item>
                <itemTitle>The Nice Item 21</itemTitle>
            </item>
            <item>
                <itemTitle>The Nice Item 22</itemTitle>
            </item>
            <item>
                <itemTitle>The Nice Item 23</itemTitle>
            </item>
        </items>
    </box>
</doc>

【问题讨论】:

【参考方案1】:

你真正所说的“第 1 页”、“第 2 页”……你的真正意思是:

第 1 部分是所有框。这可能是 1 到 x 页,具体取决于盒子的数量。所有的页面模板都是一样的,所以这是一个页面序列和一个页面母版。

第 2 节到第 n 节是列出所有项目的每个框的页面序列。所有这些页面序列都是相同的(一个简单的页面母版)。

您没有解释这些标题中的照片 1 和照片 2 是什么,但我认为它们对于每个页面都是相同的。如果这不是真的并且“盒子”和“项目”不同,那么您可能有不同的标题。

所以你只有两个简单的页面主控,一个叫做“盒子”,一个叫做“项目”。您只需创建引用框的简单页面主控的“框”页面序列。您没有提供示例 XML,所以为了简单起见,我们只是说它看起来像这样:

<order>
<box>
    <name>Box 1</name>
    <items>
        <item>Item 1:1</item>
        <item>Item 1:2</item>
        <item>Item 1:3</item>
    </items>
</box>
<box>
    <name>Box 2</name>
    <items>
        <item>Item 2:1</item>
        <item>Item 2:2</item>
        <item>Item 2:3</item>
    </items>
</box>
<box>
    <name>Box 3</name>
    <items>
        <item>Item 3:1</item>
        <item>Item 3:2</item>
        <item>Item 3:3</item>
    </items>
</box>
</order>

然后这个简单的 XSL 会为盒子和盒子中的项目抛出页面:

<xsl:stylesheet version="1.0" 
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:fo="http://www.w3.org/1999/XSL/Format">
<xsl:template match="/">
    <fo:root>
        <fo:layout-master-set>
            <fo:simple-page-master master-name="boxes" page- page->
                <fo:region-body region-name="body" margin-top="0.5in" margin-bottom="0.5in" margin-left="0.5in" margin-right="0.5in"/>
            </fo:simple-page-master>
            <fo:simple-page-master master-name="items" page- page->
                <fo:region-body region-name="body" margin-top="0.5in" margin-bottom="0.5in" margin-left="0.5in" margin-right="0.5in"/>
            </fo:simple-page-master>
        </fo:layout-master-set>
        <xsl:call-template name="boxes"/>
        <xsl:call-template name="items"/>
    </fo:root>
</xsl:template>
<xsl:template name="boxes">
    <fo:page-sequence master-reference="boxes">
        <fo:flow flow-name="body">
        <!-- Output the table of boxes -->
        <xsl:for-each select="/order/box">
            <fo:block>
                <xsl:value-of select="name"/>
            </fo:block>
        </xsl:for-each>
        </fo:flow>
    </fo:page-sequence>
</xsl:template>

<xsl:template name="items">
    <xsl:for-each select="/order/box">
        <fo:page-sequence master-reference="items">
            <fo:flow flow-name="body">
            <xsl:for-each select="items/item">
                <fo:block>
                    <xsl:value-of select="."/>
                </fo:block>
            </xsl:for-each>
            </fo:flow>        
        </fo:page-sequence>
    </xsl:for-each> 
</xsl:template>
</xsl:stylesheet>

这会导致:

将此与@Tony Graham 关于制作表格的建议结合起来,您就拥有了整个布局。如果您想知道自动页面流是如何工作的,这里是上面的确切示例,其中有几个框和一个框(框 4)有更多项目......我只更改了 fo:block 字体大小以导致分页。

【讨论】:

非常感谢布朗先生您非常全面的回复。我在问题中添加了一些代码。每个页面的标题都是相同的。不幸的是,我在 2 个模板中的每一个中都复制了它的代码。我认为没有办法重用它。即使我使用了break-before =“page”,我的第一页不适合的行也不会移动到下一页,但会在第一页上丢失。可能是因为经常使用这样的定位标记: 你好,再次。我已经更改了布局,使其与您建议的布局相同。一切看起来都很好,除了需要继续进入新页面的内容行。它们不在标题下方(在所有页面上都是相同的,并且在静态内容中),而是在它的顶部。必须有一种方法可以将布局分成两部分,第一个用于标题,第二个用于其他所有内容。必要时,其他所有部分的文本应始终在标题部分下方显示在新页面上。请问你知道怎么做吗?谢谢 重复内容应放在静态内容中。这就是你做页眉和页脚的方式。为之前的区域定义一个区域,然后将您的图像和其他内容添加到其中。 再次感谢您的回复和帮助。我的标题放在 内。问题出在 的内容上,当表格行移到下一页时,它们不是在标题下方,而是在它的顶部(想象一下在您在上面附加的照片中,项目 4:5 显示在属于标题的文本顶部,而不是标题下方)。有没有办法在标题下显示该内容? 您是否在使用绝对定位的块容器之前将内容放置在区域中?我们看不到您的代码,但有一个非常简单的规则,除非您想在一个页面上的确切位置印上某些东西,否则您可能做错了。 region-before 是一个很小的区域,将在每一页上重复,只需将您的简单内容布局在一个三列表中。它将在每一页上重复,并且不应与流程重叠,除非您通过增加范围来允许它。【参考方案2】:

使用fo:table,并为每个fo:table-row 指定一个合适的值height (https://www.w3.org/TR/xsl11/#height),以获得所需的每页行数。

您可能还需要指定keep-together.within-page="always" 以避免破坏表格行。 FOP 在fo:table-row(或fo:table-cell)上可能支持也可能不支持。

【讨论】:

非常感谢您的建议。我将这些添加到表格行中。

以上是关于我的多页迭代 PDF 转换器应该使用啥 XSLT:FO 布局?的主要内容,如果未能解决你的问题,请参考以下文章

ImageMagick或GhostScript:将多页TIFF转换为多页PDF

Ghostscript错误使用pdfwrite将多页PS转换为多页PDF

将多页 PDF 转换为单个图像

将多页 PDF 转换为 PNG 并返回 (Linux)

多页Excel转换成PDF时如何保存为单独文件

如何进行dwg转pdf 多页转换