]> source.dussan.org Git - xmlgraphics-fop.git/commitdiff
Added support for the role property on fo:page-sequence, fo:flow and fo:static-content.
authorVincent Hennebert <vhennebert@apache.org>
Fri, 25 May 2012 15:15:28 +0000 (15:15 +0000)
committerVincent Hennebert <vhennebert@apache.org>
Fri, 25 May 2012 15:15:28 +0000 (15:15 +0000)
This allows to change the mapping of those FOs to PDF structure types when enabling accessibility.

Suggested by Martin Koegler as part of Bugzilla #50852

git-svn-id: https://svn.apache.org/repos/asf/xmlgraphics/fop/trunk@1342680 13f79535-47bb-0310-9956-ffa450edef68

14 files changed:
src/java/org/apache/fop/accessibility/DummyStructureTreeEventHandler.java
src/java/org/apache/fop/accessibility/StructureTree2SAXEventAdapter.java
src/java/org/apache/fop/accessibility/StructureTreeEventHandler.java
src/java/org/apache/fop/accessibility/fo/StructureTreeEventTrigger.java
src/java/org/apache/fop/fo/pagination/AbstractPageSequence.java
src/java/org/apache/fop/fo/pagination/Flow.java
src/java/org/apache/fop/render/intermediate/IFParser.java
src/java/org/apache/fop/render/intermediate/IFStructureTreeBuilder.java
src/java/org/apache/fop/render/pdf/FOToPDFRoleMap.java
src/java/org/apache/fop/render/pdf/PDFLogicalStructureHandler.java
src/java/org/apache/fop/render/pdf/PDFStructureTreeBuilder.java
status.xml
test/java/org/apache/fop/render/intermediate/IFStructureTreeBuilderTestCase.java
test/pdf/accessibility/role.fo

index 66eaece89e843dbe305d306d197bf012b5408f6b..c63c721e5169a915f3d9dc16c27bb14abcdb5013 100644 (file)
@@ -34,7 +34,7 @@ public final class DummyStructureTreeEventHandler implements StructureTreeEventH
     private DummyStructureTreeEventHandler() { }
 
     /** {@inheritDoc} */
-    public void startPageSequence(Locale locale) {
+    public void startPageSequence(Locale locale, String role) {
     }
 
     /** {@inheritDoc} */
index 79c589f9b71206d7208a329e6e39b3387c127383..7a375ef2002771270ec01bd34981811c618b6929 100644 (file)
@@ -30,6 +30,7 @@ import org.apache.fop.fo.FOElementMapping;
 import org.apache.fop.fo.extensions.ExtensionElementMapping;
 import org.apache.fop.fo.extensions.InternalElementMapping;
 import org.apache.fop.render.intermediate.IFConstants;
+import org.apache.fop.util.XMLConstants;
 
 /**
  * Converts structure tree events to SAX events.
@@ -52,16 +53,19 @@ public final class StructureTree2SAXEventAdapter implements StructureTreeEventHa
     }
 
     /** {@inheritDoc} */
-    public void startPageSequence(Locale locale) {
+    public void startPageSequence(Locale locale, String role) {
         try {
-
+            AttributesImpl attributes = new AttributesImpl();
+            if (role != null) {
+                attributes.addAttribute("", "type", "type", XMLConstants.CDATA, role);
+            }
             contentHandler.startPrefixMapping(
                     InternalElementMapping.STANDARD_PREFIX, InternalElementMapping.URI);
             contentHandler.startPrefixMapping(
                     ExtensionElementMapping.STANDARD_PREFIX, ExtensionElementMapping.URI);
             contentHandler.startElement(IFConstants.NAMESPACE,
                     IFConstants.EL_STRUCTURE_TREE, IFConstants.EL_STRUCTURE_TREE,
-                    new AttributesImpl());
+                    attributes);
         } catch (SAXException e) {
             throw new RuntimeException(e);
         }
index 4b94d61f1e507531c9cc0b11e2514ba9d2e8d397..46b724626eb387614c24c8f90260bdc01558d693 100644 (file)
@@ -34,8 +34,9 @@ public interface StructureTreeEventHandler {
      * Starts a page sequence structure tree node.
      *
      * @param locale The locale of the page sequence
+     * @param role the value of the role property. May be null.
      */
-    void startPageSequence(Locale locale);
+    void startPageSequence(Locale locale, String role);
 
     /**
      * Starts a structure tree node.
index 957f408700d4fa05d5cc144fba64f815e2605396..51f7f2bed4b8bc74b2059457419f574e137751e4 100644 (file)
@@ -89,7 +89,8 @@ class StructureTreeEventTrigger extends FOEventHandler {
                 locale = new Locale(pageSeq.getLanguage());
             }
         }
-        structureTreeEventHandler.startPageSequence(locale);
+        String role = pageSeq.getCommonAccessibility().getRole();
+        structureTreeEventHandler.startPageSequence(locale, role);
     }
 
     @Override
index 8640ab19b645143ff86c6968057067c0d8d9be1f..d1ccf7273055139a88ec5b9f6c1156e7cccad8e4 100644 (file)
@@ -24,6 +24,8 @@ import org.apache.fop.datatypes.Numeric;
 import org.apache.fop.fo.FONode;
 import org.apache.fop.fo.FObj;
 import org.apache.fop.fo.PropertyList;
+import org.apache.fop.fo.properties.CommonAccessibility;
+import org.apache.fop.fo.properties.CommonAccessibilityHolder;
 
 /**
  * Abstract base class for the <a href="http://www.w3.org/TR/xsl/#fo_page-sequence">
@@ -31,9 +33,8 @@ import org.apache.fop.fo.PropertyList;
  * <a href="http://xmlgraphics.apache.org/fop/0.95/extensions.html#external-document">
  * <code>fox:external-document</code></a> extension object.
  */
-public abstract class AbstractPageSequence extends FObj {
+public abstract class AbstractPageSequence extends FObj implements CommonAccessibilityHolder {
 
-    // The value of properties relevant for fo:page-sequence.
     /** initial page number */
     protected Numeric initialPageNumber;
     /** forced page count */
@@ -42,11 +43,12 @@ public abstract class AbstractPageSequence extends FObj {
     private int letterValue;
     private char groupingSeparator;
     private int groupingSize;
-    private Numeric referenceOrientation; //XSL 1.1
+    private Numeric referenceOrientation;
     private String language;
     private String country;
     private String numberConversionFeatures;
-    // End of property values
+
+    private CommonAccessibility commonAccessibility;
 
     private PageNumberGenerator pageNumberGenerator;
 
@@ -76,6 +78,7 @@ public abstract class AbstractPageSequence extends FObj {
         language = pList.get(PR_LANGUAGE).getString();
         country = pList.get(PR_COUNTRY).getString();
         numberConversionFeatures = pList.get(PR_X_NUMBER_CONVERSION_FEATURES).getString();
+        commonAccessibility = CommonAccessibility.getInstance(pList);
     }
 
     /** {@inheritDoc} */
@@ -130,6 +133,10 @@ public abstract class AbstractPageSequence extends FObj {
         return pageNumberGenerator.makeFormattedPageNumber(pageNumber);
     }
 
+    public CommonAccessibility getCommonAccessibility() {
+        return commonAccessibility;
+    }
+
     /**
      * Public accessor for the ancestor Root.
      * @return the ancestor Root
index 153f06fc1f58b8912dbce89c44129d2a2e529228..981485e3208a41370a7c06ccca3833cd9015828f 100644 (file)
@@ -26,16 +26,19 @@ import org.apache.fop.fo.FONode;
 import org.apache.fop.fo.FObj;
 import org.apache.fop.fo.PropertyList;
 import org.apache.fop.fo.ValidationException;
+import org.apache.fop.fo.properties.CommonAccessibility;
+import org.apache.fop.fo.properties.CommonAccessibilityHolder;
 
 /**
  * Class modelling the <a href="http://www.w3.org/TR/xsl/#fo_flow">
  * <code>fo:flow</code></a> object.
  *
  */
-public class Flow extends FObj {
-    // The value of properties relevant for fo:flow.
+public class Flow extends FObj implements CommonAccessibilityHolder {
+
     private String flowName;
-    // End of property values
+
+    private CommonAccessibility commonAccessibility;
 
     /** used for FO validation */
     private boolean blockItemFound = false;
@@ -52,6 +55,7 @@ public class Flow extends FObj {
     public void bind(PropertyList pList) throws FOPException {
         super.bind(pList);
         flowName = pList.get(PR_FLOW_NAME).getString();
+        commonAccessibility = CommonAccessibility.getInstance(pList);
     }
 
     /** {@inheritDoc} */
@@ -120,6 +124,10 @@ public class Flow extends FObj {
         return flowName;
     }
 
+    public CommonAccessibility getCommonAccessibility() {
+        return commonAccessibility;
+    }
+
     /** {@inheritDoc} */
     public String getLocalName() {
         return "flow";
index 33e6931dedaf3d3f14c858eebc42287addf1cd6c..827eec8206cb9942611001584fcee7e647bd714b 100644 (file)
@@ -156,7 +156,7 @@ public class IFParser implements IFConstants {
 
         private ContentHandler navParser;
 
-        private ContentHandler structureTreeHandler;
+        private StructureTreeHandler structureTreeHandler;
 
         private Attributes pageSequenceAttributes;
 
@@ -165,12 +165,18 @@ public class IFParser implements IFConstants {
 
         private final class StructureTreeHandler extends DefaultHandler {
 
+            private final Locale pageSequenceLanguage;
+
             private final StructureTreeEventHandler structureTreeEventHandler;
 
             private StructureTreeHandler(StructureTreeEventHandler structureTreeEventHandler,
                     Locale pageSequenceLanguage) throws SAXException {
+                this.pageSequenceLanguage = pageSequenceLanguage;
                 this.structureTreeEventHandler = structureTreeEventHandler;
-                structureTreeEventHandler.startPageSequence(pageSequenceLanguage);
+            }
+
+            void startStructureTree(String type) {
+                structureTreeEventHandler.startPageSequence(pageSequenceLanguage, type);
             }
 
             public void endDocument() throws SAXException {
@@ -263,6 +269,8 @@ public class IFParser implements IFConstants {
 
                     } else if (localName.equals(EL_STRUCTURE_TREE)) {
                         if (userAgent.isAccessibilityEnabled()) {
+                            String type = attributes.getValue("type");
+                            structureTreeHandler.startStructureTree(type);
                             delegate = structureTreeHandler;
                         } else {
                             /* Delegate to a handler that does nothing */
index 3d98859148099cf4c91a5db9d39a467086ee1a33..9ba9afd81b22bdea97840f063151754d6bb6c30a 100644 (file)
@@ -189,11 +189,11 @@ final class IFStructureTreeBuilder implements StructureTreeEventHandler {
         pageSequenceEventRecorders.get(pageSequenceIndex).replay(handler);
     }
 
-    public void startPageSequence(Locale locale) {
+    public void startPageSequence(Locale locale, String role) {
         SAXEventRecorder eventRecorder = new SAXEventRecorder();
         pageSequenceEventRecorders.add(eventRecorder);
         delegate = StructureTree2SAXEventAdapter.newInstance(eventRecorder);
-        delegate.startPageSequence(locale);
+        delegate.startPageSequence(locale, role);
     }
 
     public void endPageSequence() {
index 9b76d0620d9871bd811ebde9dddc0e7a2bbff164..0f752e886d65ca8a85b0b779a9507162a6789108 100644 (file)
@@ -103,9 +103,9 @@ final class FOToPDFRoleMap {
         addStructureType("Formula");
         addStructureType("Form");
 
-        NON_STRUCT = (PDFName) STANDARD_STRUCTURE_TYPES.get("NonStruct");
+        NON_STRUCT = STANDARD_STRUCTURE_TYPES.get("NonStruct");
         assert NON_STRUCT != null;
-        THEAD = (PDFName) STANDARD_STRUCTURE_TYPES.get("THead");
+        THEAD = STANDARD_STRUCTURE_TYPES.get("THead");
         assert THEAD != null;
 
         // Create the standard mappings
@@ -155,7 +155,7 @@ final class FOToPDFRoleMap {
     }
 
     private static void addMapping(String fo, String structureType) {
-        PDFName type = (PDFName) STANDARD_STRUCTURE_TYPES.get(structureType);
+        PDFName type = STANDARD_STRUCTURE_TYPES.get(structureType);
         assert type != null;
         addMapping(fo, new SimpleMapper(type));
     }
@@ -165,21 +165,6 @@ final class FOToPDFRoleMap {
     }
 
 
-    /**
-     * Maps a Formatting Object to a PDFName representing the associated structure type.
-     * @param fo the formatting object's local name
-     * @param parent the parent of the structure element to be mapped
-     * @return the structure type or null if no match could be found
-     */
-    public static PDFName mapFormattingObject(String fo, PDFObject parent) {
-        Mapper mapper = (Mapper) DEFAULT_MAPPINGS.get(fo);
-        if (mapper != null) {
-            return mapper.getStructureType(parent);
-        } else {
-            return NON_STRUCT;
-        }
-    }
-
     /**
      * Maps a Formatting Object to a PDFName representing the associated structure type.
      * @param fo the formatting object's local name
@@ -192,11 +177,11 @@ final class FOToPDFRoleMap {
             PDFObject parent, EventBroadcaster eventBroadcaster) {
         PDFName type = null;
         if (role == null) {
-            type = mapFormattingObject(fo, parent);
+            type = getDefaultMappingFor(fo, parent);
         } else {
-            type = (PDFName) STANDARD_STRUCTURE_TYPES.get(role);
+            type = STANDARD_STRUCTURE_TYPES.get(role);
             if (type == null) {
-                type = mapFormattingObject(fo, parent);
+                type = getDefaultMappingFor(fo, parent);
                 PDFEventProducer.Provider.get(eventBroadcaster).nonStandardStructureType(fo,
                         fo, role, type.toString().substring(1));
             }
@@ -205,6 +190,21 @@ final class FOToPDFRoleMap {
         return type;
     }
 
+    /**
+     * Maps a Formatting Object to a PDFName representing the associated structure type.
+     * @param fo the formatting object's local name
+     * @param parent the parent of the structure element to be mapped
+     * @return the structure type or NonStruct if no match could be found
+     */
+    private static PDFName getDefaultMappingFor(String fo, PDFObject parent) {
+        Mapper mapper = DEFAULT_MAPPINGS.get(fo);
+        if (mapper != null) {
+            return mapper.getStructureType(parent);
+        } else {
+            return NON_STRUCT;
+        }
+    }
+
     private interface Mapper {
         PDFName getStructureType(PDFObject parent);
     }
@@ -226,14 +226,13 @@ final class FOToPDFRoleMap {
     private static class TableCellMapper implements Mapper {
 
         public PDFName getStructureType(PDFObject parent) {
-            PDFStructElem grandParent = (PDFStructElem)
-                ((PDFStructElem) parent).getParentStructElem();
+            PDFStructElem grandParent = ((PDFStructElem) parent).getParentStructElem();
             //TODO What to do with cells from table-footer? Currently they are mapped on TD.
             PDFName type;
             if (THEAD.equals(grandParent.getStructureType())) {
-               type = (PDFName) STANDARD_STRUCTURE_TYPES.get("TH");
+               type = STANDARD_STRUCTURE_TYPES.get("TH");
             } else {
-                type = (PDFName) STANDARD_STRUCTURE_TYPES.get("TD");
+                type = STANDARD_STRUCTURE_TYPES.get("TD");
             }
             assert type != null;
             return type;
index 1c9f9b49d05f46a7d5c6f0bc45bf59b10abb2657..6559e8d56f2b4fa71d3195676f9bdc296804effa 100644 (file)
@@ -19,8 +19,6 @@
 
 package org.apache.fop.render.pdf;
 
-import java.util.Locale;
-
 import org.apache.fop.pdf.PDFArray;
 import org.apache.fop.pdf.PDFDictionary;
 import org.apache.fop.pdf.PDFDocument;
@@ -29,7 +27,6 @@ import org.apache.fop.pdf.PDFName;
 import org.apache.fop.pdf.PDFPage;
 import org.apache.fop.pdf.PDFParentTree;
 import org.apache.fop.pdf.PDFStructElem;
-import org.apache.fop.pdf.PDFStructTreeRoot;
 
 
 /**
@@ -59,8 +56,6 @@ class PDFLogicalStructureHandler {
      */
     private PDFArray pageParentTreeArray;
 
-    private PDFStructElem rootStructureElement;
-
     /**
      * Class providing the necessary information for bracketing content
      * associated to a structure element as a marked-content sequence.
@@ -95,22 +90,10 @@ class PDFLogicalStructureHandler {
      */
     PDFLogicalStructureHandler(PDFDocument pdfDoc) {
         this.pdfDoc = pdfDoc;
-        PDFStructTreeRoot structTreeRoot = pdfDoc.makeStructTreeRoot(parentTree);
-        rootStructureElement = pdfDoc.makeStructureElement(
-                FOToPDFRoleMap.mapFormattingObject("root", structTreeRoot), structTreeRoot);
-        structTreeRoot.addKid(rootStructureElement);
     }
 
-
-    PDFStructElem createPageSequence(Locale language) {
-        PDFStructElem structElemPart = pdfDoc.makeStructureElement(
-                FOToPDFRoleMap.mapFormattingObject("page-sequence", rootStructureElement),
-                rootStructureElement);
-        rootStructureElement.addKid(structElemPart);
-        if (language != null) {
-            structElemPart.setLanguage(language);
-        }
-        return structElemPart;
+    PDFParentTree getParentTree() {
+        return parentTree;
     }
 
     private int getNextParentTreeKey() {
index 2a2a4a392e0b523edcc9e192b7603e3f0c2ec579..3839d47bc7cb855fd90e37a4a718eaabc7ee2434 100644 (file)
@@ -29,7 +29,11 @@ import org.apache.fop.accessibility.StructureTreeEventHandler;
 import org.apache.fop.events.EventBroadcaster;
 import org.apache.fop.fo.extensions.ExtensionElementMapping;
 import org.apache.fop.pdf.PDFFactory;
+import org.apache.fop.pdf.PDFName;
+import org.apache.fop.pdf.PDFObject;
+import org.apache.fop.pdf.PDFParentTree;
 import org.apache.fop.pdf.PDFStructElem;
+import org.apache.fop.pdf.PDFStructTreeRoot;
 
 class PDFStructureTreeBuilder implements StructureTreeEventHandler {
 
@@ -41,21 +45,42 @@ class PDFStructureTreeBuilder implements StructureTreeEventHandler {
 
     private LinkedList<PDFStructElem> ancestors = new LinkedList<PDFStructElem>();
 
+    private PDFStructElem rootStructureElement;
+
     void setPdfFactory(PDFFactory pdfFactory) {
         this.pdfFactory = pdfFactory;
     }
 
     void setLogicalStructureHandler(PDFLogicalStructureHandler logicalStructureHandler) {
         this.logicalStructureHandler = logicalStructureHandler;
+        createRootStructureElement();
+    }
+
+    private void createRootStructureElement() {
+        assert rootStructureElement == null;
+        PDFParentTree parentTree = logicalStructureHandler.getParentTree();
+        PDFStructTreeRoot structTreeRoot = pdfFactory.getDocument().makeStructTreeRoot(parentTree);
+        rootStructureElement = createStructureElement("root", structTreeRoot, null);
+        structTreeRoot.addKid(rootStructureElement);
     }
 
     void setEventBroadcaster(EventBroadcaster eventBroadcaster) {
         this.eventBroadcaster = eventBroadcaster;
     }
 
-    public void startPageSequence(Locale locale) {
+    public void startPageSequence(Locale language, String role) {
         ancestors = new LinkedList<PDFStructElem>();
-        ancestors.add(logicalStructureHandler.createPageSequence(locale));
+        PDFStructElem structElem = createStructureElement("page-sequence", rootStructureElement, role);
+        if (language != null) {
+            structElem.setLanguage(language);
+        }
+        rootStructureElement.addKid(structElem);
+        ancestors.add(structElem);
+    }
+
+    private PDFStructElem createStructureElement(String name, PDFObject parent, String role) {
+        PDFName structureType = FOToPDFRoleMap.mapFormattingObject(name, role, parent, eventBroadcaster);
+        return pdfFactory.getDocument().makeStructureElement(structureType, parent);
     }
 
     public void endPageSequence() {
@@ -64,12 +89,10 @@ class PDFStructureTreeBuilder implements StructureTreeEventHandler {
     public StructureTreeElement startNode(String name, Attributes attributes) {
         PDFStructElem parent = ancestors.getFirst();
         String role = attributes.getValue("role");
-        PDFStructElem created;
-        created = pdfFactory.getDocument().makeStructureElement(
-                FOToPDFRoleMap.mapFormattingObject(name, role, parent, eventBroadcaster), parent);
-        parent.addKid(created);
-        ancestors.addFirst(created);
-        return created;
+        PDFStructElem structElem = createStructureElement(name, parent, role);
+        parent.addKid(structElem);
+        ancestors.addFirst(structElem);
+        return structElem;
     }
 
     public void endNode(String name) {
@@ -83,34 +106,30 @@ class PDFStructureTreeBuilder implements StructureTreeEventHandler {
     public StructureTreeElement startImageNode(String name, Attributes attributes) {
         PDFStructElem parent = ancestors.getFirst();
         String role = attributes.getValue("role");
-        PDFStructElem created;
-        created = pdfFactory.getDocument().makeStructureElement(
-                FOToPDFRoleMap.mapFormattingObject(name, role, parent, eventBroadcaster), parent);
-        parent.addKid(created);
+        PDFStructElem structElem = createStructureElement(name, parent, role);
+        parent.addKid(structElem);
         String altTextNode = attributes.getValue(ExtensionElementMapping.URI, "alt-text");
         if (altTextNode != null) {
-            created.put("Alt", altTextNode);
+            structElem.put("Alt", altTextNode);
         } else {
-            created.put("Alt", "No alternate text specified");
+            structElem.put("Alt", "No alternate text specified");
         }
-        ancestors.addFirst(created);
-        return created;
+        ancestors.addFirst(structElem);
+        return structElem;
     }
 
     public StructureTreeElement startReferencedNode(String name, Attributes attributes) {
         PDFStructElem parent = ancestors.getFirst();
         String role = attributes.getValue("role");
-        PDFStructElem created;
+        PDFStructElem structElem;
         if ("#PCDATA".equals(name)) {
-            created = new PDFStructElem.Placeholder(parent, name);
+            structElem = new PDFStructElem.Placeholder(parent, name);
         } else {
-            created = pdfFactory.getDocument().makeStructureElement(
-                    FOToPDFRoleMap.mapFormattingObject(name, role, parent,
-                            eventBroadcaster), parent);
+            structElem = createStructureElement(name, parent, role);
         }
-        parent.addKid(created);
-        ancestors.addFirst(created);
-        return created;
+        parent.addKid(structElem);
+        ancestors.addFirst(structElem);
+        return structElem;
     }
 
 }
index f7186e0953a7e61ea187c7de1f6497f8891fabe3..fc5b5c6308976057c2208fcbbde0ef4732c3e335 100644 (file)
       documents. Example: the fix of marks layering will be such a case when it's done.
     -->
     <release version="FOP Trunk" date="TBD">
+      <action context="Code" dev="VH" type="add">
+        Added support for the role property on fo:page-sequence, fo:flow and fo:static-content. This 
+        allows to change the mapping of those FOs to PDF structure types when enabling 
+        accessibility.
+      </action>
       <action context="Renderers" dev="VH" type="fix" due-to="Martin Koegler">
         Stop outputting the Type entry for structure elements in order to create a smaller PDF 
         ouptut when accessibility is enabled.
index 65c6b25a6b7f5fa3ef05d2ca9daa8d08f17bfddb..6819fed794daf5596f34a09489e7db1f8295f0f8 100644 (file)
@@ -61,7 +61,7 @@ public class IFStructureTreeBuilderTestCase {
             // Expected
         }
 
-        sut.startPageSequence(null);
+        sut.startPageSequence(null, null);
         sut.endPageSequence();
 
         sut.replayEventsForPageSequence(handler, 0);
@@ -89,7 +89,7 @@ public class IFStructureTreeBuilderTestCase {
         final String nodeName = "block";
         final ContentHandler handler = mock(ContentHandler.class);
 
-        sut.startPageSequence(null);
+        sut.startPageSequence(null, null);
         sut.startNode(nodeName, createSimpleAttributes(attributes));
         sut.endPageSequence();
 
@@ -105,7 +105,7 @@ public class IFStructureTreeBuilderTestCase {
         final String nodeName = "block";
         final ContentHandler handler = mock(ContentHandler.class);
 
-        sut.startPageSequence(null);
+        sut.startPageSequence(null, null);
         sut.endNode(nodeName);
         sut.endPageSequence();
 
index ced8a4d44ecd3f09e449e9377f3dd061207f59ec..8856385925ca4d807b9fce1dce81e5d1b3ee9892 100644 (file)
 <fo:root xmlns:fo="http://www.w3.org/1999/XSL/Format" language="en" country="GB">
   <fo:layout-master-set>
     <fo:simple-page-master master-name="page"
-      page-height="220pt" page-width="320pt" margin="10pt">
-      <fo:region-body/>
+      page-height="240pt" page-width="320pt" margin="10pt" margin-bottom="8pt">
+      <fo:region-body margin-bottom="20pt"/>
+      <fo:region-after extent="10pt"/>
     </fo:simple-page-master>
   </fo:layout-master-set>
-  <fo:page-sequence master-reference="page">
-    <fo:flow flow-name="xsl-region-body" hyphenate="true" font-family="sans-serif">
+  <fo:page-sequence master-reference="page" role="Art">
+    <fo:static-content flow-name="xsl-region-after" role="NonStruct" font-size="8pt">
+      <fo:block text-align="center"><fo:page-number/></fo:block>
+    </fo:static-content>
+    <fo:flow flow-name="xsl-region-body" role="NonStruct"  hyphenate="true" 
+      font-family="sans-serif">
       <fo:block role="H1" font-weight="bold" font-size="150%"
         space-before.minimum="1.5em"
         space-before.optimum="2em"