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-ffa450edef68tags/fop-1_1rc1old
@@ -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} */ |
@@ -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); | |||
} |
@@ -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. |
@@ -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 |
@@ -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 |
@@ -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"; |
@@ -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 */ |
@@ -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() { |
@@ -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; |
@@ -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() { |
@@ -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; | |||
} | |||
} |
@@ -63,6 +63,11 @@ | |||
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. |
@@ -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(); | |||
@@ -19,12 +19,17 @@ | |||
<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" |