From 2c2c292121674e5e67fc2f5762be66e1528d5161 Mon Sep 17 00:00:00 2001 From: Vincent Hennebert Date: Thu, 2 Aug 2012 11:22:32 +0000 Subject: [PATCH] Bugzilla #53639: When PDF accessibility is enabled, the Scope attribute must be present in the structure tree for table header elements. git-svn-id: https://svn.apache.org/repos/asf/xmlgraphics/fop/trunk@1368420 13f79535-47bb-0310-9956-ffa450edef68 --- src/java/org/apache/fop/pdf/PDFDocument.java | 20 +- .../org/apache/fop/pdf/PDFStructElem.java | 29 +-- .../fop/pdf/StandardStructureAttributes.java | 69 +++++++ .../fop/pdf/StandardStructureTypes.java | 132 +++++++++++++ .../org/apache/fop/pdf/StructureType.java | 34 ++++ .../org/apache/fop/pdf/VersionController.java | 17 ++ .../apache/fop/render/pdf/FOToPDFRoleMap.java | 180 +++++------------- .../pdf/PDFLogicalStructureHandler.java | 2 +- .../render/pdf/PDFStructureTreeBuilder.java | 22 ++- status.xml | 4 + .../fop/pdf/TableHeaderScopeTestCase.java | 131 +++++++++++++ test/pdf/1.5/test.pdf | Bin 92661 -> 92694 bytes test/pdf/accessibility/fop.xconf | 1 + 13 files changed, 485 insertions(+), 156 deletions(-) create mode 100644 src/java/org/apache/fop/pdf/StandardStructureAttributes.java create mode 100644 src/java/org/apache/fop/pdf/StandardStructureTypes.java create mode 100644 src/java/org/apache/fop/pdf/StructureType.java create mode 100644 test/java/org/apache/fop/pdf/TableHeaderScopeTestCase.java diff --git a/src/java/org/apache/fop/pdf/PDFDocument.java b/src/java/org/apache/fop/pdf/PDFDocument.java index dad404d11..e46a22c4a 100644 --- a/src/java/org/apache/fop/pdf/PDFDocument.java +++ b/src/java/org/apache/fop/pdf/PDFDocument.java @@ -37,6 +37,7 @@ import java.util.Map; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.apache.fop.pdf.StandardStructureAttributes.Table.Scope; import org.apache.fop.pdf.xref.CrossReferenceStream; import org.apache.fop.pdf.xref.CrossReferenceTable; import org.apache.fop.pdf.xref.TrailerDictionary; @@ -362,13 +363,30 @@ public class PDFDocument { * hierarchy * @return a dictionary of type StructElem */ - public PDFStructElem makeStructureElement(PDFName structureType, PDFObject parent) { + public PDFStructElem makeStructureElement(StructureType structureType, PDFObject parent) { PDFStructElem structElem = new PDFStructElem(parent, structureType); assignObjectNumber(structElem); structureTreeElements.add(structElem); return structElem; } + /** + * Creates and returns a structure element. + * + * @param structureType the structure type of the new element (value for the + * S entry) + * @param parent the parent of the new structure element in the structure + * hierarchy + * @param scope the scope of the given table header element + * @return a dictionary of type StructElem + */ + public PDFStructElem makeStructureElement(StructureType structureType, PDFObject parent, + Scope scope) { + PDFStructElem structElem = makeStructureElement(structureType, parent); + versionController.addTableHeaderScopeAttribute(structElem, scope); + return structElem; + } + /** * Get the {@link PDFInfo} object for this document. * diff --git a/src/java/org/apache/fop/pdf/PDFStructElem.java b/src/java/org/apache/fop/pdf/PDFStructElem.java index 2a756fe9b..28cebb3ee 100644 --- a/src/java/org/apache/fop/pdf/PDFStructElem.java +++ b/src/java/org/apache/fop/pdf/PDFStructElem.java @@ -26,6 +26,7 @@ import java.util.List; import java.util.Locale; import org.apache.fop.accessibility.StructureTreeElement; +import org.apache.fop.pdf.StandardStructureAttributes.Table; import org.apache.fop.util.LanguageTags; /** @@ -33,7 +34,7 @@ import org.apache.fop.util.LanguageTags; */ public class PDFStructElem extends PDFDictionary implements StructureTreeElement, CompressedObject { - private static final PDFName TABLE = new PDFName("Table"); + private StructureType structureType; private PDFStructElem parentElement; @@ -50,12 +51,17 @@ public class PDFStructElem extends PDFDictionary implements StructureTreeElement * @param parent parent of this element * @param structureType the structure type of this element */ - PDFStructElem(PDFObject parent, PDFName structureType) { + PDFStructElem(PDFObject parent, StructureType structureType) { + this(parent); + this.structureType = structureType; + put("S", structureType.getName()); + setParent(parent); + } + + private PDFStructElem(PDFObject parent) { if (parent instanceof PDFStructElem) { parentElement = (PDFStructElem) parent; } - put("S", structureType); - setParent(parent); } /** @@ -113,8 +119,8 @@ public class PDFStructElem extends PDFDictionary implements StructureTreeElement * * @return the value of the S entry */ - public PDFName getStructureType() { - return (PDFName) get("S"); + public StructureType getStructureType() { + return structureType; } /** @@ -201,7 +207,7 @@ public class PDFStructElem extends PDFDictionary implements StructureTreeElement private void setTableAttributeRowColumnSpan(String typeSpan, int span) { PDFDictionary attribute = new PDFDictionary(); - attribute.put("O", TABLE); + attribute.put("O", Table.NAME); attribute.put(typeSpan, span); if (attributes == null) { attributes = new ArrayList(2); @@ -228,13 +234,8 @@ public class PDFStructElem extends PDFDictionary implements StructureTreeElement } } - /** - * Constructor - * @param parent - - * @param name - - */ - public Placeholder(PDFObject parent, String name) { - super(parent, new PDFName(name)); + public Placeholder(PDFObject parent) { + super(parent); } } diff --git a/src/java/org/apache/fop/pdf/StandardStructureAttributes.java b/src/java/org/apache/fop/pdf/StandardStructureAttributes.java new file mode 100644 index 000000000..0a93d46bb --- /dev/null +++ b/src/java/org/apache/fop/pdf/StandardStructureAttributes.java @@ -0,0 +1,69 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* $Id$ */ + +package org.apache.fop.pdf; + +/** + * Standard attributes, as defined in section 10.7.5 of the PDF Reference, Fourth edition (PDF 1.5). + */ +public final class StandardStructureAttributes { + + public static final class Table { + + /** + * The name to use as an attribute owner. This is the value of the 'O' entry in + * the attribute's dictionary. + */ + public static final PDFName NAME = new PDFName("Table"); + + public static enum Scope { + ROW("Row"), + COLUMN("Column"), + BOTH("Both"); + + private final PDFName name; + + private Scope(String name) { + this.name = new PDFName(name); + } + + /** + * Returns the name of this attribute. + * + * @return a name suitable for use as a value in the attribute's dictionary + */ + public PDFName getName() { + return name; + } + + /** + * Sets the given scope on the given table header element. + */ + static void addScopeAttribute(PDFStructElem th, Scope scope) { + PDFDictionary scopeAttribute = new PDFDictionary(); + scopeAttribute.put("O", Table.NAME); + scopeAttribute.put("Scope", scope.getName()); + th.put("A", scopeAttribute); + } + } + } + + private StandardStructureAttributes() { } + +} diff --git a/src/java/org/apache/fop/pdf/StandardStructureTypes.java b/src/java/org/apache/fop/pdf/StandardStructureTypes.java new file mode 100644 index 000000000..dc045e180 --- /dev/null +++ b/src/java/org/apache/fop/pdf/StandardStructureTypes.java @@ -0,0 +1,132 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* $Id$ */ + +package org.apache.fop.pdf; + +import java.util.HashMap; +import java.util.Map; + +/** + * Standard structure types, as defined in section 10.7.4 of the PDF Reference, Fourth Edition (PDF 1.5). + */ +public final class StandardStructureTypes { + + public static final class Grouping { + + public static final StructureType DOCUMENT = new StructureTypeImpl("Document"); + public static final StructureType PART = new StructureTypeImpl("Part"); + public static final StructureType ART = new StructureTypeImpl("Art"); + public static final StructureType SECT = new StructureTypeImpl("Sect"); + public static final StructureType DIV = new StructureTypeImpl("Div"); + public static final StructureType BLOCK_QUOTE = new StructureTypeImpl("BlockQuote"); + public static final StructureType CAPTION = new StructureTypeImpl("Caption"); + public static final StructureType TOC = new StructureTypeImpl("TOC"); + public static final StructureType TOCI = new StructureTypeImpl("TOCI"); + public static final StructureType INDEX = new StructureTypeImpl("Index"); + public static final StructureType NON_STRUCT = new StructureTypeImpl("NonStruct"); + public static final StructureType PRIVATE = new StructureTypeImpl("Private"); + } + + public static final class Paragraphlike { + public static final StructureType H = new StructureTypeImpl("H"); + public static final StructureType H1 = new StructureTypeImpl("H1"); + public static final StructureType H2 = new StructureTypeImpl("H2"); + public static final StructureType H3 = new StructureTypeImpl("H3"); + public static final StructureType H4 = new StructureTypeImpl("H4"); + public static final StructureType H5 = new StructureTypeImpl("H5"); + public static final StructureType H6 = new StructureTypeImpl("H6"); + public static final StructureType P = new StructureTypeImpl("P"); + } + + public static final class List { + public static final StructureType L = new StructureTypeImpl("L"); + public static final StructureType LI = new StructureTypeImpl("LI"); + public static final StructureType LBL = new StructureTypeImpl("Lbl"); + public static final StructureType LBODY = new StructureTypeImpl("LBody"); + } + + public static final class Table { + public static final StructureType TABLE = new StructureTypeImpl("Table"); + public static final StructureType TR = new StructureTypeImpl("TR"); + public static final StructureType TH = new StructureTypeImpl("TH"); + public static final StructureType TD = new StructureTypeImpl("TD"); + public static final StructureType THEAD = new StructureTypeImpl("THead"); + public static final StructureType TBODY = new StructureTypeImpl("TBody"); + public static final StructureType TFOOT = new StructureTypeImpl("TFoot"); + } + + public static final class InlineLevelStructure { + public static final StructureType SPAN = new StructureTypeImpl("Span"); + public static final StructureType QUOTE = new StructureTypeImpl("Quote"); + public static final StructureType NOTE = new StructureTypeImpl("Note"); + public static final StructureType REFERENCE = new StructureTypeImpl("Reference"); + public static final StructureType BIB_ENTRY = new StructureTypeImpl("BibEntry"); + public static final StructureType CODE = new StructureTypeImpl("Code"); + public static final StructureType LINK = new StructureTypeImpl("Link"); + public static final StructureType ANNOT = new StructureTypeImpl("Annot"); + } + + public static final class RubyOrWarichu { + public static final StructureType RUBY = new StructureTypeImpl("Ruby"); + public static final StructureType RB = new StructureTypeImpl("RB"); + public static final StructureType RT = new StructureTypeImpl("RT"); + public static final StructureType RP = new StructureTypeImpl("RP"); + public static final StructureType WARICHU = new StructureTypeImpl("Warichu"); + public static final StructureType WT = new StructureTypeImpl("WT"); + public static final StructureType WP = new StructureTypeImpl("WP"); + } + + public static final class Illustration { + public static final StructureType FIGURE = new StructureTypeImpl("Figure"); + public static final StructureType FORMULA = new StructureTypeImpl("Formula"); + public static final StructureType FORM = new StructureTypeImpl("Form"); + } + + private static class StructureTypeImpl implements StructureType { + + private final PDFName name; + + protected StructureTypeImpl(String name) { + this.name = new PDFName(name); + StandardStructureTypes.STRUCTURE_TYPES.put(name, this); + } + + public PDFName getName() { + return name; + } + + } + + private static final Map STRUCTURE_TYPES = new HashMap(); + + private StandardStructureTypes() { } + + /** + * Returns the standard structure type of the given name. + * + * @param name the name of a structure type, case sensitive. For example, Document, + * Sect, H1, etc. + * @return the corresponding {@code StructureType} instance, or {@code null} if the given + * name does not correspond to a standard structure type + */ + public static StructureType get(String name) { + return STRUCTURE_TYPES.get(name); + } + +} diff --git a/src/java/org/apache/fop/pdf/StructureType.java b/src/java/org/apache/fop/pdf/StructureType.java new file mode 100644 index 000000000..7b9b4c169 --- /dev/null +++ b/src/java/org/apache/fop/pdf/StructureType.java @@ -0,0 +1,34 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* $Id$ */ + +package org.apache.fop.pdf; + +/** + * A structure type, as defined in Section 10.6.2 of the PDF Reference, fourth edition (PDF 1.5). + */ +public interface StructureType { + + /** + * Returns the name of this structure type. + * + * @return the name object identifying this structure type + */ + PDFName getName(); + +} diff --git a/src/java/org/apache/fop/pdf/VersionController.java b/src/java/org/apache/fop/pdf/VersionController.java index 4a92f4994..bd5e4d20f 100644 --- a/src/java/org/apache/fop/pdf/VersionController.java +++ b/src/java/org/apache/fop/pdf/VersionController.java @@ -19,6 +19,8 @@ package org.apache.fop.pdf; +import org.apache.fop.pdf.StandardStructureAttributes.Table.Scope; + /** * An abstraction that controls the mutability of the PDF version for a document. */ @@ -47,6 +49,8 @@ public abstract class VersionController { */ public abstract void setPDFVersion(Version version); + abstract void addTableHeaderScopeAttribute(PDFStructElem th, Scope scope); + @Override public String toString() { return version.toString(); @@ -67,6 +71,13 @@ public abstract class VersionController { public void setPDFVersion(Version version) { throw new IllegalStateException("Cannot change the version of this PDF document."); } + + @Override + void addTableHeaderScopeAttribute(PDFStructElem th, Scope scope) { + if (super.version.compareTo(Version.V1_4) > 0) { + Scope.addScopeAttribute(th, scope); + } + } } /** @@ -91,6 +102,12 @@ public abstract class VersionController { doc.getRoot().setVersion(version); } } + + @Override + void addTableHeaderScopeAttribute(PDFStructElem th, Scope scope) { + setPDFVersion(Version.V1_5); + Scope.addScopeAttribute(th, scope); + } } /** diff --git a/src/java/org/apache/fop/render/pdf/FOToPDFRoleMap.java b/src/java/org/apache/fop/render/pdf/FOToPDFRoleMap.java index 0f752e886..68bfd8686 100644 --- a/src/java/org/apache/fop/render/pdf/FOToPDFRoleMap.java +++ b/src/java/org/apache/fop/render/pdf/FOToPDFRoleMap.java @@ -19,145 +19,66 @@ package org.apache.fop.render.pdf; -import java.util.HashMap; import java.util.Map; import org.apache.fop.events.EventBroadcaster; -import org.apache.fop.pdf.PDFName; import org.apache.fop.pdf.PDFObject; import org.apache.fop.pdf.PDFStructElem; +import org.apache.fop.pdf.StandardStructureTypes; +import org.apache.fop.pdf.StructureType; /** * This class provides the standard mappings from Formatting Objects to PDF structure types. */ final class FOToPDFRoleMap { - /** - * Standard structure types defined by the PDF Reference, Fourth Edition (PDF 1.5). - */ - private static final Map STANDARD_STRUCTURE_TYPES - = new HashMap(); - - private static final Map DEFAULT_MAPPINGS - = new java.util.HashMap(); - - private static final PDFName THEAD; - private static final PDFName NON_STRUCT; + private static final Map DEFAULT_MAPPINGS = new java.util.HashMap(); static { - // Create PDFNames for the standard structure types - // Table 10.18: Grouping elements - addStructureType("Document"); - addStructureType("Part"); - addStructureType("Art"); - addStructureType("Sect"); - addStructureType("Div"); - addStructureType("BlockQuote"); - addStructureType("Caption"); - addStructureType("TOC"); - addStructureType("TOCI"); - addStructureType("Index"); - addStructureType("NonStruct"); - addStructureType("Private"); - // Table 10.20: Paragraphlike elements - addStructureType("H"); - addStructureType("H1"); - addStructureType("H2"); - addStructureType("H3"); - addStructureType("H4"); - addStructureType("H5"); - addStructureType("H6"); - addStructureType("P"); - // Table 10.21: List elements - addStructureType("L"); - addStructureType("LI"); - addStructureType("Lbl"); - addStructureType("LBody"); - // Table 10.22: Table elements - addStructureType("Table"); - addStructureType("TR"); - addStructureType("TH"); - addStructureType("TD"); - addStructureType("THead"); - addStructureType("TBody"); - addStructureType("TFoot"); - // Table 10.23: Inline-level structure elements - addStructureType("Span"); - addStructureType("Quote"); - addStructureType("Note"); - addStructureType("Reference"); - addStructureType("BibEntry"); - addStructureType("Code"); - addStructureType("Link"); - addStructureType("Annot"); - // Table 10.24: Ruby and Warichu elements - addStructureType("Ruby"); - addStructureType("RB"); - addStructureType("RT"); - addStructureType("RP"); - addStructureType("Warichu"); - addStructureType("WT"); - addStructureType("WP"); - // Table 10.25: Illustration elements - addStructureType("Figure"); - addStructureType("Formula"); - addStructureType("Form"); - - NON_STRUCT = STANDARD_STRUCTURE_TYPES.get("NonStruct"); - assert NON_STRUCT != null; - THEAD = STANDARD_STRUCTURE_TYPES.get("THead"); - assert THEAD != null; - // Create the standard mappings // Declarations and Pagination and Layout Formatting Objects - addMapping("root", "Document"); - addMapping("page-sequence", "Part"); - addMapping("flow", "Sect"); - addMapping("static-content", "Sect"); + addMapping("root", StandardStructureTypes.Grouping.DOCUMENT); + addMapping("page-sequence", StandardStructureTypes.Grouping.PART); + addMapping("flow", StandardStructureTypes.Grouping.SECT); + addMapping("static-content", StandardStructureTypes.Grouping.SECT); // Block-level Formatting Objects - addMapping("block", "P"); - addMapping("block-container", "Div"); + addMapping("block", StandardStructureTypes.Paragraphlike.P); + addMapping("block-container", StandardStructureTypes.Grouping.DIV); // Inline-level Formatting Objects - addMapping("character", "Span"); - addMapping("external-graphic", "Figure"); - addMapping("instream-foreign-object", "Figure"); - addMapping("inline", "Span"); - addMapping("inline-container", "Div"); - addMapping("page-number", "Quote"); - addMapping("page-number-citation", "Quote"); - addMapping("page-number-citation-last", "Quote"); + addMapping("character", StandardStructureTypes.InlineLevelStructure.SPAN); + addMapping("external-graphic", StandardStructureTypes.Illustration.FIGURE); + addMapping("instream-foreign-object", StandardStructureTypes.Illustration.FIGURE); + addMapping("inline", StandardStructureTypes.InlineLevelStructure.SPAN); + addMapping("inline-container", StandardStructureTypes.Grouping.DIV); + addMapping("page-number", StandardStructureTypes.InlineLevelStructure.QUOTE); + addMapping("page-number-citation", StandardStructureTypes.InlineLevelStructure.QUOTE); + addMapping("page-number-citation-last", StandardStructureTypes.InlineLevelStructure.QUOTE); // Formatting Objects for Tables - addMapping("table-and-caption", "Div"); - addMapping("table", "Table"); - addMapping("table-caption", "Caption"); - addMapping("table-header", "THead"); - addMapping("table-footer", "TFoot"); - addMapping("table-body", "TBody"); - addMapping("table-row", "TR"); + addMapping("table-and-caption", StandardStructureTypes.Grouping.DIV); + addMapping("table", StandardStructureTypes.Table.TABLE); + addMapping("table-caption", StandardStructureTypes.Grouping.CAPTION); + addMapping("table-header", StandardStructureTypes.Table.THEAD); + addMapping("table-footer", StandardStructureTypes.Table.TFOOT); + addMapping("table-body", StandardStructureTypes.Table.TBODY); + addMapping("table-row", StandardStructureTypes.Table.TR); addMapping("table-cell", new TableCellMapper()); // Formatting Objects for Lists - addMapping("list-block", "L"); - addMapping("list-item", "LI"); - addMapping("list-item-body", "LBody"); - addMapping("list-item-label", "Lbl"); + addMapping("list-block", StandardStructureTypes.List.L); + addMapping("list-item", StandardStructureTypes.List.LI); + addMapping("list-item-body", StandardStructureTypes.List.LBODY); + addMapping("list-item-label", StandardStructureTypes.List.LBL); // Dynamic Effects: Link and Multi Formatting Objects - addMapping("basic-link", "Link"); + addMapping("basic-link", StandardStructureTypes.InlineLevelStructure.LINK); // Out-of-Line Formatting Objects - addMapping("float", "Div"); - addMapping("footnote", "Note"); - addMapping("footnote-body", "Sect"); - addMapping("wrapper", "Span"); - addMapping("marker", "Private"); - } - - private static void addStructureType(String structureType) { - STANDARD_STRUCTURE_TYPES.put(structureType, new PDFName(structureType)); + addMapping("float", StandardStructureTypes.Grouping.DIV); + addMapping("footnote", StandardStructureTypes.InlineLevelStructure.NOTE); + addMapping("footnote-body", StandardStructureTypes.Grouping.SECT); + addMapping("wrapper", StandardStructureTypes.InlineLevelStructure.SPAN); + addMapping("marker", StandardStructureTypes.Grouping.PRIVATE); } - private static void addMapping(String fo, String structureType) { - PDFName type = STANDARD_STRUCTURE_TYPES.get(structureType); - assert type != null; - addMapping(fo, new SimpleMapper(type)); + private static void addMapping(String fo, StructureType structureType) { + addMapping(fo, new SimpleMapper(structureType)); } private static void addMapping(String fo, Mapper mapper) { @@ -173,13 +94,13 @@ final class FOToPDFRoleMap { * @param eventBroadcaster the event broadcaster * @return the structure type or null if no match could be found */ - public static PDFName mapFormattingObject(String fo, String role, + public static StructureType mapFormattingObject(String fo, String role, PDFObject parent, EventBroadcaster eventBroadcaster) { - PDFName type = null; + StructureType type = null; if (role == null) { type = getDefaultMappingFor(fo, parent); } else { - type = STANDARD_STRUCTURE_TYPES.get(role); + type = StandardStructureTypes.get(role); if (type == null) { type = getDefaultMappingFor(fo, parent); PDFEventProducer.Provider.get(eventBroadcaster).nonStandardStructureType(fo, @@ -196,28 +117,28 @@ final class FOToPDFRoleMap { * @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) { + private static StructureType getDefaultMappingFor(String fo, PDFObject parent) { Mapper mapper = DEFAULT_MAPPINGS.get(fo); if (mapper != null) { return mapper.getStructureType(parent); } else { - return NON_STRUCT; + return StandardStructureTypes.Grouping.NON_STRUCT; } } private interface Mapper { - PDFName getStructureType(PDFObject parent); + StructureType getStructureType(PDFObject parent); } private static class SimpleMapper implements Mapper { - private PDFName structureType; + private StructureType structureType; - public SimpleMapper(PDFName structureType) { + public SimpleMapper(StructureType structureType) { this.structureType = structureType; } - public PDFName getStructureType(PDFObject parent) { + public StructureType getStructureType(PDFObject parent) { return structureType; } @@ -225,17 +146,14 @@ final class FOToPDFRoleMap { private static class TableCellMapper implements Mapper { - public PDFName getStructureType(PDFObject parent) { + public StructureType getStructureType(PDFObject parent) { 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 = STANDARD_STRUCTURE_TYPES.get("TH"); + if (grandParent.getStructureType() == StandardStructureTypes.Table.THEAD) { + return StandardStructureTypes.Table.TH; } else { - type = STANDARD_STRUCTURE_TYPES.get("TD"); + return StandardStructureTypes.Table.TD; } - assert type != null; - return type; } } diff --git a/src/java/org/apache/fop/render/pdf/PDFLogicalStructureHandler.java b/src/java/org/apache/fop/render/pdf/PDFLogicalStructureHandler.java index 6559e8d56..ee00d2401 100644 --- a/src/java/org/apache/fop/render/pdf/PDFLogicalStructureHandler.java +++ b/src/java/org/apache/fop/render/pdf/PDFLogicalStructureHandler.java @@ -132,7 +132,7 @@ class PDFLogicalStructureHandler { ? structureTreeElement.getParentStructElem() : structureTreeElement; pageParentTreeArray.add(parent); - String type = parent.getStructureType().toString(); + String type = parent.getStructureType().getName().toString(); int mcid = pageParentTreeArray.length() - 1; return new MarkedContentInfo(type, mcid, structureTreeElement); } diff --git a/src/java/org/apache/fop/render/pdf/PDFStructureTreeBuilder.java b/src/java/org/apache/fop/render/pdf/PDFStructureTreeBuilder.java index 0f8e74515..1377bbfce 100644 --- a/src/java/org/apache/fop/render/pdf/PDFStructureTreeBuilder.java +++ b/src/java/org/apache/fop/render/pdf/PDFStructureTreeBuilder.java @@ -29,18 +29,18 @@ 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; +import org.apache.fop.pdf.StandardStructureAttributes.Table.Scope; +import org.apache.fop.pdf.StandardStructureTypes.Table; +import org.apache.fop.pdf.StructureType; class PDFStructureTreeBuilder implements StructureTreeEventHandler { private PDFFactory pdfFactory; - private PDFLogicalStructureHandler logicalStructureHandler; - private EventBroadcaster eventBroadcaster; private LinkedList ancestors = new LinkedList(); @@ -52,11 +52,10 @@ class PDFStructureTreeBuilder implements StructureTreeEventHandler { } void setLogicalStructureHandler(PDFLogicalStructureHandler logicalStructureHandler) { - this.logicalStructureHandler = logicalStructureHandler; - createRootStructureElement(); + createRootStructureElement(logicalStructureHandler); } - private void createRootStructureElement() { + private void createRootStructureElement(PDFLogicalStructureHandler logicalStructureHandler) { assert rootStructureElement == null; PDFParentTree parentTree = logicalStructureHandler.getParentTree(); PDFStructTreeRoot structTreeRoot = pdfFactory.getDocument().makeStructTreeRoot(parentTree); @@ -79,8 +78,13 @@ class PDFStructureTreeBuilder implements StructureTreeEventHandler { } private PDFStructElem createStructureElement(String name, PDFObject parent, String role) { - PDFName structureType = FOToPDFRoleMap.mapFormattingObject(name, role, parent, eventBroadcaster); - return pdfFactory.getDocument().makeStructureElement(structureType, parent); + StructureType structureType = FOToPDFRoleMap.mapFormattingObject(name, role, parent, + eventBroadcaster); + if (structureType == Table.TH) { + return pdfFactory.getDocument().makeStructureElement(structureType, parent, Scope.COLUMN); + } else { + return pdfFactory.getDocument().makeStructureElement(structureType, parent); + } } public void endPageSequence() { @@ -135,7 +139,7 @@ class PDFStructureTreeBuilder implements StructureTreeEventHandler { String role = attributes.getValue("role"); PDFStructElem structElem; if ("#PCDATA".equals(name)) { - structElem = new PDFStructElem.Placeholder(parent, name); + structElem = new PDFStructElem.Placeholder(parent); } else { structElem = createStructureElement(name, parent, role); } diff --git a/status.xml b/status.xml index 055d40dcc..b1635fea9 100644 --- a/status.xml +++ b/status.xml @@ -62,6 +62,10 @@ documents. Example: the fix of marks layering will be such a case when it's done. --> + + When PDF accessibility is enabled, the Scope attribute must be present in the structure tree + for table header elements. + Added an event if a glyph and its metric information does not exist in the character set diff --git a/test/java/org/apache/fop/pdf/TableHeaderScopeTestCase.java b/test/java/org/apache/fop/pdf/TableHeaderScopeTestCase.java new file mode 100644 index 000000000..89682628d --- /dev/null +++ b/test/java/org/apache/fop/pdf/TableHeaderScopeTestCase.java @@ -0,0 +1,131 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* $Id$ */ + +package org.apache.fop.pdf; + +import org.junit.Test; +import org.mockito.ArgumentMatcher; +import org.mockito.verification.VerificationMode; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.argThat; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +import org.apache.fop.pdf.StandardStructureAttributes.Table.Scope; +import org.apache.fop.pdf.StandardStructureTypes.Table; + +public class TableHeaderScopeTestCase { + + private static final String ATTRIBUTE_ENTRY = "A"; + + private VersionController controller; + + @Test + public void pdfDocumentDelegatesToVersionController() { + for (Scope scope : Scope.values()) { + testMakeStructureElementWithScope(scope); + } + } + + private void testMakeStructureElementWithScope(Scope scope) { + VersionController controller = mock(VersionController.class); + PDFDocument document = new PDFDocument("Test", controller); + document.makeStructTreeRoot(null); + document.makeStructureElement(Table.TH, null, scope); + verify(controller).addTableHeaderScopeAttribute(any(PDFStructElem.class), eq(scope)); + } + + @Test + public void versionControllerMayDelegateToScope() { + fixedController14doesNotAddAttribute(); + fixedController15addsAttribute(); + dynamicControllerAddsAttribute(); + } + + private void fixedController14doesNotAddAttribute() { + controller = VersionController.getFixedVersionController(Version.V1_4); + scopeMustNotBeAdded(); + } + + private void fixedController15addsAttribute() { + controller = VersionController.getFixedVersionController(Version.V1_5); + scopeMustBeAdded(); + } + + private void dynamicControllerAddsAttribute() { + PDFDocument document = new PDFDocument("Test"); + controller = VersionController.getDynamicVersionController(Version.V1_4, document); + scopeMustBeAdded(); + assertEquals(Version.V1_5, controller.getPDFVersion()); + } + + private void scopeMustBeAdded() { + scopeMustBeAdded(times(1)); + } + + private void scopeMustNotBeAdded() { + scopeMustBeAdded(never()); + } + + private void scopeMustBeAdded(VerificationMode nTimes) { + PDFStructElem structElem = mock(PDFStructElem.class); + controller.addTableHeaderScopeAttribute(structElem, Scope.COLUMN); + verify(structElem, nTimes).put(eq(ATTRIBUTE_ENTRY), any()); + } + + @Test + public void scopeAddsTheAttribute() { + for (Scope scope : Scope.values()) { + scopeAttributeMustBeAdded(scope); + } + } + + private void scopeAttributeMustBeAdded(Scope scope) { + PDFStructElem structElem = mock(PDFStructElem.class); + Scope.addScopeAttribute(structElem, scope); + verify(structElem).put(eq(ATTRIBUTE_ENTRY), scopeAttribute(scope)); + } + + private PDFDictionary scopeAttribute(Scope scope) { + return argThat(new isScopeAttribute(scope)); + } + + private static class isScopeAttribute extends ArgumentMatcher { + + private final Scope expectedScope; + + public isScopeAttribute(Scope expectedScope) { + this.expectedScope = expectedScope; + } + + @Override + public boolean matches(Object argument) { + PDFDictionary attribute = (PDFDictionary) argument; + return "/Table".equals(attribute.get("O").toString()) + && expectedScope.getName().toString().equals(attribute.get("Scope").toString()); + } + + } + +} diff --git a/test/pdf/1.5/test.pdf b/test/pdf/1.5/test.pdf index 6175a270fc9b29702dc3397c8815d9eb06eea5b7..d97648e1edca50f561957fa6a1b04593adb0e8e0 100644 GIT binary patch delta 1725 zcma)+TWnNS6ozN1Be0u}1sX`j(p4Hl4C?Irg_Ig*+KQ4wCcS{vAgu^bY8oCf z(U6?THZc)-@Xa)uiO!RW5Bi`+BY`Mj5e)H~K2&MB)l%0$dXOiam;XEeS?k|>?K9_0 zoys0NojrPZf#*^P#g+Z&^q0 zPIg_uyG0jnWE|?aiS~4wB2FaOLq}DU7QE?uaHl*G%ZBYrz*AA8B znrV;9Rjvc75g!aQ9JSz<89{0kGZM9=1&kuqbgfZSCo_iBYGy2IEj%-hw9@ZU8z-3w zs4Wd-DbEJSEaf>x*OcdlWtQ^1<;+rE5Sm$oG=GrHS~T3z4TnZ(&#XtIDh+|ud*(PA ztpsxdjn$4hiN?`_PNDI_FsDfs`36m($(%)NXaMI(4XH#?T~Jr9Z-uiEcNL*F;&zx9-)VzfY-op$I9r6paiksA z#CLZVDe#})ms8am4x`{Bj| z_nE2q%wDh%;^DopZ6q`M=Ui!KoZrFk=MXYqr#QcmV&>-*VIai?O)0Kel;VOTDK<`} z$dGX4_I|5aYByz56yK!SwCoN}rzl77;O8l-bPTY&Q}?;wQ|#-@WGWxjmk%C@rw3sv OeFr34xw36jJNp+{g`N!n delta 1589 zcmaizU1*kN7{}ksHrcM_KC+yOx@*qmz>n_d{=Sz=^L1~VVjI;)V`fVYR)UQRf`qnC zI`E)^2fU$!^+_FcQoO{9KB0qJQ3*w%=0Zq95s_+JZnnDKO`qXW@5%pn{jdA}-}m!e z?{oae^2r~{UwW?6NRwD2rF-vKzIvJ2BF+7YZ8fr0y5gzoZE&NNHq{Q@=oDYAefM;w zQY7eJQl_});s-aSpZ#~;Ta#i{wKE*&~wetZoy z6xaTB9HOpXSTt|PnYD0BIy+etUoL}nY0Fe^IxA8w?T(rK zv34ulmnQ1cpJ%$`zO8)TGrulRADEqt%T_{TdgSW2@$@QQ(*Mtybo_i{+Beq~zgP?F z*NynhnLO<4$5#kVbt4<{0pCaIN1Wmug-Wl5UgP&txT zdsLn^SX6QR>t&~M$SiL}FNtZ5c9BEV9?^(S^6G&TY&~lzj z6k5)6hHK9AN}}aF?<`u*3re6hG;DeB87(wA!Kxj)gp9Qc zoh0M$a=||3^Q<`mt;fG=oLAk z4SLP$1A0UJAP&7{bsoJVC;63o&&nh6fp{VYgCM7+#gMQ%jX{z#JfgsPCMXP?XZei< z&T~BSzj1MA<$I`Ksd%;rR>g`ugm|tMR>sfUp(%Ffp>Doy7TRx?+9x_-WBjHQ zmd7)_P&GD{hm#Or+s*YoyPzUoXovpz!gek%?}bgJoSLx<1vm@~xp#FyZ5-_Y8Si)q zR>Xk<$R&x7XIo)SobMGnxp#B`su%ai*v4@u3a~mp-v2cc&ea%I<-<}Qjw{OvkI?p%fy`!dYU zW(dz_Sh+pJ^_w%Sd?Um4H5p../../resources/fonts/ttf/ + 1.4 null -- 2.39.5