<xs:attribute name="word-spacing" type="mf:lengthType"/>
<xs:attribute name="dx" type="mf:lengthListType"/>
<xs:attribute name="dp" type="mf:dpListType"/>
+ <xs:attribute name="hyphenated" type="xs:boolean"/>
</xs:extension>
</xs:simpleContent>
</xs:complexType>
private static final long serialVersionUID = 7315900267242540809L;
+ private boolean isHyphenated;
+
/**
* Create a text inline area
*/
updateLevel(level);
}
+ /**
+ * Records that the last word in this text area is hyphenated.
+ */
+ public void setHyphenated() {
+ this.isHyphenated = true;
+ }
+
+ /**
+ * Returns {@code true} if the last word in this area is hyphenated.
+ */
+ public boolean isHyphenated() {
+ return isHyphenated;
+ }
+
/**
* Get the whole text string.
* Renderers whose space adjustment handling is not affected
wordChars.append(foText.getCommonHyphenation().getHyphChar(font));
// [TBD] expand bidi word levels, letter space adjusts, gpos adjusts
// [TBD] [GA] problematic in bidi context... what is level of hyphen?
+ textArea.setHyphenated();
}
/**
private String location;
+ private boolean hyphenated;
+
/**
* Main constructor.
* @param ua the user agent
return location;
}
+ /**
+ * Records that the last text in the currently processed text area is hyphenated.
+ */
+ public void setHyphenated(boolean hyphenated) {
+ this.hyphenated = hyphenated;
+ }
+
+ /**
+ * Returns {@code true} if the last text in the currently processed text area is hyphenated.
+ */
+ public boolean isHyphenated() {
+ return hyphenated;
+ }
+
}
dp = IFUtil.convertDXToDP ( dx );
}
establishStructureTreeElement(lastAttributes);
+ boolean isHyphenated = Boolean.valueOf(lastAttributes.getValue("hyphenated"));
+ if (isHyphenated) {
+ documentHandler.getContext().setHyphenated(isHyphenated);
+ }
painter.drawText(x, y, letterSpacing, wordSpacing, dp, content.toString());
+ documentHandler.getContext().setHyphenated(false);
resetStructureTreeElement();
}
textUtil.flush();
textUtil.setStartPosition(rx, bl);
textUtil.setSpacing(text.getTextLetterSpaceAdjust(), text.getTextWordSpaceAdjust());
+ documentHandler.getContext().setHyphenated(text.isHyphenated());
super.renderText(text);
textUtil.flush();
renderTextDecoration(tf, size, text, bl, rx);
+ documentHandler.getContext().setHyphenated(false);
resetStructurePointer();
}
}
}
addStructureReference(atts);
+ if (getContext().isHyphenated()) {
+ addAttribute(atts, "hyphenated", "true");
+ }
handler.startElement(EL_TEXT, atts);
char[] chars = text.toCharArray();
handler.characters(chars, 0, chars.length);
import org.apache.fop.pdf.PDFPaintingState;
import org.apache.fop.pdf.PDFResourceContext;
import org.apache.fop.pdf.PDFStream;
+import org.apache.fop.pdf.PDFText;
import org.apache.fop.pdf.PDFTextUtil;
import org.apache.fop.pdf.PDFXObject;
}
/**
- * Begins a new marked content sequence (BDC or BMC). If the parameter structElemType is null,
- * the sequenceNum is ignored and instead of a BDC with the MCID as parameter, an "Artifact"
- * and a BMC command is generated.
- * @param structElemType Structure Element Type
- * @param mcid Sequence number
+ * Begins a new marked content sequence (BDC or BMC). If {@code structElemType} is
+ * null, a BMC operator with an "Artifact" tag is generated. Otherwise, a BDC operator
+ * with {@code structElemType} as a tag is generated, and the given mcid stored in its
+ * property list.
+ *
+ * @param structElemType the type of the associated structure element
+ * @param mcid the marked content identifier
*/
protected void beginMarkedContentSequence(String structElemType, int mcid) {
+ beginMarkedContentSequence(structElemType, mcid, null);
+ }
+
+ /**
+ * Begins a new marked content sequence (BDC or BMC). If {@code structElemType} is
+ * null, a BMC operator with an "Artifact" tag is generated. Otherwise, a BDC operator
+ * with {@code structElemType} as a tag is generated, and the given mcid and actual
+ * text are stored in its property list.
+ *
+ * @param structElemType the type of the associated structure element
+ * @param mcid the marked content identifier
+ * @param actualText the replacement text for the marked content
+ */
+ protected void beginMarkedContentSequence(String structElemType, int mcid, String actualText) {
assert !this.inMarkedContentSequence;
assert !this.inArtifactMode;
if (structElemType != null) {
- currentStream.add(structElemType + " <</MCID " + String.valueOf(mcid) + ">>\n"
+ String actualTextProperty = actualText == null ? ""
+ : " /ActualText " + PDFText.escapeText(actualText);
+ currentStream.add(structElemType + " <</MCID " + String.valueOf(mcid)
+ + actualTextProperty + ">>\n"
+ "BDC\n");
} else {
currentStream.add("/Artifact\nBMC\n");
currentState.restore();
}
- /**
- * Separates 2 text elements, ending the current marked content sequence and
- * starting a new one.
- *
- * @param structElemType structure element type
- * @param mcid sequence number
- * @see #beginMarkedContentSequence(String, int)
- */
- protected void separateTextElements(String structElemType, int mcid) {
- textutil.endTextObject();
- endMarkedContentSequence();
- beginMarkedContentSequence(structElemType, mcid);
- textutil.beginTextObject();
- }
-
/** Indicates the beginning of a text object. */
protected void beginTextObject() {
if (!textutil.isInTextObject()) {
* @see #beginMarkedContentSequence(String, int)
*/
protected void beginTextObject(String structElemType, int mcid) {
+ beginTextObject(structElemType, mcid, null);
+ }
+
+ /**
+ * Indicates the beginning of a marked-content text object.
+ *
+ * @param structElemType structure element type
+ * @param mcid sequence number
+ * @param actualText the replacement text for the marked content
+ * @see #beginTextObject()
+ * @see #beginMarkedContentSequence(String, int, String))
+ */
+ protected void beginTextObject(String structElemType, int mcid, String actualText) {
if (!textutil.isInTextObject()) {
- beginMarkedContentSequence(structElemType, mcid);
+ beginMarkedContentSequence(structElemType, mcid, actualText);
textutil.beginTextObject();
}
}
PDFStructElem structElem = (PDFStructElem) getContext().getStructureTreeElement();
languageAvailabilityChecker.checkLanguageAvailability(text);
MarkedContentInfo mci = logicalStructureHandler.addTextContentItem(structElem);
- if (generator.getTextUtil().isInTextObject()) {
- generator.separateTextElements(mci.tag, mci.mcid);
- }
+ String actualText = getContext().isHyphenated() ? text.substring(0, text.length() - 1) : null;
+ generator.endTextObject();
generator.updateColor(state.getTextColor(), true, null);
- generator.beginTextObject(mci.tag, mci.mcid);
+ generator.beginTextObject(mci.tag, mci.mcid, actualText);
} else {
generator.updateColor(state.getTextColor(), true, null);
generator.beginTextObject();
documents. Example: the fix of marks layering will be such a case when it's done.
-->
<release version="FOP Trunk" date="TBD">
+ <action context="Renderers" dev="VH" type="add" fixes-bug="54081">
+ PDF accessibility: properly tag hyphenated words.
+ </action>
<action context="Code" dev="CB" type="fix" fixes-bug="48955" due-to="Peter Hancock">
Allow AFP font codepage names to be less than 8 chars
</action>
--- /dev/null
+<?xml version="1.0" standalone="no"?>
+<!--
+ 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.
+-->
+<testcase>
+ <info>
+ <p>
+ This test checks that lines ending with a hyphenated word are properly marked as such.
+ </p>
+ </info>
+ <fo>
+ <fo:root xmlns:fo="http://www.w3.org/1999/XSL/Format" xml:lang="en-US">
+ <fo:layout-master-set>
+ <fo:simple-page-master master-name="page"
+ page-height="170pt" page-width="220pt" margin="10pt">
+ <fo:region-body display-align="center"/>
+ </fo:simple-page-master>
+ </fo:layout-master-set>
+ <fo:page-sequence master-reference="page" hyphenate="true">
+ <fo:flow flow-name="xsl-region-body" text-align="justify">
+ <fo:block-container width="140pt" start-indent="30pt">
+ <fo:block start-indent="0">Hyphenated text. Hyphenated text. Hyphenated text. Hyphenated
+ text. Hyphenated text.</fo:block>
+ </fo:block-container>
+ <fo:block-container width="140pt" space-before="10pt" start-indent="30pt">
+ <fo:block start-indent="0">Hyphenated text with ‘special’ character. Hyphenated text
+ with ‘special’ character.</fo:block>
+ </fo:block-container>
+ </fo:flow>
+ </fo:page-sequence>
+ </fo:root>
+ </fo>
+ <if-checks xmlns:if="http://xmlgraphics.apache.org/fop/intermediate">
+ <eval expected="true" xpath="/descendant::if:text[1]/@hyphenated"/>
+ <eval expected="" xpath="/descendant::if:text[2]/@hyphenated"/>
+ <eval expected="true" xpath="/descendant::if:text[3]/@hyphenated"/>
+ <eval expected="" xpath="/descendant::if:text[4]/@hyphenated"/>
+ <eval expected="true" xpath="/descendant::if:text[5]/@hyphenated"/>
+ <eval expected="true" xpath="/descendant::if:text[6]/@hyphenated"/>
+ <eval expected="true" xpath="/descendant::if:text[7]/@hyphenated"/>
+ <eval expected="" xpath="/descendant::if:text[8]/@hyphenated"/>
+ </if-checks>
+</testcase>
--- /dev/null
+<?xml version="1.0"?>
+<fo:root xmlns:fo="http://www.w3.org/1999/XSL/Format" xml:lang="en-US">
+ <fo:layout-master-set>
+ <fo:simple-page-master master-name="page"
+ page-height="170pt" page-width="220pt" margin="10pt">
+ <fo:region-body display-align="center"/>
+ </fo:simple-page-master>
+ </fo:layout-master-set>
+ <fo:page-sequence master-reference="page" hyphenate="true">
+ <fo:flow flow-name="xsl-region-body" text-align="justify">
+ <fo:block-container width="140pt" start-indent="30pt">
+ <fo:block start-indent="0">Hyphenated text. Hyphenated text. Hyphenated text. Hyphenated
+ text. Hyphenated text.</fo:block>
+ </fo:block-container>
+ <fo:block-container width="140pt" space-before="10pt" start-indent="30pt">
+ <fo:block start-indent="0">Hyphenated text with ‘special’ character. Hyphenated text with
+ ‘special’ character.</fo:block>
+ </fo:block-container>
+ </fo:flow>
+ </fo:page-sequence>
+</fo:root>