/* * 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.io.IOException; import java.io.OutputStream; import java.util.ArrayList; import java.util.Arrays; 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; /** * Class representing a PDF Structure Element. */ public class PDFStructElem extends StructureHierarchyMember implements StructureTreeElement, CompressedObject { private StructureType structureType; protected PDFStructElem parentElement; /** * Elements to be added to the kids array. */ protected List<PDFObject> kids; private List<PDFDictionary> attributes; /** * Creates PDFStructElem with no entries. */ public PDFStructElem() { } /** * Creates a new structure element. * * @param parent parent of this element * @param structureType the structure type of this element */ public 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; } } /** * Returns the parent of this structure element. * * @return the parent, <code>null</code> if the parent is not a structure * element (i.e., is the structure tree root) */ public PDFStructElem getParentStructElem() { return parentElement; } /** {@inheritDoc} */ public void setParent(PDFObject parent) { if (parent != null && parent.hasObjectNumber()) { put("P", new PDFReference(parent)); } } /** * Adds a kid to this structure element. * * @param kid element to be added */ @Override public void addKid(PDFObject kid) { if (kids == null) { kids = new ArrayList<PDFObject>(); } kids.add(kid); } /** * Sets the given mcid as the kid of this structure element. This element * will then add itself to its parent structure element if it has not * already, and so will the parent, and so on. * * @param mcid mcid of the marked-content sequence corresponding to this * structure element's kid */ public void setMCIDKid(int mcid) { put("K", mcid); } /** * Sets the page reference of this structure element. * * @param page value for the Pg entry */ public void setPage(PDFPage page) { put("Pg", page); } /** * Returns the structure type of this structure element. * * @return the value of the S entry */ public StructureType getStructureType() { return structureType; } /** * Sets the language of this structure element. * @param language the language (as defined in the section about * "Natural Language Specification") */ private void setLanguage(String language) { put("Lang", language); } /** * Sets the language of this structure element. * * @param language a value for the Lang entry */ public void setLanguage(Locale language) { setLanguage(LanguageTags.toLanguageTag(language)); } /** * Returns the language of this structure element. * * @return the value of the Lang entry (<code>null</code> if no language was specified) */ public String getLanguage() { return (String) get("Lang"); } @Override protected void writeDictionary(OutputStream out, StringBuilder textBuffer) throws IOException { attachKids(); attachAttributes(); super.writeDictionary(out, textBuffer); } private void attachAttributes() { if (attributes != null) { if (attributes.size() == 1) { put("A", attributes.get(0)); } else { PDFArray array = new PDFArray(attributes); put("A", array); } } } public void addKidInSpecificOrder(int position, PDFStructElem kid) { if (kids == null) { addKid(kid); } else { if ((kids.size() - 1) < position) { kids.add(kid); } else if (kids.get(position) == null) { kids.set(position, kid); } else { if (!kids.contains(kid)) { kids.add(position, kid); } } } } /** * Attaches all valid kids to the kids array. * * @return true iff 1+ kids were added to the kids array */ protected boolean attachKids() { List<PDFObject> validKids = new ArrayList<PDFObject>(); if (kids != null) { for (PDFObject kid : kids) { if (kid instanceof Placeholder) { if (((Placeholder) kid).attachKids()) { validKids.add(kid); } } else { validKids.add(kid); } } } boolean kidsAttached = !validKids.isEmpty(); if (kidsAttached) { PDFArray array = new PDFArray(); for (PDFObject ob : validKids) { array.add(ob); } put("K", array); } return kidsAttached; } public void setTableAttributeColSpan(int colSpan) { setTableAttributeRowColumnSpan("ColSpan", colSpan); } public void setTableAttributeRowSpan(int rowSpan) { setTableAttributeRowColumnSpan("RowSpan", rowSpan); } private void setTableAttributeRowColumnSpan(String typeSpan, int span) { PDFDictionary attribute = new PDFDictionary(); attribute.put("O", Table.NAME); attribute.put(typeSpan, span); if (attributes == null) { attributes = new ArrayList<PDFDictionary>(2); } attributes.add(attribute); } public List<PDFObject> getKids() { return this.kids; } public int output(OutputStream stream) throws IOException { if (getDocument().getProfile().getPDFUAMode().isEnabled()) { if (entries.containsKey("Alt") && "".equals(get("Alt"))) { put("Alt", "No alternate text specified"); } else if (kids != null) { for (PDFObject kid : kids) { if (kid instanceof PDFStructElem && !(kid instanceof Placeholder) && structureType.toString().equals("P") && isBSLE(((PDFStructElem) kid).getStructureType().toString())) { structureType = StandardStructureTypes.Grouping.DIV; put("S", StandardStructureTypes.Grouping.DIV.getName()); break; } } } } return super.output(stream); } private boolean isBSLE(String type) { String[] blseValues = {"Table", "L", "P"}; return Arrays.asList(blseValues).contains(type); } /** * Class representing a placeholder for a PDF Structure Element. */ public static class Placeholder extends PDFStructElem { @Override public void outputInline(OutputStream out, StringBuilder textBuffer) throws IOException { if (kids != null) { assert kids.size() > 0; for (int i = 0; i < kids.size(); i++) { if (i > 0) { textBuffer.append(' '); } Object obj = kids.get(i); if (obj instanceof PDFStructElem) { ((PDFStructElem) obj).setParent(parentElement); } formatObject(obj, out, textBuffer); } } } public Placeholder(PDFObject parent) { super(parent); } } }