More on PDF Printing and XSL-FO layouts

In two previous articles we described a robust and low-cost PDF printing architecture for APEX, consisting in the OC4J application servers working as a FOP printing processor for XSL-FO. The downside of this printing configuration is the manual design of Apex Report Layouts and the lack of tools to automatize the design. ApexNinjas.com team has designed a simple, beta-style and rudimentary tool for this scope, called ALGEN.

The possibilities of APEX PDF printing configuration using XSL-FO templates are virtually infinite, but they can be reduced to these simple "must-have" features:

1. Customize the header and the footer of the PDF

2. Display paragraphs, tables and images in the PDF body

3. Add conditional display statements, that allow the rendering of the PDF elements depending on items from the APEX application.

A simple example for a PDF document with image header and footer and with a body containing a text paragraph, a table and an image, can be found here (login with demo/demo and press the "Print PDF with images" button). The Report Layout was generated using ALGEN. You can view it by accessing the application and viewing the PDF WITH IMAGE layout.

Customizing Report Layouts

A customized report layout, whether is was customized using a text editor or the ALGEN application, must be pasted in the Report Layout "Page Template" region:

Application Builder -> Shared Components -> Report Layouts

apex ninjas report layout

Choose a previously generated Report Layout and paste the customized XSL-FO content in the "Page Template" region:

apex ninjas report layout 2

Click "Apply Changes". Go to Shared Components -> Report Queries, select the Report for which the Layout was customized and assign the Report Layout to it:

Known Limitations: the 32k limit

The Page Template cannot accept more that 32K length as input. This is a well-known limitation that applies to PLSQL varchar2 variables, which have a maximum size of 32767 bytes. This means that all of the string input fields in APEX application builder map to an internal PLSQL varchar2(32767) variable, as wwv_flow.accept always handles your page items as varchar2(32767). Attempting to insert report layout bigger that 32k will result in a 404 webpage.

This limitation might disappear in future APEX releases, if all www_flow.accept requests will handle page items as CLOBs.

Display images in the PDF body with XSL-FO

Use this code to display an image in the PDF body:

<fo:block text-align="left"><fo:external-graphic height="auto" width="auto" src="http://ziarpiatraneamt.ro/wp-content/uploads/2010/08/ninja.jpg"/></fo:block>

Display images in the PDF header/footer with XSL-FO

Use this code to display an image in the PDF header. This is an example of the entire "region-header" region. The image is displayed inside a rowset that contains only one line (ex.: ROWSET1 is 'select 1 from dual' report query). We use this workaround because, for some reason, images in the header and footer cannot be displayed inside a simple body block, like above.

<fo:static-content flow-name="region-header">

    <fo:table start-indent="0.0pt" width="170mm"> <xsl:variable name="_XDOFOPOS2" select="number(1)"/>

    <xsl:variable name="_XDOFOTOTAL" select="number(1)"/> <fo:table-column column-width="170mm"/>

       <fo:table-header> <fo:table-row> <fo:table-cell> <fo:block> </fo:block> </fo:table-cell> </fo:table-row> </fo:table-header>

       <fo:table-body> <xsl:for-each select="DATA/ROWSET1/ROWSET1_ROW">

            <fo:table-row> <fo:table-cell> <fo:block>

              <fo:external-graphic src="http://localhost:8081/apex/SCH_BLOG.blog_image_display?p_image_id=640"/>

            </fo:block> </fo:table-cell> </fo:table-row> </xsl:for-each> </fo:table-body>

    </fo:table>

</fo:static-content>

This is an example for displaying an image in the footer, similar to the header. The last line (before </fo:static-content>) is for displaying the page numer. You can also customize the way page numbers are displayed.

<fo:static-content flow-name="region-footer">

    <fo:table start-indent="0.0pt" width="170mm"> <xsl:variable name="_XDOFOPOS2" select="number(1)"/>

    <xsl:variable name="_XDOFOTOTAL" select="number(1)"/> <fo:table-column column-width="170mm"/>

        <fo:table-header> <fo:table-row> <fo:table-cell> <fo:block> </fo:block> </fo:table-cell> </fo:table-row> </fo:table-header>

        <fo:table-body> <xsl:for-each select="DATA/ROWSET1/ROWSET1_ROW">

           <fo:table-row> <fo:table-cell> <fo:block>

             <fo:external-graphic src="http://localhost:8081/apex/SCH_BLOG.blog_image_display?p_image_id=642"/>

           </fo:block> </fo:table-cell> </fo:table-row> </xsl:for-each> </fo:table-body>

    </fo:table>

    <fo:block xsl:use-attribute-sets="footer page-number"> <fo:page-number/> </fo:block>

</fo:static-content>

Conditional display in PDF with XSL-FO

To conditional display PDF elements, enclose them into the following tags:

<xsl:if test="DATA/ROWSETx/ROWSETx_ROW/COL_NAME/text()">

</xsl:if>

The if statement can be only used against an existing rowset. In the above example, if the ROWSET does not return any value in the COL_NAME column, it will not render the enclosed XSL_FO statements.

A working example can be tested in the Apex Testing Ground: Switch the "Display Image" select list and check the result by using the "Print PDF With Images + IF" button. You will see that the ninja group image, in the PDF body, is displayed or not, according to the "Display Image" item value.

The full example is listed bellow:

1. the Report Query (Report3):

The Report Query can also be downloaded as an XML from APEX 4.0.

2. the Report Layout (ALGEN with image + IF), the "Page Template" source:

<?xml version = '1.0' encoding = 'utf-8'?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:fo="http://www.w3.org/1999/XSL/Format" xmlns:xlink="http://www.w3.org/1999/xlink">
    <xsl:variable name="_XDOFOPOS" select="''"/>
    <xsl:variable name="_XDOFOPOS2" select="number(1)"/>
    <xsl:variable name="_XDOFOTOTAL" select="number(1)"/>
    <xsl:variable name="_XDOFOOSTOTAL" select="number(0)"/>
    <xsl:attribute-set name="padding">
            <xsl:attribute name="padding-bottom">0.25pt</xsl:attribute>
            <xsl:attribute name="padding-top">0.25pt</xsl:attribute>
  </xsl:attribute-set>
    <xsl:attribute-set name="text">
                <xsl:attribute name="text-align">start</xsl:attribute>
                <xsl:attribute name="orphans">2</xsl:attribute>
                <xsl:attribute name="start-indent">0.0pt</xsl:attribute>
                <xsl:attribute name="linefeed-treatment">preserve</xsl:attribute>
                <xsl:attribute name="padding-top">0.0pt</xsl:attribute>
                <xsl:attribute name="end-indent">0.0pt</xsl:attribute>
              <xsl:attribute name="padding-bottom">0.0pt</xsl:attribute>
              <xsl:attribute name="height">0.0pt</xsl:attribute>
              <xsl:attribute name="widows">2</xsl:attribute>
  </xsl:attribute-set>
  <xsl:attribute-set name="align-left">
      <xsl:attribute name="text-align">left</xsl:attribute>
  </xsl:attribute-set>
  <xsl:attribute-set name="align-center">
      <xsl:attribute name="text-align">center</xsl:attribute>
  </xsl:attribute-set>
  <xsl:attribute-set name="align-right">
  <xsl:attribute name="text-align">right</xsl:attribute>
  </xsl:attribute-set><xsl:attribute-set name="footer">
      <xsl:attribute name="text-align">right</xsl:attribute>
      <xsl:attribute name="start-indent">0.4pt</xsl:attribute>
      <xsl:attribute name="end-indent">0.4pt</xsl:attribute>
  </xsl:attribute-set><xsl:attribute-set name="text_2">
      <xsl:attribute name="start-indent">5.4pt</xsl:attribute>
      <xsl:attribute name="end-indent">23.4pt</xsl:attribute> </xsl:attribute-set>
          <xsl:attribute-set name="text_20">
          <xsl:attribute name="height">13.872pt</xsl:attribute>
          <xsl:attribute name="end-indent">5.4pt</xsl:attribute>
  </xsl:attribute-set>
  <xsl:attribute-set name="text_0">
      <xsl:attribute name="end-indent">5.4pt</xsl:attribute>
  </xsl:attribute-set>
  <xsl:attribute-set name="page-footer">
        <xsl:attribute name="color">black</xsl:attribute>
        <xsl:attribute name="font-family">Arial</xsl:attribute>
        <xsl:attribute name="white-space-collapse">false</xsl:attribute>
        <xsl:attribute name="font-size">6pt</xsl:attribute>
        <xsl:attribute name="font-weight">normal</xsl:attribute>
  </xsl:attribute-set>
  <xsl:attribute-set name="body-font">
        <xsl:attribute name="height">5pt</xsl:attribute>
        <xsl:attribute name="font-family">arial</xsl:attribute>
        <xsl:attribute name="white-space-collapse">false</xsl:attribute>
        <xsl:attribute name="font-size">10pt</xsl:attribute>
        <xsl:attribute name="font-weight">normal</xsl:attribute>
  </xsl:attribute-set>
  <xsl:attribute-set name="page-number">
        <xsl:attribute name="font-family">arial</xsl:attribute>
        <xsl:attribute name="font-size">10pt</xsl:attribute>
        <xsl:attribute name="font-weight">normal</xsl:attribute> <
        xsl:attribute name="color">black</xsl:attribute>
  </xsl:attribute-set>
  <xsl:attribute-set name="border">
      <xsl:attribute name="border-top">1pt solid black</xsl:attribute>
        <xsl:attribute name="border-bottom">1pt solid black</xsl:attribute>
        <xsl:attribute name="border-start-width">1pt</xsl:attribute>
        <xsl:attribute name="border-start-color">black</xsl:attribute>
        <xsl:attribute name="border-start-style">solid</xsl:attribute>
        <xsl:attribute name="border-end-width">1pt</xsl:attribute>
        <xsl:attribute name="border-end-color">black</xsl:attribute>
        <xsl:attribute name="border-end-style">solid</xsl:attribute>
  </xsl:attribute-set>
  <xsl:attribute-set name="cell">
       <xsl:attribute name="background-color">white</xsl:attribute>
       <xsl:attribute name="color">black</xsl:attribute>
       <xsl:attribute name="padding-start">0pt</xsl:attribute>
       <xsl:attribute name="vertical-align">top</xsl:attribute>
       <xsl:attribute name="padding-top">0pt</xsl:attribute>
       <xsl:attribute name="padding-end">0</xsl:attribute>
       <xsl:attribute name="number-columns-spanned">1</xsl:attribute>
       <xsl:attribute name="height">10pt</xsl:attribute>
       <xsl:attribute name="padding-bottom">0pt</xsl:attribute>
       <xsl:attribute name="font-size">8pt</xsl:attribute>
    </xsl:attribute-set><xsl:attribute-set name="header-color">
         <xsl:attribute name="background-color">yellow</xsl:attribute>
         <xsl:attribute name="color">black</xsl:attribute>
    </xsl:attribute-set><xsl:template match="/">
    
    <fo:root>
        <fo:layout-master-set>
            <fo:simple-page-master master-name="A4" margin-left="25mm" margin-right="20mm" page-height="297mm" page-width="210mm" margin-top="10mm" margin-bottom="10mm">
                <fo:region-before region-name="region-header" extent="54.0pt"/>
                <fo:region-body region-name="region-body" margin-top="54.0pt" margin-bottom="54.0pt"/>
                <fo:region-after region-name="region-footer" extent="54.0pt" display-align="after"/>
            </fo:simple-page-master>
      </fo:layout-master-set>
    
    <fo:page-sequence master-reference="A4">
        <xsl:variable name="_PW" select="number(842)"/>
            <xsl:variable name="_PH" select="number(595)"/>
         <xsl:variable name="_ML" select="number(72.0)"/>
            <xsl:variable name="_MR" select="number(72.0)"/>
            <xsl:variable name="_MT" select="number(90.0)"/>
            <xsl:variable name="_MB" select="number(90.0)"/>
            <xsl:variable name="_HY" select="number(36.0)"/>
            <xsl:variable name="_FY" select="number(36.0)"/>
            <xsl:variable name="_SECTION_NAME" select="string('A4')"/>

  <fo:static-content flow-name="region-header">
      <fo:table start-indent="0.0pt" width="170mm">
          <xsl:variable name="_XDOFOPOS2" select="number(1)"/> <xsl:variable name="_XDOFOTOTAL" select="number(1)"/>
              <fo:table-column column-width="170mm"/>
                  <fo:table-header>
                      <fo:table-row><fo:table-cell><fo:block></fo:block></fo:table-cell> </fo:table-row>
                  </fo:table-header>
              <fo:table-body>
       <xsl:for-each select="DATA/ROWSET1/ROWSET1_ROW">
           <fo:table-row>
               <fo:table-cell>
                   <fo:block><fo:external-graphic src="http://localhost:8081/apex/SCH_BLOG.blog_image_display?p_image_id=640"/>
                   </fo:block>
               </fo:table-cell>
           </fo:table-row>
       </xsl:for-each>
       </fo:table-body></fo:table></fo:static-content>
       
       <fo:static-content flow-name="region-footer"><fo:table start-indent="0.0pt" width="170mm">
           <xsl:variable name="_XDOFOPOS2" select="number(1)"/> <xsl:variable name="_XDOFOTOTAL" select="number(1)"/>
               <fo:table-column column-width="170mm"/>
                   <fo:table-header> <fo:table-row><fo:table-cell><fo:block></fo:block></fo:table-cell> </fo:table-row> </fo:table-header>
                   <fo:table-body> <xsl:for-each select="DATA/ROWSET1/ROWSET1_ROW"> <fo:table-row> <fo:table-cell><fo:block>
                       <fo:external-graphic src="http://localhost:8081/apex/SCH_BLOG.blog_image_display?p_image_id=642"/></fo:block>
                   </fo:table-cell> </fo:table-row></xsl:for-each></fo:table-body></fo:table>
         <fo:block xsl:use-attribute-sets="footer page-number"><fo:page-number/></fo:block></fo:static-content>
        
         <fo:flow flow-name="region-body"
             <fo:block font-weight="normal" font-size="14pt" font-family="arial" text-align="center" color="blue" text-decoration="none" font-style="normal">This is demonstration on how images can be used in XSL-FO based PDF documents!</fo:block>
             <fo:block color="white">.</fo:block>
             <fo:block xsl:use-attribute-sets="padding">
                 <fo:table start-indent="0.0pt" width="100mm">
                     <xsl:variable name="_XDOFOPOS2" select="number(1)"/>
                     <xsl:variable name="_XDOFOTOTAL" select="number(1)"/>
                 <fo:table-column column-width="60mm"/>
                 <fo:table-column column-width="40mm"/>
             <fo:table-header> <fo:table-row>
                 <fo:table-cell xsl:use-attribute-sets="border header-color"><fo:block>Country</fo:block></fo:table-cell>
                 <fo:table-cell xsl:use-attribute-sets="border header-color"><fo:block>Region Name</fo:block></fo:table-cell>
                              </fo:table-row>
             </fo:table-header>
            
             <fo:table-body> <xsl:for-each select="DATA/ROWSET2/ROWSET2_ROW">
                 <fo:table-row>
                     <fo:table-cell xsl:use-attribute-sets="border cell"><fo:block><xsl:value-of select="COUNTRY_NAME"/></fo:block></fo:table-cell>
                     <fo:table-cell xsl:use-attribute-sets="border cell"><fo:block><xsl:value-of select="REGION_NAME"/></fo:block></fo:table-cell>
                 </fo:table-row>
             </xsl:for-each> </fo:table-body> </fo:table> </fo:block>

<xsl:if test="DATA/ROWSET4/ROWSET4_ROW/DISPL/text()">
 <fo:block text-align="left">
      <fo:external-graphic height="auto" width="auto" src="http://ziarpiatraneamt.ro/wp-content/uploads/2010/08/ninja.jpg"/>
 </fo:block>
</xsl:if>

<fo:block xsl:use-attribute-sets="text text_2 text_20">
    <fo:inline id="{concat('page-total-', $_SECTION_NAME, $_XDOFOPOS)}"/>
  <fo:inline id="{concat('page-total', $_XDOFOPOS)}"/>
</fo:block>
</fo:flow>
</fo:page-sequence>
</fo:root>
</xsl:template>
</xsl:stylesheet>

Leave a Reply