From 69beec0c8ff0bb191c459184bc61b6a243ba693b Mon Sep 17 00:00:00 2001 From: Vincent Hennebert Date: Fri, 24 Aug 2012 14:10:39 +0000 Subject: Bugzilla 53778: When PDF accessibility is enabled, the contents for the different regions must appear in the proper order in the structure tree. git-svn-id: https://svn.apache.org/repos/asf/xmlgraphics/fop/trunk@1376923 13f79535-47bb-0310-9956-ffa450edef68 --- .../org/apache/fop/render/pdf/FOToPDFRoleMap.java | 162 ---------- .../apache/fop/render/pdf/PDFEventProducer.java | 3 +- .../fop/render/pdf/PDFStructureTreeBuilder.java | 331 +++++++++++++++++---- .../fop/render/pdf/PageSequenceStructElem.java | 79 +++++ .../org/apache/fop/render/pdf/TableStructElem.java | 48 +++ 5 files changed, 399 insertions(+), 224 deletions(-) delete mode 100644 src/java/org/apache/fop/render/pdf/FOToPDFRoleMap.java create mode 100644 src/java/org/apache/fop/render/pdf/PageSequenceStructElem.java create mode 100644 src/java/org/apache/fop/render/pdf/TableStructElem.java (limited to 'src/java/org/apache/fop/render/pdf') diff --git a/src/java/org/apache/fop/render/pdf/FOToPDFRoleMap.java b/src/java/org/apache/fop/render/pdf/FOToPDFRoleMap.java deleted file mode 100644 index 68bfd8686..000000000 --- a/src/java/org/apache/fop/render/pdf/FOToPDFRoleMap.java +++ /dev/null @@ -1,162 +0,0 @@ -/* - * 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.render.pdf; - -import java.util.Map; - -import org.apache.fop.events.EventBroadcaster; -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 { - - private static final Map DEFAULT_MAPPINGS = new java.util.HashMap(); - - static { - // Create the standard mappings - // Declarations and Pagination and Layout Formatting Objects - 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", StandardStructureTypes.Paragraphlike.P); - addMapping("block-container", StandardStructureTypes.Grouping.DIV); - // Inline-level Formatting Objects - 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", 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", 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", StandardStructureTypes.InlineLevelStructure.LINK); - // Out-of-Line Formatting Objects - 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, StructureType structureType) { - addMapping(fo, new SimpleMapper(structureType)); - } - - private static void addMapping(String fo, Mapper mapper) { - DEFAULT_MAPPINGS.put(fo, mapper); - } - - - /** - * Maps a Formatting Object to a PDFName representing the associated structure type. - * @param fo the formatting object's local name - * @param role the value of the formatting object's role property - * @param parent the parent of the structure element to be mapped - * @param eventBroadcaster the event broadcaster - * @return the structure type or null if no match could be found - */ - public static StructureType mapFormattingObject(String fo, String role, - PDFObject parent, EventBroadcaster eventBroadcaster) { - StructureType type = null; - if (role == null) { - type = getDefaultMappingFor(fo, parent); - } else { - type = StandardStructureTypes.get(role); - if (type == null) { - type = getDefaultMappingFor(fo, parent); - PDFEventProducer.Provider.get(eventBroadcaster).nonStandardStructureType(fo, - fo, role, type.toString().substring(1)); - } - } - assert type != null; - 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 StructureType getDefaultMappingFor(String fo, PDFObject parent) { - Mapper mapper = DEFAULT_MAPPINGS.get(fo); - if (mapper != null) { - return mapper.getStructureType(parent); - } else { - return StandardStructureTypes.Grouping.NON_STRUCT; - } - } - - private interface Mapper { - StructureType getStructureType(PDFObject parent); - } - - private static class SimpleMapper implements Mapper { - - private StructureType structureType; - - public SimpleMapper(StructureType structureType) { - this.structureType = structureType; - } - - public StructureType getStructureType(PDFObject parent) { - return structureType; - } - - } - - private static class TableCellMapper implements Mapper { - - 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. - if (grandParent.getStructureType() == StandardStructureTypes.Table.THEAD) { - return StandardStructureTypes.Table.TH; - } else { - return StandardStructureTypes.Table.TD; - } - } - - } - - private FOToPDFRoleMap() { } -} diff --git a/src/java/org/apache/fop/render/pdf/PDFEventProducer.java b/src/java/org/apache/fop/render/pdf/PDFEventProducer.java index 40062f73f..4b8253867 100644 --- a/src/java/org/apache/fop/render/pdf/PDFEventProducer.java +++ b/src/java/org/apache/fop/render/pdf/PDFEventProducer.java @@ -59,12 +59,11 @@ public interface PDFEventProducer extends EventProducer { * Custom structure type is not standard as per the PDF reference. * * @param source the event source - * @param fo the local name of the formatting object having the custom type * @param type custom structure type * @param fallback default structure type used as a fallback * @event.severity WARN */ - void nonStandardStructureType(Object source, String fo, String type, String fallback); + void nonStandardStructureType(Object source, String type, String fallback); /** * The encryption length must be a multiple of 8 between 40 and 128. diff --git a/src/java/org/apache/fop/render/pdf/PDFStructureTreeBuilder.java b/src/java/org/apache/fop/render/pdf/PDFStructureTreeBuilder.java index 1377bbfce..0d4a6b7fb 100644 --- a/src/java/org/apache/fop/render/pdf/PDFStructureTreeBuilder.java +++ b/src/java/org/apache/fop/render/pdf/PDFStructureTreeBuilder.java @@ -21,24 +21,271 @@ package org.apache.fop.render.pdf; import java.util.LinkedList; import java.util.Locale; +import java.util.Map; import org.xml.sax.Attributes; +import org.xml.sax.helpers.AttributesImpl; import org.apache.fop.accessibility.StructureTreeElement; import org.apache.fop.accessibility.StructureTreeEventHandler; import org.apache.fop.events.EventBroadcaster; import org.apache.fop.fo.extensions.ExtensionElementMapping; +import org.apache.fop.fo.pagination.Flow; import org.apache.fop.pdf.PDFFactory; -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; +import org.apache.fop.pdf.StandardStructureTypes.Grouping; import org.apache.fop.pdf.StandardStructureTypes.Table; +import org.apache.fop.pdf.StructureHierarchyMember; import org.apache.fop.pdf.StructureType; +import org.apache.fop.util.XMLUtil; class PDFStructureTreeBuilder implements StructureTreeEventHandler { + private static final String ROLE = "role"; + + private static final Map BUILDERS + = new java.util.HashMap(); + + private static final StructureElementBuilder DEFAULT_BUILDER + = new DefaultStructureElementBuilder(Grouping.NON_STRUCT); + + static { + // Declarations and Pagination and Layout Formatting Objects + StructureElementBuilder regionBuilder = new RegionBuilder(); + addBuilder("root", StandardStructureTypes.Grouping.DOCUMENT); + addBuilder("page-sequence", new PageSequenceBuilder()); + addBuilder("static-content", regionBuilder); + addBuilder("flow", regionBuilder); + // Block-level Formatting Objects + addBuilder("block", StandardStructureTypes.Paragraphlike.P); + addBuilder("block-container", StandardStructureTypes.Grouping.DIV); + // Inline-level Formatting Objects + addBuilder("character", StandardStructureTypes.InlineLevelStructure.SPAN); + addBuilder("external-graphic", new ImageBuilder()); + addBuilder("instream-foreign-object", new ImageBuilder()); + addBuilder("inline", StandardStructureTypes.InlineLevelStructure.SPAN); + addBuilder("inline-container", StandardStructureTypes.Grouping.DIV); + addBuilder("page-number", StandardStructureTypes.InlineLevelStructure.QUOTE); + addBuilder("page-number-citation", StandardStructureTypes.InlineLevelStructure.QUOTE); + addBuilder("page-number-citation-last", StandardStructureTypes.InlineLevelStructure.QUOTE); + // Formatting Objects for Tables + addBuilder("table-and-caption", StandardStructureTypes.Grouping.DIV); + addBuilder("table", new TableBuilder()); + addBuilder("table-caption", StandardStructureTypes.Grouping.CAPTION); + addBuilder("table-header", StandardStructureTypes.Table.THEAD); + addBuilder("table-footer", new TableFooterBuilder()); + addBuilder("table-body", StandardStructureTypes.Table.TBODY); + addBuilder("table-row", StandardStructureTypes.Table.TR); + addBuilder("table-cell", new TableCellBuilder()); + // Formatting Objects for Lists + addBuilder("list-block", StandardStructureTypes.List.L); + addBuilder("list-item", StandardStructureTypes.List.LI); + addBuilder("list-item-body", StandardStructureTypes.List.LBODY); + addBuilder("list-item-label", StandardStructureTypes.List.LBL); + // Dynamic Effects: Link and Multi Formatting Objects + addBuilder("basic-link", StandardStructureTypes.InlineLevelStructure.LINK); + // Out-of-Line Formatting Objects + addBuilder("float", StandardStructureTypes.Grouping.DIV); + addBuilder("footnote", StandardStructureTypes.InlineLevelStructure.NOTE); + addBuilder("footnote-body", StandardStructureTypes.Grouping.SECT); + addBuilder("wrapper", StandardStructureTypes.InlineLevelStructure.SPAN); + addBuilder("marker", StandardStructureTypes.Grouping.PRIVATE); + + addBuilder("#PCDATA", new PlaceholderBuilder()); + } + + private static void addBuilder(String fo, StructureType structureType) { + addBuilder(fo, new DefaultStructureElementBuilder(structureType)); + } + + private static void addBuilder(String fo, StructureElementBuilder mapper) { + BUILDERS.put(fo, mapper); + } + + private interface StructureElementBuilder { + + PDFStructElem build(StructureHierarchyMember parent, Attributes attributes, PDFFactory pdfFactory, + EventBroadcaster eventBroadcaster); + + } + + private static class DefaultStructureElementBuilder implements StructureElementBuilder { + + private final StructureType defaultStructureType; + + DefaultStructureElementBuilder(StructureType structureType) { + this.defaultStructureType = structureType; + } + + public final PDFStructElem build(StructureHierarchyMember parent, Attributes attributes, + PDFFactory pdfFactory, EventBroadcaster eventBroadcaster) { + String role = attributes.getValue(ROLE); + StructureType structureType; + if (role == null) { + structureType = defaultStructureType; + } else { + structureType = StandardStructureTypes.get(role); + if (structureType == null) { + structureType = defaultStructureType; + PDFEventProducer.Provider.get(eventBroadcaster).nonStandardStructureType(role, role, + structureType.toString()); + } + } + PDFStructElem structElem = createStructureElement(parent, structureType); + setAttributes(structElem, attributes); + addKidToParent(structElem, parent, attributes); + registerStructureElement(structElem, pdfFactory); + return structElem; + } + + protected PDFStructElem createStructureElement(StructureHierarchyMember parent, + StructureType structureType) { + return new PDFStructElem(parent, structureType); + } + + protected void setAttributes(PDFStructElem structElem, Attributes attributes) { + } + + protected void addKidToParent(PDFStructElem kid, StructureHierarchyMember parent, + Attributes attributes) { + parent.addKid(kid); + } + + protected void registerStructureElement(PDFStructElem structureElement, PDFFactory pdfFactory) { + pdfFactory.getDocument().registerStructureElement(structureElement); + } + + } + + private static class PageSequenceBuilder extends DefaultStructureElementBuilder { + + PageSequenceBuilder() { + super(StandardStructureTypes.Grouping.PART); + } + + @Override + protected PDFStructElem createStructureElement(StructureHierarchyMember parent, + StructureType structureType) { + return new PageSequenceStructElem(parent, structureType); + } + + } + + private static class RegionBuilder extends DefaultStructureElementBuilder { + + RegionBuilder() { + super(StandardStructureTypes.Grouping.SECT); + } + + @Override + protected void addKidToParent(PDFStructElem kid, StructureHierarchyMember parent, + Attributes attributes) { + String flowName = attributes.getValue(Flow.FLOW_NAME); + ((PageSequenceStructElem) parent).addContent(flowName, kid); + } + + } + + private static class ImageBuilder extends DefaultStructureElementBuilder { + + ImageBuilder() { + super(StandardStructureTypes.Illustration.FIGURE); + } + + @Override + protected void setAttributes(PDFStructElem structElem, Attributes attributes) { + String altTextNode = attributes.getValue(ExtensionElementMapping.URI, "alt-text"); + if (altTextNode == null) { + altTextNode = "No alternate text specified"; + } + structElem.put("Alt", altTextNode); + } + + } + + private static class TableBuilder extends DefaultStructureElementBuilder { + + TableBuilder() { + super(StandardStructureTypes.Table.TABLE); + } + + @Override + protected PDFStructElem createStructureElement(StructureHierarchyMember parent, + StructureType structureType) { + return new TableStructElem(parent, structureType); + } + } + + private static class TableFooterBuilder extends DefaultStructureElementBuilder { + + public TableFooterBuilder() { + super(StandardStructureTypes.Table.TFOOT); + } + + @Override + protected void addKidToParent(PDFStructElem kid, StructureHierarchyMember parent, + Attributes attributes) { + ((TableStructElem) parent).addTableFooter(kid); + } + } + + private static class TableCellBuilder extends DefaultStructureElementBuilder { + + TableCellBuilder() { + super(StandardStructureTypes.Table.TD); + } + + @Override + protected PDFStructElem createStructureElement(StructureHierarchyMember parent, + StructureType structureType) { + PDFStructElem grandParent = ((PDFStructElem) parent).getParentStructElem(); + //TODO What to do with cells from table-footer? Currently they are mapped on TD. + if (grandParent.getStructureType() == StandardStructureTypes.Table.THEAD) { + structureType = StandardStructureTypes.Table.TH; + } else { + structureType = StandardStructureTypes.Table.TD; + } + return super.createStructureElement(parent, structureType); + } + + @Override + protected void registerStructureElement(PDFStructElem structureElement, PDFFactory pdfFactory) { + if (structureElement.getStructureType() == Table.TH) { + pdfFactory.getDocument().registerStructureElement(structureElement, Scope.COLUMN); + } else { + pdfFactory.getDocument().registerStructureElement(structureElement); + } + } + + @Override + protected void setAttributes(PDFStructElem structElem, Attributes attributes) { + String columnSpan = attributes.getValue("number-columns-spanned"); + if (columnSpan != null) { + structElem.setTableAttributeColSpan(Integer.parseInt(columnSpan)); + } + String rowSpan = attributes.getValue("number-rows-spanned"); + if (rowSpan != null) { + structElem.setTableAttributeRowSpan(Integer.parseInt(rowSpan)); + } + } + + } + + private static class PlaceholderBuilder implements StructureElementBuilder { + + public PDFStructElem build(StructureHierarchyMember parent, Attributes attributes, + PDFFactory pdfFactory, EventBroadcaster eventBroadcaster) { + PDFStructElem elem = new PDFStructElem.Placeholder(parent); + parent.addKid(elem); + return elem; + } + + } + private PDFFactory pdfFactory; private EventBroadcaster eventBroadcaster; @@ -51,6 +298,10 @@ class PDFStructureTreeBuilder implements StructureTreeEventHandler { this.pdfFactory = pdfFactory; } + void setEventBroadcaster(EventBroadcaster eventBroadcaster) { + this.eventBroadcaster = eventBroadcaster; + } + void setLogicalStructureHandler(PDFLogicalStructureHandler logicalStructureHandler) { createRootStructureElement(logicalStructureHandler); } @@ -59,93 +310,53 @@ class PDFStructureTreeBuilder implements StructureTreeEventHandler { assert rootStructureElement == null; PDFParentTree parentTree = logicalStructureHandler.getParentTree(); PDFStructTreeRoot structTreeRoot = pdfFactory.getDocument().makeStructTreeRoot(parentTree); - rootStructureElement = createStructureElement("root", structTreeRoot, null); - structTreeRoot.addKid(rootStructureElement); + rootStructureElement = createStructureElement("root", structTreeRoot, + new AttributesImpl(), pdfFactory, eventBroadcaster); } - void setEventBroadcaster(EventBroadcaster eventBroadcaster) { - this.eventBroadcaster = eventBroadcaster; - } + private static PDFStructElem createStructureElement(String name, StructureHierarchyMember parent, + Attributes attributes, PDFFactory pdfFactory, EventBroadcaster eventBroadcaster) { + StructureElementBuilder builder = BUILDERS.get(name); + if (builder == null) { + // TODO is a fallback really necessary? + builder = DEFAULT_BUILDER; + } + return builder.build(parent, attributes, pdfFactory, eventBroadcaster); + } public void startPageSequence(Locale language, String role) { ancestors = new LinkedList(); - PDFStructElem structElem = createStructureElement("page-sequence", rootStructureElement, role); + AttributesImpl attributes = new AttributesImpl(); + attributes.addAttribute("", ROLE, ROLE, XMLUtil.CDATA, role); + PDFStructElem structElem = createStructureElement("page-sequence", + rootStructureElement, attributes, pdfFactory, eventBroadcaster); if (language != null) { structElem.setLanguage(language); } - rootStructureElement.addKid(structElem); ancestors.add(structElem); } - private PDFStructElem createStructureElement(String name, PDFObject parent, String role) { - 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() { } public StructureTreeElement startNode(String name, Attributes attributes) { PDFStructElem parent = ancestors.getFirst(); - String role = attributes.getValue("role"); - PDFStructElem structElem = createStructureElement(name, parent, role); - setSpanAttributes(structElem, attributes); - parent.addKid(structElem); + PDFStructElem structElem = createStructureElement(name, parent, attributes, + pdfFactory, eventBroadcaster); ancestors.addFirst(structElem); return structElem; } - private void setSpanAttributes(PDFStructElem structElem, Attributes attributes) { - String columnSpan = attributes.getValue("number-columns-spanned"); - if (columnSpan != null) { - structElem.setTableAttributeColSpan(Integer.parseInt(columnSpan)); - } - String rowSpan = attributes.getValue("number-rows-spanned"); - if (rowSpan != null) { - structElem.setTableAttributeRowSpan(Integer.parseInt(rowSpan)); - } - } - public void endNode(String name) { - removeFirstAncestor(); - } - - private void removeFirstAncestor() { ancestors.removeFirst(); } public StructureTreeElement startImageNode(String name, Attributes attributes) { - PDFStructElem parent = ancestors.getFirst(); - String role = attributes.getValue("role"); - PDFStructElem structElem = createStructureElement(name, parent, role); - parent.addKid(structElem); - String altTextNode = attributes.getValue(ExtensionElementMapping.URI, "alt-text"); - if (altTextNode != null) { - structElem.put("Alt", altTextNode); - } else { - structElem.put("Alt", "No alternate text specified"); - } - ancestors.addFirst(structElem); - return structElem; + return startNode(name, attributes); } public StructureTreeElement startReferencedNode(String name, Attributes attributes) { - PDFStructElem parent = ancestors.getFirst(); - String role = attributes.getValue("role"); - PDFStructElem structElem; - if ("#PCDATA".equals(name)) { - structElem = new PDFStructElem.Placeholder(parent); - } else { - structElem = createStructureElement(name, parent, role); - } - parent.addKid(structElem); - ancestors.addFirst(structElem); - return structElem; + return startNode(name, attributes); } } diff --git a/src/java/org/apache/fop/render/pdf/PageSequenceStructElem.java b/src/java/org/apache/fop/render/pdf/PageSequenceStructElem.java new file mode 100644 index 000000000..09d5b81a2 --- /dev/null +++ b/src/java/org/apache/fop/render/pdf/PageSequenceStructElem.java @@ -0,0 +1,79 @@ +/* + * 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.render.pdf; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.fop.pdf.PDFArray; +import org.apache.fop.pdf.PDFObject; +import org.apache.fop.pdf.PDFStructElem; +import org.apache.fop.pdf.StructureType; + +class PageSequenceStructElem extends PDFStructElem { + + private List regionBefores = new ArrayList(); + + private List regionAfters = new ArrayList(); + + private List regionStarts = new ArrayList(); + + private List regionEnds = new ArrayList(); + + PageSequenceStructElem(PDFObject parent, StructureType structureType) { + super(parent, structureType); + } + + void addContent(String flowName, PDFStructElem content) { + if (flowName.equals("xsl-region-before")) { + regionBefores.add(content); + } else if (flowName.equals("xsl-region-after")) { + regionAfters.add(content); + } else if (flowName.equals("xsl-region-start")) { + regionStarts.add(content); + } else if (flowName.equals("xsl-region-end")) { + regionEnds.add(content); + } else { + addKid(content); + } + } + + @Override + protected boolean attachKids() { + assert !kids.isEmpty(); + PDFArray k = new PDFArray(); + addRegions(k, regionBefores); + addRegions(k, regionStarts); + addRegions(k, kids); + addRegions(k, regionEnds); + addRegions(k, regionAfters); + put("K", k); + return true; + } + + private void addRegions(PDFArray k, List regions) { + if (!regions.isEmpty()) { + for (PDFObject kid : regions) { + k.add(kid); + } + } + } + +} diff --git a/src/java/org/apache/fop/render/pdf/TableStructElem.java b/src/java/org/apache/fop/render/pdf/TableStructElem.java new file mode 100644 index 000000000..c44acb25c --- /dev/null +++ b/src/java/org/apache/fop/render/pdf/TableStructElem.java @@ -0,0 +1,48 @@ +/* + * 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.render.pdf; + +import org.apache.fop.pdf.PDFObject; +import org.apache.fop.pdf.PDFStructElem; +import org.apache.fop.pdf.StructureType; + +class TableStructElem extends PDFStructElem { + + private PDFStructElem tableFooter; + + public TableStructElem(PDFObject parent, StructureType structureType) { + super(parent, structureType); + } + + void addTableFooter(PDFStructElem footer) { + assert tableFooter == null; + tableFooter = footer; + } + + @Override + protected boolean attachKids() { + assert !kids.isEmpty(); + if (tableFooter != null) { + kids.add(tableFooter); + } + return super.attachKids(); + } + +} -- cgit v1.2.3 From a1af0b2481adf1d4bdb1279972a9d4e51e381f6a Mon Sep 17 00:00:00 2001 From: Vincent Hennebert Date: Wed, 26 Sep 2012 11:31:40 +0000 Subject: Bugzilla #53902: Added possibility to define 'header' table columns (the same way as fo:table-header allows to define header rows). When accessibility is enabled, this allows to set the appropriate Scope attribute on the corresponding TH cells. git-svn-id: https://svn.apache.org/repos/asf/xmlgraphics/fop/trunk@1390412 13f79535-47bb-0310-9956-ffa450edef68 --- .../fo/StructureTreeEventTrigger.java | 33 ++++++ src/java/org/apache/fop/fo/Constants.java | 5 +- src/java/org/apache/fop/fo/FOPropertyMapping.java | 6 ++ .../fop/fo/extensions/ExtensionElementMapping.java | 1 + .../fop/fo/extensions/InternalElementMapping.java | 3 + .../org/apache/fop/fo/flow/table/TableColumn.java | 12 +++ .../fop/render/pdf/PDFStructureTreeBuilder.java | 29 +++--- status.xml | 5 + .../fo/FO2StructureTreeConverterTestCase.java | 5 + .../fop/accessibility/fo/fo2StructureTree.xsl | 31 +++++- .../fop/accessibility/fo/table-header_scope.fo | 57 +++++++++++ test/java/org/apache/fop/fo/FONodeMocks.java | 30 ++++-- .../fop/fo/flow/table/HeaderColumnTestCase.java | 112 +++++++++++++++++++++ .../pdf/background-image_svg_repeat.pdf | Bin 16997 -> 17081 bytes .../pdf/background-image_svg_single.pdf | Bin 9872 -> 9893 bytes test/pdf/accessibility/pdf/image_svg.pdf | Bin 14161 -> 14203 bytes test/pdf/accessibility/pdf/image_wmf.pdf | Bin 247758 -> 248963 bytes test/pdf/accessibility/pdf/text_font-embedding.pdf | Bin 19682 -> 28436 bytes test/pdf/accessibility/pdf/th_scope.pdf | Bin 0 -> 15495 bytes test/pdf/accessibility/th_scope.fo | 68 +++++++++++++ 20 files changed, 371 insertions(+), 26 deletions(-) create mode 100644 test/java/org/apache/fop/accessibility/fo/table-header_scope.fo create mode 100644 test/java/org/apache/fop/fo/flow/table/HeaderColumnTestCase.java create mode 100644 test/pdf/accessibility/pdf/th_scope.pdf create mode 100644 test/pdf/accessibility/th_scope.fo (limited to 'src/java/org/apache/fop/render/pdf') diff --git a/src/java/org/apache/fop/accessibility/fo/StructureTreeEventTrigger.java b/src/java/org/apache/fop/accessibility/fo/StructureTreeEventTrigger.java index 93b815d30..09a814ef5 100644 --- a/src/java/org/apache/fop/accessibility/fo/StructureTreeEventTrigger.java +++ b/src/java/org/apache/fop/accessibility/fo/StructureTreeEventTrigger.java @@ -20,6 +20,7 @@ package org.apache.fop.accessibility.fo; import java.util.Locale; +import java.util.Stack; import org.xml.sax.SAXException; import org.xml.sax.helpers.AttributesImpl; @@ -30,6 +31,7 @@ import org.apache.fop.fo.FOEventHandler; import org.apache.fop.fo.FONode; import org.apache.fop.fo.FOText; import org.apache.fop.fo.extensions.ExtensionElementMapping; +import org.apache.fop.fo.extensions.InternalElementMapping; import org.apache.fop.fo.flow.AbstractGraphics; import org.apache.fop.fo.flow.BasicLink; import org.apache.fop.fo.flow.Block; @@ -70,6 +72,10 @@ class StructureTreeEventTrigger extends FOEventHandler { private LayoutMasterSet layoutMasterSet; + private final Stack tables = new Stack
(); + + private final Stack inTableHeader = new Stack(); + public StructureTreeEventTrigger(StructureTreeEventHandler structureTreeEventHandler) { this.structureTreeEventHandler = structureTreeEventHandler; } @@ -195,42 +201,51 @@ class StructureTreeEventTrigger extends FOEventHandler { @Override public void startTable(Table tbl) { + tables.push(tbl); startElement(tbl); } @Override public void endTable(Table tbl) { endElement(tbl); + tables.pop(); } @Override public void startHeader(TableHeader header) { + inTableHeader.push(Boolean.TRUE); startElement(header); } @Override public void endHeader(TableHeader header) { endElement(header); + inTableHeader.pop(); } @Override public void startFooter(TableFooter footer) { + // TODO Shouldn't it be true? + inTableHeader.push(Boolean.FALSE); startElement(footer); } @Override public void endFooter(TableFooter footer) { endElement(footer); + inTableHeader.pop(); } @Override public void startBody(TableBody body) { + inTableHeader.push(Boolean.FALSE); startElement(body); } @Override public void endBody(TableBody body) { endElement(body); + inTableHeader.pop(); } @Override @@ -248,6 +263,24 @@ class StructureTreeEventTrigger extends FOEventHandler { AttributesImpl attributes = new AttributesImpl(); addSpanAttribute(attributes, "number-columns-spanned", tc.getNumberColumnsSpanned()); addSpanAttribute(attributes, "number-rows-spanned", tc.getNumberRowsSpanned()); + boolean rowHeader = inTableHeader.peek(); + boolean columnHeader = tables.peek().getColumn(tc.getColumnNumber() - 1).isHeader(); + if (rowHeader || columnHeader) { + final String th = "TH"; + String role = tc.getCommonAccessibility().getRole(); + /* Do not override a custom role */ + if (role == null) { + role = th; + addNoNamespaceAttribute(attributes, "role", th); + } + if (role.equals(th)) { + if (columnHeader) { + String scope = rowHeader ? "Both" : "Row"; + addAttribute(attributes, InternalElementMapping.URI, InternalElementMapping.SCOPE, + InternalElementMapping.STANDARD_PREFIX, scope); + } + } + } startElement(tc, attributes); } diff --git a/src/java/org/apache/fop/fo/Constants.java b/src/java/org/apache/fop/fo/Constants.java index 2d10dcdd9..6688042e2 100644 --- a/src/java/org/apache/fop/fo/Constants.java +++ b/src/java/org/apache/fop/fo/Constants.java @@ -785,8 +785,11 @@ public interface Constants { */ int PR_X_NUMBER_CONVERSION_FEATURES = 276; + /** Scope for table header */ + int PR_X_HEADER_COLUMN = 277; + /** Number of property constants defined */ - int PROPERTY_COUNT = 276; + int PROPERTY_COUNT = 277; // compound property constants diff --git a/src/java/org/apache/fop/fo/FOPropertyMapping.java b/src/java/org/apache/fop/fo/FOPropertyMapping.java index d0d13fc17..a92b71ef9 100644 --- a/src/java/org/apache/fop/fo/FOPropertyMapping.java +++ b/src/java/org/apache/fop/fo/FOPropertyMapping.java @@ -2513,6 +2513,12 @@ public final class FOPropertyMapping implements Constants { m.setInherited(false); m.setDefault("false"); addPropertyMaker("table-omit-header-at-break", m); + + // fox:scope + m = new EnumProperty.Maker(PR_X_HEADER_COLUMN); + m.useGeneric(genericBoolean); + m.setDefault("false"); + addPropertyMaker("fox:header", m); } private void createWritingModeProperties() { diff --git a/src/java/org/apache/fop/fo/extensions/ExtensionElementMapping.java b/src/java/org/apache/fop/fo/extensions/ExtensionElementMapping.java index f0e03399f..a040edf1a 100644 --- a/src/java/org/apache/fop/fo/extensions/ExtensionElementMapping.java +++ b/src/java/org/apache/fop/fo/extensions/ExtensionElementMapping.java @@ -52,6 +52,7 @@ public class ExtensionElementMapping extends ElementMapping { PROPERTY_ATTRIBUTES.add("disable-column-balancing"); //These are FOP's extension properties for accessibility PROPERTY_ATTRIBUTES.add("alt-text"); + PROPERTY_ATTRIBUTES.add("header"); } /** diff --git a/src/java/org/apache/fop/fo/extensions/InternalElementMapping.java b/src/java/org/apache/fop/fo/extensions/InternalElementMapping.java index 687952d25..f257dd79d 100644 --- a/src/java/org/apache/fop/fo/extensions/InternalElementMapping.java +++ b/src/java/org/apache/fop/fo/extensions/InternalElementMapping.java @@ -43,12 +43,15 @@ public class InternalElementMapping extends ElementMapping { /** The "struct-ref" attribute, to refer to a structure tree element. */ public static final String STRUCT_REF = "struct-ref"; + public static final String SCOPE = "scope"; + private static final Set PROPERTY_ATTRIBUTES = new java.util.HashSet(); static { //These are FOP's extension properties for accessibility PROPERTY_ATTRIBUTES.add(STRUCT_ID); PROPERTY_ATTRIBUTES.add(STRUCT_REF); + PROPERTY_ATTRIBUTES.add(SCOPE); } /** diff --git a/src/java/org/apache/fop/fo/flow/table/TableColumn.java b/src/java/org/apache/fop/fo/flow/table/TableColumn.java index 5047822da..33cbff884 100644 --- a/src/java/org/apache/fop/fo/flow/table/TableColumn.java +++ b/src/java/org/apache/fop/fo/flow/table/TableColumn.java @@ -24,6 +24,7 @@ import org.xml.sax.Locator; import org.apache.fop.apps.FOPException; import org.apache.fop.datatypes.Length; +import org.apache.fop.fo.Constants; import org.apache.fop.fo.FONode; import org.apache.fop.fo.PropertyList; import org.apache.fop.fo.ValidationException; @@ -44,6 +45,7 @@ public class TableColumn extends TableFObj { private Length columnWidth; private int numberColumnsRepeated; private int numberColumnsSpanned; + private boolean isHeader; // Unused but valid items, commented out for performance: // private int visibility; // End of property values @@ -120,6 +122,7 @@ public class TableColumn extends TableFObj { if (!this.implicitColumn) { this.pList = pList; } + isHeader = (pList.get(Constants.PR_X_HEADER_COLUMN).getEnum() == Constants.EN_TRUE); } /** {@inheritDoc} */ @@ -263,4 +266,13 @@ public class TableColumn extends TableFObj { this.pList = null; } + /** + * Returns {@code true} if this column is made of header cells. + * + * @return {@code true} if cells in this column are like TH cells in HTML + */ + public boolean isHeader() { + return isHeader; + } + } diff --git a/src/java/org/apache/fop/render/pdf/PDFStructureTreeBuilder.java b/src/java/org/apache/fop/render/pdf/PDFStructureTreeBuilder.java index 0d4a6b7fb..031224ffb 100644 --- a/src/java/org/apache/fop/render/pdf/PDFStructureTreeBuilder.java +++ b/src/java/org/apache/fop/render/pdf/PDFStructureTreeBuilder.java @@ -30,6 +30,7 @@ import org.apache.fop.accessibility.StructureTreeElement; import org.apache.fop.accessibility.StructureTreeEventHandler; import org.apache.fop.events.EventBroadcaster; import org.apache.fop.fo.extensions.ExtensionElementMapping; +import org.apache.fop.fo.extensions.InternalElementMapping; import org.apache.fop.fo.pagination.Flow; import org.apache.fop.pdf.PDFFactory; import org.apache.fop.pdf.PDFParentTree; @@ -138,7 +139,7 @@ class PDFStructureTreeBuilder implements StructureTreeEventHandler { PDFStructElem structElem = createStructureElement(parent, structureType); setAttributes(structElem, attributes); addKidToParent(structElem, parent, attributes); - registerStructureElement(structElem, pdfFactory); + registerStructureElement(structElem, pdfFactory, attributes); return structElem; } @@ -155,7 +156,8 @@ class PDFStructureTreeBuilder implements StructureTreeEventHandler { parent.addKid(kid); } - protected void registerStructureElement(PDFStructElem structureElement, PDFFactory pdfFactory) { + protected void registerStructureElement(PDFStructElem structureElement, PDFFactory pdfFactory, + Attributes attributes) { pdfFactory.getDocument().registerStructureElement(structureElement); } @@ -240,22 +242,15 @@ class PDFStructureTreeBuilder implements StructureTreeEventHandler { } @Override - protected PDFStructElem createStructureElement(StructureHierarchyMember parent, - StructureType structureType) { - PDFStructElem grandParent = ((PDFStructElem) parent).getParentStructElem(); - //TODO What to do with cells from table-footer? Currently they are mapped on TD. - if (grandParent.getStructureType() == StandardStructureTypes.Table.THEAD) { - structureType = StandardStructureTypes.Table.TH; - } else { - structureType = StandardStructureTypes.Table.TD; - } - return super.createStructureElement(parent, structureType); - } - - @Override - protected void registerStructureElement(PDFStructElem structureElement, PDFFactory pdfFactory) { + protected void registerStructureElement(PDFStructElem structureElement, PDFFactory pdfFactory, + Attributes attributes) { if (structureElement.getStructureType() == Table.TH) { - pdfFactory.getDocument().registerStructureElement(structureElement, Scope.COLUMN); + String scopeAttribute = attributes.getValue(InternalElementMapping.URI, + InternalElementMapping.SCOPE); + Scope scope = (scopeAttribute == null) + ? Scope.COLUMN + : Scope.valueOf(scopeAttribute.toUpperCase(Locale.ENGLISH)); + pdfFactory.getDocument().registerStructureElement(structureElement, scope); } else { pdfFactory.getDocument().registerStructureElement(structureElement); } diff --git a/status.xml b/status.xml index 7571ddfda..40e4c8db6 100644 --- a/status.xml +++ b/status.xml @@ -62,6 +62,11 @@ documents. Example: the fix of marks layering will be such a case when it's done. --> + + Added possibility to define ‘header’ table columns (the same way as fo:table-header allows + to define header rows). When accessibility is enabled, this allows to set the appropriate + Scope attribute on the corresponding TH cells. + Full font embedding in PDF diff --git a/test/java/org/apache/fop/accessibility/fo/FO2StructureTreeConverterTestCase.java b/test/java/org/apache/fop/accessibility/fo/FO2StructureTreeConverterTestCase.java index 6092912f9..87b970576 100644 --- a/test/java/org/apache/fop/accessibility/fo/FO2StructureTreeConverterTestCase.java +++ b/test/java/org/apache/fop/accessibility/fo/FO2StructureTreeConverterTestCase.java @@ -91,6 +91,11 @@ public class FO2StructureTreeConverterTestCase { testConverter("/org/apache/fop/fo/pagination/side-regions.fo"); } + @Test + public void headerTableCellMustPropagateScope() throws Exception { + testConverter("table-header_scope.fo"); + } + private static InputStream getResource(String name) { return FO2StructureTreeConverterTestCase.class.getResourceAsStream(name); } diff --git a/test/java/org/apache/fop/accessibility/fo/fo2StructureTree.xsl b/test/java/org/apache/fop/accessibility/fo/fo2StructureTree.xsl index c739462e4..90d74a7c4 100644 --- a/test/java/org/apache/fop/accessibility/fo/fo2StructureTree.xsl +++ b/test/java/org/apache/fop/accessibility/fo/fo2StructureTree.xsl @@ -21,7 +21,7 @@ xmlns:fox="http://xmlgraphics.apache.org/fop/extensions" xmlns:foi="http://xmlgraphics.apache.org/fop/internal"> - + @@ -92,10 +92,37 @@ - + + + + + + + + + + TH + Row + + + + + + + + + TH + + Both + + + + + diff --git a/test/java/org/apache/fop/accessibility/fo/table-header_scope.fo b/test/java/org/apache/fop/accessibility/fo/table-header_scope.fo new file mode 100644 index 000000000..c6272d546 --- /dev/null +++ b/test/java/org/apache/fop/accessibility/fo/table-header_scope.fo @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + Table Header + + + Column 1 + + + Column 2 + + + + + + + Row 1 + + + Cell 1.1 + + + Cell 1.2 + + + + + Row 2 + + + Cell 2.1 + + + Cell 2.2 + + + + + + + diff --git a/test/java/org/apache/fop/fo/FONodeMocks.java b/test/java/org/apache/fop/fo/FONodeMocks.java index 001173179..ba969b9fa 100644 --- a/test/java/org/apache/fop/fo/FONodeMocks.java +++ b/test/java/org/apache/fop/fo/FONodeMocks.java @@ -19,19 +19,22 @@ package org.apache.fop.fo; +import java.io.IOException; + import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyString; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; -import java.io.IOException; - import org.apache.xmlgraphics.image.loader.ImageException; import org.apache.xmlgraphics.image.loader.ImageManager; import org.apache.xmlgraphics.image.loader.ImageSessionContext; import org.apache.fop.apps.FOUserAgent; import org.apache.fop.apps.FopFactory; +import org.apache.fop.events.EventBroadcaster; +import org.apache.fop.fo.flow.table.ColumnNumberManager; +import org.apache.fop.fo.flow.table.ColumnNumberManagerHolder; /** * A helper class for creating mocks of {@link FONode} and its descendants. @@ -51,27 +54,36 @@ public final class FONodeMocks { public static FONode mockFONode() { FONode mockFONode = mock(FONode.class); mockGetFOEventHandler(mockFONode); + mockGetImageManager(mockFONode.getFOEventHandler().getUserAgent()); return mockFONode; } - private static void mockGetFOEventHandler(FONode mockFONode) { + public static FOEventHandler mockGetFOEventHandler(FONode mockFONode) { FOEventHandler mockFOEventHandler = mock(FOEventHandler.class); mockGetUserAgent(mockFOEventHandler); when(mockFONode.getFOEventHandler()).thenReturn(mockFOEventHandler); + return mockFOEventHandler; } - private static void mockGetUserAgent(FOEventHandler mockFOEventHandler) { + public static FOUserAgent mockGetUserAgent(FOEventHandler mockFOEventHandler) { FOUserAgent mockFOUserAgent = mock(FOUserAgent.class); - mockGetImageManager(mockFOUserAgent); when(mockFOEventHandler.getUserAgent()).thenReturn(mockFOUserAgent); + return mockFOUserAgent; } - private static void mockGetImageManager(FOUserAgent mockFOUserAgent) { + public static EventBroadcaster mockGetEventBroadcaster(FOUserAgent mockFOUserAgent) { + EventBroadcaster mockBroadcaster = mock(EventBroadcaster.class); + when(mockFOUserAgent.getEventBroadcaster()).thenReturn(mockBroadcaster); + return mockBroadcaster; + } + + public static ImageManager mockGetImageManager(FOUserAgent mockFOUserAgent) { try { ImageManager mockImageManager = mock(ImageManager.class); when(mockImageManager.getImageInfo(anyString(), any(ImageSessionContext.class))) .thenReturn(null); when(mockFOUserAgent.getImageManager()).thenReturn(mockImageManager); + return mockImageManager; } catch (ImageException e) { throw new RuntimeException(e); } catch (IOException e) { @@ -79,4 +91,10 @@ public final class FONodeMocks { } } + public static ColumnNumberManager mockGetColumnNumberManager(ColumnNumberManagerHolder mock) { + ColumnNumberManager mockColumnNumberManager = mock(ColumnNumberManager.class); + when(mock.getColumnNumberManager()).thenReturn(mockColumnNumberManager); + return mockColumnNumberManager; + } + } diff --git a/test/java/org/apache/fop/fo/flow/table/HeaderColumnTestCase.java b/test/java/org/apache/fop/fo/flow/table/HeaderColumnTestCase.java new file mode 100644 index 000000000..4cc94b5e0 --- /dev/null +++ b/test/java/org/apache/fop/fo/flow/table/HeaderColumnTestCase.java @@ -0,0 +1,112 @@ +/* + * 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.fo.flow.table; + +import org.junit.Test; +import org.xml.sax.Attributes; +import org.xml.sax.Locator; +import org.xml.sax.helpers.AttributesImpl; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyString; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import org.apache.fop.apps.FOUserAgent; +import org.apache.fop.events.EventBroadcaster; +import org.apache.fop.fo.ElementMappingRegistry; +import org.apache.fop.fo.FOEventHandler; +import org.apache.fop.fo.FONodeMocks; +import org.apache.fop.fo.FOValidationEventProducer; +import org.apache.fop.fo.PropertyList; +import org.apache.fop.fo.StaticPropertyList; +import org.apache.fop.fo.ValidationException; +import org.apache.fop.fo.expr.PropertyException; +import org.apache.fop.fo.extensions.ExtensionElementMapping; +import org.apache.fop.util.XMLUtil; + +/** + * Tests that the fox:header property is correctly parsed and set up at the FO tree level. + */ +public class HeaderColumnTestCase { + + @Test + public void testWrongValue() throws ValidationException { + Table parent = createTableParent(); + EventBroadcaster mockEventBroadcaster = FONodeMocks.mockGetEventBroadcaster( + parent.getFOEventHandler().getUserAgent()); + FOValidationEventProducer eventProducer = mockGetEventProducerFor(mockEventBroadcaster); + TableColumn column = new TableColumn(parent); + PropertyList propertyList = new StaticPropertyList(column, null); + Attributes atts = createScopeAttribute("blah"); + propertyList.addAttributesToList(atts); + verify(eventProducer).invalidPropertyValue(any(), eq("fo:table-column"), + eq("fox:header"), eq("blah"), any(PropertyException.class), any(Locator.class)); + } + + @Test + public void testCorrectValue() throws Exception { + testCorrectValue(true); + testCorrectValue(false); + } + + private void testCorrectValue(boolean expectedValue) throws Exception { + Table parent = createTableParent(); + FONodeMocks.mockGetColumnNumberManager(parent); + TableColumn column = new TableColumn(parent, true); + PropertyList propertyList = new StaticPropertyList(column, null); + Attributes atts = createScopeAttribute(String.valueOf(expectedValue)); + propertyList.addAttributesToList(atts); + column.bind(propertyList); + assertEquals(expectedValue, column.isHeader()); + } + + private Table createTableParent() { + Table parent = mock(Table.class); + FOEventHandler mockFOEventHandler = FONodeMocks.mockGetFOEventHandler(parent); + FOUserAgent mockUserAgent = mockFOEventHandler.getUserAgent(); + mockGetElementMappingRegistry(mockUserAgent); + return parent; + } + + private Attributes createScopeAttribute(String value) { + AttributesImpl atts = new AttributesImpl(); + atts.addAttribute(ExtensionElementMapping.URI, "header", "fox:header", XMLUtil.CDATA, value); + return atts; + } + + private ElementMappingRegistry mockGetElementMappingRegistry(FOUserAgent mockUserAgent) { + ElementMappingRegistry mockRegistry = mock(ElementMappingRegistry.class); + when(mockRegistry.getElementMapping(anyString())).thenReturn(new ExtensionElementMapping()); + when(mockUserAgent.getElementMappingRegistry()).thenReturn(mockRegistry); + return mockRegistry; + } + + private FOValidationEventProducer mockGetEventProducerFor(EventBroadcaster mockEventBroadcaster) { + FOValidationEventProducer mockEventProducer = mock(FOValidationEventProducer.class); + when(mockEventBroadcaster.getEventProducerFor(eq(FOValidationEventProducer.class))) + .thenReturn(mockEventProducer); + return mockEventProducer; + } + +} diff --git a/test/pdf/accessibility/pdf/background-image_svg_repeat.pdf b/test/pdf/accessibility/pdf/background-image_svg_repeat.pdf index 41c2ee500..0921d734f 100644 Binary files a/test/pdf/accessibility/pdf/background-image_svg_repeat.pdf and b/test/pdf/accessibility/pdf/background-image_svg_repeat.pdf differ diff --git a/test/pdf/accessibility/pdf/background-image_svg_single.pdf b/test/pdf/accessibility/pdf/background-image_svg_single.pdf index 8c2dc1006..9c8af4fb6 100644 Binary files a/test/pdf/accessibility/pdf/background-image_svg_single.pdf and b/test/pdf/accessibility/pdf/background-image_svg_single.pdf differ diff --git a/test/pdf/accessibility/pdf/image_svg.pdf b/test/pdf/accessibility/pdf/image_svg.pdf index a9428fd3f..cc0a3ebba 100644 Binary files a/test/pdf/accessibility/pdf/image_svg.pdf and b/test/pdf/accessibility/pdf/image_svg.pdf differ diff --git a/test/pdf/accessibility/pdf/image_wmf.pdf b/test/pdf/accessibility/pdf/image_wmf.pdf index c15a05223..368afe60d 100644 Binary files a/test/pdf/accessibility/pdf/image_wmf.pdf and b/test/pdf/accessibility/pdf/image_wmf.pdf differ diff --git a/test/pdf/accessibility/pdf/text_font-embedding.pdf b/test/pdf/accessibility/pdf/text_font-embedding.pdf index d1e4c6e28..47ca60bdb 100644 Binary files a/test/pdf/accessibility/pdf/text_font-embedding.pdf and b/test/pdf/accessibility/pdf/text_font-embedding.pdf differ diff --git a/test/pdf/accessibility/pdf/th_scope.pdf b/test/pdf/accessibility/pdf/th_scope.pdf new file mode 100644 index 000000000..596ffb7db Binary files /dev/null and b/test/pdf/accessibility/pdf/th_scope.pdf differ diff --git a/test/pdf/accessibility/th_scope.fo b/test/pdf/accessibility/th_scope.fo new file mode 100644 index 000000000..4e22b9160 --- /dev/null +++ b/test/pdf/accessibility/th_scope.fo @@ -0,0 +1,68 @@ + + + + + + + + + + + + + + + + + Table Header + + + Column 1 + + + Column 2 + + + + + + + Row 1 + + + Cell 1.1 + + + Cell 1.2 + + + + + Row 2 + + + Cell 2.1 + + + Cell 2.2 + + + + + Non-header + + + Cell 3.1 + + + Cell 3.2 + + + + + + + -- cgit v1.2.3