From 4b2dd37c9ee5cd21b8f18dd86729e588bfd22250 Mon Sep 17 00:00:00 2001 From: Simon Pepping Date: Wed, 15 Jun 2011 18:37:11 +0000 Subject: [PATCH] Patch to revision 1136002 of branch Temp_ComplexScripts by Glenn Adams; see Bugzilla 49687 git-svn-id: https://svn.apache.org/repos/asf/xmlgraphics/fop/branches/Temp_ComplexScripts@1136144 13f79535-47bb-0310-9956-ffa450edef68 --- checkstyle-suppressions.xml | 1 + findbugs-exclude.xml | 354 +-------------- .../complexscripts/arab/ttx/arab-001.ttx | 1 - .../complexscripts/arab/ttx/arab-002.ttx | 1 - .../complexscripts/arab/ttx/arab-003.ttx | 1 - .../complexscripts/arab/ttx/arab-004.ttx | 1 - .../fop-intermediate-format-ng-content.xsd | 8 +- .../org/apache/fop/area/AreaTreeParser.java | 6 +- src/java/org/apache/fop/area/Block.java | 8 + src/java/org/apache/fop/area/CTM.java | 8 +- src/java/org/apache/fop/area/LineArea.java | 39 +- .../org/apache/fop/area/LinkResolver.java | 33 +- .../apache/fop/area/inline/BasicLinkArea.java | 19 + .../apache/fop/area/inline/InlineParent.java | 2 +- .../fop/area/inline/InlineViewport.java | 11 + .../org/apache/fop/area/inline/TextArea.java | 25 +- .../fop/area/inline/UnresolvedPageNumber.java | 14 +- .../org/apache/fop/area/inline/WordArea.java | 21 +- .../org/apache/fop/fo/FOPropertyMapping.java | 61 ++- src/java/org/apache/fop/fo/FOText.java | 83 ++-- src/java/org/apache/fop/fo/FObj.java | 22 + .../apache/fop/fo/flow/AbstractGraphics.java | 3 +- src/java/org/apache/fop/fo/flow/Block.java | 21 - .../org/apache/fop/fo/flow/Character.java | 36 -- src/java/org/apache/fop/fo/flow/Leader.java | 27 +- .../apache/fop/fo/pagination/RegionEnd.java | 5 +- .../apache/fop/fo/pagination/RegionStart.java | 6 +- .../fop/fonts/ArabicScriptProcessor.java | 86 ++-- .../fop/fonts/DefaultScriptProcessor.java | 72 ++- .../org/apache/fop/fonts/EmbedFontInfo.java | 2 +- src/java/org/apache/fop/fonts/Font.java | 10 + .../apache/fop/fonts/GlyphContextTester.java | 5 +- .../apache/fop/fonts/GlyphCoverageTable.java | 8 +- .../fop/fonts/GlyphDefinitionTable.java | 16 + .../fop/fonts/GlyphPositioningTable.java | 58 ++- .../fop/fonts/GlyphProcessingState.java | 2 +- .../org/apache/fop/fonts/GlyphSequence.java | 346 +++++++++++++- .../fop/fonts/GlyphSubstitutionState.java | 18 +- .../fop/fonts/GlyphSubstitutionTable.java | 18 +- src/java/org/apache/fop/fonts/GlyphTable.java | 22 +- src/java/org/apache/fop/fonts/LazyFont.java | 14 + .../org/apache/fop/fonts/MultiByteFont.java | 109 ++++- .../org/apache/fop/fonts/ScriptProcessor.java | 44 +- .../org/apache/fop/fonts/Substitutable.java | 13 + .../fop/fonts/autodetect/FontInfoFinder.java | 2 +- .../apache/fop/fonts/truetype/TTFFile.java | 91 +--- .../org/apache/fop/layoutmgr/BidiUtil.java | 429 ++++++++++++++---- .../ExternalDocumentLayoutManager.java | 2 +- .../inline/AbstractGraphicsLayoutManager.java | 23 +- ...stractPageNumberCitationLayoutManager.java | 41 +- .../inline/BasicLinkLayoutManager.java | 6 + .../inline/CharacterLayoutManager.java | 10 +- .../layoutmgr/inline/LineLayoutManager.java | 51 ++- .../PageNumberCitationLastLayoutManager.java | 8 +- .../PageNumberCitationLayoutManager.java | 19 +- .../layoutmgr/inline/ScaledBaselineTable.java | 2 +- .../layoutmgr/inline/TextLayoutManager.java | 279 ++++++++---- .../list/ListBlockLayoutManager.java | 4 +- .../list/ListItemContentLayoutManager.java | 6 +- .../layoutmgr/list/ListItemLayoutManager.java | 5 +- .../fop/layoutmgr/table/ColumnSetup.java | 42 ++ .../render/AbstractPathOrientedRenderer.java | 324 +++++++------ .../apache/fop/render/AbstractRenderer.java | 60 ++- .../org/apache/fop/render/afp/AFPPainter.java | 8 +- .../intermediate/AbstractIFPainter.java | 52 +-- .../render/intermediate/BorderPainter.java | 74 +-- .../fop/render/intermediate/IFPainter.java | 12 +- .../fop/render/intermediate/IFParser.java | 2 +- .../fop/render/intermediate/IFRenderer.java | 15 +- .../fop/render/intermediate/IFSerializer.java | 22 +- .../fop/render/java2d/Java2DPainter.java | 8 +- .../org/apache/fop/render/pcl/PCLPainter.java | 10 +- .../org/apache/fop/render/pdf/PDFPainter.java | 36 +- .../org/apache/fop/render/ps/PSPainter.java | 12 +- src/java/org/apache/fop/traits/Direction.java | 16 + .../org/apache/fop/traits/WritingMode.java | 26 ++ .../apache/fop/traits/WritingModeTraits.java | 16 + .../org/apache/fop/util/CharUtilities.java | 424 ++++++++++++++--- .../ComplexScriptsTestSuite.java | 4 + ...ng-mode_rl_region-body_margin_relative.xml | 36 +- ...ng-mode_rl_region-body_margin_relative.xml | 40 +- ...ng-mode_rl_region-body_margin_relative.xml | 78 ++-- ...ng-mode_rl_region-body_margin_relative.xml | 78 ++-- 83 files changed, 2686 insertions(+), 1347 deletions(-) diff --git a/checkstyle-suppressions.xml b/checkstyle-suppressions.xml index e2809e63a..659df638d 100644 --- a/checkstyle-suppressions.xml +++ b/checkstyle-suppressions.xml @@ -4,5 +4,6 @@ + diff --git a/findbugs-exclude.xml b/findbugs-exclude.xml index efe992714..61aae8f0c 100644 --- a/findbugs-exclude.xml +++ b/findbugs-exclude.xml @@ -1,78 +1,13 @@ - - + - - - - - - - - - - - + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -96,41 +31,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -156,46 +56,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -288,11 +148,6 @@ - - - - - @@ -308,6 +163,15 @@ + + + + + + + + + @@ -1333,126 +1197,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -4638,71 +4382,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -5395,11 +5074,6 @@ - - - - - diff --git a/src/codegen/complexscripts/arab/ttx/arab-001.ttx b/src/codegen/complexscripts/arab/ttx/arab-001.ttx index d43eb9e5f..e69de29bb 100644 --- a/src/codegen/complexscripts/arab/ttx/arab-001.ttx +++ b/src/codegen/complexscripts/arab/ttx/arab-001.ttx @@ -1 +0,0 @@ - diff --git a/src/codegen/complexscripts/arab/ttx/arab-002.ttx b/src/codegen/complexscripts/arab/ttx/arab-002.ttx index 7a0c0c2a5..e69de29bb 100644 --- a/src/codegen/complexscripts/arab/ttx/arab-002.ttx +++ b/src/codegen/complexscripts/arab/ttx/arab-002.ttx @@ -1 +0,0 @@ - diff --git a/src/codegen/complexscripts/arab/ttx/arab-003.ttx b/src/codegen/complexscripts/arab/ttx/arab-003.ttx index 57ba7da35..e69de29bb 100644 --- a/src/codegen/complexscripts/arab/ttx/arab-003.ttx +++ b/src/codegen/complexscripts/arab/ttx/arab-003.ttx @@ -1 +0,0 @@ - diff --git a/src/codegen/complexscripts/arab/ttx/arab-004.ttx b/src/codegen/complexscripts/arab/ttx/arab-004.ttx index cf3ae37a6..e69de29bb 100644 --- a/src/codegen/complexscripts/arab/ttx/arab-004.ttx +++ b/src/codegen/complexscripts/arab/ttx/arab-004.ttx @@ -1 +0,0 @@ - diff --git a/src/documentation/intermediate-format-ng/fop-intermediate-format-ng-content.xsd b/src/documentation/intermediate-format-ng/fop-intermediate-format-ng-content.xsd index ac7357f8c..695d724fe 100644 --- a/src/documentation/intermediate-format-ng/fop-intermediate-format-ng-content.xsd +++ b/src/documentation/intermediate-format-ng/fop-intermediate-format-ng-content.xsd @@ -100,10 +100,10 @@ - - - - + + + + diff --git a/src/java/org/apache/fop/area/AreaTreeParser.java b/src/java/org/apache/fop/area/AreaTreeParser.java index 627635531..384266091 100644 --- a/src/java/org/apache/fop/area/AreaTreeParser.java +++ b/src/java/org/apache/fop/area/AreaTreeParser.java @@ -840,12 +840,13 @@ public class AreaTreeParser { = ConversionUtils.toIntArray( lastAttributes.getValue("letter-adjust"), "\\s"); int level = XMLUtil.getAttributeAsInt(lastAttributes, "level", -1); + boolean reversed = XMLUtil.getAttributeAsBoolean(lastAttributes, "reversed", false); int[][] gposAdjustments = XMLUtil.getAttributeAsPositionAdjustments(lastAttributes, "position-adjust"); content.flip(); WordArea word = new WordArea ( offset, level, content.toString().trim(), letterAdjust, - null, gposAdjustments ); + null, gposAdjustments, reversed ); AbstractTextArea text = getCurrentText(); word.setParentArea(text); text.addChildArea(word); @@ -912,7 +913,8 @@ public class AreaTreeParser { private class InlineViewportMaker extends AbstractMaker { public void startElement(Attributes attributes) { - InlineViewport viewport = new InlineViewport(null); + int level = XMLUtil.getAttributeAsInt(attributes, "level", -1); + InlineViewport viewport = new InlineViewport(null, level); transferForeignObjects(attributes, viewport); setAreaAttributes(attributes, viewport); setTraits(attributes, viewport, SUBSET_COMMON); diff --git a/src/java/org/apache/fop/area/Block.java b/src/java/org/apache/fop/area/Block.java index 7e5520ae3..c6e31f71d 100644 --- a/src/java/org/apache/fop/area/Block.java +++ b/src/java/org/apache/fop/area/Block.java @@ -132,5 +132,13 @@ public class Block extends BlockParent { return (startIndent != null ? startIndent : 0); } + /** + * @return the end-indent trait + */ + public int getEndIndent() { + Integer endIndent = (Integer)getTrait(Trait.END_INDENT); + return (endIndent != null ? endIndent : 0); + } + } diff --git a/src/java/org/apache/fop/area/CTM.java b/src/java/org/apache/fop/area/CTM.java index cded874d6..73943ed65 100644 --- a/src/java/org/apache/fop/area/CTM.java +++ b/src/java/org/apache/fop/area/CTM.java @@ -44,7 +44,7 @@ public class CTM implements Serializable { private double a, b, c, d, e, f; private static final CTM CTM_LRTB = new CTM(1, 0, 0, 1, 0, 0); - private static final CTM CTM_RLTB = new CTM(-1, 0, 0, 1, 0, 0); + private static final CTM CTM_RLTB = new CTM(1, 0, 0, 1, 0, 0); private static final CTM CTM_TBRL = new CTM(0, 1, -1, 0, 0, 0); /** @@ -141,16 +141,12 @@ public class CTM implements Serializable { case EN_LR_TB: return new CTM(CTM_LRTB); case EN_RL_TB: - wmctm = new CTM(CTM_RLTB); - wmctm.e = ipd; - return wmctm; - //return CTM_RLTB.translate(ipd, 0); + return new CTM(CTM_RLTB); case EN_TB_RL: // CJK case EN_TB_LR: // CJK wmctm = new CTM(CTM_TBRL); wmctm.e = bpd; return wmctm; - //return CTM_TBRL.translate(0, ipd); default: return null; } diff --git a/src/java/org/apache/fop/area/LineArea.java b/src/java/org/apache/fop/area/LineArea.java index 707e64f2e..1d3262487 100644 --- a/src/java/org/apache/fop/area/LineArea.java +++ b/src/java/org/apache/fop/area/LineArea.java @@ -21,6 +21,7 @@ package org.apache.fop.area; import java.io.Serializable; import java.util.ArrayList; +import java.util.Iterator; import java.util.List; import org.apache.fop.area.inline.InlineArea; @@ -130,6 +131,15 @@ public class LineArea extends Area { * @param inlineAreas the list of inline areas */ public void setInlineAreas ( List inlineAreas ) { + for ( Iterator it = inlineAreas.iterator(); it.hasNext();) { + InlineArea ia = it.next(); + Area pa = ia.getParentArea(); + if ( pa == null ) { + ia.setParentArea ( this ); + } else { + assert pa == this; + } + } this.inlineAreas = inlineAreas; } @@ -157,6 +167,21 @@ public class LineArea extends Area { } } + /** + * Get the end indent of this line area. + * The end indent is used for offsetting the end of + * the inline areas for alignment or other indents. + * + * @return the end indent value + */ + public int getEndIndent() { + if (hasTrait(Trait.END_INDENT)) { + return getTraitAsInteger(Trait.END_INDENT); + } else { + return 0; + } + } + /** * Updates the extents of the line area from its children. */ @@ -187,17 +212,21 @@ public class LineArea extends Area { * @param ipdVariation the difference between old and new ipd */ public void handleIPDVariation(int ipdVariation) { + int si = getStartIndent(); + int ei = getEndIndent(); switch (adjustingInfo.lineAlignment) { case EN_START: - // nothing to do in this case + // adjust end indent + addTrait(Trait.END_INDENT, ei - ipdVariation); break; case EN_CENTER: - // re-compute indent - addTrait(Trait.START_INDENT, getStartIndent() - ipdVariation / 2); + // adjust start and end indents + addTrait(Trait.START_INDENT, si - ipdVariation / 2); + addTrait(Trait.END_INDENT, ei - ipdVariation / 2); break; case EN_END: - // re-compute indent - addTrait(Trait.START_INDENT, getStartIndent() - ipdVariation); + // adjust start indent + addTrait(Trait.START_INDENT, si - ipdVariation); break; case EN_JUSTIFY: // compute variation factor diff --git a/src/java/org/apache/fop/area/LinkResolver.java b/src/java/org/apache/fop/area/LinkResolver.java index 1e135701f..b458a1baf 100644 --- a/src/java/org/apache/fop/area/LinkResolver.java +++ b/src/java/org/apache/fop/area/LinkResolver.java @@ -20,6 +20,7 @@ package org.apache.fop.area; // Java +import java.util.ArrayList; import java.util.List; import java.io.Serializable; @@ -33,6 +34,7 @@ public class LinkResolver implements Resolvable, Serializable { private boolean resolved = false; private String idRef; private Area area; + private transient List dependents = null; /** * Create a new link resolver. @@ -79,8 +81,35 @@ public class LinkResolver implements Resolvable, Serializable { public void resolveIDRef(String id, PageViewport pv) { if (idRef.equals(id) && pv != null) { resolved = true; - Trait.InternalLink iLink = new Trait.InternalLink(pv.getKey(), idRef); - area.addTrait(Trait.INTERNAL_LINK, iLink); + if ( area != null ) { + Trait.InternalLink iLink = new Trait.InternalLink(pv.getKey(), idRef); + area.addTrait(Trait.INTERNAL_LINK, iLink); + area = null; // break circular reference from basic link area to this resolver + } + resolveDependents(id, pv); } } + + /** + * Add dependent resolvable. Used to resolve second-order resolvables that + * depend on resolution of this resolver. + * @param dependent resolvable + */ + public void addDependent(Resolvable dependent) { + if ( dependents == null ) { + dependents = new ArrayList(); + } + dependents.add(dependent); + } + + private void resolveDependents(String id, PageViewport pv) { + if ( dependents != null ) { + List pages = new ArrayList(); + pages.add(pv); + for ( Resolvable r : dependents ) { + r.resolveIDRef(id, pages); + } + } + } + } diff --git a/src/java/org/apache/fop/area/inline/BasicLinkArea.java b/src/java/org/apache/fop/area/inline/BasicLinkArea.java index 1fedf5624..c91181946 100644 --- a/src/java/org/apache/fop/area/inline/BasicLinkArea.java +++ b/src/java/org/apache/fop/area/inline/BasicLinkArea.java @@ -20,6 +20,7 @@ package org.apache.fop.area.inline; import org.apache.fop.area.Area; +import org.apache.fop.area.LinkResolver; /** * An inline area produced by an fo:basic-link element. This class implements a different @@ -33,6 +34,8 @@ public class BasicLinkArea extends InlineParent { private static final long serialVersionUID = 5183753430412208151L; + private LinkResolver resolver; + @Override public void setParentArea(Area parentArea) { super.setParentArea(parentArea); @@ -50,4 +53,20 @@ public class BasicLinkArea extends InlineParent { setBPD(getVirtualBPD()); } + /** + * Establish (or remove) back-pointer to link resolver. + * @param resolver the link resolver that will resolve this basic link or null + */ + public void setResolver(LinkResolver resolver) { + assert ( resolver == null ) || ( this.resolver == null ); + this.resolver = resolver; + } + + /** + * Obtain back-pointer to link resolver. + * @return resolver the link resolver that will resolve this basic link or null + */ + public LinkResolver getResolver() { + return this.resolver; + } } diff --git a/src/java/org/apache/fop/area/inline/InlineParent.java b/src/java/org/apache/fop/area/inline/InlineParent.java index 7419ff066..aefc30cad 100644 --- a/src/java/org/apache/fop/area/inline/InlineParent.java +++ b/src/java/org/apache/fop/area/inline/InlineParent.java @@ -62,8 +62,8 @@ public class InlineParent extends InlineArea { childArea.setParentArea(this); if (autoSize) { increaseIPD(childArea.getAllocIPD()); - updateLevel ( childArea.getBidiLevel() ); } + updateLevel ( childArea.getBidiLevel() ); int childOffset = childArea.getVirtualOffset(); minChildOffset = Math.min(minChildOffset, childOffset); maxAfterEdge = Math.max(maxAfterEdge, childOffset + childArea.getVirtualBPD()); diff --git a/src/java/org/apache/fop/area/inline/InlineViewport.java b/src/java/org/apache/fop/area/inline/InlineViewport.java index bc32a92d0..202a7dad4 100644 --- a/src/java/org/apache/fop/area/inline/InlineViewport.java +++ b/src/java/org/apache/fop/area/inline/InlineViewport.java @@ -50,6 +50,17 @@ public class InlineViewport extends InlineArea implements Viewport { * @param child the child content area of this viewport */ public InlineViewport(Area child) { + this(child, -1); + } + + /** + * Create a new viewport area with the content area. + * + * @param child the child content area of this viewport + * @param bidiLevel the bidirectional embedding level (or -1 if not defined) + */ + public InlineViewport(Area child, int bidiLevel) { + super(0, bidiLevel); this.content = child; } diff --git a/src/java/org/apache/fop/area/inline/TextArea.java b/src/java/org/apache/fop/area/inline/TextArea.java index f426eb9da..1d27827f7 100644 --- a/src/java/org/apache/fop/area/inline/TextArea.java +++ b/src/java/org/apache/fop/area/inline/TextArea.java @@ -19,6 +19,8 @@ package org.apache.fop.area.inline; +import java.util.Arrays; + import org.apache.fop.util.CharUtilities; /** @@ -62,13 +64,24 @@ public class TextArea extends AbstractTextArea { addWord(word, 0, null, null, null, offset); } + /** + * Create and add a WordArea child to this TextArea. + * + * @param word the word string + * @param offset the offset for the next area + * @param level bidirectional level that applies to entire word + */ + public void addWord(String word, int offset, int level) { + addWord(word, 0, null, makeLevels(level, word.length()), null, offset); + } + /** * Create and add a WordArea child to this TextArea. * * @param word the word string * @param ipd the word's ipd * @param letterAdjust the letter adjustment array (may be null) - * @param levels array of resolved bidirection levels of word characters, + * @param levels array of resolved bidirectional levels of word characters, * or null if default level * @param gposAdjustments array of general position adjustments or null if none apply * @param blockProgressionOffset the offset for the next area @@ -170,5 +183,15 @@ public class TextArea extends AbstractTextArea { } } + private int[] makeLevels ( int level, int count ) { + if ( level >= 0 ) { + int[] levels = new int [ count ]; + Arrays.fill ( levels, level ); + return levels; + } else { + return null; + } + } + } diff --git a/src/java/org/apache/fop/area/inline/UnresolvedPageNumber.java b/src/java/org/apache/fop/area/inline/UnresolvedPageNumber.java index d3bb91045..6625bf8fa 100644 --- a/src/java/org/apache/fop/area/inline/UnresolvedPageNumber.java +++ b/src/java/org/apache/fop/area/inline/UnresolvedPageNumber.java @@ -82,6 +82,15 @@ public class UnresolvedPageNumber extends TextArea implements Resolvable { return new String[] {pageIDRef}; } + /** + * Get the (resolved or unresolved) text. + * + * @return the text + */ + public String getText() { + return text; + } + /** * Resolve the page number idref * This resolves the idref for this object by getting the page number @@ -89,6 +98,9 @@ public class UnresolvedPageNumber extends TextArea implements Resolvable { * for this ID. The page number text is then set to the String value * of the page number. * + * TODO: [GA] May need to run bidi algorithm and script processor + * on resolved page number. + * * @param id an id whose PageViewports have been determined * @param pages the list of PageViewports associated with this ID */ @@ -103,7 +115,7 @@ public class UnresolvedPageNumber extends TextArea implements Resolvable { // replace the text removeText(); text = page.getPageNumberString(); - addWord(text, 0); + addWord(text, 0, getBidiLevel()); // update ipd if (font != null) { handleIPDVariation(font.getWordWidth(text) - getIPD()); diff --git a/src/java/org/apache/fop/area/inline/WordArea.java b/src/java/org/apache/fop/area/inline/WordArea.java index b680e65f7..8db7c255d 100644 --- a/src/java/org/apache/fop/area/inline/WordArea.java +++ b/src/java/org/apache/fop/area/inline/WordArea.java @@ -62,17 +62,34 @@ public class WordArea extends InlineArea { * @param levels array of per-character (glyph) bidirectional levels, * in case word area is heterogenously leveled * @param gposAdjustments array of general position adjustments or null if none apply + * @param reversed true if word is known to be reversed at construction time */ public WordArea ( int blockProgressionOffset, int level, String word, int[] letterAdjust, int[] levels, - int[][] gposAdjustments ) { + int[][] gposAdjustments, boolean reversed ) { super ( blockProgressionOffset, level ); int length = ( word != null ) ? word.length() : 0; this.word = word; this.letterAdjust = maybeAdjustLength ( letterAdjust, length ); this.levels = maybePopulateLevels ( levels, level, length ); this.gposAdjustments = maybeAdjustLength ( gposAdjustments, length ); - this.reversed = false; + this.reversed = reversed; + } + + /** + * Create a word area + * @param blockProgressionOffset the offset for this area + * @param level the bidirectional embedding level (or -1 if not defined) for word as a group + * @param word the word string + * @param letterAdjust the letter adjust array (may be null) + * @param levels array of per-character (glyph) bidirectional levels, + * in case word area is heterogenously leveled + * @param gposAdjustments array of general position adjustments or null if none apply + */ + public WordArea + ( int blockProgressionOffset, int level, String word, int[] letterAdjust, int[] levels, + int[][] gposAdjustments ) { + this ( blockProgressionOffset, level, word, letterAdjust, levels, gposAdjustments, false ); } /** @return Returns the word. */ diff --git a/src/java/org/apache/fop/fo/FOPropertyMapping.java b/src/java/org/apache/fop/fo/FOPropertyMapping.java index 263aeaca1..9e3af6bdf 100644 --- a/src/java/org/apache/fop/fo/FOPropertyMapping.java +++ b/src/java/org/apache/fop/fo/FOPropertyMapping.java @@ -1493,6 +1493,37 @@ public final class FOPropertyMapping implements Constants { addPropertyMaker("fox:block-progression-unit", l); } + private Property calcWritingModeDependent ( int pv, int wm ) { + if ( pv == EN_LEFT ) { + if ( wm == Constants.EN_LR_TB ) { + pv = EN_START; + } else if ( wm == Constants.EN_RL_TB ) { + pv = EN_END; + } else { + pv = EN_START; + } + } else if ( pv == EN_RIGHT ) { + if ( wm == Constants.EN_LR_TB ) { + pv = EN_END; + } else if ( wm == Constants.EN_RL_TB ) { + pv = EN_START; + } else { + pv = EN_END; + } + } + return makeWritingModeDependentEnum ( pv ); + } + + private Property makeWritingModeDependentEnum ( int pv ) { + if ( pv == EN_START ) { + return getEnumProperty ( EN_START, "START" ); + } else if ( pv == EN_END ) { + return getEnumProperty ( EN_END, "END" ); + } else { + return null; + } + } + private void createBlockAndLineProperties() { // CSOK: MethodLength PropertyMaker m; @@ -1578,16 +1609,29 @@ public final class FOPropertyMapping implements Constants { addPropertyMaker("white-space-treatment", m); // text-align TODO: make it a StringProperty with enums. - m = new EnumProperty.Maker(PR_TEXT_ALIGN); + m = new EnumProperty.Maker(PR_TEXT_ALIGN) { + public Property get(int subpropId, PropertyList propertyList, + boolean bTryInherit, boolean bTryDefault) throws PropertyException { + Property p = super.get(subpropId, propertyList, bTryInherit, bTryDefault); + if ( p != null ) { + int pv = p.getEnum(); + if ( ( pv == EN_LEFT ) || ( pv == EN_RIGHT ) ) { + p = calcWritingModeDependent + ( pv, propertyList.get(Constants.PR_WRITING_MODE).getEnum() ); + } + } + return p; + } + }; m.setInherited(true); - // Note: both 'end', 'right' and 'outside' are mapped to END - // both 'start', 'left' and 'inside' are mapped to START m.addEnum("center", getEnumProperty(EN_CENTER, "CENTER")); m.addEnum("end", getEnumProperty(EN_END, "END")); - m.addEnum("right", getEnumProperty(EN_END, "END")); m.addEnum("start", getEnumProperty(EN_START, "START")); - m.addEnum("left", getEnumProperty(EN_START, "START")); m.addEnum("justify", getEnumProperty(EN_JUSTIFY, "JUSTIFY")); + // [GA] must defer writing-mode relative mapping of left/right + m.addEnum("left", getEnumProperty(EN_LEFT, "LEFT")); + m.addEnum("right", getEnumProperty(EN_RIGHT, "RIGHT")); + // [GA] inside and outside are not correctly implemented by the following mapping m.addEnum("inside", getEnumProperty(EN_START, "START")); m.addEnum("outside", getEnumProperty(EN_END, "END")); m.setDefault("start"); @@ -1607,7 +1651,6 @@ public final class FOPropertyMapping implements Constants { } return p; } - private Property calcRelative(PropertyList propertyList) throws PropertyException { Property corresponding = propertyList.get(PR_TEXT_ALIGN); if (corresponding == null) { @@ -1622,6 +1665,12 @@ public final class FOPropertyMapping implements Constants { return getEnumProperty(EN_START, "START"); } else if (correspondingValue == EN_CENTER) { return getEnumProperty(EN_CENTER, "CENTER"); + } else if (correspondingValue == EN_LEFT) { + return calcWritingModeDependent + ( EN_LEFT, propertyList.get(Constants.PR_WRITING_MODE).getEnum() ); + } else if (correspondingValue == EN_RIGHT) { + return calcWritingModeDependent + ( EN_RIGHT, propertyList.get(Constants.PR_WRITING_MODE).getEnum() ); } else { return null; } diff --git a/src/java/org/apache/fop/fo/FOText.java b/src/java/org/apache/fop/fo/FOText.java index 8c4fd1fe0..bff08c1e8 100644 --- a/src/java/org/apache/fop/fo/FOText.java +++ b/src/java/org/apache/fop/fo/FOText.java @@ -107,21 +107,31 @@ public class FOText extends FONode implements CharSequence { /** {@inheritDoc} */ protected void characters(char[] data, int start, int length, PropertyList list, Locator locator) throws FOPException { - - if (this.charBuffer == null) { + if (charBuffer == null) { // buffer not yet initialized, do so now - this.charBuffer = CharBuffer.allocate(length); + int newLength = ( length < 16 ) ? 16 : length; + charBuffer = CharBuffer.allocate(newLength); } else { // allocate a larger buffer, and transfer contents - int newLength = this.charBuffer.limit() + length; - CharBuffer newBuffer = CharBuffer.allocate(newLength); - this.charBuffer.rewind(); - newBuffer.put(this.charBuffer); - this.charBuffer = newBuffer; + int requires = charBuffer.position() + length; + int capacity = charBuffer.capacity(); + if ( requires > capacity ) { + int newCapacity = capacity * 2; + if ( requires > newCapacity ) { + newCapacity = requires; + } + CharBuffer newBuffer = CharBuffer.allocate(newCapacity); + charBuffer.rewind(); + newBuffer.put(charBuffer); + charBuffer = newBuffer; + } } + // extend limit to capacity + charBuffer.limit(charBuffer.capacity()); // append characters - this.charBuffer.put(data, start, length); - + charBuffer.put(data, start, length); + // shrink limit to position + charBuffer.limit(charBuffer.position()); } /** @@ -131,19 +141,19 @@ public class FOText extends FONode implements CharSequence { */ public char[] getCharArray() { - if (this.charBuffer == null) { + if (charBuffer == null) { return null; } - if (this.charBuffer.hasArray()) { - return this.charBuffer.array(); + if (charBuffer.hasArray()) { + return charBuffer.array(); } // only if the buffer implementation has // no accessible backing array, return a new one - char[] ca = new char[this.charBuffer.limit()]; - this.charBuffer.rewind(); - this.charBuffer.get(ca); + char[] ca = new char[charBuffer.limit()]; + charBuffer.rewind(); + charBuffer.get(ca); return ca; } @@ -155,10 +165,10 @@ public class FOText extends FONode implements CharSequence { if (removeChildren) { // not really removing, just make sure the char buffer // pointed to is really a different one - if (this.charBuffer != null) { - ft.charBuffer = CharBuffer.allocate(this.charBuffer.limit()); - this.charBuffer.rewind(); - ft.charBuffer.put(this.charBuffer); + if (charBuffer != null) { + ft.charBuffer = CharBuffer.allocate(charBuffer.limit()); + charBuffer.rewind(); + ft.charBuffer.put(charBuffer); ft.charBuffer.rewind(); } } @@ -190,9 +200,12 @@ public class FOText extends FONode implements CharSequence { /** {@inheritDoc} */ protected void endOfNode() throws FOPException { + if ( charBuffer != null ) { + charBuffer.rewind(); + } super.endOfNode(); getFOEventHandler().characters( - this.getCharArray(), 0, this.charBuffer.limit()); + this.getCharArray(), 0, charBuffer.limit()); } /** {@inheritDoc} */ @@ -211,20 +224,20 @@ public class FOText extends FONode implements CharSequence { */ public boolean willCreateArea() { if (whiteSpaceCollapse == Constants.EN_FALSE - && this.charBuffer.limit() > 0) { + && charBuffer.limit() > 0) { return true; } char ch; - this.charBuffer.rewind(); - while (this.charBuffer.hasRemaining()) { - ch = this.charBuffer.get(); + charBuffer.rewind(); + while (charBuffer.hasRemaining()) { + ch = charBuffer.get(); if (!((ch == CharUtilities.SPACE) || (ch == CharUtilities.LINEFEED_CHAR) || (ch == CharUtilities.CARRIAGE_RETURN) || (ch == CharUtilities.TAB))) { // not whitespace - this.charBuffer.rewind(); + charBuffer.rewind(); return true; } } @@ -267,13 +280,13 @@ public class FOText extends FONode implements CharSequence { return; } - this.charBuffer.rewind(); - CharBuffer tmp = this.charBuffer.slice(); + charBuffer.rewind(); + CharBuffer tmp = charBuffer.slice(); char c; - int lim = this.charBuffer.limit(); + int lim = charBuffer.limit(); int pos = -1; while (++pos < lim) { - c = this.charBuffer.get(); + c = charBuffer.get(); switch (textTransform) { case Constants.EN_UPPERCASE: tmp.put(Character.toUpperCase(c)); @@ -688,25 +701,25 @@ public class FOText extends FONode implements CharSequence { /** {@inheritDoc} */ public char charAt(int position) { - return this.charBuffer.get(position); + return charBuffer.get(position); } /** {@inheritDoc} */ public CharSequence subSequence(int start, int end) { - return this.charBuffer.subSequence(start, end); + return charBuffer.subSequence(start, end); } /** {@inheritDoc} */ public int length() { - return this.charBuffer.limit(); + return charBuffer.limit(); } /** * Resets the backing java.nio.CharBuffer */ public void resetBuffer() { - if (this.charBuffer != null) { - this.charBuffer.rewind(); + if (charBuffer != null) { + charBuffer.rewind(); } } diff --git a/src/java/org/apache/fop/fo/FObj.java b/src/java/org/apache/fop/fo/FObj.java index 176f749fd..7d0e37693 100644 --- a/src/java/org/apache/fop/fo/FObj.java +++ b/src/java/org/apache/fop/fo/FObj.java @@ -67,6 +67,8 @@ public abstract class FObj extends FONode implements Constants { /** Markers added to this element. */ private Map markers = null; + private int bidiLevel = -1; + // The value of properties relevant for all fo objects private String id = null; // End of property values @@ -556,6 +558,26 @@ public abstract class FObj extends FONode implements Constants { return "fo"; } + /** + * Set resolved bidirectional level of FO. + * @param bidiLevel a non-negative bidi embedding level + */ + public void setBidiLevel(int bidiLevel) { + assert bidiLevel >= 0; + if ( bidiLevel >= 0 ) { + this.bidiLevel = bidiLevel; + } + } + + /** + * Obtain resolved bidirectional level of FO. + * @return either a non-negative bidi embedding level or -1 + * in case no bidi levels have been assigned + */ + public int getBidiLevel() { + return bidiLevel; + } + /** * Add a new extension attachment to this FObj. * (see org.apache.fop.fo.FONode for details) diff --git a/src/java/org/apache/fop/fo/flow/AbstractGraphics.java b/src/java/org/apache/fop/fo/flow/AbstractGraphics.java index eeb06c1df..71199c2a2 100644 --- a/src/java/org/apache/fop/fo/flow/AbstractGraphics.java +++ b/src/java/org/apache/fop/fo/flow/AbstractGraphics.java @@ -72,8 +72,6 @@ public abstract class AbstractGraphics extends FObj // private int scalingMethod; // End of property values - - /** * constructs an instream-foreign-object object (called by Maker). * @@ -230,4 +228,5 @@ public abstract class AbstractGraphics extends FObj /** @return the graphic's intrinsic alignment-adjust */ public abstract Length getIntrinsicAlignmentAdjust(); + } diff --git a/src/java/org/apache/fop/fo/flow/Block.java b/src/java/org/apache/fop/fo/flow/Block.java index d5fc901a3..9d3297502 100644 --- a/src/java/org/apache/fop/fo/flow/Block.java +++ b/src/java/org/apache/fop/fo/flow/Block.java @@ -91,9 +91,6 @@ public class Block extends FObjMixed implements BreakPropertySet, StructurePoint // private int visibility; // End of FO trait values - /* default paragraph bidi level */ - private int bidiLevel = -1; - /** * Base constructor * @@ -346,24 +343,6 @@ public class Block extends FObjMixed implements BreakPropertySet, StructurePoint return NullCharIterator.getInstance(); } - /** - * Get the default paragraph bidirectional embedding level. - * - * @return the default paragraph bidirectional embedding level - */ - public int getBidiLevel() { - return bidiLevel; - } - - /** - * Set the default paragraph bidirectional embedding level. - * - * @param bidiLevel the default paragraph bidirectional embedding level - */ - public void setBidiLevel ( int bidiLevel ) { - this.bidiLevel = bidiLevel; - } - /** {@inheritDoc} */ public String getLocalName() { return "block"; diff --git a/src/java/org/apache/fop/fo/flow/Character.java b/src/java/org/apache/fop/fo/flow/Character.java index d01162a45..7328b5644 100644 --- a/src/java/org/apache/fop/fo/flow/Character.java +++ b/src/java/org/apache/fop/fo/flow/Character.java @@ -79,9 +79,6 @@ public class Character extends FObj implements StructurePointerPropertySet { // private int visibility; // End of property values - /* bidi level */ - private int bidiLevel; - /** constant indicating that the character is OK */ public static final int OK = 0; /** constant indicating that the character does not fit */ @@ -231,39 +228,6 @@ public class Character extends FObj implements StructurePointerPropertySet { return FO_CHARACTER; } - /** - * Set bidirectional level for character FO as a whole. - * @param level the resolved level - */ - public void setBidiLevel ( int level ) { - this.bidiLevel = level; - } - - /** - * Obtain bidirectional level of each character - * represented by this FO. - * @return a non-empty array of bidi levels - */ - public int[] getBidiLevels() { - return new int[] {bidiLevel}; - } - - /** - * Obtain bidirectional level of character at - * specified position, which must be zero for this - * FO. - * @param position an offset position into FO's characters - * @return a resolved bidi level or -1 if default - * @throws IndexOutOfBoundsException if position is not zero - */ - public int bidiLevelAt ( int position ) throws IndexOutOfBoundsException { - if ( position != 0 ) { - throw new IndexOutOfBoundsException(); - } else { - return bidiLevel; - } - } - private class FOCharIterator extends CharIterator { private boolean bFirst = true; diff --git a/src/java/org/apache/fop/fo/flow/Leader.java b/src/java/org/apache/fop/fo/flow/Leader.java index b04bcde9a..10b2c3e7e 100644 --- a/src/java/org/apache/fop/fo/flow/Leader.java +++ b/src/java/org/apache/fop/fo/flow/Leader.java @@ -19,10 +19,13 @@ package org.apache.fop.fo.flow; +import org.xml.sax.Locator; + import org.apache.fop.apps.FOPException; import org.apache.fop.datatypes.Length; import org.apache.fop.fo.FONode; import org.apache.fop.fo.PropertyList; +import org.apache.fop.fo.ValidationException; import org.apache.fop.fo.properties.LengthRangeProperty; /** @@ -30,9 +33,7 @@ import org.apache.fop.fo.properties.LengthRangeProperty; * fo:leader object. * The main property of fo:leader is leader-pattern. * The following patterns are treated: rule, space, dots and use-content. - * TODO implement validateChildNode() */ -// [TBD] implement validateChildNode() public class Leader extends InlineLevel { // The value of properties relevant for fo:leader. // See also superclass InlineLevel @@ -97,6 +98,28 @@ public class Leader extends InlineLevel { // textShadow = pList.get(PR_TEXT_SHADOW); } + /** + * {@inheritDoc} + *
XSL Content Model: (#PCDATA|%inline;)* + *
Additionally: "The content must not contain an + * fo:leader, fo:inline-container, fo:block-container, fo:float, + * fo:footnote, or fo:marker either as a direct child or as a + * descendant." + */ + protected void validateChildNode(Locator loc, String nsURI, String localName) + throws ValidationException { + if (FO_URI.equals(nsURI)) { + if ( localName.equals("leader") + || localName.equals("inline-container") + || localName.equals("block-container") + || localName.equals("float") + || localName.equals("marker") + || !isInlineItem(nsURI, localName) ) { + invalidChildError(loc, nsURI, localName); + } + } + } + /** @return the "rule-style" property */ public int getRuleStyle() { return ruleStyle; diff --git a/src/java/org/apache/fop/fo/pagination/RegionEnd.java b/src/java/org/apache/fop/fo/pagination/RegionEnd.java index 6385c986b..ce0258524 100644 --- a/src/java/org/apache/fop/fo/pagination/RegionEnd.java +++ b/src/java/org/apache/fop/fo/pagination/RegionEnd.java @@ -57,11 +57,14 @@ public class RegionEnd extends RegionSE { switch ( getWritingMode().getEnumValue() ) { default: case Constants.EN_LR_TB: - case Constants.EN_RL_TB: neighbourContext = pageHeightContext; vpRect = new Rectangle(reldims.ipd - getExtent().getValue(pageWidthContext), 0, getExtent().getValue(pageWidthContext), reldims.bpd); break; + case Constants.EN_RL_TB: + neighbourContext = pageHeightContext; + vpRect = new Rectangle(0, 0, getExtent().getValue(pageWidthContext), reldims.bpd); + break; case Constants.EN_TB_LR: case Constants.EN_TB_RL: // Rectangle: x , y (of top left point), width, height diff --git a/src/java/org/apache/fop/fo/pagination/RegionStart.java b/src/java/org/apache/fop/fo/pagination/RegionStart.java index 16bcb23ff..c2fc3fe17 100644 --- a/src/java/org/apache/fop/fo/pagination/RegionStart.java +++ b/src/java/org/apache/fop/fo/pagination/RegionStart.java @@ -57,10 +57,14 @@ public class RegionStart extends RegionSE { switch ( getWritingMode().getEnumValue() ) { default: case Constants.EN_LR_TB: - case Constants.EN_RL_TB: neighbourContext = pageHeightContext; vpRect = new Rectangle(0, 0, getExtent().getValue(pageWidthContext), reldims.bpd); break; + case Constants.EN_RL_TB: + neighbourContext = pageHeightContext; + vpRect = new Rectangle(reldims.ipd - getExtent().getValue(pageWidthContext), 0, + getExtent().getValue(pageWidthContext), reldims.bpd); + break; case Constants.EN_TB_LR: case Constants.EN_TB_RL: neighbourContext = pageWidthContext; diff --git a/src/java/org/apache/fop/fonts/ArabicScriptProcessor.java b/src/java/org/apache/fop/fonts/ArabicScriptProcessor.java index 2701fa4bf..f8305e182 100644 --- a/src/java/org/apache/fop/fonts/ArabicScriptProcessor.java +++ b/src/java/org/apache/fop/fonts/ArabicScriptProcessor.java @@ -70,11 +70,31 @@ public class ArabicScriptProcessor extends DefaultScriptProcessor { private static class SubstitutionScriptContextTester implements ScriptContextTester { private static Map/**/ testerMap = new HashMap/**/(); static { - testerMap.put ( "fina", new GlyphContextTester() { public boolean test ( GlyphSequence gs, int index ) { return inFinalContext ( gs, index ); } } ); - testerMap.put ( "init", new GlyphContextTester() { public boolean test ( GlyphSequence gs, int index ) { return inInitialContext ( gs, index ); } } ); - testerMap.put ( "isol", new GlyphContextTester() { public boolean test ( GlyphSequence gs, int index ) { return inIsolateContext ( gs, index ); } } ); - testerMap.put ( "medi", new GlyphContextTester() { public boolean test ( GlyphSequence gs, int index ) { return inMedialContext ( gs, index ); } } ); - testerMap.put ( "liga", new GlyphContextTester() { public boolean test ( GlyphSequence gs, int index ) { return inLigatureContext ( gs, index ); } } ); + testerMap.put ( "fina", new GlyphContextTester() { + public boolean test ( String script, String language, String feature, GlyphSequence gs, int index ) { + return inFinalContext ( script, language, feature, gs, index ); + } + } ); + testerMap.put ( "init", new GlyphContextTester() { + public boolean test ( String script, String language, String feature, GlyphSequence gs, int index ) { + return inInitialContext ( script, language, feature, gs, index ); + } + } ); + testerMap.put ( "isol", new GlyphContextTester() { + public boolean test ( String script, String language, String feature, GlyphSequence gs, int index ) { + return inIsolateContext ( script, language, feature, gs, index ); + } + } ); + testerMap.put ( "liga", new GlyphContextTester() { + public boolean test ( String script, String language, String feature, GlyphSequence gs, int index ) { + return inLigatureContext ( script, language, feature, gs, index ); + } + } ); + testerMap.put ( "medi", new GlyphContextTester() { + public boolean test ( String script, String language, String feature, GlyphSequence gs, int index ) { + return inMedialContext ( script, language, feature, gs, index ); + } + } ); } public GlyphContextTester getTester ( String feature ) { return (GlyphContextTester) testerMap.get ( feature ); @@ -117,7 +137,15 @@ public class ArabicScriptProcessor extends DefaultScriptProcessor { return posContextTester; } - private static boolean inFinalContext ( GlyphSequence gs, int index ) { + /** {@inheritDoc} */ + @Override + public GlyphSequence reorderCombiningMarks ( GlyphDefinitionTable gdef, GlyphSequence gs, int[][] gpa, String script, String language ) { + // a side effect of BIDI reordering is to order combining marks before their base, so we need to override the default here to + // prevent double reordering + return gs; + } + + private static boolean inFinalContext ( String script, String language, String feature, GlyphSequence gs, int index ) { GlyphSequence.CharAssociation a = gs.getAssociation ( index ); int[] ca = gs.getCharacterArray ( false ); int nc = gs.getCharacterCount(); @@ -138,7 +166,7 @@ public class ArabicScriptProcessor extends DefaultScriptProcessor { } } - private static boolean inMedialContext ( GlyphSequence gs, int index ) { + private static boolean inInitialContext ( String script, String language, String feature, GlyphSequence gs, int index ) { GlyphSequence.CharAssociation a = gs.getAssociation ( index ); int[] ca = gs.getCharacterArray ( false ); int nc = gs.getCharacterCount(); @@ -147,11 +175,9 @@ public class ArabicScriptProcessor extends DefaultScriptProcessor { } else { int s = a.getStart(); int e = a.getEnd(); - if ( ! hasMedialPrecedingContext ( ca, nc, s, e ) ) { - return false; - } else if ( ! hasMedialThisContext ( ca, nc, s, e ) ) { + if ( ! hasInitialPrecedingContext ( ca, nc, s, e ) ) { return false; - } else if ( ! hasMedialFollowingContext ( ca, nc, s, e ) ) { + } else if ( ! hasInitialFollowingContext ( ca, nc, s, e ) ) { return false; } else { return true; @@ -159,38 +185,38 @@ public class ArabicScriptProcessor extends DefaultScriptProcessor { } } - private static boolean inInitialContext ( GlyphSequence gs, int index ) { + private static boolean inIsolateContext ( String script, String language, String feature, GlyphSequence gs, int index ) { GlyphSequence.CharAssociation a = gs.getAssociation ( index ); - int[] ca = gs.getCharacterArray ( false ); int nc = gs.getCharacterCount(); if ( nc == 0 ) { return false; + } else if ( ( a.getStart() == 0 ) && ( a.getEnd() == nc ) ) { + return true; } else { - int s = a.getStart(); - int e = a.getEnd(); - if ( ! hasInitialPrecedingContext ( ca, nc, s, e ) ) { - return false; - } else if ( ! hasInitialFollowingContext ( ca, nc, s, e ) ) { - return false; - } else { - return true; - } + return false; } } - private static boolean inIsolateContext ( GlyphSequence gs, int index ) { + private static boolean inLigatureContext ( String script, String language, String feature, GlyphSequence gs, int index ) { GlyphSequence.CharAssociation a = gs.getAssociation ( index ); + int[] ca = gs.getCharacterArray ( false ); int nc = gs.getCharacterCount(); if ( nc == 0 ) { return false; - } else if ( ( a.getStart() == 0 ) && ( a.getEnd() == nc ) ) { - return true; } else { - return false; + int s = a.getStart(); + int e = a.getEnd(); + if ( ! hasLigaturePrecedingContext ( ca, nc, s, e ) ) { + return false; + } else if ( ! hasLigatureFollowingContext ( ca, nc, s, e ) ) { + return false; + } else { + return true; + } } } - private static boolean inLigatureContext ( GlyphSequence gs, int index ) { + private static boolean inMedialContext ( String script, String language, String feature, GlyphSequence gs, int index ) { GlyphSequence.CharAssociation a = gs.getAssociation ( index ); int[] ca = gs.getCharacterArray ( false ); int nc = gs.getCharacterCount(); @@ -199,9 +225,11 @@ public class ArabicScriptProcessor extends DefaultScriptProcessor { } else { int s = a.getStart(); int e = a.getEnd(); - if ( ! hasLigaturePrecedingContext ( ca, nc, s, e ) ) { + if ( ! hasMedialPrecedingContext ( ca, nc, s, e ) ) { return false; - } else if ( ! hasLigatureFollowingContext ( ca, nc, s, e ) ) { + } else if ( ! hasMedialThisContext ( ca, nc, s, e ) ) { + return false; + } else if ( ! hasMedialFollowingContext ( ca, nc, s, e ) ) { return false; } else { return true; diff --git a/src/java/org/apache/fop/fonts/DefaultScriptProcessor.java b/src/java/org/apache/fop/fonts/DefaultScriptProcessor.java index 4ed5c524c..61df47f98 100644 --- a/src/java/org/apache/fop/fonts/DefaultScriptProcessor.java +++ b/src/java/org/apache/fop/fonts/DefaultScriptProcessor.java @@ -24,7 +24,9 @@ import java.util.Map; // CSOFF: LineLengthCheck /** - * Default script processor, which performs the identity substitution and produces no positioning information. + * Default script processor, which enables default glyph composition/decomposition, common ligatures, localized forms + * and kerning. + * * @author Glenn Adams */ public class DefaultScriptProcessor extends ScriptProcessor { @@ -47,24 +49,92 @@ public class DefaultScriptProcessor extends ScriptProcessor { super ( script ); } + @Override /** {@inheritDoc} */ public String[] getSubstitutionFeatures() { return gsubFeatures; } + @Override /** {@inheritDoc} */ public ScriptContextTester getSubstitutionContextTester() { return null; } + @Override /** {@inheritDoc} */ public String[] getPositioningFeatures() { return gposFeatures; } + @Override /** {@inheritDoc} */ public ScriptContextTester getPositioningContextTester() { return null; } + @Override + /** {@inheritDoc} */ + public GlyphSequence reorderCombiningMarks ( GlyphDefinitionTable gdef, GlyphSequence gs, int[][] gpa, String script, String language ) { + int ng = gs.getGlyphCount(); + int[] ga = gs.getGlyphArray ( false ); + int nm = 0; + // count combining marks + for ( int i = 0; i < ng; i++ ) { + int gid = ga [ i ]; + if ( gdef.isGlyphClass ( gid, GlyphDefinitionTable.GLYPH_CLASS_MARK ) ) { + nm++; + } + } + // only reorder if there is at least one mark and at least one non-mark glyph + if ( ( nm > 0 ) && ( ( ng - nm ) > 0 ) ) { + GlyphSequence.CharAssociation[] aa = gs.getAssociations ( 0, -1 ); + int[] nga = new int [ ng ]; + int[][] npa = ( gpa != null ) ? new int [ ng ][] : null; + GlyphSequence.CharAssociation[] naa = new GlyphSequence.CharAssociation [ ng ]; + int k = 0; + GlyphSequence.CharAssociation ba = null; + int bg = -1; + int[] bpa = null; + for ( int i = 0; i < ng; i++ ) { + int gid = ga [ i ]; + int[] pa = ( gpa != null ) ? gpa [ i ] : null; + GlyphSequence.CharAssociation ca = aa [ i ]; + if ( gdef.isGlyphClass ( gid, GlyphDefinitionTable.GLYPH_CLASS_MARK ) ) { + nga [ k ] = gid; naa [ k ] = ca; + if ( npa != null ) { + npa [ k ] = pa; + } + k++; + } else { + if ( bg != -1 ) { + nga [ k ] = bg; naa [ k ] = ba; + if ( npa != null ) { + npa [ k ] = bpa; + } + k++; + bg = -1; ba = null; bpa = null; + } + if ( bg == -1 ) { + bg = gid; ba = ca; bpa = pa; + } + } + } + if ( bg != -1 ) { + nga [ k ] = bg; naa [ k ] = ba; + if ( npa != null ) { + npa [ k ] = bpa; + } + k++; + } + assert k == ng; + if ( npa != null ) { + System.arraycopy ( npa, 0, gpa, 0, ng ); + } + return new GlyphSequence ( gs, null, nga, null, null, naa, null ); + } else { + return gs; + } + } + } diff --git a/src/java/org/apache/fop/fonts/EmbedFontInfo.java b/src/java/org/apache/fop/fonts/EmbedFontInfo.java index 7cc0eb265..8848c0a87 100644 --- a/src/java/org/apache/fop/fonts/EmbedFontInfo.java +++ b/src/java/org/apache/fop/fonts/EmbedFontInfo.java @@ -29,7 +29,7 @@ import java.util.List; public class EmbedFontInfo implements Serializable { /** Serialization Version UID */ - private static final long serialVersionUID = 8755432068669997368L; + private static final long serialVersionUID = 8755432068669997369L; /** filename of the metrics file */ protected String metricsFile; diff --git a/src/java/org/apache/fop/fonts/Font.java b/src/java/org/apache/fop/fonts/Font.java index aa8fb7b6f..a1febdb22 100644 --- a/src/java/org/apache/fop/fonts/Font.java +++ b/src/java/org/apache/fop/fonts/Font.java @@ -369,6 +369,16 @@ public class Font implements Substitutable, Positionable { } } + /** {@inheritDoc} */ + public CharSequence reorderCombiningMarks ( CharSequence cs, int[][] gpa, String script, String language ) { + if ( metric instanceof Substitutable ) { + Substitutable s = (Substitutable) metric; + return s.reorderCombiningMarks ( cs, gpa, script, language ); + } else { + throw new UnsupportedOperationException(); + } + } + /** {@inheritDoc} */ public boolean performsPositioning() { if ( metric instanceof Positionable ) { diff --git a/src/java/org/apache/fop/fonts/GlyphContextTester.java b/src/java/org/apache/fop/fonts/GlyphContextTester.java index f318b8145..7e2f7bf38 100644 --- a/src/java/org/apache/fop/fonts/GlyphContextTester.java +++ b/src/java/org/apache/fop/fonts/GlyphContextTester.java @@ -27,10 +27,13 @@ public interface GlyphContextTester { /** * Perform a test on a glyph sequence in a specific (originating) character context. + * @param script governing script + * @param language governing language + * @param feature governing feature * @param gs glyph sequence to test * @param index index into glyph sequence to test * @return true if test is satisfied */ - boolean test ( GlyphSequence gs, int index ); + boolean test ( String script, String language, String feature, GlyphSequence gs, int index ); } diff --git a/src/java/org/apache/fop/fonts/GlyphCoverageTable.java b/src/java/org/apache/fop/fonts/GlyphCoverageTable.java index e0544eeb4..17e24c041 100644 --- a/src/java/org/apache/fop/fonts/GlyphCoverageTable.java +++ b/src/java/org/apache/fop/fonts/GlyphCoverageTable.java @@ -23,6 +23,9 @@ import java.util.Arrays; import java.util.List; import java.util.Iterator; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + // CSOFF: LineLengthCheck // CSOFF: InnerAssignmentCheck // CSOFF: NoWhitespaceAfterCheck @@ -33,6 +36,9 @@ import java.util.Iterator; */ public final class GlyphCoverageTable extends GlyphMappingTable implements GlyphCoverageMapping { + /* logging instance */ + private static final Log log = LogFactory.getLog(GlyphCoverageTable.class); // CSOK: ConstantNameCheck + /** empty mapping table */ public static final int GLYPH_COVERAGE_TYPE_EMPTY = GLYPH_MAPPING_TYPE_EMPTY; @@ -178,7 +184,7 @@ public final class GlyphCoverageTable extends GlyphMappingTable implements Glyph if ( gid > gidMax ) { map [ i++ ] = gidMax = gid; } else { - throw new AdvancedTypographicTableFormatException ( "out of order or duplicate glyph index: " + gid ); + log.info ( "ignoring out of order or duplicate glyph index: " + gid ); } } else { throw new AdvancedTypographicTableFormatException ( "illegal glyph index: " + gid ); diff --git a/src/java/org/apache/fop/fonts/GlyphDefinitionTable.java b/src/java/org/apache/fop/fonts/GlyphDefinitionTable.java index b1dfea21b..0a4e1caba 100644 --- a/src/java/org/apache/fop/fonts/GlyphDefinitionTable.java +++ b/src/java/org/apache/fop/fonts/GlyphDefinitionTable.java @@ -91,6 +91,22 @@ public class GlyphDefinitionTable extends GlyphTable { } } + /** + * Reorder combining marks in glyph sequence so that they precede (within the sequence) the base + * character to which they are applied. N.B. In the case of LTR segments, marks are not reordered by this, + * method since when the segment is reversed by BIDI processing, marks are automatically reordered to precede + * their base glyph. + * @param gs an input glyph sequence + * @param gpa associated glyph position adjustments (also reordered) + * @param script a script identifier + * @param language a language identifier + * @return the reordered (output) glyph sequence + */ + public GlyphSequence reorderCombiningMarks ( GlyphSequence gs, int[][] gpa, String script, String language ) { + ScriptProcessor sp = ScriptProcessor.getInstance ( script ); + return sp.reorderCombiningMarks ( this, gs, gpa, script, language ); + } + /** {@inheritDoc} */ protected void addSubtable ( GlyphSubtable subtable ) { if ( subtable instanceof GlyphClassSubtable ) { diff --git a/src/java/org/apache/fop/fonts/GlyphPositioningTable.java b/src/java/org/apache/fop/fonts/GlyphPositioningTable.java index 5dc618b29..c47b9df99 100644 --- a/src/java/org/apache/fop/fonts/GlyphPositioningTable.java +++ b/src/java/org/apache/fop/fonts/GlyphPositioningTable.java @@ -391,10 +391,9 @@ public class GlyphPositioningTable extends GlyphTable { } /** {@inheritDoc} */ public boolean position ( GlyphPositioningState ps ) { + boolean applied = false; int gi = ps.getGlyph(0), ci; - if ( ( ci = getCoverageIndex ( gi ) ) < 0 ) { - return false; - } else { + if ( ( ci = getCoverageIndex ( gi ) ) >= 0 ) { int[] counts = ps.getGlyphsAvailable ( 0 ); int nga = counts[0]; if ( nga > 1 ) { @@ -415,11 +414,12 @@ public class GlyphPositioningTable extends GlyphTable { } } ps.consume ( counts[0] + counts[1] ); + applied = true; } } } - return true; } + return applied; } /** * Obtain associated pair values. @@ -582,10 +582,9 @@ public class GlyphPositioningTable extends GlyphTable { } /** {@inheritDoc} */ public boolean position ( GlyphPositioningState ps ) { + boolean applied = false; int gi = ps.getGlyph(0), ci; - if ( ( ci = getCoverageIndex ( gi ) ) < 0 ) { - return false; - } else { + if ( ( ci = getCoverageIndex ( gi ) ) >= 0 ) { int[] counts = ps.getGlyphsAvailable ( 0 ); int nga = counts[0]; if ( nga > 1 ) { @@ -610,11 +609,12 @@ public class GlyphPositioningTable extends GlyphTable { } // consume only first glyph of exit/entry glyph pair ps.consume ( 1 ); + applied = true; } } } - return true; } + return applied; } /** * Obtain exit anchor for first glyph with coverage index ci1 and entry anchor for second @@ -698,10 +698,9 @@ public class GlyphPositioningTable extends GlyphTable { } /** {@inheritDoc} */ public boolean position ( GlyphPositioningState ps ) { + boolean applied = false; int giMark = ps.getGlyph(), ciMark; - if ( ( ciMark = getCoverageIndex ( giMark ) ) < 0 ) { - return false; - } else { + if ( ( ciMark = getCoverageIndex ( giMark ) ) >= 0 ) { MarkAnchor ma = getMarkAnchor ( ciMark, giMark ); if ( ma != null ) { for ( int i = 0, n = ps.getPosition(); i < n; i++ ) { @@ -723,12 +722,13 @@ public class GlyphPositioningTable extends GlyphTable { } } ps.consume(1); + applied = true; break; } } } - return true; } + return applied; } /** * Obtain mark anchor associated with mark coverage index. @@ -841,10 +841,9 @@ public class GlyphPositioningTable extends GlyphTable { } /** {@inheritDoc} */ public boolean position ( GlyphPositioningState ps ) { + boolean applied = false; int giMark = ps.getGlyph(), ciMark; - if ( ( ciMark = getCoverageIndex ( giMark ) ) < 0 ) { - return false; - } else { + if ( ( ciMark = getCoverageIndex ( giMark ) ) >= 0 ) { MarkAnchor ma = getMarkAnchor ( ciMark, giMark ); int mxc = getMaxComponentCount(); if ( ma != null ) { @@ -860,12 +859,13 @@ public class GlyphPositioningTable extends GlyphTable { } } ps.consume(1); + applied = true; break; } } } - return true; } + return applied; } /** * Obtain mark anchor associated with mark coverage index. @@ -999,10 +999,9 @@ public class GlyphPositioningTable extends GlyphTable { } /** {@inheritDoc} */ public boolean position ( GlyphPositioningState ps ) { + boolean applied = false; int giMark1 = ps.getGlyph(), ciMark1; - if ( ( ciMark1 = getCoverageIndex ( giMark1 ) ) < 0 ) { - return false; - } else { + if ( ( ciMark1 = getCoverageIndex ( giMark1 ) ) >= 0 ) { MarkAnchor ma = getMark1Anchor ( ciMark1, giMark1 ); if ( ma != null ) { if ( ps.hasPrev() ) { @@ -1013,10 +1012,11 @@ public class GlyphPositioningTable extends GlyphTable { } } ps.consume(1); + applied = true; } } - return true; } + return applied; } /** * Obtain mark 1 anchor associated with mark 1 coverage index. @@ -1129,17 +1129,17 @@ public class GlyphPositioningTable extends GlyphTable { } /** {@inheritDoc} */ public boolean position ( GlyphPositioningState ps ) { + boolean applied = false; int gi = ps.getGlyph(), ci; - if ( ( ci = getCoverageIndex ( gi ) ) < 0 ) { - return false; - } else { + if ( ( ci = getCoverageIndex ( gi ) ) >= 0 ) { int[] rv = new int[1]; RuleLookup[] la = getLookups ( ci, gi, ps, rv ); if ( la != null ) { ps.apply ( la, rv[0] ); + applied = true; } - return true; } + return applied; } /** * Obtain rule lookups set associated current input glyph context. @@ -1455,19 +1455,17 @@ public class GlyphPositioningTable extends GlyphTable { } /** {@inheritDoc} */ public boolean position ( GlyphPositioningState ps ) { + boolean applied = false; int gi = ps.getGlyph(), ci; - if ( ( ci = getCoverageIndex ( gi ) ) < 0 ) { - return false; - } else { + if ( ( ci = getCoverageIndex ( gi ) ) >= 0 ) { int[] rv = new int[1]; RuleLookup[] la = getLookups ( ci, gi, ps, rv ); if ( la != null ) { ps.apply ( la, rv[0] ); - return true; - } else { - return false; + applied = true; } } + return applied; } /** * Obtain rule lookups set associated current input glyph context. diff --git a/src/java/org/apache/fop/fonts/GlyphProcessingState.java b/src/java/org/apache/fop/fonts/GlyphProcessingState.java index d0b79cea9..3e752c46c 100644 --- a/src/java/org/apache/fop/fonts/GlyphProcessingState.java +++ b/src/java/org/apache/fop/fonts/GlyphProcessingState.java @@ -894,7 +894,7 @@ public class GlyphProcessingState { if ( gct == null ) { return true; } else { - return gct.test ( igs, index ); + return gct.test ( script, language, feature, igs, index ); } } diff --git a/src/java/org/apache/fop/fonts/GlyphSequence.java b/src/java/org/apache/fop/fonts/GlyphSequence.java index 16f2efb13..b515301ba 100644 --- a/src/java/org/apache/fop/fonts/GlyphSequence.java +++ b/src/java/org/apache/fop/fonts/GlyphSequence.java @@ -22,12 +22,15 @@ package org.apache.fop.fonts; import java.nio.IntBuffer; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; import org.apache.fop.util.CharUtilities; // CSOFF: InnerAssignmentCheck // CSOFF: LineLengthCheck +// CSOFF: WhitespaceAfterCheck // CSOFF: NoWhitespaceAfterCheck /** @@ -49,6 +52,8 @@ public class GlyphSequence implements Cloneable { private IntBuffer glyphs; /** association list */ private List associations; + /** predications flag */ + private boolean predications; /** * Instantiate a glyph sequence, reusing (i.e., not copying) the referenced @@ -59,8 +64,9 @@ public class GlyphSequence implements Cloneable { * @param characters a (possibly null) buffer of associated (originating) characters * @param glyphs a (possibly null) buffer of glyphs * @param associations a (possibly null) array of glyph to character associations + * @param predications true if predications are enabled */ - public GlyphSequence ( IntBuffer characters, IntBuffer glyphs, List associations ) { + public GlyphSequence ( IntBuffer characters, IntBuffer glyphs, List associations, boolean predications ) { if ( characters == null ) { characters = IntBuffer.allocate ( DEFAULT_CHARS_CAPACITY ); } @@ -73,6 +79,21 @@ public class GlyphSequence implements Cloneable { this.characters = characters; this.glyphs = glyphs; this.associations = associations; + this.predications = predications; + } + + /** + * Instantiate a glyph sequence, reusing (i.e., not copying) the referenced + * character and glyph buffers and associations. If characters is null, then + * an empty character buffer is created. If glyphs is null, then a glyph buffer + * is created whose capacity is that of the character buffer. If associations is + * null, then identity associations are created. + * @param characters a (possibly null) buffer of associated (originating) characters + * @param glyphs a (possibly null) buffer of glyphs + * @param associations a (possibly null) array of glyph to character associations + */ + public GlyphSequence ( IntBuffer characters, IntBuffer glyphs, List associations ) { + this ( characters, glyphs, associations, false ); } /** @@ -82,7 +103,7 @@ public class GlyphSequence implements Cloneable { * @param gs an existing glyph sequence */ public GlyphSequence ( GlyphSequence gs ) { - this ( gs.characters.duplicate(), copyBuffer ( gs.glyphs ), copyAssociations ( gs.associations ) ); + this ( gs.characters.duplicate(), copyBuffer ( gs.glyphs ), copyAssociations ( gs.associations ), gs.predications ); } /** @@ -100,7 +121,7 @@ public class GlyphSequence implements Cloneable { * @param lal lookahead association list */ public GlyphSequence ( GlyphSequence gs, int[] bga, int[] iga, int[] lga, CharAssociation[] bal, CharAssociation[] ial, CharAssociation[] lal ) { - this ( gs.characters.duplicate(), concatGlyphs ( bga, iga, lga ), concatAssociations ( bal, ial, lal ) ); + this ( gs.characters.duplicate(), concatGlyphs ( bga, iga, lga ), concatAssociations ( bal, ial, lal ), gs.predications ); } /** @@ -271,6 +292,52 @@ public class GlyphSequence implements Cloneable { return aa; } + /** + * Enable or disable predications. + * @param enable true if predications are to be enabled; otherwise false to disable + */ + public void setPredications ( boolean enable ) { + this.predications = enable; + } + + /** + * Obtain predications state. + * @return true if predications are enabled + */ + public boolean getPredications() { + return this.predications; + } + + /** + * Set predication at glyph sequence OFFSET. + * @param offset offset (index) into glyph sequence + * @param key predication key + * @param value predication value + */ + public void setPredication ( int offset, String key, Object value ) { + if ( predications ) { + CharAssociation[] aa = getAssociations ( offset, 1 ); + CharAssociation ca = aa[0]; + ca.setPredication ( key, value ); + } + } + + /** + * Get predication KEY at glyph sequence OFFSET. + * @param offset offset (index) into glyph sequence + * @param key predication key + * @return predication KEY at OFFSET or null if none exists + */ + public Object getPredication ( int offset, String key ) { + if ( predications ) { + CharAssociation[] aa = getAssociations ( offset, 1 ); + CharAssociation ca = aa[0]; + return ca.getPredication ( key ); + } else { + return null; + } + } + /** * Compare glyphs. * @param gb buffer containing glyph indices with which this glyph sequence's glyphs are to be compared @@ -419,6 +486,96 @@ public class GlyphSequence implements Cloneable { } } + /** + * Join (concatenate) glyph sequences. + * @param gs original glyph sequence from which to reuse character array reference + * @param sa array of glyph sequences, whose glyph arrays and association lists are to be concatenated + * @return new glyph sequence referring to character array of GS and concatenated glyphs and associations of SA + */ + public static GlyphSequence join ( GlyphSequence gs, GlyphSequence[] sa ) { + assert sa != null; + int tg = 0; + int ta = 0; + for ( int i = 0, n = sa.length; i < n; i++ ) { + GlyphSequence s = sa [ i ]; + IntBuffer ga = s.getGlyphs(); + assert ga != null; + int ng = ga.limit(); + List al = s.getAssociations(); + assert al != null; + int na = al.size(); + assert na == ng; + tg += ng; + ta += na; + } + IntBuffer uga = IntBuffer.allocate ( tg ); + ArrayList ual = new ArrayList ( ta ); + for ( int i = 0, n = sa.length; i < n; i++ ) { + GlyphSequence s = sa [ i ]; + uga.put ( s.getGlyphs() ); + ual.addAll ( s.getAssociations() ); + } + return new GlyphSequence ( gs.getCharacters(), uga, ual, gs.getPredications() ); + } + + /** + * Reorder sequence such that [SOURCE,SOURCE+COUNT) is moved just prior to TARGET. + * @param gs input sequence + * @param source index of sub-sequence to reorder + * @param count length of sub-sequence to reorder + * @param target index to which source sub-sequence is to be moved + * @return reordered sequence (or original if no reordering performed) + */ + public static GlyphSequence reorder ( GlyphSequence gs, int source, int count, int target ) { + if ( source != target ) { + int ng = gs.getGlyphCount(); + int[] ga = gs.getGlyphArray ( false ); + int[] nga = new int [ ng ]; + GlyphSequence.CharAssociation[] aa = gs.getAssociations ( 0, ng ); + GlyphSequence.CharAssociation[] naa = new GlyphSequence.CharAssociation [ ng ]; + if ( source < target ) { + int t = 0; + for ( int s = 0, e = source; s < e; s++, t++ ) { + nga[t] = ga[s]; + naa[t] = aa[s]; + } + for ( int s = source + count, e = target; s < e; s++, t++ ) { + nga[t] = ga[s]; + naa[t] = aa[s]; + } + for ( int s = source, e = source + count; s < e; s++, t++ ) { + nga[t] = ga[s]; + naa[t] = aa[s]; + } + for ( int s = target, e = ng; s < e; s++, t++ ) { + nga[t] = ga[s]; + naa[t] = aa[s]; + } + } else { + int t = 0; + for ( int s = 0, e = target; s < e; s++, t++ ) { + nga[t] = ga[s]; + naa[t] = aa[s]; + } + for ( int s = source, e = source + count; s < e; s++, t++ ) { + nga[t] = ga[s]; + naa[t] = aa[s]; + } + for ( int s = target, e = source; s < e; s++, t++ ) { + nga[t] = ga[s]; + naa[t] = aa[s]; + } + for ( int s = source + count, e = ng; s < e; s++, t++ ) { + nga[t] = ga[s]; + naa[t] = aa[s]; + } + } + return new GlyphSequence ( gs, null, nga, null, null, naa, null ); + } else { + return gs; + } + } + private static int[] toArray ( IntBuffer ib ) { if ( ib != null ) { int n = ib.limit(); @@ -462,22 +619,40 @@ public class GlyphSequence implements Cloneable { } /** - * A structure class encapsulating an interval of character codes (in a CharSequence) - * expressed as an offset and count (of code elements in a CharSequence, i.e., number of - * UTF-16 code elements. N.B. count does not necessarily designate the number of Unicode - * scalar values expressed by the CharSequence; in particular, it does not do so if there - * is one or more UTF-16 surrogate pairs present in the CharSequence.) + * A structure class encapsulating an interval of characters + * expressed as an offset and count of Unicode scalar values (in + * an IntBuffer). A CharAssociation is used to + * maintain a backpointer from a glyph to one or more character + * intervals from which the glyph was derived. + * + * Each glyph in a glyph sequence is associated with a single + * CharAssociation instance. + * + * A CharAssociation instance is additionally (and + * optionally) used to record predication information about the + * glyph, such as whether the glyph was produced by the + * application of a specific substitution table or whether its + * position was adjusted by a specific poisitioning table. */ public static class CharAssociation implements Cloneable { + // instance state private final int offset; private final int count; private final int[] subIntervals; + private Map predications; + + // class state + private static volatile Map predicationMergers; + + interface PredicationMerger { + Object merge ( String key, Object v1, Object v2 ); + } /** * Instantiate a character association. - * @param offset into array of UTF-16 code elements (in associated CharSequence) - * @param count of UTF-16 character code elements (in associated CharSequence) + * @param offset into array of Unicode scalar values (in associated IntBuffer) + * @param count of Unicode scalar values (in associated IntBuffer) * @param subIntervals if disjoint, then array of sub-intervals, otherwise null; even * members of array are sub-interval starts, and odd members are sub-interval * ends (exclusive) @@ -542,15 +717,149 @@ public class GlyphSequence implements Cloneable { return ( subIntervals != null ) ? ( subIntervals.length / 2 ) : 0; } + /** + * @param offset of interval in sequence + * @param count length of interval + * @return true if this association is contained within [offset,offset+count) + */ + public boolean contained ( int offset, int count ) { + int s = offset; + int e = offset + count; + if ( ! isDisjoint() ) { + int s0 = getStart(); + int e0 = getEnd(); + return ( s0 >= s ) && ( e0 <= e ); + } else { + int ns = getSubIntervalCount(); + for ( int i = 0; i < ns; i++ ) { + int s0 = subIntervals [ 2 * i + 0 ]; + int e0 = subIntervals [ 2 * i + 1 ]; + if ( ( s0 >= s ) && ( e0 <= e ) ) { + return true; + } + } + return false; + } + } + + /** + * Set predication . + * @param key predication key + * @param value predication value + */ + public void setPredication ( String key, Object value ) { + if ( predications == null ) { + predications = new HashMap(); + } + if ( predications != null ) { + predications.put ( key, value ); + } + } + + /** + * Get predication KEY. + * @param key predication key + * @return predication KEY at OFFSET or null if none exists + */ + public Object getPredication ( String key ) { + if ( predications != null ) { + return predications.get ( key ); + } else { + return null; + } + } + + /** + * Merge predication . + * @param key predication key + * @param value predication value + */ + public void mergePredication ( String key, Object value ) { + if ( predications == null ) { + predications = new HashMap(); + } + if ( predications != null ) { + if ( predications.containsKey ( key ) ) { + Object v1 = predications.get ( key ); + Object v2 = value; + predications.put ( key, mergePredicationValues ( key, v1, v2 ) ); + } else { + predications.put ( key, value ); + } + } + } + + /** + * Merge predication values V1 and V2 on KEY. Uses registered PredicationMerger + * if one exists, otherwise uses V2 if non-null, otherwise uses V1. + * @param key predication key + * @param v1 first (original) predication value + * @param v2 second (to be merged) predication value + * @return merged value + */ + public static Object mergePredicationValues ( String key, Object v1, Object v2 ) { + PredicationMerger pm = getPredicationMerger ( key ); + if ( pm != null ) { + return pm.merge ( key, v1, v2 ); + } else if ( v2 != null ) { + return v2; + } else { + return v1; + } + } + + /** + * Merge predications from another CA. + * @param ca from which to merge + */ + public void mergePredications ( CharAssociation ca ) { + if ( ca.predications != null ) { + for ( Map.Entry e : ca.predications.entrySet() ) { + mergePredication ( e.getKey(), e.getValue() ); + } + } + } + /** {@inheritDoc} */ public Object clone() { try { - return super.clone(); + CharAssociation ca = (CharAssociation) super.clone(); + if ( predications != null ) { + ca.predications = new HashMap ( predications ); + } + return ca; } catch ( CloneNotSupportedException e ) { return null; } } + /** + * Register predication merger PM for KEY. + * @param key for predication merger + * @param pm predication merger + */ + public static void setPredicationMerger ( String key, PredicationMerger pm ) { + if ( predicationMergers == null ) { + predicationMergers = new HashMap(); + } + if ( predicationMergers != null ) { + predicationMergers.put ( key, pm ); + } + } + + /** + * Obtain predication merger for KEY. + * @param key for predication merger + * @return predication merger or null if none exists + */ + public static PredicationMerger getPredicationMerger ( String key ) { + if ( predicationMergers != null ) { + return predicationMergers.get ( key ); + } else { + return null; + } + } + /** * Replicate association to form repeat new associations. * @param a association to replicate @@ -572,17 +881,26 @@ public class GlyphSequence implements Cloneable { * @return (possibly disjoint) association containing joined associations */ public static CharAssociation join ( CharAssociation[] aa ) { + CharAssociation ca; // extract sorted intervals int[] ia = extractIntervals ( aa ); if ( ( ia == null ) || ( ia.length == 0 ) ) { - return new CharAssociation ( 0, 0 ); + ca = new CharAssociation ( 0, 0 ); } else if ( ia.length == 2 ) { int s = ia[0]; int e = ia[1]; - return new CharAssociation ( s, e - s ); + ca = new CharAssociation ( s, e - s ); } else { - return new CharAssociation ( mergeIntervals ( ia ) ); + ca = new CharAssociation ( mergeIntervals ( ia ) ); } + return mergePredicates ( ca, aa ); + } + + private static CharAssociation mergePredicates ( CharAssociation ca, CharAssociation[] aa ) { + for ( CharAssociation a : aa ) { + ca.mergePredications ( a ); + } + return ca; } private static int getSubIntervalsStart ( int[] ia ) { diff --git a/src/java/org/apache/fop/fonts/GlyphSubstitutionState.java b/src/java/org/apache/fop/fonts/GlyphSubstitutionState.java index 9868283eb..25ea77d7c 100644 --- a/src/java/org/apache/fop/fonts/GlyphSubstitutionState.java +++ b/src/java/org/apache/fop/fonts/GlyphSubstitutionState.java @@ -40,6 +40,8 @@ public class GlyphSubstitutionState extends GlyphProcessingState { private IntBuffer ogb; /** current output glyph to character associations */ private List oal; + /** character association predications */ + private boolean predications; /** * Construct glyph substitution state. @@ -53,6 +55,7 @@ public class GlyphSubstitutionState extends GlyphProcessingState { super ( gs, script, language, feature, sct ); this.ogb = IntBuffer.allocate ( gs.getGlyphCount() ); this.oal = new ArrayList ( gs.getGlyphCount() ); + this.predications = gs.getPredications(); } /** @@ -97,12 +100,16 @@ public class GlyphSubstitutionState extends GlyphProcessingState { * Put (write) glyph into glyph output buffer. * @param glyph to write * @param a character association that applies to glyph + * @param predication a predication value to add to association A if predications enabled */ - public void putGlyph ( int glyph, GlyphSequence.CharAssociation a ) { + public void putGlyph ( int glyph, GlyphSequence.CharAssociation a, Object predication ) { if ( ! ogb.hasRemaining() ) { ogb = growBuffer ( ogb ); } ogb.put ( glyph ); + if ( predications && ( predication != null ) ) { + a.setPredication ( feature, predication ); + } oal.add ( a ); } @@ -110,13 +117,14 @@ public class GlyphSubstitutionState extends GlyphProcessingState { * Put (write) array of glyphs into glyph output buffer. * @param glyphs to write * @param associations array of character associations that apply to glyphs + * @param predication optional predicaion object to be associated with glyphs' associations */ - public void putGlyphs ( int[] glyphs, GlyphSequence.CharAssociation[] associations ) { + public void putGlyphs ( int[] glyphs, GlyphSequence.CharAssociation[] associations, Object predication ) { assert glyphs != null; assert associations != null; assert associations.length >= glyphs.length; for ( int i = 0, n = glyphs.length; i < n; i++ ) { - putGlyph ( glyphs [ i ], associations [ i ] ); + putGlyph ( glyphs [ i ], associations [ i ], predication ); } } @@ -187,7 +195,7 @@ public class GlyphSubstitutionState extends GlyphProcessingState { } } // output glyphs and associations - putGlyphs ( getGlyphs ( 0, nog, false, null, null, null ), getAssociations ( 0, nog, false, null, null, null ) ); + putGlyphs ( getGlyphs ( 0, nog, false, null, null, null ), getAssociations ( 0, nog, false, null, null, null ), null ); // consume replaced input glyphs consume ( nog ); return true; @@ -204,7 +212,7 @@ public class GlyphSubstitutionState extends GlyphProcessingState { super.applyDefault(); int gi = getGlyph(); if ( gi != 65535 ) { - putGlyph ( gi, getAssociation() ); + putGlyph ( gi, getAssociation(), null ); } } diff --git a/src/java/org/apache/fop/fonts/GlyphSubstitutionTable.java b/src/java/org/apache/fop/fonts/GlyphSubstitutionTable.java index 940430da1..72ba6aad6 100644 --- a/src/java/org/apache/fop/fonts/GlyphSubstitutionTable.java +++ b/src/java/org/apache/fop/fonts/GlyphSubstitutionTable.java @@ -250,7 +250,7 @@ public class GlyphSubstitutionTable extends GlyphTable { if ( ( go < 0 ) || ( go > 65535 ) ) { go = 65535; } - ss.putGlyph ( go, ss.getAssociation() ); + ss.putGlyph ( go, ss.getAssociation(), Boolean.TRUE ); ss.consume(1); return true; } @@ -378,7 +378,7 @@ public class GlyphSubstitutionTable extends GlyphTable { } else { int[] ga = getGlyphsForCoverageIndex ( ci, gi ); if ( ga != null ) { - ss.putGlyphs ( ga, GlyphSequence.CharAssociation.replicate ( ss.getAssociation(), ga.length ) ); + ss.putGlyphs ( ga, GlyphSequence.CharAssociation.replicate ( ss.getAssociation(), ga.length ), Boolean.TRUE ); ss.consume(1); } return true; @@ -472,7 +472,7 @@ public class GlyphSubstitutionTable extends GlyphTable { if ( ( go < 0 ) || ( go > 65535 ) ) { go = 65535; } - ss.putGlyph ( go, ss.getAssociation() ); + ss.putGlyph ( go, ss.getAssociation(), Boolean.TRUE ); ss.consume(1); return true; } @@ -575,10 +575,10 @@ public class GlyphSubstitutionTable extends GlyphTable { // fetch associations of matched component glyphs GlyphSequence.CharAssociation[] laa = ss.getAssociations ( 0, nga ); // output ligature glyph and its association - ss.putGlyph ( go, GlyphSequence.CharAssociation.join ( laa ) ); + ss.putGlyph ( go, GlyphSequence.CharAssociation.join ( laa ), Boolean.TRUE ); // fetch and output ignored glyphs (if necessary) if ( ngi > 0 ) { - ss.putGlyphs ( ss.getIgnoredGlyphs ( 0, ngi ), ss.getIgnoredAssociations ( 0, ngi ) ); + ss.putGlyphs ( ss.getIgnoredGlyphs ( 0, ngi ), ss.getIgnoredAssociations ( 0, ngi ), null ); } ss.consume ( nga + ngi ); } @@ -1333,8 +1333,8 @@ public class GlyphSubstitutionTable extends GlyphTable { public Ligature ( int ligature, int[] components ) { if ( ( ligature < 0 ) || ( ligature > 65535 ) ) { throw new AdvancedTypographicTableFormatException ( "invalid ligature glyph index: " + ligature ); - } else if ( ( components == null ) || ( components.length == 0 ) ) { - throw new AdvancedTypographicTableFormatException ( "invalid ligature components, must be non-empty array" ); + } else if ( components == null ) { + throw new AdvancedTypographicTableFormatException ( "invalid ligature components, must be non-null array" ); } else { for ( int i = 0, n = components.length; i < n; i++ ) { int gc = components [ i ]; @@ -1419,8 +1419,8 @@ public class GlyphSubstitutionTable extends GlyphTable { * @param ligatures array of ligatures */ public LigatureSet ( Ligature[] ligatures ) { - if ( ( ligatures == null ) || ( ligatures.length == 0 ) ) { - throw new AdvancedTypographicTableFormatException ( "invalid ligatures, must be non-empty array" ); + if ( ligatures == null ) { + throw new AdvancedTypographicTableFormatException ( "invalid ligatures, must be non-null array" ); } else { this.ligatures = ligatures; int ncMax = -1; diff --git a/src/java/org/apache/fop/fonts/GlyphTable.java b/src/java/org/apache/fop/fonts/GlyphTable.java index 654752184..1bc7d37f7 100644 --- a/src/java/org/apache/fop/fonts/GlyphTable.java +++ b/src/java/org/apache/fop/fonts/GlyphTable.java @@ -337,9 +337,9 @@ public class GlyphTable { } else if ( feature.equals("*") ) { throw new AdvancedTypographicTableFormatException ( "feature must not be wildcard" ); } else { - this.script = script; - this.language = language; - this.feature = feature; + this.script = script.trim(); + this.language = language.trim(); + this.feature = feature.trim(); } } @@ -670,12 +670,24 @@ public class GlyphTable { /** * {@inheritDoc} * @return the result of comparing the identifier of the specified lookup table with - * the identifier of this lookup table + * the identifier of this lookup table; lookup table identifiers take the form + * "lu(DIGIT)+", with comparison based on numerical ordering of numbers expressed by + * (DIGIT)+. */ public int compareTo ( Object o ) { if ( o instanceof LookupTable ) { LookupTable lt = (LookupTable) o; - return id.compareTo ( lt.id ); + assert id.startsWith ( "lu" ); + int i = Integer.parseInt ( id.substring ( 2 ) ); + assert lt.id.startsWith ( "lu" ); + int j = Integer.parseInt ( lt.id.substring ( 2 ) ); + if ( i < j ) { + return -1; + } else if ( i > j ) { + return 1; + } else { + return 0; + } } else { return -1; } diff --git a/src/java/org/apache/fop/fonts/LazyFont.java b/src/java/org/apache/fop/fonts/LazyFont.java index f747f725c..ef48cf877 100644 --- a/src/java/org/apache/fop/fonts/LazyFont.java +++ b/src/java/org/apache/fop/fonts/LazyFont.java @@ -408,6 +408,20 @@ public class LazyFont extends Typeface implements FontDescriptor, Substitutable, } } + /** + * {@inheritDoc} + */ + public CharSequence reorderCombiningMarks + ( CharSequence cs, int[][] gpa, String script, String language ) { + load(true); + if ( realFontDescriptor instanceof Substitutable ) { + return ((Substitutable)realFontDescriptor). + reorderCombiningMarks(cs, gpa, script, language); + } else { + return cs; + } + } + /** * {@inheritDoc} */ diff --git a/src/java/org/apache/fop/fonts/MultiByteFont.java b/src/java/org/apache/fop/fonts/MultiByteFont.java index 213d2f77d..33e99ff67 100644 --- a/src/java/org/apache/fop/fonts/MultiByteFont.java +++ b/src/java/org/apache/fop/fonts/MultiByteFont.java @@ -50,7 +50,12 @@ public class MultiByteFont extends CIDFont implements Substitutable, Positionabl private CIDSubset subset = new CIDSubset(); - /** A map from Unicode indices to glyph indices */ + /** + * A map from Unicode indices to glyph indices. No assumption + * about ordering is made below. If lookup is changed to a binary + * search (from the current linear search), then addPrivateUseMapping() + * needs to be changed to perform ordered inserts. + */ private BFEntry[] bfentries = null; /* advanced typographic support */ @@ -58,6 +63,15 @@ public class MultiByteFont extends CIDFont implements Substitutable, Positionabl private GlyphSubstitutionTable gsub; private GlyphPositioningTable gpos; + /* dynamic private use (character) mappings */ + private int numMapped; + private int numUnmapped; + private int nextPrivateUse = 0xE000; + private int firstPrivate; + private int lastPrivate; + private int firstUnmapped; + private int lastUnmapped; + /** * Default constructor */ @@ -170,14 +184,14 @@ public class MultiByteFont extends CIDFont implements Substitutable, Positionabl * @param c the Unicode character index * @return the glyph index (or 0 if the glyph is not available) */ - // [TBD] - needs optimization + // [TBD] - needs optimization, i.e., change from linear search to binary search private int findGlyphIndex(int c) { int idx = c; int retIdx = SingleByteEncoding.NOT_FOUND_CODE_POINT; for (int i = 0; (i < bfentries.length) && retIdx == 0; i++) { if (bfentries[i].getUnicodeStart() <= idx - && bfentries[i].getUnicodeEnd() >= idx) { + && bfentries[i].getUnicodeEnd() >= idx) { retIdx = bfentries[i].getGlyphStartIndex() + idx @@ -187,16 +201,74 @@ public class MultiByteFont extends CIDFont implements Substitutable, Positionabl return retIdx; } + /** + * Add a private use mapping {PU,GI} to the existing BFENTRIES map. + * N.B. Does not insert in order, merely appends to end of existing map. + */ + private synchronized void addPrivateUseMapping ( int pu, int gi ) { + assert findGlyphIndex ( pu ) == SingleByteEncoding.NOT_FOUND_CODE_POINT; + BFEntry[] bfeOld = bfentries; + int bfeCnt = bfeOld.length; + BFEntry[] bfeNew = new BFEntry [ bfeCnt + 1 ]; + System.arraycopy ( bfeOld, 0, bfeNew, 0, bfeCnt ); + bfeNew [ bfeCnt ] = new BFEntry ( pu, pu, gi ); + bfentries = bfeNew; + } + + /** + * Given a glyph index, create a new private use mapping, augmenting the bfentries + * table. This is needed to accommodate the presence of an (output) glyph index in a + * complex script glyph substitution that does not correspond to a character in the + * font's CMAP. The creation of such private use mappings is deferred until an + * attempt is actually made to perform the reverse lookup from the glyph index. This + * is necessary in order to avoid exhausting the private use space on fonts containing + * many such non-mapped glyph indices, if these mappings had been created statically + * at font load time. + * @param gi glyph index + * @returns unicode scalar value + */ + private int createPrivateUseMapping ( int gi ) { + while ( ( nextPrivateUse < 0xF900 ) + && ( findGlyphIndex(nextPrivateUse) != SingleByteEncoding.NOT_FOUND_CODE_POINT ) ) { + nextPrivateUse++; + } + if ( nextPrivateUse < 0xF900 ) { + int pu = nextPrivateUse; + addPrivateUseMapping ( pu, gi ); + if ( firstPrivate == 0 ) { + firstPrivate = pu; + } + lastPrivate = pu; + numMapped++; + if (log.isDebugEnabled()) { + log.debug ( "Create private use mapping from " + + CharUtilities.format ( pu ) + + " to glyph index " + gi + + " in font '" + getFullName() + "'" ); + } + return pu; + } else { + if ( firstUnmapped == 0 ) { + firstUnmapped = gi; + } + lastUnmapped = gi; + numUnmapped++; + log.warn ( "Exhausted private use area: unable to map " + + numUnmapped + " glyphs in glyph index range [" + + firstUnmapped + "," + lastUnmapped + + "] (inclusive) of font '" + getFullName() + "'" ); + return 0; + } + } + /** * Returns the Unicode scalar value that corresponds to the glyph index. If more than * one correspondence exists, then the first one is returned (ordered by bfentries[]). - * If the glyph index is Typeface.NOT_FOUND, then returns the Unicode replacement - * character (0x00FFFD). * @param gi glyph index * @returns unicode scalar value */ - // [TBD] - needs optimization - private int findCharacterFromGlyphIndex ( int gi ) { + // [TBD] - needs optimization, i.e., change from linear search to binary search + private int findCharacterFromGlyphIndex ( int gi, boolean augment ) { int cc = 0; for ( int i = 0, n = bfentries.length; i < n; i++ ) { BFEntry be = bfentries [ i ]; @@ -207,9 +279,17 @@ public class MultiByteFont extends CIDFont implements Substitutable, Positionabl break; } } + if ( ( cc == 0 ) && augment ) { + cc = createPrivateUseMapping ( gi ); + } return cc; } + private int findCharacterFromGlyphIndex ( int gi ) { + return findCharacterFromGlyphIndex ( gi, true ); + } + + /** {@inheritDoc} */ public char mapChar(char c) { notifyMapOperation(); @@ -363,6 +443,19 @@ public class MultiByteFont extends CIDFont implements Substitutable, Positionabl } } + /** {@inheritDoc} */ + public CharSequence reorderCombiningMarks + ( CharSequence cs, int[][] gpa, String script, String language ) { + if ( gdef != null ) { + GlyphSequence igs = mapCharsToGlyphs ( cs ); + GlyphSequence ogs = gdef.reorderCombiningMarks ( igs, gpa, script, language ); + CharSequence ocs = mapGlyphsToChars ( ogs ); + return ocs; + } else { + return cs; + } + } + /** {@inheritDoc} */ public boolean performsPositioning() { return gpos != null; @@ -438,8 +531,10 @@ public class MultiByteFont extends CIDFont implements Substitutable, Positionabl ( "ill-formed UTF-16 sequence, " + "contains isolated low surrogate at index " + i ); } + notifyMapOperation(); gi = findGlyphIndex ( cc ); if ( gi == SingleByteEncoding.NOT_FOUND_CODE_POINT ) { + warnMissingGlyph ( (char) cc ); gi = giMissing; } cb.put ( cc ); diff --git a/src/java/org/apache/fop/fonts/ScriptProcessor.java b/src/java/org/apache/fop/fonts/ScriptProcessor.java index 99d2823b8..601a27053 100644 --- a/src/java/org/apache/fop/fonts/ScriptProcessor.java +++ b/src/java/org/apache/fop/fonts/ScriptProcessor.java @@ -24,6 +24,8 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import org.apache.fop.util.CharUtilities; + // CSOFF: InnerAssignmentCheck // CSOFF: LineLengthCheck // CSOFF: ParameterNumberCheck @@ -57,11 +59,19 @@ public abstract class ScriptProcessor { } /** - * Obtain script specific substitution features. + * Obtain script specific required substitution features. * @return array of suppported substitution features or null */ public abstract String[] getSubstitutionFeatures(); + /** + * Obtain script specific optional substitution features. + * @return array of suppported substitution features or null + */ + public String[] getOptionalSubstitutionFeatures() { + return new String[0]; + } + /** * Obtain script specific substitution context tester. * @return substitution context tester or null @@ -90,7 +100,7 @@ public abstract class ScriptProcessor { * @param sct a script specific context tester (or null) * @return the substituted (output) glyph sequence */ - public final GlyphSequence substitute ( GlyphSequence gs, String script, String language, GlyphTable.UseSpec[] usa, ScriptContextTester sct ) { + public GlyphSequence substitute ( GlyphSequence gs, String script, String language, GlyphTable.UseSpec[] usa, ScriptContextTester sct ) { assert usa != null; for ( int i = 0, n = usa.length; i < n; i++ ) { GlyphTable.UseSpec us = usa [ i ]; @@ -100,11 +110,35 @@ public abstract class ScriptProcessor { } /** - * Obtain script specific positioning features. + * Reorder combining marks in glyph sequence so that they precede (within the sequence) the base + * character to which they are applied. N.B. In the case of RTL segments, marks are not reordered by this, + * method since when the segment is reversed by BIDI processing, marks are automatically reordered to precede + * their base glyph. + * @param gdef the glyph definition table that applies + * @param gs an input glyph sequence + * @param gpa associated glyph position adjustments (also reordered) + * @param script a script identifier + * @param language a language identifier + * @return the reordered (output) glyph sequence + */ + public GlyphSequence reorderCombiningMarks ( GlyphDefinitionTable gdef, GlyphSequence gs, int[][] gpa, String script, String language ) { + return gs; + } + + /** + * Obtain script specific required positioning features. * @return array of suppported positioning features or null */ public abstract String[] getPositioningFeatures(); + /** + * Obtain script specific optional positioning features. + * @return array of suppported positioning features or null + */ + public String[] getOptionalPositioningFeatures() { + return new String[0]; + } + /** * Obtain script specific positioning context tester. * @return positioning context tester or null @@ -141,7 +175,7 @@ public abstract class ScriptProcessor { * @param sct a script specific context tester (or null) * @return true if some adjustment is not zero; otherwise, false */ - public final boolean position ( GlyphSequence gs, String script, String language, int fontSize, GlyphTable.UseSpec[] usa, int[] widths, int[][] adjustments, ScriptContextTester sct ) { + public boolean position ( GlyphSequence gs, String script, String language, int fontSize, GlyphTable.UseSpec[] usa, int[] widths, int[][] adjustments, ScriptContextTester sct ) { assert usa != null; boolean adjusted = false; for ( int i = 0, n = usa.length; i < n; i++ ) { @@ -184,6 +218,8 @@ public abstract class ScriptProcessor { ScriptProcessor sp = null; if ( "arab".equals ( script ) ) { sp = new ArabicScriptProcessor ( script ); + } else if ( CharUtilities.isIndicScript ( script ) ) { + sp = IndicScriptProcessor.makeProcessor ( script ); } else { sp = new DefaultScriptProcessor ( script ); } diff --git a/src/java/org/apache/fop/fonts/Substitutable.java b/src/java/org/apache/fop/fonts/Substitutable.java index 730308589..759f3ae22 100644 --- a/src/java/org/apache/fop/fonts/Substitutable.java +++ b/src/java/org/apache/fop/fonts/Substitutable.java @@ -47,4 +47,17 @@ public interface Substitutable { */ CharSequence performSubstitution ( CharSequence cs, String script, String language ); + /** + * Reorder combining marks in character sequence so that they precede (within the sequence) the base + * character to which they are applied. N.B. In the case of LTR segments, marks are not reordered by this, + * method since when the segment is reversed by BIDI processing, marks are automatically reordered to precede + * their base character. + * @param cs character sequence within which combining marks to be reordered + * @param gpa associated glyph position adjustments (also reordered) + * @param script a script identifier + * @param language a language identifier + * @return output sequence containing reordered "font characters" + */ + CharSequence reorderCombiningMarks ( CharSequence cs, int[][] gpa, String script, String language ); + } diff --git a/src/java/org/apache/fop/fonts/autodetect/FontInfoFinder.java b/src/java/org/apache/fop/fonts/autodetect/FontInfoFinder.java index 1d3a10219..be098c74d 100644 --- a/src/java/org/apache/fop/fonts/autodetect/FontInfoFinder.java +++ b/src/java/org/apache/fop/fonts/autodetect/FontInfoFinder.java @@ -190,7 +190,7 @@ public class FontInfoFinder { // try to determine triplet information from font file CustomFont customFont = null; - if (fontURL.toExternalForm().endsWith(".ttc")) { + if (fontURL.toExternalForm().toLowerCase().endsWith(".ttc")) { // Get a list of the TTC Font names List ttcNames = null; String fontFileURL = fontURL.toExternalForm().trim(); diff --git a/src/java/org/apache/fop/fonts/truetype/TTFFile.java b/src/java/org/apache/fop/fonts/truetype/TTFFile.java index 1f0705c68..a4ec79b67 100644 --- a/src/java/org/apache/fop/fonts/truetype/TTFFile.java +++ b/src/java/org/apache/fop/fonts/truetype/TTFFile.java @@ -45,8 +45,6 @@ import org.apache.fop.fonts.GlyphSubstitutionTable; import org.apache.fop.fonts.GlyphSubtable; import org.apache.fop.fonts.GlyphTable; -import org.apache.fop.util.CharUtilities; - // CSOFF: AvoidNestedBlocksCheck // CSOFF: NoWhitespaceAfterCheck // CSOFF: InnerAssignmentCheck @@ -623,7 +621,6 @@ public class TTFFile { return false; } // Create cmaps for bfentries - augmentCMaps(); createCMaps(); readKerning(in); @@ -645,67 +642,6 @@ public class TTFFile { return true; } - /** - * Augment the previously ingested CMAP data with new entries to ensure - * that every glyph index has a corresponding Unicode value. This is required - * by GSUB/GPOS processing which can emit glyph indices that are not in the - * normal CMAP (and, for which, on other platforms, the glyph indices are used - * directly for rendering purposes (rather than character codes). However, in - * the case of FOP IF representation, character codes are used, and, consequently - * every glyph needs some character value. Here, we assign them to the Unicode - * private use range, starting at 0xE000 up to 0xF8FF. If there are existing - * assignments in this range, we just skip over them. Note that it is possible - * to exhaust this range of 6400 code values in the case a font has an - * extraordinary number of unmapped glyphs. In that case, we do not make - * any further assignments, but print a warning message. - */ - private void augmentCMaps() { - int numMapped = 0; - int numUnmapped = 0; - int nextPrivateUse = 0xE000; - int firstPrivate = 0; - int lastPrivate = 0; - int firstUnmapped = 0; - int lastUnmapped = 0; - for ( int i = 0, n = numberOfGlyphs; i < n; i++ ) { - Integer uc = glyphToUnicode ( i ); - if ( uc == null ) { - while ( ( nextPrivateUse < 0xF900 ) && ( unicodeToGlyphMap.get(Integer.valueOf(nextPrivateUse)) != null ) ) { - nextPrivateUse++; - } - if ( nextPrivateUse < 0xF900 ) { - int pu = nextPrivateUse; - unicodeMappings.add ( new UnicodeMapping ( i, pu ) ); - if ( firstPrivate == 0 ) { - firstPrivate = pu; - } - lastPrivate = pu; - numMapped++; - } else { - if ( firstUnmapped == 0 ) { - firstUnmapped = i; - } - lastUnmapped = i; - numUnmapped++; - } - } - } - if ( numMapped > 0 ) { - if (log.isDebugEnabled()) { - log.debug ( "augment CMAP for " - + numMapped - + " glyphs, mapped to private use characters in the range [" - + CharUtilities.format ( firstPrivate ) + "," - + CharUtilities.format ( lastPrivate ) + "] (inclusive)" ); - } - } - if ( numUnmapped > 0 ) { - log.warn ( "Exhausted private use area: unable to map " - + numUnmapped + " glyphs in glyph index range [" - + firstUnmapped + "," + lastUnmapped + "] (inclusive) of font '" + getFullName() + "'" ); - } - } - private void createCMaps() { cmaps = new java.util.ArrayList(); TTFCmapEntry tce = new TTFCmapEntry(); @@ -1523,7 +1459,7 @@ public class TTFFile { capHeight = os2CapHeight; } if (capHeight == 0) { - log.warn("capHeight value could not be determined." + log.debug("capHeight value could not be determined." + " The font may not work as expected."); } } @@ -1533,7 +1469,7 @@ public class TTFFile { xHeight = os2xHeight; } if (xHeight == 0) { - log.warn("xHeight value could not be determined." + log.debug("xHeight value could not be determined." + " The font may not work as expected."); } } @@ -3235,9 +3171,6 @@ public class TTFFile { int es = in.readTTFUShort(); // read delta format int df = in.readTTFUShort(); - // read deltas - int n = ( es - ss ) + 1; - int[] da = new int [ n ]; int s1, m1, dm, dd, s2; if ( df == 1 ) { s1 = 14; m1 = 0x3; dm = 1; dd = 4; s2 = 2; @@ -3246,8 +3179,16 @@ public class TTFFile { } else if ( df == 3 ) { s1 = 8; m1 = 0xFF; dm = 127; dd = 256; s2 = 8; } else { - throw new AdvancedTypographicTableFormatException ( "unsupported device table delta format: " + df ); + log.debug ( "unsupported device table delta format: " + df + ", ignoring device table" ); + return null; + } + // read deltas + int n = ( es - ss ) + 1; + if ( n < 0 ) { + log.debug ( "invalid device table delta count: " + n + ", ignoring device table" ); + return null; } + int[] da = new int [ n ]; for ( int i = 0; ( i < n ) && ( s2 > 0 );) { int p = in.readTTFUShort(); for ( int j = 0, k = 16 / s2; j < k; j++ ) { @@ -5413,11 +5354,9 @@ public class TTFFile { dirOffsets[i] = in.readTTFULong(); } - if (log.isDebugEnabled()) { - log.debug("This is a TrueType collection file with " - + numDirectories + " fonts"); - log.debug("Containing the following fonts: "); - } + log.info("This is a TrueType collection file with " + + numDirectories + " fonts"); + log.info("Containing the following fonts: "); for (int i = 0; (i < numDirectories); i++) { in.seekSet(dirOffsets[i]); @@ -5425,7 +5364,7 @@ public class TTFFile { readName(in); - log.debug(fullName); + log.info(fullName); fontNames.add(fullName); // Reset names diff --git a/src/java/org/apache/fop/layoutmgr/BidiUtil.java b/src/java/org/apache/fop/layoutmgr/BidiUtil.java index 5c5c72534..5a4028e8b 100644 --- a/src/java/org/apache/fop/layoutmgr/BidiUtil.java +++ b/src/java/org/apache/fop/layoutmgr/BidiUtil.java @@ -22,6 +22,7 @@ package org.apache.fop.layoutmgr; import java.util.Arrays; import java.util.ArrayList; import java.util.Iterator; +import java.util.LinkedList; import java.util.List; import java.util.Stack; import java.util.Vector; @@ -29,23 +30,28 @@ import java.util.Vector; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.apache.fop.area.Area; import org.apache.fop.area.LineArea; +import org.apache.fop.area.LinkResolver; import org.apache.fop.area.inline.Anchor; +import org.apache.fop.area.inline.BasicLinkArea; import org.apache.fop.area.inline.InlineArea; import org.apache.fop.area.inline.InlineBlockParent; import org.apache.fop.area.inline.InlineParent; +import org.apache.fop.area.inline.InlineViewport; import org.apache.fop.area.inline.Leader; import org.apache.fop.area.inline.Space; import org.apache.fop.area.inline.SpaceArea; import org.apache.fop.area.inline.TextArea; +import org.apache.fop.area.inline.UnresolvedPageNumber; import org.apache.fop.area.inline.WordArea; -import org.apache.fop.area.inline.InlineViewport; import org.apache.fop.fo.CharIterator; import org.apache.fop.fo.Constants; import org.apache.fop.fo.FObj; import org.apache.fop.fo.FONode; import org.apache.fop.fo.FOText; import org.apache.fop.fo.flow.AbstractPageNumberCitation; +import org.apache.fop.fo.flow.AbstractGraphics; import org.apache.fop.fo.flow.BidiOverride; import org.apache.fop.fo.flow.Block; import org.apache.fop.fo.flow.BlockContainer; @@ -54,10 +60,13 @@ import org.apache.fop.fo.flow.ExternalGraphic; import org.apache.fop.fo.flow.Inline; import org.apache.fop.fo.flow.InlineContainer; import org.apache.fop.fo.flow.InlineLevel; +import org.apache.fop.fo.flow.ListItem; import org.apache.fop.fo.flow.PageNumber; +import org.apache.fop.fo.flow.Wrapper; import org.apache.fop.fo.pagination.Flow; import org.apache.fop.fo.pagination.PageSequence; import org.apache.fop.traits.Direction; +import org.apache.fop.traits.WritingModeTraits; import org.apache.fop.traits.WritingModeTraitsGetter; import org.apache.fop.text.bidi.BidiClassUtils; import org.apache.fop.util.CharUtilities; @@ -181,10 +190,17 @@ public final class BidiUtil { if ( r != null ) { r.append ( ( (Character) fn ) .charIterator(), fn ); } + } else if ( ( fn instanceof AbstractPageNumberCitation ) || ( fn instanceof AbstractGraphics ) ) { + if ( r != null ) { + r.append ( CharUtilities.OBJECT_REPLACEMENT_CHARACTER, fn ); + } } else if ( fn instanceof BidiOverride ) { if ( r != null ) { ranges = collectBidiOverrideRanges ( (BidiOverride) fn, r, ranges ); } + } else if ( fn instanceof ListItem ) { + ranges = collectRanges ( ( (ListItem) fn ) .getLabel(), ranges ); + ranges = collectRanges ( ( (ListItem) fn ) .getBody(), ranges ); } else if ( fn instanceof PageSequence ) { ranges = collectRanges ( ( (PageSequence) fn ) .getMainFlow(), ranges ); } else { @@ -230,28 +246,32 @@ public final class BidiUtil { runs = collectRuns ( (WordArea) ia, runs ); } else if ( ia instanceof SpaceArea ) { runs = collectRuns ( (SpaceArea) ia, runs ); - } else if ( ia instanceof InlineParent ) { - runs = collectRuns ( (InlineParent) ia, runs ); - } else if ( ia instanceof InlineViewport ) { - runs = collectRuns ( (InlineViewport) ia, runs ); + } else if ( ia instanceof Anchor ) { + runs = collectRuns ( (Anchor) ia, runs ); } else if ( ia instanceof Leader ) { runs = collectRuns ( (Leader) ia, runs ); } else if ( ia instanceof Space ) { runs = collectRuns ( (Space) ia, runs ); - } else if ( ia instanceof Anchor ) { - runs = collectRuns ( (Anchor) ia, runs ); + } else if ( ia instanceof UnresolvedPageNumber ) { + runs = collectRuns ( (UnresolvedPageNumber) ia, runs ); } else if ( ia instanceof InlineBlockParent ) { runs = collectRuns ( (InlineBlockParent) ia, runs ); + } else if ( ia instanceof InlineViewport ) { + runs = collectRuns ( (InlineViewport) ia, runs ); + } else if ( ia instanceof InlineParent ) { + runs = collectRuns ( (InlineParent) ia, runs ); } } return runs; } private static List collectRuns ( Anchor a, List runs ) { + runs.add ( new InlineRun ( a, new int[] {a.getBidiLevel()}) ); return runs; } private static List collectRuns ( InlineBlockParent a, List runs ) { + runs.add ( new InlineRun ( a, new int[] {a.getBidiLevel()}) ); return runs; } @@ -259,11 +279,18 @@ public final class BidiUtil { return collectRuns ( a.getChildAreas(), runs ); } + private static List collectRuns ( InlineViewport a, List runs ) { + runs.add ( new InlineRun ( a, new int[] {a.getBidiLevel()}) ); + return runs; + } + private static List collectRuns ( Leader a, List runs ) { + runs.add ( new InlineRun ( a, new int[] {a.getBidiLevel()}) ); return runs; } private static List collectRuns ( Space a, List runs ) { + runs.add ( new InlineRun ( a, new int[] {a.getBidiLevel()}) ); return runs; } @@ -272,7 +299,8 @@ public final class BidiUtil { return runs; } - private static List collectRuns ( InlineViewport a, List runs ) { + private static List collectRuns ( UnresolvedPageNumber a, List runs ) { + runs.add ( new InlineRun ( a, new int[] {a.getBidiLevel()}) ); return runs; } @@ -360,63 +388,15 @@ public final class BidiUtil { return runs; } private static void replaceInlines ( LineArea la, List runs ) { - List inlines = new ArrayList(); + List inlines = new ArrayList(); for ( Iterator it = runs.iterator(); it.hasNext(); ) { InlineRun ir = (InlineRun) it.next(); inlines.add ( ir.getInline() ); } - inlines = unflattenInlines ( inlines ); - la.setInlineAreas ( inlines ); - } - private static List unflattenInlines ( List inlines ) { - List inlinesNew = new ArrayList(); // unflattened inlines being consed - TextArea tLast = null; // last text area parent - TextArea tNew = null; // new text area being consed - int lLast = -1; // last bidi level - for ( Iterator it = inlines.iterator(); it.hasNext(); ) { - InlineArea ia = (InlineArea) it.next(); - if ( ( ia instanceof WordArea ) || ( ia instanceof SpaceArea ) ) { - TextArea t = (TextArea) ia.getParentArea(); - int l = ia.getBidiLevel(); - if ( isEndOfTextArea ( t, tLast, l, lLast ) ) { - if ( tNew != null ) { - inlinesNew.add ( tNew ); - tNew = null; - } - } - if ( tNew == null ) { - tNew = createUnflattenedText ( t ); - } - tNew.addChildArea ( ia ); - tLast = t; - lLast = l; - } else { - inlinesNew.add ( ia ); - } - } - if ( tNew != null ) { - inlinesNew.add ( tNew ); - } - return inlinesNew; - } - private static boolean isEndOfTextArea ( TextArea t, TextArea tLast, int level, int levelLast ) { - if ( ( tLast != null ) && ( t != tLast ) ) { - return true; - } else if ( ( levelLast != -1 ) && ( level != levelLast ) ) { - return true; - } else { - return false; - } + la.setInlineAreas ( unflattenInlines ( inlines ) ); } - private static TextArea createUnflattenedText ( TextArea t ) { - TextArea tNew = new TextArea(); - if ( t != null ) { - tNew.setBPD ( t.getBPD() ); - tNew.setTraits ( t.getTraits() ); - tNew.setBlockProgressionOffset ( t.getBlockProgressionOffset() ); - tNew.setBaselineOffset ( t.getBaselineOffset() ); - } - return tNew; + private static List unflattenInlines ( List inlines ) { + return new UnflattenProcessor ( inlines ) .unflatten(); } private static void dumpRuns ( String header, List runs ) { log.debug ( header ); @@ -470,6 +450,8 @@ public final class BidiUtil { private static boolean isRangeBoundary ( FONode fn ) { if ( fn instanceof Block ) { // fo:block return true; + } else if ( fn instanceof Character ) { // fo:character + return false; } else if ( fn instanceof InlineLevel ) { // fo:inline, fo:leader, fo:bidi-override, fo:title return false; } else if ( fn instanceof InlineContainer ) { // fo:inline-container @@ -480,7 +462,9 @@ public final class BidiUtil { return false; } else if ( fn instanceof PageNumber ) { // fo:page-number return false; - } else if ( fn instanceof ExternalGraphic ) { // fo:external-graphic + } else if ( fn instanceof AbstractGraphics ) { // fo:external-graphic, fo:instream-foreign-object + return false; + } else if ( fn instanceof Wrapper ) { // fo:wrapper return false; } else if ( fn instanceof FOText ) { // #PCDATA return false; @@ -572,12 +556,12 @@ public final class BidiUtil { } void resolve() { WritingModeTraitsGetter tg; - if ( ( tg = getWritingModeTraitsGetter ( getNode() ) ) != null ) { + if ( ( tg = WritingModeTraits.getWritingModeTraitsGetter ( getNode() ) ) != null ) { resolve ( tg.getInlineProgressionDirection() ); } } public String toString() { - StringBuffer sb = new StringBuffer ( "DR: " + fn.getLocalName() + "{ <" + CharUtilities.toNCRefs ( buffer.toString() ) + ">" ); + StringBuffer sb = new StringBuffer ( "DR: " + fn.getLocalName() + " { <" + CharUtilities.toNCRefs ( buffer.toString() ) + ">" ); sb.append ( ", intervals <" ); boolean first = true; for ( Iterator it = intervals.iterator(); it.hasNext(); ) { @@ -589,7 +573,7 @@ public final class BidiUtil { } sb.append ( ti.toString() ); } - sb.append(">}"); + sb.append("> }"); return sb.toString(); } private void resolve ( Direction paragraphEmbeddingLevel ) { @@ -684,14 +668,6 @@ public final class BidiUtil { } } } - private WritingModeTraitsGetter getWritingModeTraitsGetter ( FONode fn ) { - for ( FONode n = fn; n != null; n = n.getParent() ) { - if ( n instanceof WritingModeTraitsGetter ) { - return (WritingModeTraitsGetter) n; - } - } - return null; - } } private static class TextInterval { @@ -745,6 +721,10 @@ public final class BidiUtil { ( (FOText) fn ) .setBidiLevel ( level, start - textStart, end - textStart ); } else if ( fn instanceof Character ) { ( (Character) fn ) .setBidiLevel ( level ); + } else if ( fn instanceof AbstractPageNumberCitation ) { + ( (AbstractPageNumberCitation) fn ) .setBidiLevel ( level ); + } else if ( fn instanceof AbstractGraphics ) { + ( (AbstractGraphics) fn ) .setBidiLevel ( level ); } } public boolean equals ( Object o ) { @@ -778,6 +758,10 @@ public final class BidiUtil { c = 'C'; } else if ( fn instanceof BidiOverride ) { c = 'B'; + } else if ( fn instanceof AbstractPageNumberCitation ) { + c = '#'; + } else if ( fn instanceof AbstractGraphics ) { + c = 'G'; } else { c = '?'; } @@ -923,18 +907,21 @@ public final class BidiUtil { } else if ( inline instanceof SpaceArea ) { c = 'S'; content = ( (SpaceArea) inline ) .getSpace(); - } else if ( inline instanceof InlineParent ) { - c = 'I'; - } else if ( inline instanceof InlineBlockParent ) { - c = 'B'; - } else if ( inline instanceof InlineViewport ) { - c = 'V'; - } else if ( inline instanceof Leader ) { - c = 'L'; } else if ( inline instanceof Anchor ) { c = 'A'; + } else if ( inline instanceof Leader ) { + c = 'L'; } else if ( inline instanceof Space ) { c = 'G'; // 'G' => glue + } else if ( inline instanceof UnresolvedPageNumber ) { + c = '#'; + content = ( (UnresolvedPageNumber) inline ) .getText(); + } else if ( inline instanceof InlineBlockParent ) { + c = 'B'; + } else if ( inline instanceof InlineViewport ) { + c = 'V'; + } else if ( inline instanceof InlineParent ) { + c = 'I'; } else { c = '?'; } @@ -990,6 +977,288 @@ public final class BidiUtil { } } + /** + * The UnflattenProcessor class is used to reconstruct (by unflattening) a line's + * area hierarachy after leaf inline area reordering is complete. + */ + static class UnflattenProcessor { + private List il; // list of flattened inline areas being unflattened + private List ilNew; // list of unflattened inline areas being constructed + private int iaLevelLast; // last (previous) level of current inline area (if applicable) or -1 + private TextArea tcOrig; // original text area container + private TextArea tcNew; // new text area container being constructed + private Stack icOrig; // stack of original inline parent containers + private Stack icNew; // stack of new inline parent containers being constructed + UnflattenProcessor ( List inlines ) { + this.il = inlines; + this.ilNew = new ArrayList(); + this.iaLevelLast = -1; + this.icOrig = new Stack(); + this.icNew = new Stack(); + } + List unflatten() { + if ( il != null ) { + for ( Iterator it = il.iterator(); it.hasNext(); ) { + process ( it.next() ); + } + } + finishAll(); + return ilNew; + } + private void process ( InlineArea ia ) { + process ( findInlineContainers ( ia ), findTextContainer ( ia ), ia ); + } + private void process ( List ich, TextArea tc, InlineArea ia ) { + maybeFinishTextContainer ( tc, ia ); + maybeFinishInlineContainers ( ich, tc, ia ); + update ( ich, tc, ia ); + } + private boolean shouldFinishTextContainer ( TextArea tc, InlineArea ia ) { + if ( ( tcOrig != null ) && ( tc != tcOrig ) ) { + return true; + } else if ( ( iaLevelLast != -1 ) && ( ia.getBidiLevel() != iaLevelLast ) ) { + return true; + } else { + return false; + } + } + private void finishTextContainer() { + finishTextContainer ( null, null ); + } + private void finishTextContainer ( TextArea tc, InlineArea ia ) { + if ( tcNew != null ) { + updateIPD ( tcNew ); + if ( ! icNew.empty() ) { + icNew.peek().addChildArea ( tcNew ); + } else { + ilNew.add ( tcNew ); + } + } + tcNew = null; + } + private void maybeFinishTextContainer ( TextArea tc, InlineArea ia ) { + if ( shouldFinishTextContainer ( tc, ia ) ) { + finishTextContainer ( tc, ia ); + } + } + private boolean shouldFinishInlineContainer ( List ich, TextArea tc, InlineArea ia ) { + if ( ( ich == null ) || ich.isEmpty() ) { + return ! icOrig.empty(); + } else { + if ( ! icOrig.empty() ) { + InlineParent ic = ich.get(0); + InlineParent ic0 = icOrig.peek(); + return ( ic != ic0 ) && ! isInlineParentOf ( ic, ic0 ); + } else { + return false; + } + } + } + private void finishInlineContainer() { + finishInlineContainer ( null, null, null ); + } + private void finishInlineContainer ( List ich, TextArea tc, InlineArea ia ) { + if ( ( ich != null ) && ! ich.isEmpty() ) { // finish non-matching inner inline container(s) + for ( Iterator it = ich.iterator(); it.hasNext(); ) { + InlineParent ic = it.next(); + InlineParent ic0 = icOrig.empty() ? null : icOrig.peek(); + if ( ic0 == null ) { + assert icNew.empty(); + } else if ( ic != ic0 ) { + assert ! icNew.empty(); + InlineParent icO0 = icOrig.pop(); + InlineParent icN0 = icNew.pop(); + assert icO0 != null; + assert icN0 != null; + if ( icNew.empty() ) { + ilNew.add ( icN0 ); + } else { + icNew.peek().addChildArea ( icN0 ); + } + if ( ! icOrig.empty() && ( icOrig.peek() == ic ) ) { + break; + } + } else { + break; + } + } + } else { // finish all inline containers + while ( ! icNew.empty() ) { + InlineParent icO0 = icOrig.pop(); + InlineParent icN0 = icNew.pop(); + assert icO0 != null; + assert icN0 != null; + if ( icNew.empty() ) { + ilNew.add ( icN0 ); + } else { + icNew.peek().addChildArea ( icN0 ); + } + } + } + } + private void maybeFinishInlineContainers ( List ich, TextArea tc, InlineArea ia ) { + if ( shouldFinishInlineContainer ( ich, tc, ia ) ) { + finishInlineContainer ( ich, tc, ia ); + } + } + private void finishAll() { + finishTextContainer(); + finishInlineContainer(); + } + private void update ( List ich, TextArea tc, InlineArea ia ) { + if ( ( ich != null ) && ! ich.isEmpty() ) { + pushInlineContainers ( ich ); + } + if ( tc != null ) { + pushTextContainer ( tc, ia ); + } else { + pushNonTextInline ( ia ); + } + iaLevelLast = ia.getBidiLevel(); + tcOrig = tc; + } + private void pushInlineContainers ( List ich ) { + LinkedList icl = new LinkedList(); + for ( Iterator it = ich.iterator(); it.hasNext(); ) { + InlineParent ic = it.next(); + if ( icOrig.search ( ic ) >= 0 ) { + break; + } else { + icl.addFirst ( ic ); + } + } + for ( Iterator it = icl.iterator(); it.hasNext(); ) { + InlineParent ic = it.next(); + icOrig.push ( ic ); + icNew.push ( generateInlineContainer ( ic ) ); + } + } + private void pushTextContainer ( TextArea tc, InlineArea ia ) { + if ( tc instanceof UnresolvedPageNumber ) { + tcNew = tc; + } else { + if ( tcNew == null ) { + tcNew = generateTextContainer ( tc ); + } + tcNew.addChildArea ( ia ); + } + } + private void pushNonTextInline ( InlineArea ia ) { + if ( icNew.empty() ) { + ilNew.add ( ia ); + } else { + icNew.peek().addChildArea ( ia ); + } + } + private InlineParent generateInlineContainer ( InlineParent i ) { + if ( i instanceof BasicLinkArea ) { + return generateBasicLinkArea ( (BasicLinkArea) i ); + } else { + return generateInlineContainer0 ( i ); + } + } + private InlineParent generateBasicLinkArea ( BasicLinkArea l ) { + BasicLinkArea lc = new BasicLinkArea(); + if ( l != null ) { + initializeInlineContainer ( lc, l ); + initializeLinkArea ( lc, l ); + } + return lc; + } + private void initializeLinkArea ( BasicLinkArea lc, BasicLinkArea l ) { + assert lc != null; + assert l != null; + LinkResolver r = l.getResolver(); + if ( r != null ) { + String[] idrefs = r.getIDRefs(); + if ( idrefs.length > 0 ) { + String idref = idrefs[0]; + LinkResolver lr = new LinkResolver ( idref, lc ); + lc.setResolver ( lr ); + r.addDependent ( lr ); + } + } + } + private InlineParent generateInlineContainer0 ( InlineParent i ) { + InlineParent ic = new InlineParent(); + if ( i != null ) { + initializeInlineContainer ( ic, i ); + } + return ic; + } + private void initializeInlineContainer ( InlineParent ic, InlineParent i ) { + assert ic != null; + assert i != null; + ic.setTraits ( i.getTraits() ); + ic.setBPD ( i.getBPD() ); + ic.setBlockProgressionOffset ( i.getBlockProgressionOffset() ); + } + private TextArea generateTextContainer ( TextArea t ) { + TextArea tc = new TextArea(); + if ( t != null ) { + tc.setTraits ( t.getTraits() ); + tc.setBPD ( t.getBPD() ); + tc.setBlockProgressionOffset ( t.getBlockProgressionOffset() ); + tc.setBaselineOffset ( t.getBaselineOffset() ); + tc.setTextWordSpaceAdjust ( t.getTextWordSpaceAdjust() ); + tc.setTextLetterSpaceAdjust ( t.getTextLetterSpaceAdjust() ); + } + return tc; + } + private void updateIPD ( TextArea tc ) { + int numAdjustable = 0; + for ( Iterator it = tc.getChildAreas().iterator(); it.hasNext(); ) { + InlineArea ia = (InlineArea) it.next(); + if ( ia instanceof SpaceArea ) { + SpaceArea sa = (SpaceArea) ia; + if ( sa.isAdjustable() ) { + numAdjustable++; + } + } + } + if ( numAdjustable > 0 ) { + tc.setIPD ( tc.getIPD() + ( numAdjustable * tc.getTextWordSpaceAdjust() ) ); + } + } + private TextArea findTextContainer ( InlineArea ia ) { + assert ia != null; + TextArea t = null; + while ( t == null ) { + if ( ia instanceof TextArea ) { + t = (TextArea) ia; + } else { + Area p = ia.getParentArea(); + if ( p instanceof InlineArea ) { + ia = (InlineArea) p; + } else { + break; + } + } + } + return t; + } + private List findInlineContainers ( InlineArea ia ) { + assert ia != null; + List ich = new ArrayList(); + Area a = ia.getParentArea(); + while ( a != null ) { + if ( a instanceof InlineArea ) { + if ( ( a instanceof InlineParent ) && ! ( a instanceof TextArea ) ) { + ich.add ( (InlineParent) a ); + } + a = ( (InlineArea) a ) .getParentArea(); + } else { + a = null; + } + } + return ich; + } + private boolean isInlineParentOf ( InlineParent ic0, InlineParent ic1 ) { + assert ic0 != null; + return ic0.getParentArea() == ic1; + } + } + /** * The UnicodeBidiAlgorithm class implements functionality prescribed by * the Unicode Bidirectional Algorithm, Unicode Standard Annex #9. diff --git a/src/java/org/apache/fop/layoutmgr/ExternalDocumentLayoutManager.java b/src/java/org/apache/fop/layoutmgr/ExternalDocumentLayoutManager.java index 533e5ac1b..7f0355e47 100644 --- a/src/java/org/apache/fop/layoutmgr/ExternalDocumentLayoutManager.java +++ b/src/java/org/apache/fop/layoutmgr/ExternalDocumentLayoutManager.java @@ -185,7 +185,7 @@ public class ExternalDocumentLayoutManager extends AbstractPageSequenceLayoutMan TraitSetter.setProducerID(imageArea, fobj.getId()); transferForeignAttributes(imageArea); - InlineViewport vp = new InlineViewport(imageArea); + InlineViewport vp = new InlineViewport(imageArea, fobj.getBidiLevel()); TraitSetter.setProducerID(vp, fobj.getId()); vp.setIPD(imageSize.width); vp.setBPD(imageSize.height); diff --git a/src/java/org/apache/fop/layoutmgr/inline/AbstractGraphicsLayoutManager.java b/src/java/org/apache/fop/layoutmgr/inline/AbstractGraphicsLayoutManager.java index 4cd06b973..3f8833040 100644 --- a/src/java/org/apache/fop/layoutmgr/inline/AbstractGraphicsLayoutManager.java +++ b/src/java/org/apache/fop/layoutmgr/inline/AbstractGraphicsLayoutManager.java @@ -59,6 +59,7 @@ public abstract class AbstractGraphicsLayoutManager extends LeafNodeLayoutManage Dimension intrinsicSize = new Dimension( fobj.getIntrinsicWidth(), fobj.getIntrinsicHeight()); + int bidiLevel = fobj.getBidiLevel(); //TODO Investigate if the line-height property has to be taken into the calculation //somehow. There was some code here that hints in this direction but it was disabled. @@ -67,6 +68,7 @@ public abstract class AbstractGraphicsLayoutManager extends LeafNodeLayoutManage Rectangle placement = imageLayout.getPlacement(); CommonBorderPaddingBackground borderProps = fobj.getCommonBorderPaddingBackground(); + setCommonBorderPaddingBackground(borderProps); //Determine extra BPD from borders and padding int beforeBPD = borderProps.getPadding(CommonBorderPaddingBackground.BEFORE, false, this); @@ -75,16 +77,21 @@ public abstract class AbstractGraphicsLayoutManager extends LeafNodeLayoutManage placement.y += beforeBPD; //Determine extra IPD from borders and padding - int startIPD = borderProps.getPadding(CommonBorderPaddingBackground.START, false, this); - startIPD += borderProps.getBorderWidth(CommonBorderPaddingBackground.START, false); - - placement.x += startIPD; + if ( ( bidiLevel == -1 ) || ( ( bidiLevel & 1 ) == 0 ) ) { + int startIPD = borderProps.getPadding(CommonBorderPaddingBackground.START, false, this); + startIPD += borderProps.getBorderWidth(CommonBorderPaddingBackground.START, false); + placement.x += startIPD; + } else { + int endIPD = borderProps.getPadding(CommonBorderPaddingBackground.END, false, this); + endIPD += borderProps.getBorderWidth(CommonBorderPaddingBackground.END, false); + placement.x += endIPD; + } Area viewportArea = getChildArea(); TraitSetter.setProducerID(viewportArea, fobj.getId()); transferForeignAttributes(viewportArea); - InlineViewport vp = new InlineViewport(viewportArea); + InlineViewport vp = new InlineViewport(viewportArea, bidiLevel); TraitSetter.addPtr(vp, fobj.getPtr()); // used for accessibility TraitSetter.setProducerID(vp, fobj.getId()); vp.setIPD(imageLayout.getViewportSize().width); @@ -94,11 +101,11 @@ public abstract class AbstractGraphicsLayoutManager extends LeafNodeLayoutManage vp.setBlockProgressionOffset(0); // Common Border, Padding, and Background Properties - TraitSetter.addBorders(vp, fobj.getCommonBorderPaddingBackground() + TraitSetter.addBorders(vp, borderProps , false, false, false, false, this); - TraitSetter.addPadding(vp, fobj.getCommonBorderPaddingBackground() + TraitSetter.addPadding(vp, borderProps , false, false, false, false, this); - TraitSetter.addBackground(vp, fobj.getCommonBorderPaddingBackground(), this); + TraitSetter.addBackground(vp, borderProps, this); return vp; } diff --git a/src/java/org/apache/fop/layoutmgr/inline/AbstractPageNumberCitationLayoutManager.java b/src/java/org/apache/fop/layoutmgr/inline/AbstractPageNumberCitationLayoutManager.java index e090fbae6..3d031c31b 100644 --- a/src/java/org/apache/fop/layoutmgr/inline/AbstractPageNumberCitationLayoutManager.java +++ b/src/java/org/apache/fop/layoutmgr/inline/AbstractPageNumberCitationLayoutManager.java @@ -81,10 +81,7 @@ public abstract class AbstractPageNumberCitationLayoutManager extends LeafNodeLa } /** {@inheritDoc} */ - public InlineArea get(LayoutContext context) { - curArea = getPageNumberCitationInlineArea(); - return curArea; - } + public abstract InlineArea get(LayoutContext context); /** * {@inheritDoc} @@ -97,35 +94,6 @@ public abstract class AbstractPageNumberCitationLayoutManager extends LeafNodeLa } } - /** - * If id can be resolved then simply return a text area, otherwise - * return a resolvable area - * - * @return a corresponding InlineArea - */ - private InlineArea getPageNumberCitationInlineArea() { - PageViewport page = getPSLM().getFirstPVWithID(fobj.getRefId()); - TextArea text; - if (page != null) { - String str = page.getPageNumberString(); - // get page string from parent, build area - text = new TextArea(); - int width = getStringWidth(str); - text.addWord(str, 0); - text.setIPD(width); - resolved = true; - } else { - resolved = false; - text = new UnresolvedPageNumber(fobj.getRefId(), font); - String str = "MMM"; // reserve three spaces for page number - int width = getStringWidth(str); - text.setIPD(width); - } - updateTextAreaTraits(text); - - return text; - } - /** * Updates the traits for the generated text area. * @param text the text area @@ -152,5 +120,12 @@ public abstract class AbstractPageNumberCitationLayoutManager extends LeafNodeLa return width; } + /** + * @return bidi level governing abstract page number citation + */ + protected int getBidiLevel() { + return fobj.getBidiLevel(); + } + } diff --git a/src/java/org/apache/fop/layoutmgr/inline/BasicLinkLayoutManager.java b/src/java/org/apache/fop/layoutmgr/inline/BasicLinkLayoutManager.java index 40c9a324e..8ea698c31 100644 --- a/src/java/org/apache/fop/layoutmgr/inline/BasicLinkLayoutManager.java +++ b/src/java/org/apache/fop/layoutmgr/inline/BasicLinkLayoutManager.java @@ -69,6 +69,12 @@ public class BasicLinkLayoutManager extends InlineLayoutManager { res.resolveIDRef(idref, pslm.getFirstPVWithID(idref)); if (!res.isResolved()) { pslm.addUnresolvedArea(idref, res); + if ( area instanceof BasicLinkArea ) { + // establish back-pointer from BasicLinkArea to LinkResolver to + // handle inline area unflattening during line bidi reordering; + // needed to create internal link trait on synthesized basic link area + ((BasicLinkArea)area).setResolver(res); + } } } else if (fobj.hasExternalDestination()) { String url = URISpecification.getURL(fobj.getExternalDestination()); diff --git a/src/java/org/apache/fop/layoutmgr/inline/CharacterLayoutManager.java b/src/java/org/apache/fop/layoutmgr/inline/CharacterLayoutManager.java index a4ca5b240..3ec22949d 100644 --- a/src/java/org/apache/fop/layoutmgr/inline/CharacterLayoutManager.java +++ b/src/java/org/apache/fop/layoutmgr/inline/CharacterLayoutManager.java @@ -77,17 +77,18 @@ public class CharacterLayoutManager extends LeafNodeLayoutManager { private TextArea getCharacterInlineArea(Character node) { TextArea text = new TextArea(); char ch = node.getCharacter(); + int ipd = font.getCharWidth(ch); int blockProgressionOffset = 0; - int level = node.bidiLevelAt(0); + int level = node.getBidiLevel(); if (CharUtilities.isAnySpace(ch)) { // add space unless it's zero-width: if (!CharUtilities.isZeroWidthSpace(ch)) { - text.addSpace(ch, 0, CharUtilities.isAdjustableSpace(ch), + text.addSpace(ch, ipd, CharUtilities.isAdjustableSpace(ch), blockProgressionOffset, level); } } else { int[] levels = ( level >= 0 ) ? new int[] {level} : null; - text.addWord(String.valueOf(ch), 0, null, levels, null, blockProgressionOffset); + text.addWord(String.valueOf(ch), ipd, null, levels, null, blockProgressionOffset); } TraitSetter.setProducerID(text, node.getId()); TraitSetter.addTextDecoration(text, node.getTextDecoration()); @@ -109,9 +110,8 @@ public class CharacterLayoutManager extends LeafNodeLayoutManager { Character fobj = (Character)this.fobj; - ipd = MinOptMax.getInstance(font.getCharWidth(fobj.getCharacter())); + ipd = MinOptMax.getInstance(curArea.getIPD()); - curArea.setIPD(ipd.getOpt()); curArea.setBPD(font.getAscender() - font.getDescender()); TraitSetter.addFontTraits(curArea, font); diff --git a/src/java/org/apache/fop/layoutmgr/inline/LineLayoutManager.java b/src/java/org/apache/fop/layoutmgr/inline/LineLayoutManager.java index ea92eb53a..dbb24eb34 100644 --- a/src/java/org/apache/fop/layoutmgr/inline/LineLayoutManager.java +++ b/src/java/org/apache/fop/layoutmgr/inline/LineLayoutManager.java @@ -112,6 +112,7 @@ public class LineLayoutManager extends InlineStackingLayoutManager private final double dAdjust; // Percentage to adjust (stretch or shrink) private final double ipdAdjust; // Percentage to adjust (stretch or shrink) private final int startIndent; + private final int endIndent; private final int lineHeight; private final int lineWidth; private final int spaceBefore; @@ -120,8 +121,8 @@ public class LineLayoutManager extends InlineStackingLayoutManager LineBreakPosition( // CSOK: ParameterNumber LayoutManager lm, int index, int startIndex, int breakIndex, - int shrink, int stretch, int diff, double ipdA, double adjust, int ind, - int lh, int lw, int sb, int sa, int bl) { + int shrink, int stretch, int diff, double ipdA, double adjust, int si, + int ei, int lh, int lw, int sb, int sa, int bl) { super(lm, breakIndex); availableShrink = shrink; availableStretch = stretch; @@ -130,7 +131,8 @@ public class LineLayoutManager extends InlineStackingLayoutManager this.startIndex = startIndex; ipdAdjust = ipdA; dAdjust = adjust; - startIndent = ind; + startIndent = si; + endIndent = ei; lineHeight = lh; lineWidth = lw; spaceBefore = sb; @@ -335,13 +337,38 @@ public class LineLayoutManager extends InlineStackingLayoutManager int total) { // compute indent and adjustment ratio, according to // the value of text-align and text-align-last - int indent = 0; + int startIndent; + int endIndent; int difference = bestActiveNode.difference; int textAlign = (bestActiveNode.line < total) ? alignment : alignmentLast; - indent += (textAlign == Constants.EN_CENTER) + + switch ( textAlign ) { + case Constants.EN_START: + startIndent = 0; + endIndent = difference > 0 ? difference : 0; + break; + case Constants.EN_END: + startIndent = difference > 0 ? difference : 0; + endIndent = 0; + break; + case Constants.EN_CENTER: + startIndent = difference / 2; + endIndent = startIndent; + break; + default: + case Constants.EN_JUSTIFY: + startIndent = 0; + endIndent = 0; + break; + } + + /* + startIndent += (textAlign == Constants.EN_CENTER) ? difference / 2 : (textAlign == Constants.EN_END) ? difference : 0; - indent += (bestActiveNode.line == 1 && indentFirstPart && isFirstInBlock) + */ + startIndent += (bestActiveNode.line == 1 && indentFirstPart && isFirstInBlock) ? textIndent : 0; + double ratio = (textAlign == Constants.EN_JUSTIFY || difference < 0 && -difference <= bestActiveNode.availableShrink) ? bestActiveNode.adjustRatio : 0; @@ -379,7 +406,7 @@ public class LineLayoutManager extends InlineStackingLayoutManager bestActiveNode.availableShrink - (addedPositions > 0 ? 0 : ((Paragraph) par).lineFiller.getShrink()), bestActiveNode.availableStretch, - difference, ratio, indent), activePossibility); + difference, ratio, startIndent, endIndent), activePossibility); addedPositions++; } @@ -391,7 +418,8 @@ public class LineLayoutManager extends InlineStackingLayoutManager private LineBreakPosition makeLineBreakPosition( // CSOK: ParameterNumber KnuthSequence par, int firstElementIndex, int lastElementIndex, int availableShrink, - int availableStretch, int difference, double ratio, int indent) { + int availableStretch, int difference, double ratio, int startIndent, + int endIndent) { // line height calculation - spaceBefore may differ from spaceAfter // by 1mpt due to rounding int spaceBefore = (lineHeight - lead - follow) / 2; @@ -458,14 +486,14 @@ public class LineLayoutManager extends InlineStackingLayoutManager knuthParagraphs.indexOf(par), firstElementIndex, lastElementIndex, availableShrink, availableStretch, - difference, ratio, 0, indent, + difference, ratio, 0, startIndent, endIndent, 0, ipd, 0, 0, 0); } else { return new LineBreakPosition(thisLLM, knuthParagraphs.indexOf(par), firstElementIndex, lastElementIndex, availableShrink, availableStretch, - difference, ratio, 0, indent, + difference, ratio, 0, startIndent, endIndent, lineLead + lineFollow, ipd, spaceBefore, spaceAfter, lineLead); @@ -1432,6 +1460,9 @@ public class LineLayoutManager extends InlineStackingLayoutManager if (lbp.startIndent != 0) { lineArea.addTrait(Trait.START_INDENT, lbp.startIndent); } + if (lbp.endIndent != 0) { + lineArea.addTrait(Trait.END_INDENT, new Integer(lbp.endIndent)); + } lineArea.setBPD(lbp.lineHeight); lineArea.setIPD(lbp.lineWidth); lineArea.setBidiLevel(bidiLevel); diff --git a/src/java/org/apache/fop/layoutmgr/inline/PageNumberCitationLastLayoutManager.java b/src/java/org/apache/fop/layoutmgr/inline/PageNumberCitationLastLayoutManager.java index 56bcdaa3b..b1ba38565 100644 --- a/src/java/org/apache/fop/layoutmgr/inline/PageNumberCitationLastLayoutManager.java +++ b/src/java/org/apache/fop/layoutmgr/inline/PageNumberCitationLastLayoutManager.java @@ -56,22 +56,24 @@ public class PageNumberCitationLastLayoutManager extends AbstractPageNumberCitat */ private InlineArea getPageNumberCitationLastInlineArea(LayoutManager parentLM) { TextArea text = null; - resolved = false; + int level = getBidiLevel(); if (!getPSLM().associateLayoutManagerID(fobj.getRefId())) { text = new UnresolvedPageNumber(fobj.getRefId(), font, UnresolvedPageNumber.LAST); getPSLM().addUnresolvedArea(fobj.getRefId(), (Resolvable)text); String str = "MMM"; // reserve three spaces for page number int width = getStringWidth(str); + text.setBidiLevel(level); text.setIPD(width); + resolved = false; } else { PageViewport page = getPSLM().getLastPVWithID(fobj.getRefId()); String str = page.getPageNumberString(); // get page string from parent, build area text = new TextArea(); int width = getStringWidth(str); - text.addWord(str, 0); + text.setBidiLevel(level); + text.addWord(str, 0, level); text.setIPD(width); - resolved = true; } diff --git a/src/java/org/apache/fop/layoutmgr/inline/PageNumberCitationLayoutManager.java b/src/java/org/apache/fop/layoutmgr/inline/PageNumberCitationLayoutManager.java index 958a854ac..16ce09ec5 100644 --- a/src/java/org/apache/fop/layoutmgr/inline/PageNumberCitationLayoutManager.java +++ b/src/java/org/apache/fop/layoutmgr/inline/PageNumberCitationLayoutManager.java @@ -50,27 +50,32 @@ public class PageNumberCitationLayoutManager extends AbstractPageNumberCitationL /** * if id can be resolved then simply return a word, otherwise * return a resolvable area + * + * TODO: [GA] May need to run bidi algorithm and script processor + * on resolved page number. */ private InlineArea getPageNumberCitationInlineArea() { PageViewport page = getPSLM().getFirstPVWithID(fobj.getRefId()); TextArea text = null; + int level = getBidiLevel(); if (page != null) { String str = page.getPageNumberString(); // get page string from parent, build area text = new TextArea(); - int width = getStringWidth(str); - text.addWord(str, 0); - text.setIPD(width); + int width = getStringWidth(str); // TODO: [GA] !I18N! + text.setBidiLevel(level); + text.addWord(str, 0, level); + text.setIPD(width); // TODO: [GA] !I18N! resolved = true; } else { - resolved = false; text = new UnresolvedPageNumber(fobj.getRefId(), font); String str = "MMM"; // reserve three spaces for page number - int width = getStringWidth(str); - text.setIPD(width); + int width = getStringWidth(str); // TODO: [GA] !I18N! + text.setBidiLevel(level); + text.setIPD(width); // TODO: [GA] !I18N! + resolved = false; } updateTextAreaTraits(text); - return text; } diff --git a/src/java/org/apache/fop/layoutmgr/inline/ScaledBaselineTable.java b/src/java/org/apache/fop/layoutmgr/inline/ScaledBaselineTable.java index 191853037..1ccba23d7 100644 --- a/src/java/org/apache/fop/layoutmgr/inline/ScaledBaselineTable.java +++ b/src/java/org/apache/fop/layoutmgr/inline/ScaledBaselineTable.java @@ -140,7 +140,7 @@ final class ScaledBaselineTable { } private boolean isHorizontalWritingMode() { - return writingMode == WritingMode.LR_TB || writingMode == WritingMode.RL_TB; + return writingMode.isHorizontal(); } /** diff --git a/src/java/org/apache/fop/layoutmgr/inline/TextLayoutManager.java b/src/java/org/apache/fop/layoutmgr/inline/TextLayoutManager.java index d174ff0c1..54fa7d475 100644 --- a/src/java/org/apache/fop/layoutmgr/inline/TextLayoutManager.java +++ b/src/java/org/apache/fop/layoutmgr/inline/TextLayoutManager.java @@ -19,6 +19,7 @@ package org.apache.fop.layoutmgr.inline; +import java.util.Arrays; import java.util.ArrayList; import java.util.LinkedList; import java.util.List; @@ -75,6 +76,7 @@ public class TextLayoutManager extends LeafNodeLayoutManager { private final int startIndex; private final int breakIndex; + private int wordCharLength; private final int wordSpaceCount; private int letterSpaceCount; private MinOptMax areaIPD; @@ -92,6 +94,7 @@ public class TextLayoutManager extends LeafNodeLayoutManager { assert startIndex <= breakIndex; this.startIndex = startIndex; this.breakIndex = breakIndex; + this.wordCharLength = -1; this.wordSpaceCount = wordSpaceCount; this.letterSpaceCount = letterSpaceCount; this.areaIPD = areaIPD; @@ -103,8 +106,22 @@ public class TextLayoutManager extends LeafNodeLayoutManager { this.gposAdjustments = gposAdjustments; } - private int getCharLength() { - return breakIndex - startIndex; + /** + * Obtain number of 'characters' contained in word. If word + * is mapped, then this number may be less than or greater than the + * original length (breakIndex - startIndex). We compute and + * memoize thius length upon first invocation of this method. + */ + private int getWordLength() { + if ( wordCharLength == -1 ) { + if ( foText.hasMapping ( startIndex, breakIndex ) ) { + wordCharLength = foText.getMapping ( startIndex, breakIndex ).length(); + } else { + assert breakIndex >= startIndex; + wordCharLength = breakIndex - startIndex; + } + } + return wordCharLength; } private void addToAreaIPD(MinOptMax idp) { @@ -312,7 +329,7 @@ public class TextLayoutManager extends LeafNodeLayoutManager { // changes. However, it seems as if they should use the AreaInfo from // firstAreaInfoIndex.. lastAreaInfoIndex rather than just the last areaInfo. // This needs to be checked. - int textLength = areaInfo.getCharLength(); + int textLength = areaInfo.getWordLength(); if (areaInfo.letterSpaceCount == textLength && !areaInfo.isHyphenated && context.isLastArea()) { // the line ends at a character like "/" or "-"; @@ -398,26 +415,27 @@ public class TextLayoutManager extends LeafNodeLayoutManager { private final class TextAreaBuilder { - private final MinOptMax width; - private final int adjust; - private final LayoutContext context; - private final int firstIndex; - private final int lastIndex; - private final boolean isLastArea; - private final Font font; - - private int blockProgressionDimension; - private AreaInfo areaInfo; - private StringBuffer wordChars; - private int[] letterSpaceAdjust; - private int letterSpaceAdjustIndex; - private int[] wordLevels; - private int wordLevelsCount; - private int wordIPD; - private int[][] gposAdjustments; - private int gposAdjustmentsIndex; - - private TextArea textArea; + // constructor initialized state + private final MinOptMax width; // content ipd + private final int adjust; // content ipd adjustment + private final LayoutContext context; // layout context + private final int firstIndex; // index of first AreaInfo + private final int lastIndex; // index of last AreaInfo + private final boolean isLastArea; // true if last inline area in line area + private final Font font; // applicable font + + // other, non-constructor state + private TextArea textArea; // text area being constructed + private int blockProgressionDimension; // calculated bpd + private AreaInfo areaInfo; // current area info when iterating over words + private StringBuffer wordChars; // current word's character buffer + private int[] letterSpaceAdjust; // current word's letter space adjustments + private int letterSpaceAdjustIndex; // last written letter space adjustment index + private int[] wordLevels; // current word's bidi levels + private int wordLevelsIndex; // last written bidi level index + private int wordIPD; // accumulated ipd of current word + private int[][] gposAdjustments; // current word's glyph position adjustments + private int gposAdjustmentsIndex; // last written glyph position adjustment index /** * Creates a new TextAreaBuilder which itself builds an inline word area. This @@ -499,7 +517,7 @@ public class TextLayoutManager extends LeafNodeLayoutManager { * Sets the text of the TextArea, split into words and spaces. */ private void setText() { - int wordStartIndex = -1; + int areaInfoIndex = -1; int wordCharLength = 0; for (int wordIndex = firstIndex; wordIndex <= lastIndex; wordIndex++) { areaInfo = getAreaInfo(wordIndex); @@ -507,15 +525,15 @@ public class TextLayoutManager extends LeafNodeLayoutManager { addSpaces(); } else { // areaInfo stores information about a word fragment - if (wordStartIndex == -1) { + if (areaInfoIndex == -1) { // here starts a new word - wordStartIndex = wordIndex; + areaInfoIndex = wordIndex; wordCharLength = 0; } - wordCharLength += areaInfo.getCharLength(); + wordCharLength += areaInfo.getWordLength(); if (isWordEnd(wordIndex)) { - addWord(wordStartIndex, wordIndex, wordCharLength); - wordStartIndex = -1; + addWord(areaInfoIndex, wordIndex, wordCharLength); + areaInfoIndex = -1; } } } @@ -525,13 +543,27 @@ public class TextLayoutManager extends LeafNodeLayoutManager { return areaInfoIndex == lastIndex || getAreaInfo(areaInfoIndex + 1).isSpace; } - private void addWord(int startIndex, int endIndex, int charLength) { + /** + * Add word with fragments from STARTINDEX to ENDINDEX, where + * total length of (possibly mapped) word is CHARLENGTH. + * A word is composed from one or more word fragments, where each + * fragment corresponds to distinct instance in a sequence of + * area info instances starting at STARTINDEX continuing through (and + * including) ENDINDEX. + * @param startIndex index of first area info of word to add + * @param endIndex index of last area info of word to add + * @param wordLength number of (mapped) characters in word + */ + private void addWord(int startIndex, int endIndex, int wordLength) { int blockProgressionOffset = 0; boolean gposAdjusted = false; if (isHyphenated(endIndex)) { - charLength++; + // TODO may be problematic in some I18N contexts [GA] + wordLength++; } - initWord(charLength); + initWord(wordLength); + // iterate over word's fragments, adding word chars (with bidi + // levels), letter space adjustments, and glyph position adjustments for (int i = startIndex; i <= endIndex; i++) { AreaInfo wordAreaInfo = getAreaInfo(i); addWordChars(wordAreaInfo); @@ -541,22 +573,46 @@ public class TextLayoutManager extends LeafNodeLayoutManager { } } if (isHyphenated(endIndex)) { + // TODO may be problematic in some I18N contexts [GA] addHyphenationChar(); } if ( !gposAdjusted ) { gposAdjustments = null; } - textArea.addWord(wordChars.toString(), wordIPD, letterSpaceAdjust, wordLevels, - gposAdjustments, blockProgressionOffset); + textArea.addWord(wordChars.toString(), wordIPD, letterSpaceAdjust, + getNonEmptyLevels(), gposAdjustments, blockProgressionOffset); + } + + private int[] getNonEmptyLevels() { + if ( wordLevels != null ) { + assert wordLevelsIndex <= wordLevels.length; + boolean empty = true; + for ( int i = 0, n = wordLevelsIndex; i < n; i++ ) { + if ( wordLevels [ i ] >= 0 ) { + empty = false; + break; + } + } + return empty ? null : wordLevels; + } else { + return null; + } } - private void initWord(int charLength) { - wordChars = new StringBuffer(charLength); - letterSpaceAdjust = new int[charLength]; + /** + * Fully allocate word character buffer, letter space adjustments + * array, bidi levels array, and glyph position adjustments array. + * based on full word length, including all (possibly mapped) fragments. + * @param wordLength length of word including all (possibly mapped) fragments + */ + private void initWord(int wordLength) { + wordChars = new StringBuffer(wordLength); + letterSpaceAdjust = new int[wordLength]; letterSpaceAdjustIndex = 0; - wordLevels = new int[charLength]; - wordLevelsCount = 0; - gposAdjustments = new int[charLength][4]; + wordLevels = new int[wordLength]; + wordLevelsIndex = 0; + Arrays.fill ( wordLevels, -1 ); + gposAdjustments = new int[wordLength][4]; gposAdjustmentsIndex = 0; wordIPD = 0; } @@ -567,8 +623,17 @@ public class TextLayoutManager extends LeafNodeLayoutManager { private void addHyphenationChar() { 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? } + /** + * Given a word area info associated with a word fragment, + * (1) concatenate (possibly mapped) word characters to word character buffer; + * (2) concatenante (possibly mapped) word bidi levels to levels buffer; + * (3) update word's IPD with optimal IPD of fragment. + * @param wordAreaInfo fragment info + */ private void addWordChars(AreaInfo wordAreaInfo) { int s = wordAreaInfo.startIndex; int e = wordAreaInfo.breakIndex; @@ -584,65 +649,85 @@ public class TextLayoutManager extends LeafNodeLayoutManager { wordIPD += wordAreaInfo.areaIPD.getOpt(); } + /** + * Given a (possibly null) bidi levels array associated with a word fragment, + * concatenante (possibly mapped) word bidi levels to levels buffer. + * @param levels bidi levels array or null + */ private void addWordLevels ( int[] levels ) { - if ( levels != null ) { - int n = levels.length; - int need = wordLevelsCount + n; - if ( need > wordLevels.length ) { - int[] wordLevelsNew = new int [ need * 2 ]; - System.arraycopy ( wordLevels, 0, wordLevelsNew, 0, wordLevelsCount ); - wordLevels = wordLevelsNew; + int numLevels = ( levels != null ) ? levels.length : 0; + if ( numLevels > 0 ) { + int need = wordLevelsIndex + numLevels; + if ( need <= wordLevels.length ) { + System.arraycopy ( levels, 0, wordLevels, wordLevelsIndex, numLevels ); + } else { + throw new IllegalStateException + ( "word levels array too short: expect at least " + + need + " entries, but has only " + wordLevels.length + " entries" ); } - System.arraycopy ( levels, 0, wordLevels, wordLevelsCount, n ); - wordLevelsCount += n; } + wordLevelsIndex += numLevels; } + /** + * Given a word area info associated with a word fragment, + * concatenate letter space adjustments for each (possibly mapped) character. + * @param wordAreaInfo fragment info + */ private void addLetterAdjust(AreaInfo wordAreaInfo) { int letterSpaceCount = wordAreaInfo.letterSpaceCount; - for (int i = wordAreaInfo.startIndex; i < wordAreaInfo.breakIndex; i++) { - if (letterSpaceAdjustIndex > 0) { - MinOptMax adj = letterSpaceAdjustArray[i]; - letterSpaceAdjust[letterSpaceAdjustIndex] = adj == null ? 0 : adj.getOpt(); + int wordLength = wordAreaInfo.getWordLength(); + int taAdjust = textArea.getTextLetterSpaceAdjust(); + for ( int i = 0, n = wordLength; i < n; i++ ) { + int j = letterSpaceAdjustIndex + i; + if ( j > 0 ) { + int k = wordAreaInfo.startIndex + i; + MinOptMax adj = ( k < letterSpaceAdjustArray.length ) + ? letterSpaceAdjustArray [ k ] : null; + letterSpaceAdjust [ j ] = ( adj == null ) ? 0 : adj.getOpt(); } - if (letterSpaceCount > 0) { - letterSpaceAdjust[letterSpaceAdjustIndex] - += textArea.getTextLetterSpaceAdjust(); + if ( letterSpaceCount > 0 ) { + letterSpaceAdjust [ j ] += taAdjust; letterSpaceCount--; } - letterSpaceAdjustIndex++; } + letterSpaceAdjustIndex += wordLength; } + /** + * Given a word area info associated with a word fragment, + * concatenate glyph position adjustments for each (possibly mapped) character. + * @param wordAreaInfo fragment info + * @return true if an adjustment was non-zero + */ private boolean addGlyphPositionAdjustments(AreaInfo wordAreaInfo) { boolean adjusted = false; int[][] gpa = wordAreaInfo.gposAdjustments; - if ( gpa != null ) { - // ensure that gposAdjustments is of sufficient length - int need = gposAdjustmentsIndex + gpa.length; - if ( need > gposAdjustments.length ) { - int[][] gposAdjustmentsNew = new int [ need ][]; - System.arraycopy ( gposAdjustments, 0, - gposAdjustmentsNew, 0, gposAdjustments.length ); - for ( int i = gposAdjustments.length; i < need; i++ ) { - gposAdjustmentsNew [ i ] = new int[4]; - } - gposAdjustments = gposAdjustmentsNew; - } - // add gpos adjustments from word area info, incrementing gposAdjustmentsIndex - for ( int i = gposAdjustmentsIndex, - n = gposAdjustmentsIndex + gpa.length, j = 0; i < n; i++ ) { - int[] wpa1 = gposAdjustments [ i ]; - int[] wpa2 = gpa [ j++ ]; - for ( int k = 0; k < 4; k++ ) { - int a = wpa2 [ k ]; - if ( a != 0 ) { - wpa1 [ k ] += a; adjusted = true; + int numAdjusts = ( gpa != null ) ? gpa.length : 0; + int wordLength = wordAreaInfo.getWordLength(); + if ( numAdjusts > 0 ) { + int need = gposAdjustmentsIndex + numAdjusts; + if ( need <= gposAdjustments.length ) { + for ( int i = 0, n = wordLength, j = 0; i < n; i++ ) { + if ( i < numAdjusts ) { + int[] wpa1 = gposAdjustments [ gposAdjustmentsIndex + i ]; + int[] wpa2 = gpa [ j++ ]; + for ( int k = 0; k < 4; k++ ) { + int a = wpa2 [ k ]; + if ( a != 0 ) { + wpa1 [ k ] += a; adjusted = true; + } + } } } - gposAdjustmentsIndex++; + } else { + throw new IllegalStateException + ( "gpos adjustments array too short: expect at least " + + need + " entries, but has only " + gposAdjustments.length + + " entries" ); } } + gposAdjustmentsIndex += wordLength; return adjusted; } @@ -794,7 +879,8 @@ public class TextLayoutManager extends LeafNodeLayoutManager { } } else if (inWhitespace) { if (ch != CharUtilities.SPACE || breakOpportunity) { - prevAreaInfo = processWhitespace(alignment, sequence, breakOpportunity); + prevAreaInfo = processWhitespace(alignment, sequence, + breakOpportunity, prevLevel); } } else { if (areaInfo != null) { @@ -839,7 +925,7 @@ public class TextLayoutManager extends LeafNodeLayoutManager { if (inWord) { processWord(alignment, sequence, prevAreaInfo, ch, false, false, prevLevel); } else if (inWhitespace) { - processWhitespace(alignment, sequence, !keepTogether); + processWhitespace(alignment, sequence, !keepTogether, prevLevel); } else if (areaInfo != null) { processLeftoverAreaInfo(alignment, sequence, areaInfo, ch == CharUtilities.ZERO_WIDTH_SPACE); @@ -881,7 +967,7 @@ public class TextLayoutManager extends LeafNodeLayoutManager { } private AreaInfo processWhitespace(final int alignment, - final KnuthSequence sequence, final boolean breakOpportunity) { + final KnuthSequence sequence, final boolean breakOpportunity, int level) { if (LOG.isDebugEnabled()) { LOG.debug ( "PS: [" + thisStart + "," + nextStart + "]" ); @@ -893,7 +979,7 @@ public class TextLayoutManager extends LeafNodeLayoutManager { AreaInfo areaInfo = new AreaInfo ( thisStart, nextStart, nextStart - thisStart, 0, wordSpaceIPD.mult(nextStart - thisStart), - false, true, breakOpportunity, spaceFont, -1, null ); + false, true, breakOpportunity, spaceFont, level, null ); addAreaInfo(areaInfo); @@ -920,10 +1006,10 @@ public class TextLayoutManager extends LeafNodeLayoutManager { + " }" ); } - // extract unmapped character sequence + // 1. extract unmapped character sequence CharSequence ics = foText.subSequence ( s, e ); - // if script is not specified (by FO property) or it is specified as 'auto', + // 2. if script is not specified (by FO property) or it is specified as 'auto', // then compute dominant script if ( ( script == null ) || "auto".equals(script) ) { script = CharUtilities.scriptTagFromCode ( CharUtilities.dominantScript ( ics ) ); @@ -932,13 +1018,10 @@ public class TextLayoutManager extends LeafNodeLayoutManager { language = "dflt"; } - // perform mapping (of chars to glyphs ... to glyphs ... to chars) + // 3. perform mapping of chars to glyphs ... to glyphs ... to chars CharSequence mcs = font.performSubstitution ( ics, script, language ); - // memoize mapping - foText.addMapping ( s, e, mcs ); - - // compute glyph position adjustment on (substituted) characters + // 4. compute glyph position adjustments on (substituted) characters int[][] gpa; if ( font.performsPositioning() ) { gpa = font.performPositioning ( mcs, script, language ); @@ -946,6 +1029,16 @@ public class TextLayoutManager extends LeafNodeLayoutManager { gpa = null; } + // 5. reorder combining marks so that they precede (within the mapped char sequence) the + // base to which they are applied; N.B. position adjustments (gpa) are reordered in place + mcs = font.reorderCombiningMarks ( mcs, gpa, script, language ); + + // 6. if mapped sequence differs from input sequence, then memoize mapped sequence + if ( !CharUtilities.isSameSequence ( mcs, ics ) ) { + foText.addMapping ( s, e, mcs ); + } + + // 7. compute word ipd based on final position adjustments MinOptMax ipd = MinOptMax.ZERO; for ( int i = 0, n = mcs.length(); i < n; i++ ) { char c = mcs.charAt ( i ); @@ -1054,7 +1147,7 @@ public class TextLayoutManager extends LeafNodeLayoutManager { Font font = FontSelector.selectFontForCharactersInText ( foText, thisStart, lastIndex, foText, this ); AreaInfo areaInfo; - if ( font.performsSubstitution() ) { + if ( font.performsSubstitution() || font.performsPositioning() ) { areaInfo = processWordMapping ( lastIndex, font, prevAreaInfo, breakOpportunity ? ch : 0, endsWithHyphen, level ); } else { @@ -1162,7 +1255,7 @@ public class TextLayoutManager extends LeafNodeLayoutManager { // add letter spaces boolean isWordEnd = (stopIndex == areaInfo.breakIndex) - && (areaInfo.letterSpaceCount < areaInfo.getCharLength()); + && (areaInfo.letterSpaceCount < areaInfo.getWordLength()); int letterSpaceCount = isWordEnd ? stopIndex - startIndex - 1 : stopIndex - startIndex; assert letterSpaceCount >= 0; @@ -1283,7 +1376,7 @@ public class TextLayoutManager extends LeafNodeLayoutManager { int leafValue = ((LeafPosition) pos).getLeafPos() + changeOffset; if (leafValue != -1) { AreaInfo areaInfo = getAreaInfo(leafValue); - StringBuffer buffer = new StringBuffer(areaInfo.getCharLength()); + StringBuffer buffer = new StringBuffer(areaInfo.getWordLength()); for (int i = areaInfo.startIndex; i < areaInfo.breakIndex; i++) { buffer.append(foText.charAt(i)); } diff --git a/src/java/org/apache/fop/layoutmgr/list/ListBlockLayoutManager.java b/src/java/org/apache/fop/layoutmgr/list/ListBlockLayoutManager.java index 5772c33ff..5bd451ffb 100644 --- a/src/java/org/apache/fop/layoutmgr/list/ListBlockLayoutManager.java +++ b/src/java/org/apache/fop/layoutmgr/list/ListBlockLayoutManager.java @@ -120,8 +120,8 @@ public class ListBlockLayoutManager extends BlockStackingLayoutManager } /** - * The table area is a reference area that contains areas for - * columns, bodies, rows and the contents are in cells. + * A list block generates one or more normal block areas whose child areas are + * normal block areas returned by the children of fo:list-block. See XSL-FO 1.1 6.8.2. * * @param parentIter the position iterator * @param layoutContext the layout context for adding areas diff --git a/src/java/org/apache/fop/layoutmgr/list/ListItemContentLayoutManager.java b/src/java/org/apache/fop/layoutmgr/list/ListItemContentLayoutManager.java index 8ef0f62cf..72e4e227a 100644 --- a/src/java/org/apache/fop/layoutmgr/list/ListItemContentLayoutManager.java +++ b/src/java/org/apache/fop/layoutmgr/list/ListItemContentLayoutManager.java @@ -45,7 +45,7 @@ public class ListItemContentLayoutManager extends BlockStackingLayoutManager { private Block curBlockArea; - private int xoffset; + private int xOffset; private int itemIPD; /** @@ -80,7 +80,7 @@ public class ListItemContentLayoutManager extends BlockStackingLayoutManager { * @param off the x offset */ public void setXOffset(int off) { - xoffset = off; + xOffset = off; } /** @@ -175,7 +175,7 @@ public class ListItemContentLayoutManager extends BlockStackingLayoutManager { curBlockArea = new Block(); curBlockArea.setPositioning(Block.ABSOLUTE); // set position - curBlockArea.setXOffset(xoffset); + curBlockArea.setXOffset(xOffset); //TODO: Check - itemIPD never set? curBlockArea.setIPD(itemIPD); //curBlockArea.setHeight(); diff --git a/src/java/org/apache/fop/layoutmgr/list/ListItemLayoutManager.java b/src/java/org/apache/fop/layoutmgr/list/ListItemLayoutManager.java index 8b9282aea..bc7daa24f 100644 --- a/src/java/org/apache/fop/layoutmgr/list/ListItemLayoutManager.java +++ b/src/java/org/apache/fop/layoutmgr/list/ListItemLayoutManager.java @@ -465,7 +465,6 @@ public class ListItemLayoutManager extends BlockStackingLayoutManager /** * Add the areas for the break points. - * This sets the offset of each cell as it is added. * * @param parentIter the position iterator * @param layoutContext the layout context for adding areas @@ -507,12 +506,12 @@ public class ListItemLayoutManager extends BlockStackingLayoutManager int bodyFirstIndex = ((ListItemPosition) positionList.getFirst()).getBodyFirstIndex(); int bodyLastIndex = ((ListItemPosition) positionList.getLast()).getBodyLastIndex(); - //Determine previous break if any + //Determine previous break if any (in item label list) int previousBreak = ElementListUtils.determinePreviousBreak(labelList, labelFirstIndex); SpaceResolver.performConditionalsNotification(labelList, labelFirstIndex, labelLastIndex, previousBreak); - //Determine previous break if any + //Determine previous break if any (in item body list) previousBreak = ElementListUtils.determinePreviousBreak(bodyList, bodyFirstIndex); SpaceResolver.performConditionalsNotification(bodyList, bodyFirstIndex, bodyLastIndex, previousBreak); diff --git a/src/java/org/apache/fop/layoutmgr/table/ColumnSetup.java b/src/java/org/apache/fop/layoutmgr/table/ColumnSetup.java index c1fc19050..aaccbd0d3 100644 --- a/src/java/org/apache/fop/layoutmgr/table/ColumnSetup.java +++ b/src/java/org/apache/fop/layoutmgr/table/ColumnSetup.java @@ -33,6 +33,9 @@ import org.apache.fop.fo.expr.RelativeNumericProperty; import org.apache.fop.fo.flow.table.Table; import org.apache.fop.fo.flow.table.TableColumn; import org.apache.fop.fo.properties.TableColLength; +import org.apache.fop.traits.Direction; +import org.apache.fop.traits.WritingModeTraits; +import org.apache.fop.traits.WritingModeTraitsGetter; /** * Class holding a number of columns making up the column setup of a row. @@ -43,6 +46,7 @@ public class ColumnSetup { private static Log log = LogFactory.getLog(ColumnSetup.class); private Table table; + private WritingModeTraitsGetter wmTraits; private List columns = new java.util.ArrayList(); private List colWidths = new java.util.ArrayList(); @@ -53,7 +57,9 @@ public class ColumnSetup { * @param table the table to construct this column setup for */ public ColumnSetup(Table table) { + assert table != null; this.table = table; + this.wmTraits = WritingModeTraits.getWritingModeTraitsGetter ( table ); prepareColumns(); initializeColumnWidths(); } @@ -232,11 +238,47 @@ public class ColumnSetup { } /** + * Determine the X offset of the indicated column, where this + * offset denotes the left edge of the column irrespective of writing + * mode. If writing mode's column progression direction is right-to-left, + * then the first column is the right-most column and the last column is + * the left-most column; otherwise, the first column is the left-most + * column. * @param col column index (1 is first column) * @param context the context for percentage based calculations * @return the X offset of the requested column */ public int getXOffset(int col, PercentBaseContext context) { + // TODO handle vertical WMs [GA] + if ( (wmTraits != null) && (wmTraits.getColumnProgressionDirection() == Direction.RL) ) { + return getXOffsetRTL(col, context); + } else { + return getXOffsetLTR(col, context); + } + } + + /* + * Determine x offset by summing widths of columns to left of specified + * column; i.e., those columns whose column numbers are greater than the + * specified column number. + */ + private int getXOffsetRTL(int col, PercentBaseContext context) { + int xoffset = 0; + for (int i = col, nc = colWidths.size(); ++i < nc;) { + int effCol = i; + if (colWidths.get(effCol) != null) { + xoffset += ((Length) colWidths.get(effCol)).getValue(context); + } + } + return xoffset; + } + + /* + * Determine x offset by summing widths of columns to left of specified + * column; i.e., those columns whose column numbers are less than the + * specified column number. + */ + private int getXOffsetLTR(int col, PercentBaseContext context) { int xoffset = 0; for (int i = col; --i >= 0;) { int effCol; diff --git a/src/java/org/apache/fop/render/AbstractPathOrientedRenderer.java b/src/java/org/apache/fop/render/AbstractPathOrientedRenderer.java index 0256c05c4..e8dd9b5bc 100644 --- a/src/java/org/apache/fop/render/AbstractPathOrientedRenderer.java +++ b/src/java/org/apache/fop/render/AbstractPathOrientedRenderer.java @@ -65,29 +65,31 @@ public abstract class AbstractPathOrientedRenderer extends PrintRenderer { * @param block the block to render the traits */ protected void handleBlockTraits(Block block) { - int borderPaddingStart = block.getBorderAndPaddingWidthStart(); - int borderPaddingBefore = block.getBorderAndPaddingWidthBefore(); + float borderPaddingStart = block.getBorderAndPaddingWidthStart() / 1000f; + float borderPaddingEnd = block.getBorderAndPaddingWidthEnd() / 1000f; + float borderPaddingBefore = block.getBorderAndPaddingWidthBefore() / 1000f; + float borderPaddingAfter = block.getBorderAndPaddingWidthAfter() / 1000f; float startx = currentIPPosition / 1000f; float starty = currentBPPosition / 1000f; float width = block.getIPD() / 1000f; float height = block.getBPD() / 1000f; - /* using start-indent now - Integer spaceStart = (Integer) block.getTrait(Trait.SPACE_START); - if (spaceStart != null) { - startx += spaceStart.floatValue() / 1000f; - }*/ - startx += block.getStartIndent() / 1000f; - startx -= block.getBorderAndPaddingWidthStart() / 1000f; - - width += borderPaddingStart / 1000f; - width += block.getBorderAndPaddingWidthEnd() / 1000f; - height += borderPaddingBefore / 1000f; - height += block.getBorderAndPaddingWidthAfter() / 1000f; - - drawBackAndBorders(block, startx, starty, - width, height); + int level = block.getBidiLevel(); + if ( ( level == -1 ) || ( ( level & 1 ) == 0 ) ) { + startx += block.getStartIndent() / 1000f; + startx -= borderPaddingStart; + } else { + startx += block.getEndIndent() / 1000f; + startx -= borderPaddingEnd; + } + + width += borderPaddingStart; + width += borderPaddingEnd; + height += borderPaddingBefore; + height += borderPaddingAfter; + + drawBackAndBorders(block, startx, starty, width, height); } /** @@ -106,7 +108,12 @@ public abstract class AbstractPathOrientedRenderer extends PrintRenderer { // adjust the current position according to region borders and padding currentBPPosition = referenceArea.getBorderAndPaddingWidthBefore(); - currentIPPosition = referenceArea.getBorderAndPaddingWidthStart(); + int level = region.getBidiLevel(); + if ( ( level == -1 ) || ( ( level & 1 ) == 0 ) ) { + currentIPPosition = referenceArea.getBorderAndPaddingWidthStart(); + } else { + currentIPPosition = referenceArea.getBorderAndPaddingWidthEnd(); + } // draw background (traits are in the RegionViewport) // and borders (traits are in the RegionReference) drawBackAndBorders(region, referenceArea, startx, starty, width, height); @@ -153,9 +160,9 @@ public abstract class AbstractPathOrientedRenderer extends PrintRenderer { drawBackground(startx, starty, width, height, (Trait.Background) backgroundArea.getTrait(Trait.BACKGROUND), - bpsBefore, bpsAfter, bpsStart, bpsEnd); + bpsBefore, bpsAfter, bpsStart, bpsEnd, backgroundArea.getBidiLevel()); drawBorders(startx, starty, width, height, - bpsBefore, bpsAfter, bpsStart, bpsEnd); + bpsBefore, bpsAfter, bpsStart, bpsEnd, borderArea.getBidiLevel()); } /** @@ -171,11 +178,44 @@ public abstract class AbstractPathOrientedRenderer extends PrintRenderer { * @param bpsAfter the border-after traits * @param bpsStart the border-start traits * @param bpsEnd the border-end traits + * @param level of bidirectional embedding */ protected void drawBackground( // CSOK: ParameterNumber float startx, float starty, float width, float height, Trait.Background back, BorderProps bpsBefore, BorderProps bpsAfter, - BorderProps bpsStart, BorderProps bpsEnd) { + BorderProps bpsStart, BorderProps bpsEnd, int level) { + BorderProps bpsTop = bpsBefore; + BorderProps bpsBottom = bpsAfter; + BorderProps bpsLeft; + BorderProps bpsRight; + if ( ( level == -1 ) || ( ( level & 1 ) == 0 ) ) { + bpsLeft = bpsStart; + bpsRight = bpsEnd; + } else { + bpsLeft = bpsEnd; + bpsRight = bpsStart; + } + drawBackground(startx, starty, width, height, back, bpsTop, bpsBottom, bpsLeft, bpsRight); + } + + /** + * Draw the background. + * This draws the background given the position and the traits. + * + * @param startx the start x position + * @param starty the start y position + * @param width the width of the area + * @param height the height of the area + * @param back the background traits + * @param bpsTop the border specification on the top edge + * @param bpsBottom the border traits associated with bottom edge + * @param bpsLeft the border specification on the left edge + * @param bpsRight the border specification on the right edge + */ + protected void drawBackground( // CSOK: ParameterNumber + float startx, float starty, float width, float height, Trait.Background back, + BorderProps bpsTop, BorderProps bpsBottom, + BorderProps bpsLeft, BorderProps bpsRight) { if (back != null) { endTextObject(); @@ -184,19 +224,19 @@ public abstract class AbstractPathOrientedRenderer extends PrintRenderer { float sy = starty; float paddRectWidth = width; float paddRectHeight = height; - if (bpsStart != null) { - sx += bpsStart.width / 1000f; - paddRectWidth -= bpsStart.width / 1000f; + if (bpsLeft != null) { + sx += bpsLeft.width / 1000f; + paddRectWidth -= bpsLeft.width / 1000f; } - if (bpsBefore != null) { - sy += bpsBefore.width / 1000f; - paddRectHeight -= bpsBefore.width / 1000f; + if (bpsTop != null) { + sy += bpsTop.width / 1000f; + paddRectHeight -= bpsTop.width / 1000f; } - if (bpsEnd != null) { - paddRectWidth -= bpsEnd.width / 1000f; + if (bpsRight != null) { + paddRectWidth -= bpsRight.width / 1000f; } - if (bpsAfter != null) { - paddRectHeight -= bpsAfter.width / 1000f; + if (bpsBottom != null) { + paddRectHeight -= bpsBottom.width / 1000f; } if (back.getColor() != null) { @@ -256,86 +296,99 @@ public abstract class AbstractPathOrientedRenderer extends PrintRenderer { * @param starty the start y position * @param width the width of the area * @param height the height of the area - * @param bpsBefore the border-before traits - * @param bpsAfter the border-after traits - * @param bpsStart the border-start traits - * @param bpsEnd the border-end traits + * @param bpsBefore the border traits associated with before edge + * @param bpsAfter the border traits associated with after edge + * @param bpsStart the border traits associated with start edge + * @param bpsEnd the border traits associated with end edge + * @param level of bidirectional embedding */ protected void drawBorders( // CSOK: ParameterNumber float startx, float starty, float width, float height, BorderProps bpsBefore, BorderProps bpsAfter, - BorderProps bpsStart, BorderProps bpsEnd) { + BorderProps bpsStart, BorderProps bpsEnd, int level) { Rectangle2D.Float borderRect = new Rectangle2D.Float(startx, starty, width, height); - drawBorders(borderRect, bpsBefore, bpsAfter, bpsStart, bpsEnd); + BorderProps bpsTop = bpsBefore; + BorderProps bpsBottom = bpsAfter; + BorderProps bpsLeft; + BorderProps bpsRight; + if ( ( level == -1 ) || ( ( level & 1 ) == 0 ) ) { + bpsLeft = bpsStart; + bpsRight = bpsEnd; + } else { + bpsLeft = bpsEnd; + bpsRight = bpsStart; + } + drawBorders(borderRect, bpsTop, bpsBottom, bpsLeft, bpsRight); } - private static final int BEFORE = 0; - private static final int END = 1; - private static final int AFTER = 2; - private static final int START = 3; + private static final int TOP = 0; + private static final int RIGHT = 1; + private static final int BOTTOM = 2; + private static final int LEFT = 3; /** * Draws borders. * @param borderRect the border rectangle - * @param bpsBefore the border specification on the before side - * @param bpsAfter the border specification on the after side - * @param bpsStart the border specification on the start side - * @param bpsEnd the border specification on the end side + * @param bpsTop the border specification on the top edge + * @param bpsBottom the border traits associated with bottom edge + * @param bpsLeft the border specification on the left edge + * @param bpsRight the border specification on the right edge */ protected void drawBorders( // CSOK: MethodLength Rectangle2D.Float borderRect, - BorderProps bpsBefore, BorderProps bpsAfter, BorderProps bpsStart, BorderProps bpsEnd) { + BorderProps bpsTop, BorderProps bpsBottom, BorderProps bpsLeft, BorderProps bpsRight) { //TODO generalize each of the four conditions into using a parameterized drawBorder() boolean[] border = new boolean[] { - (bpsBefore != null), (bpsEnd != null), - (bpsAfter != null), (bpsStart != null)}; + (bpsTop != null), (bpsRight != null), + (bpsBottom != null), (bpsLeft != null)}; float startx = borderRect.x; float starty = borderRect.y; float width = borderRect.width; float height = borderRect.height; float[] borderWidth = new float[] { - (border[BEFORE] ? bpsBefore.width / 1000f : 0.0f), - (border[END] ? bpsEnd.width / 1000f : 0.0f), - (border[AFTER] ? bpsAfter.width / 1000f : 0.0f), - (border[START] ? bpsStart.width / 1000f : 0.0f)}; + (border[TOP] ? bpsTop.width / 1000f : 0.0f), + (border[RIGHT] ? bpsRight.width / 1000f : 0.0f), + (border[BOTTOM] ? bpsBottom.width / 1000f : 0.0f), + (border[LEFT] ? bpsLeft.width / 1000f : 0.0f)}; float[] clipw = new float[] { - BorderProps.getClippedWidth(bpsBefore) / 1000f, - BorderProps.getClippedWidth(bpsEnd) / 1000f, - BorderProps.getClippedWidth(bpsAfter) / 1000f, - BorderProps.getClippedWidth(bpsStart) / 1000f}; - starty += clipw[BEFORE]; - height -= clipw[BEFORE]; - height -= clipw[AFTER]; - startx += clipw[START]; - width -= clipw[START]; - width -= clipw[END]; + BorderProps.getClippedWidth(bpsTop) / 1000f, + BorderProps.getClippedWidth(bpsRight) / 1000f, + BorderProps.getClippedWidth(bpsBottom) / 1000f, + BorderProps.getClippedWidth(bpsLeft) / 1000f}; + + starty += clipw[TOP]; + height -= clipw[TOP]; + height -= clipw[BOTTOM]; + startx += clipw[LEFT]; + width -= clipw[LEFT]; + width -= clipw[RIGHT]; boolean[] slant = new boolean[] { - (border[START] && border[BEFORE]), - (border[BEFORE] && border[END]), - (border[END] && border[AFTER]), - (border[AFTER] && border[START])}; - if (bpsBefore != null) { + (border[LEFT] && border[TOP]), + (border[TOP] && border[RIGHT]), + (border[RIGHT] && border[BOTTOM]), + (border[BOTTOM] && border[LEFT])}; + if (bpsTop != null) { endTextObject(); float sx1 = startx; - float sx2 = (slant[BEFORE] ? sx1 + borderWidth[START] - clipw[START] : sx1); + float sx2 = (slant[TOP] ? sx1 + borderWidth[LEFT] - clipw[LEFT] : sx1); float ex1 = startx + width; - float ex2 = (slant[END] ? ex1 - borderWidth[END] + clipw[END] : ex1); - float outery = starty - clipw[BEFORE]; - float clipy = outery + clipw[BEFORE]; - float innery = outery + borderWidth[BEFORE]; + float ex2 = (slant[RIGHT] ? ex1 - borderWidth[RIGHT] + clipw[RIGHT] : ex1); + float outery = starty - clipw[TOP]; + float clipy = outery + clipw[TOP]; + float innery = outery + borderWidth[TOP]; saveGraphicsState(); moveTo(sx1, clipy); float sx1a = sx1; float ex1a = ex1; - if (bpsBefore.mode == BorderProps.COLLAPSE_OUTER) { - if (bpsStart != null && bpsStart.mode == BorderProps.COLLAPSE_OUTER) { - sx1a -= clipw[START]; + if (bpsTop.mode == BorderProps.COLLAPSE_OUTER) { + if (bpsLeft != null && bpsLeft.mode == BorderProps.COLLAPSE_OUTER) { + sx1a -= clipw[LEFT]; } - if (bpsEnd != null && bpsEnd.mode == BorderProps.COLLAPSE_OUTER) { - ex1a += clipw[END]; + if (bpsRight != null && bpsRight.mode == BorderProps.COLLAPSE_OUTER) { + ex1a += clipw[RIGHT]; } lineTo(sx1a, outery); lineTo(ex1a, outery); @@ -346,30 +399,30 @@ public abstract class AbstractPathOrientedRenderer extends PrintRenderer { closePath(); clip(); drawBorderLine(sx1a, outery, ex1a, innery, true, true, - bpsBefore.style, bpsBefore.color); + bpsTop.style, bpsTop.color); restoreGraphicsState(); } - if (bpsEnd != null) { + if (bpsRight != null) { endTextObject(); float sy1 = starty; - float sy2 = (slant[END] ? sy1 + borderWidth[BEFORE] - clipw[BEFORE] : sy1); + float sy2 = (slant[RIGHT] ? sy1 + borderWidth[TOP] - clipw[TOP] : sy1); float ey1 = starty + height; - float ey2 = (slant[AFTER] ? ey1 - borderWidth[AFTER] + clipw[AFTER] : ey1); - float outerx = startx + width + clipw[END]; - float clipx = outerx - clipw[END]; - float innerx = outerx - borderWidth[END]; + float ey2 = (slant[BOTTOM] ? ey1 - borderWidth[BOTTOM] + clipw[BOTTOM] : ey1); + float outerx = startx + width + clipw[RIGHT]; + float clipx = outerx - clipw[RIGHT]; + float innerx = outerx - borderWidth[RIGHT]; saveGraphicsState(); moveTo(clipx, sy1); float sy1a = sy1; float ey1a = ey1; - if (bpsEnd.mode == BorderProps.COLLAPSE_OUTER) { - if (bpsBefore != null && bpsBefore.mode == BorderProps.COLLAPSE_OUTER) { - sy1a -= clipw[BEFORE]; + if (bpsRight.mode == BorderProps.COLLAPSE_OUTER) { + if (bpsTop != null && bpsTop.mode == BorderProps.COLLAPSE_OUTER) { + sy1a -= clipw[TOP]; } - if (bpsAfter != null && bpsAfter.mode == BorderProps.COLLAPSE_OUTER) { - ey1a += clipw[AFTER]; + if (bpsBottom != null && bpsBottom.mode == BorderProps.COLLAPSE_OUTER) { + ey1a += clipw[BOTTOM]; } lineTo(outerx, sy1a); lineTo(outerx, ey1a); @@ -379,30 +432,31 @@ public abstract class AbstractPathOrientedRenderer extends PrintRenderer { lineTo(innerx, sy2); closePath(); clip(); - drawBorderLine(innerx, sy1a, outerx, ey1a, false, false, bpsEnd.style, bpsEnd.color); + drawBorderLine(innerx, sy1a, outerx, ey1a, false, false, + bpsRight.style, bpsRight.color); restoreGraphicsState(); } - if (bpsAfter != null) { + if (bpsBottom != null) { endTextObject(); float sx1 = startx; - float sx2 = (slant[START] ? sx1 + borderWidth[START] - clipw[START] : sx1); + float sx2 = (slant[LEFT] ? sx1 + borderWidth[LEFT] - clipw[LEFT] : sx1); float ex1 = startx + width; - float ex2 = (slant[AFTER] ? ex1 - borderWidth[END] + clipw[END] : ex1); - float outery = starty + height + clipw[AFTER]; - float clipy = outery - clipw[AFTER]; - float innery = outery - borderWidth[AFTER]; + float ex2 = (slant[BOTTOM] ? ex1 - borderWidth[RIGHT] + clipw[RIGHT] : ex1); + float outery = starty + height + clipw[BOTTOM]; + float clipy = outery - clipw[BOTTOM]; + float innery = outery - borderWidth[BOTTOM]; saveGraphicsState(); moveTo(ex1, clipy); float sx1a = sx1; float ex1a = ex1; - if (bpsAfter.mode == BorderProps.COLLAPSE_OUTER) { - if (bpsStart != null && bpsStart.mode == BorderProps.COLLAPSE_OUTER) { - sx1a -= clipw[START]; + if (bpsBottom.mode == BorderProps.COLLAPSE_OUTER) { + if (bpsLeft != null && bpsLeft.mode == BorderProps.COLLAPSE_OUTER) { + sx1a -= clipw[LEFT]; } - if (bpsEnd != null && bpsEnd.mode == BorderProps.COLLAPSE_OUTER) { - ex1a += clipw[END]; + if (bpsRight != null && bpsRight.mode == BorderProps.COLLAPSE_OUTER) { + ex1a += clipw[RIGHT]; } lineTo(ex1a, outery); lineTo(sx1a, outery); @@ -412,30 +466,31 @@ public abstract class AbstractPathOrientedRenderer extends PrintRenderer { lineTo(ex2, innery); closePath(); clip(); - drawBorderLine(sx1a, innery, ex1a, outery, true, false, bpsAfter.style, bpsAfter.color); + drawBorderLine(sx1a, innery, ex1a, outery, true, false, + bpsBottom.style, bpsBottom.color); restoreGraphicsState(); } - if (bpsStart != null) { + if (bpsLeft != null) { endTextObject(); float sy1 = starty; - float sy2 = (slant[BEFORE] ? sy1 + borderWidth[BEFORE] - clipw[BEFORE] : sy1); + float sy2 = (slant[TOP] ? sy1 + borderWidth[TOP] - clipw[TOP] : sy1); float ey1 = sy1 + height; - float ey2 = (slant[START] ? ey1 - borderWidth[AFTER] + clipw[AFTER] : ey1); - float outerx = startx - clipw[START]; - float clipx = outerx + clipw[START]; - float innerx = outerx + borderWidth[START]; + float ey2 = (slant[LEFT] ? ey1 - borderWidth[BOTTOM] + clipw[BOTTOM] : ey1); + float outerx = startx - clipw[LEFT]; + float clipx = outerx + clipw[LEFT]; + float innerx = outerx + borderWidth[LEFT]; saveGraphicsState(); moveTo(clipx, ey1); float sy1a = sy1; float ey1a = ey1; - if (bpsStart.mode == BorderProps.COLLAPSE_OUTER) { - if (bpsBefore != null && bpsBefore.mode == BorderProps.COLLAPSE_OUTER) { - sy1a -= clipw[BEFORE]; + if (bpsLeft.mode == BorderProps.COLLAPSE_OUTER) { + if (bpsTop != null && bpsTop.mode == BorderProps.COLLAPSE_OUTER) { + sy1a -= clipw[TOP]; } - if (bpsAfter != null && bpsAfter.mode == BorderProps.COLLAPSE_OUTER) { - ey1a += clipw[AFTER]; + if (bpsBottom != null && bpsBottom.mode == BorderProps.COLLAPSE_OUTER) { + ey1a += clipw[BOTTOM]; } lineTo(outerx, ey1a); lineTo(outerx, sy1a); @@ -445,7 +500,7 @@ public abstract class AbstractPathOrientedRenderer extends PrintRenderer { lineTo(innerx, ey2); closePath(); clip(); - drawBorderLine(outerx, sy1a, innerx, ey1a, false, true, bpsStart.style, bpsStart.color); + drawBorderLine(outerx, sy1a, innerx, ey1a, false, true, bpsLeft.style, bpsLeft.color); restoreGraphicsState(); } } @@ -458,11 +513,11 @@ public abstract class AbstractPathOrientedRenderer extends PrintRenderer { */ protected void renderInlineAreaBackAndBorders(InlineArea area) { float borderPaddingStart = area.getBorderAndPaddingWidthStart() / 1000f; + float borderPaddingEnd = area.getBorderAndPaddingWidthEnd() / 1000f; float borderPaddingBefore = area.getBorderAndPaddingWidthBefore() / 1000f; - float bpwidth = borderPaddingStart - + (area.getBorderAndPaddingWidthEnd() / 1000f); - float bpheight = borderPaddingBefore - + (area.getBorderAndPaddingWidthAfter() / 1000f); + float borderPaddingAfter = area.getBorderAndPaddingWidthAfter() / 1000f; + float bpwidth = borderPaddingStart + borderPaddingEnd; + float bpheight = borderPaddingBefore + borderPaddingAfter; float height = area.getBPD() / 1000f; if (height != 0.0f || bpheight != 0.0f && bpwidth != 0.0f) { @@ -504,10 +559,16 @@ public abstract class AbstractPathOrientedRenderer extends PrintRenderer { AffineTransform positionTransform = new AffineTransform(); positionTransform.translate(bv.getXOffset(), bv.getYOffset()); + int level = bv.getBidiLevel(); int borderPaddingStart = bv.getBorderAndPaddingWidthStart(); + int borderPaddingEnd = bv.getBorderAndPaddingWidthEnd(); //"left/"top" (bv.getX/YOffset()) specify the position of the content rectangle - positionTransform.translate(-borderPaddingStart, -borderPaddingBefore); + if ( ( level == -1 ) || ( ( level & 1 ) == 0 ) ) { + positionTransform.translate(-borderPaddingStart, -borderPaddingBefore); + } else { + positionTransform.translate(-borderPaddingEnd, -borderPaddingBefore); + } //Free transformation for the block-container viewport String transf; @@ -528,14 +589,18 @@ public abstract class AbstractPathOrientedRenderer extends PrintRenderer { //Background and borders float borderPaddingWidth - = (borderPaddingStart + bv.getBorderAndPaddingWidthEnd()) / 1000f; + = (borderPaddingStart + borderPaddingEnd) / 1000f; float borderPaddingHeight = (borderPaddingBefore + bv.getBorderAndPaddingWidthAfter()) / 1000f; drawBackAndBorders(bv, 0, 0, width + borderPaddingWidth, height + borderPaddingHeight); //Shift to content rectangle after border painting AffineTransform contentRectTransform = new AffineTransform(); - contentRectTransform.translate(borderPaddingStart, borderPaddingBefore); + if ( ( level == -1 ) || ( ( level & 1 ) == 0 ) ) { + contentRectTransform.translate(borderPaddingStart, borderPaddingBefore); + } else { + contentRectTransform.translate(borderPaddingEnd, borderPaddingBefore); + } if (!contentRectTransform.isIdentity()) { establishTransformationMatrix(contentRectTransform); @@ -686,25 +751,28 @@ public abstract class AbstractPathOrientedRenderer extends PrintRenderer { * @param viewport the viewport to handle */ public void renderInlineViewport(InlineViewport viewport) { - + int level = viewport.getBidiLevel(); float x = currentIPPosition / 1000f; float y = (currentBPPosition + viewport.getBlockProgressionOffset()) / 1000f; float width = viewport.getIPD() / 1000f; float height = viewport.getBPD() / 1000f; // TODO: Calculate the border rect correctly. float borderPaddingStart = viewport.getBorderAndPaddingWidthStart() / 1000f; + float borderPaddingEnd = viewport.getBorderAndPaddingWidthEnd() / 1000f; float borderPaddingBefore = viewport.getBorderAndPaddingWidthBefore() / 1000f; - float bpwidth = borderPaddingStart - + (viewport.getBorderAndPaddingWidthEnd() / 1000f); - float bpheight = borderPaddingBefore - + (viewport.getBorderAndPaddingWidthAfter() / 1000f); + float borderPaddingAfter = viewport.getBorderAndPaddingWidthAfter() / 1000f; + float bpwidth = borderPaddingStart + borderPaddingEnd; + float bpheight = borderPaddingBefore + borderPaddingAfter; drawBackAndBorders(viewport, x, y, width + bpwidth, height + bpheight); if (viewport.hasClip()) { saveGraphicsState(); - - clipRect(x + borderPaddingStart, y + borderPaddingBefore, width, height); + if ( ( level == -1 ) || ( ( level & 1 ) == 0 ) ) { + clipRect(x + borderPaddingStart, y + borderPaddingBefore, width, height); + } else { + clipRect(x + borderPaddingEnd, y + borderPaddingBefore, width, height); + } } super.renderInlineViewport(viewport); diff --git a/src/java/org/apache/fop/render/AbstractRenderer.java b/src/java/org/apache/fop/render/AbstractRenderer.java index 2c4dc84bd..9c8418467 100644 --- a/src/java/org/apache/fop/render/AbstractRenderer.java +++ b/src/java/org/apache/fop/render/AbstractRenderer.java @@ -507,17 +507,10 @@ public abstract class AbstractRenderer */ protected void renderBlocks(Block parent, List blocks) { int saveIP = currentIPPosition; -// int saveBP = currentBPPosition; // Calculate the position of the content rectangle. if (parent != null && !parent.getTraitAsBoolean(Trait.IS_VIEWPORT_AREA)) { currentBPPosition += parent.getBorderAndPaddingWidthBefore(); - /* This is unnecessary now as we're going to use the *-indent traits - currentIPPosition += parent.getBorderAndPaddingWidthStart(); - Integer spaceStart = (Integer) parent.getTrait(Trait.SPACE_START); - if (spaceStart != null) { - currentIPPosition += spaceStart.intValue(); - }*/ } // the position of the containing block is used for @@ -540,9 +533,15 @@ public abstract class AbstractRenderer // a line area is rendered from the top left position // of the line, each inline object is offset from there LineArea line = (LineArea) obj; - currentIPPosition = contIP + parent.getStartIndent(); + if ( parent != null ) { + int level = parent.getBidiLevel(); + if ( ( level == -1 ) || ( ( level & 1 ) == 0 ) ) { + currentIPPosition += parent.getStartIndent(); + } else { + currentIPPosition += parent.getEndIndent(); + } + } renderLineArea(line); - //InlineArea child = (InlineArea) line.getInlineAreas().get(0); currentBPPosition += line.getAllocBPD(); } currentIPPosition = saveIP; @@ -555,6 +554,7 @@ public abstract class AbstractRenderer * @param block The block area */ protected void renderBlock(Block block) { + assert block != null; List children = block.getChildAreas(); if (block instanceof BlockViewport) { if (children != null) { @@ -604,7 +604,23 @@ public abstract class AbstractRenderer List children = line.getInlineAreas(); int saveBP = currentBPPosition; currentBPPosition += line.getSpaceBefore(); - currentIPPosition += line.getStartIndent(); + int bl = line.getBidiLevel(); + if ( bl >= 0 ) { + if ( ( bl & 1 ) == 0 ) { + currentIPPosition += line.getStartIndent(); + } else { + currentIPPosition += line.getEndIndent(); + // if line's content overflows line area, then + // ensure that overflow is drawn (extends) + // outside of left side of line area + int overflow = computeInlinesOverflow ( line ); + if ( overflow > 0 ) { + currentIPPosition -= overflow; + } + } + } else { + currentIPPosition += line.getStartIndent(); + } for (int i = 0, l = children.size(); i < l; i++) { InlineArea inline = (InlineArea) children.get(i); renderInlineArea(inline); @@ -612,6 +628,16 @@ public abstract class AbstractRenderer currentBPPosition = saveBP; } + private int computeInlinesOverflow ( LineArea line ) { + List children = line.getInlineAreas(); + int ipdConsumed = 0; + for (int i = 0, l = children.size(); i < l; i++) { + InlineArea inline = (InlineArea) children.get(i); + ipdConsumed += inline.getIPD(); + } + return ipdConsumed - line.getIPD(); + } + /** * Render the given InlineArea. * @param inlineArea inline area text to render @@ -703,11 +729,16 @@ public abstract class AbstractRenderer * @param ip the inline parent to render */ protected void renderInlineParent(InlineParent ip) { + int level = ip.getBidiLevel(); List children = ip.getChildAreas(); renderInlineAreaBackAndBorders(ip); int saveIP = currentIPPosition; int saveBP = currentBPPosition; - currentIPPosition += ip.getBorderAndPaddingWidthStart(); + if ( ( level == -1 ) || ( ( level & 1 ) == 0 ) ) { + currentIPPosition += ip.getBorderAndPaddingWidthStart(); + } else { + currentIPPosition += ip.getBorderAndPaddingWidthEnd(); + } currentBPPosition += ip.getBlockProgressionOffset(); for (int i = 0, l = children.size(); i < l; i++) { InlineArea inline = (InlineArea) children.get(i); @@ -722,8 +753,13 @@ public abstract class AbstractRenderer * @param ibp the inline block parent to render */ protected void renderInlineBlockParent(InlineBlockParent ibp) { + int level = ibp.getBidiLevel(); renderInlineAreaBackAndBorders(ibp); - currentIPPosition += ibp.getBorderAndPaddingWidthStart(); + if ( ( level == -1 ) || ( ( level & 1 ) == 0 ) ) { + currentIPPosition += ibp.getBorderAndPaddingWidthStart(); + } else { + currentIPPosition += ibp.getBorderAndPaddingWidthEnd(); + } // For inline content the BP position is updated by the enclosing line area int saveBP = currentBPPosition; currentBPPosition += ibp.getBlockProgressionOffset(); diff --git a/src/java/org/apache/fop/render/afp/AFPPainter.java b/src/java/org/apache/fop/render/afp/AFPPainter.java index 8bdff606f..30e4e7693 100644 --- a/src/java/org/apache/fop/render/afp/AFPPainter.java +++ b/src/java/org/apache/fop/render/afp/AFPPainter.java @@ -260,11 +260,11 @@ public class AFPPainter extends AbstractIFPainter { /** {@inheritDoc} */ @Override - public void drawBorderRect(Rectangle rect, BorderProps before, BorderProps after, - BorderProps start, BorderProps end) throws IFException { - if (before != null || after != null || start != null || end != null) { + public void drawBorderRect(Rectangle rect, BorderProps top, BorderProps bottom, + BorderProps left, BorderProps right) throws IFException { + if (top != null || bottom != null || left != null || right != null) { try { - this.borderPainter.drawBorders(rect, before, after, start, end); + this.borderPainter.drawBorders(rect, top, bottom, left, right); } catch (IOException ife) { throw new IFException("IO error while painting borders", ife); } diff --git a/src/java/org/apache/fop/render/intermediate/AbstractIFPainter.java b/src/java/org/apache/fop/render/intermediate/AbstractIFPainter.java index 2226274ab..c696e552d 100644 --- a/src/java/org/apache/fop/render/intermediate/AbstractIFPainter.java +++ b/src/java/org/apache/fop/render/intermediate/AbstractIFPainter.java @@ -315,55 +315,55 @@ public abstract class AbstractIFPainter implements IFPainter { } /** {@inheritDoc} */ - public void drawBorderRect(Rectangle rect, BorderProps before, BorderProps after, - BorderProps start, BorderProps end) throws IFException { - if (before != null) { + public void drawBorderRect(Rectangle rect, BorderProps top, BorderProps bottom, + BorderProps left, BorderProps right) throws IFException { + if (top != null) { Rectangle b = new Rectangle( rect.x, rect.y, - rect.width, before.width); - fillRect(b, before.color); + rect.width, top.width); + fillRect(b, top.color); } - if (end != null) { + if (right != null) { Rectangle b = new Rectangle( - rect.x + rect.width - end.width, rect.y, - end.width, rect.height); - fillRect(b, end.color); + rect.x + rect.width - right.width, rect.y, + right.width, rect.height); + fillRect(b, right.color); } - if (after != null) { + if (bottom != null) { Rectangle b = new Rectangle( - rect.x, rect.y + rect.height - after.width, - rect.width, after.width); - fillRect(b, after.color); + rect.x, rect.y + rect.height - bottom.width, + rect.width, bottom.width); + fillRect(b, bottom.color); } - if (start != null) { + if (left != null) { Rectangle b = new Rectangle( rect.x, rect.y, - start.width, rect.height); - fillRect(b, start.color); + left.width, rect.height); + fillRect(b, left.color); } } /** * Indicates whether the given border segments (if present) have only solid borders, i.e. * could be painted in a simplified fashion keeping the output file smaller. - * @param before the border segment on the before-side (top) - * @param after the border segment on the after-side (bottom) - * @param start the border segment on the start-side (left) - * @param end the border segment on the end-side (right) + * @param top the border segment on the top edge + * @param bottom the border segment on the bottom edge + * @param left the border segment on the left edge + * @param right the border segment on the right edge * @return true if any border segment has a non-solid border style */ - protected boolean hasOnlySolidBorders(BorderProps before, BorderProps after, - BorderProps start, BorderProps end) { - if (before != null && before.style != Constants.EN_SOLID) { + protected boolean hasOnlySolidBorders(BorderProps top, BorderProps bottom, + BorderProps left, BorderProps right) { + if (top != null && top.style != Constants.EN_SOLID) { return false; } - if (after != null && after.style != Constants.EN_SOLID) { + if (bottom != null && bottom.style != Constants.EN_SOLID) { return false; } - if (start != null && start.style != Constants.EN_SOLID) { + if (left != null && left.style != Constants.EN_SOLID) { return false; } - if (end != null && end.style != Constants.EN_SOLID) { + if (right != null && right.style != Constants.EN_SOLID) { return false; } return true; diff --git a/src/java/org/apache/fop/render/intermediate/BorderPainter.java b/src/java/org/apache/fop/render/intermediate/BorderPainter.java index 20402369a..cc28028ef 100644 --- a/src/java/org/apache/fop/render/intermediate/BorderPainter.java +++ b/src/java/org/apache/fop/render/intermediate/BorderPainter.java @@ -35,35 +35,35 @@ public abstract class BorderPainter { /** * Draws borders. * @param borderRect the border rectangle - * @param bpsBefore the border specification on the before side - * @param bpsAfter the border specification on the after side - * @param bpsStart the border specification on the start side - * @param bpsEnd the border specification on the end side + * @param bpsTop the border specification on the top side + * @param bpsBottom the border specification on the bottom side + * @param bpsLeft the border specification on the left side + * @param bpsRight the border specification on the end side * @throws IOException if an I/O error occurs while creating the borders */ public void drawBorders(Rectangle borderRect, // CSOK: MethodLength - BorderProps bpsBefore, BorderProps bpsAfter, - BorderProps bpsStart, BorderProps bpsEnd) throws IOException { + BorderProps bpsTop, BorderProps bpsBottom, + BorderProps bpsLeft, BorderProps bpsRight) throws IOException { int startx = borderRect.x; int starty = borderRect.y; int width = borderRect.width; int height = borderRect.height; boolean[] b = new boolean[] { - (bpsBefore != null), (bpsEnd != null), - (bpsAfter != null), (bpsStart != null)}; + (bpsTop != null), (bpsRight != null), + (bpsBottom != null), (bpsLeft != null)}; if (!b[0] && !b[1] && !b[2] && !b[3]) { return; } int[] bw = new int[] { - (b[0] ? bpsBefore.width : 0), - (b[1] ? bpsEnd.width : 0), - (b[2] ? bpsAfter.width : 0), - (b[3] ? bpsStart.width : 0)}; + (b[0] ? bpsTop.width : 0), + (b[1] ? bpsRight.width : 0), + (b[2] ? bpsBottom.width : 0), + (b[3] ? bpsLeft.width : 0)}; int[] clipw = new int[] { - BorderProps.getClippedWidth(bpsBefore), - BorderProps.getClippedWidth(bpsEnd), - BorderProps.getClippedWidth(bpsAfter), - BorderProps.getClippedWidth(bpsStart)}; + BorderProps.getClippedWidth(bpsTop), + BorderProps.getClippedWidth(bpsRight), + BorderProps.getClippedWidth(bpsBottom), + BorderProps.getClippedWidth(bpsLeft)}; starty += clipw[0]; height -= clipw[0]; height -= clipw[2]; @@ -73,7 +73,7 @@ public abstract class BorderPainter { boolean[] slant = new boolean[] { (b[3] && b[0]), (b[0] && b[1]), (b[1] && b[2]), (b[2] && b[3])}; - if (bpsBefore != null) { + if (bpsTop != null) { int sx1 = startx; int sx2 = (slant[0] ? sx1 + bw[3] - clipw[3] : sx1); int ex1 = startx + width; @@ -86,11 +86,11 @@ public abstract class BorderPainter { moveTo(sx1, clipy); int sx1a = sx1; int ex1a = ex1; - if (bpsBefore.mode == BorderProps.COLLAPSE_OUTER) { - if (bpsStart != null && bpsStart.mode == BorderProps.COLLAPSE_OUTER) { + if (bpsTop.mode == BorderProps.COLLAPSE_OUTER) { + if (bpsLeft != null && bpsLeft.mode == BorderProps.COLLAPSE_OUTER) { sx1a -= clipw[3]; } - if (bpsEnd != null && bpsEnd.mode == BorderProps.COLLAPSE_OUTER) { + if (bpsRight != null && bpsRight.mode == BorderProps.COLLAPSE_OUTER) { ex1a += clipw[1]; } lineTo(sx1a, outery); @@ -102,10 +102,10 @@ public abstract class BorderPainter { closePath(); clip(); drawBorderLine(sx1a, outery, ex1a, innery, true, true, - bpsBefore.style, bpsBefore.color); + bpsTop.style, bpsTop.color); restoreGraphicsState(); } - if (bpsEnd != null) { + if (bpsRight != null) { int sy1 = starty; int sy2 = (slant[1] ? sy1 + bw[0] - clipw[0] : sy1); int ey1 = starty + height; @@ -118,11 +118,11 @@ public abstract class BorderPainter { moveTo(clipx, sy1); int sy1a = sy1; int ey1a = ey1; - if (bpsEnd.mode == BorderProps.COLLAPSE_OUTER) { - if (bpsBefore != null && bpsBefore.mode == BorderProps.COLLAPSE_OUTER) { + if (bpsRight.mode == BorderProps.COLLAPSE_OUTER) { + if (bpsTop != null && bpsTop.mode == BorderProps.COLLAPSE_OUTER) { sy1a -= clipw[0]; } - if (bpsAfter != null && bpsAfter.mode == BorderProps.COLLAPSE_OUTER) { + if (bpsBottom != null && bpsBottom.mode == BorderProps.COLLAPSE_OUTER) { ey1a += clipw[2]; } lineTo(outerx, sy1a); @@ -133,10 +133,11 @@ public abstract class BorderPainter { lineTo(innerx, sy2); closePath(); clip(); - drawBorderLine(innerx, sy1a, outerx, ey1a, false, false, bpsEnd.style, bpsEnd.color); + drawBorderLine(innerx, sy1a, outerx, ey1a, false, false, + bpsRight.style, bpsRight.color); restoreGraphicsState(); } - if (bpsAfter != null) { + if (bpsBottom != null) { int sx1 = startx; int sx2 = (slant[3] ? sx1 + bw[3] - clipw[3] : sx1); int ex1 = startx + width; @@ -149,11 +150,11 @@ public abstract class BorderPainter { moveTo(ex1, clipy); int sx1a = sx1; int ex1a = ex1; - if (bpsAfter.mode == BorderProps.COLLAPSE_OUTER) { - if (bpsStart != null && bpsStart.mode == BorderProps.COLLAPSE_OUTER) { + if (bpsBottom.mode == BorderProps.COLLAPSE_OUTER) { + if (bpsLeft != null && bpsLeft.mode == BorderProps.COLLAPSE_OUTER) { sx1a -= clipw[3]; } - if (bpsEnd != null && bpsEnd.mode == BorderProps.COLLAPSE_OUTER) { + if (bpsRight != null && bpsRight.mode == BorderProps.COLLAPSE_OUTER) { ex1a += clipw[1]; } lineTo(ex1a, outery); @@ -164,10 +165,11 @@ public abstract class BorderPainter { lineTo(ex2, innery); closePath(); clip(); - drawBorderLine(sx1a, innery, ex1a, outery, true, false, bpsAfter.style, bpsAfter.color); + drawBorderLine(sx1a, innery, ex1a, outery, true, false, + bpsBottom.style, bpsBottom.color); restoreGraphicsState(); } - if (bpsStart != null) { + if (bpsLeft != null) { int sy1 = starty; int sy2 = (slant[0] ? sy1 + bw[0] - clipw[0] : sy1); int ey1 = sy1 + height; @@ -180,11 +182,11 @@ public abstract class BorderPainter { moveTo(clipx, ey1); int sy1a = sy1; int ey1a = ey1; - if (bpsStart.mode == BorderProps.COLLAPSE_OUTER) { - if (bpsBefore != null && bpsBefore.mode == BorderProps.COLLAPSE_OUTER) { + if (bpsLeft.mode == BorderProps.COLLAPSE_OUTER) { + if (bpsTop != null && bpsTop.mode == BorderProps.COLLAPSE_OUTER) { sy1a -= clipw[0]; } - if (bpsAfter != null && bpsAfter.mode == BorderProps.COLLAPSE_OUTER) { + if (bpsBottom != null && bpsBottom.mode == BorderProps.COLLAPSE_OUTER) { ey1a += clipw[2]; } lineTo(outerx, ey1a); @@ -195,7 +197,7 @@ public abstract class BorderPainter { lineTo(innerx, ey2); closePath(); clip(); - drawBorderLine(outerx, sy1a, innerx, ey1a, false, true, bpsStart.style, bpsStart.color); + drawBorderLine(outerx, sy1a, innerx, ey1a, false, true, bpsLeft.style, bpsLeft.color); restoreGraphicsState(); } } diff --git a/src/java/org/apache/fop/render/intermediate/IFPainter.java b/src/java/org/apache/fop/render/intermediate/IFPainter.java index 9844e5756..06dfbd181 100644 --- a/src/java/org/apache/fop/render/intermediate/IFPainter.java +++ b/src/java/org/apache/fop/render/intermediate/IFPainter.java @@ -180,15 +180,15 @@ public interface IFPainter { * Draws a border rectangle. The border segments are specified through {@link BorderProps} * instances. * @param rect the rectangle's coordinates and extent - * @param before the border segment on the before-side (top) - * @param after the border segment on the after-side (bottom) - * @param start the border segment on the start-side (left) - * @param end the border segment on the end-side (right) + * @param top the border segment on the top edge + * @param bottom the border segment on the bottom edge + * @param left the border segment on the left edge + * @param right the border segment on the right edge * @throws IFException if an error occurs while handling this event */ void drawBorderRect(Rectangle rect, - BorderProps before, BorderProps after, - BorderProps start, BorderProps end) throws IFException; + BorderProps top, BorderProps bottom, + BorderProps left, BorderProps right) throws IFException; /** * Draws a line. NOTE: Currently, only horizontal lines are implemented! diff --git a/src/java/org/apache/fop/render/intermediate/IFParser.java b/src/java/org/apache/fop/render/intermediate/IFParser.java index 54fc342ca..5f9da9a7d 100644 --- a/src/java/org/apache/fop/render/intermediate/IFParser.java +++ b/src/java/org/apache/fop/render/intermediate/IFParser.java @@ -645,7 +645,7 @@ public class IFParser implements IFConstants { } - private static final String[] SIDES = new String[] {"before", "after", "start", "end"}; + private static final String[] SIDES = new String[] {"top", "bottom", "left", "right"}; private class BorderRectHandler extends AbstractElementHandler { diff --git a/src/java/org/apache/fop/render/intermediate/IFRenderer.java b/src/java/org/apache/fop/render/intermediate/IFRenderer.java index c0991a552..36720b168 100644 --- a/src/java/org/apache/fop/render/intermediate/IFRenderer.java +++ b/src/java/org/apache/fop/render/intermediate/IFRenderer.java @@ -1324,10 +1324,21 @@ public class IFRenderer extends AbstractPathOrientedRenderer { float startx, float starty, float width, float height, BorderProps bpsBefore, BorderProps bpsAfter, - BorderProps bpsStart, BorderProps bpsEnd) { + BorderProps bpsStart, BorderProps bpsEnd, int level) { Rectangle rect = toMillipointRectangle(startx, starty, width, height); try { - painter.drawBorderRect(rect, bpsBefore, bpsAfter, bpsStart, bpsEnd); + BorderProps bpsTop = bpsBefore; + BorderProps bpsBottom = bpsAfter; + BorderProps bpsLeft; + BorderProps bpsRight; + if ( ( level == -1 ) || ( ( level & 1 ) == 0 ) ) { + bpsLeft = bpsStart; + bpsRight = bpsEnd; + } else { + bpsLeft = bpsEnd; + bpsRight = bpsStart; + } + painter.drawBorderRect(rect, bpsTop, bpsBottom, bpsLeft, bpsRight); } catch (IFException ife) { handleIFException(ife); } diff --git a/src/java/org/apache/fop/render/intermediate/IFSerializer.java b/src/java/org/apache/fop/render/intermediate/IFSerializer.java index 4d307217b..47eb17d73 100644 --- a/src/java/org/apache/fop/render/intermediate/IFSerializer.java +++ b/src/java/org/apache/fop/render/intermediate/IFSerializer.java @@ -506,9 +506,9 @@ public class IFSerializer extends AbstractXMLWritingIFDocumentHandler } /** {@inheritDoc} */ - public void drawBorderRect(Rectangle rect, BorderProps before, BorderProps after, - BorderProps start, BorderProps end) throws IFException { - if (before == null && after == null && start == null && end == null) { + public void drawBorderRect(Rectangle rect, BorderProps top, BorderProps bottom, + BorderProps left, BorderProps right) throws IFException { + if (top == null && bottom == null && left == null && right == null) { return; } try { @@ -517,17 +517,17 @@ public class IFSerializer extends AbstractXMLWritingIFDocumentHandler addAttribute(atts, "y", Integer.toString(rect.y)); addAttribute(atts, "width", Integer.toString(rect.width)); addAttribute(atts, "height", Integer.toString(rect.height)); - if (before != null) { - addAttribute(atts, "before", before.toString()); + if (top != null) { + addAttribute(atts, "top", top.toString()); } - if (after != null) { - addAttribute(atts, "after", after.toString()); + if (bottom != null) { + addAttribute(atts, "bottom", bottom.toString()); } - if (start != null) { - addAttribute(atts, "start", start.toString()); + if (left != null) { + addAttribute(atts, "left", left.toString()); } - if (end != null) { - addAttribute(atts, "end", end.toString()); + if (right != null) { + addAttribute(atts, "right", right.toString()); } handler.element(EL_BORDER_RECT, atts); } catch (SAXException e) { diff --git a/src/java/org/apache/fop/render/java2d/Java2DPainter.java b/src/java/org/apache/fop/render/java2d/Java2DPainter.java index 4acebc621..c7fa1adc5 100644 --- a/src/java/org/apache/fop/render/java2d/Java2DPainter.java +++ b/src/java/org/apache/fop/render/java2d/Java2DPainter.java @@ -185,11 +185,11 @@ public class Java2DPainter extends AbstractIFPainter { } /** {@inheritDoc} */ - public void drawBorderRect(Rectangle rect, BorderProps before, BorderProps after, - BorderProps start, BorderProps end) throws IFException { - if (before != null || after != null || start != null || end != null) { + public void drawBorderRect(Rectangle rect, BorderProps top, BorderProps bottom, + BorderProps left, BorderProps right) throws IFException { + if (top != null || bottom != null || left != null || right != null) { try { - this.borderPainter.drawBorders(rect, before, after, start, end); + this.borderPainter.drawBorders(rect, top, bottom, left, right); } catch (IOException e) { //Won't happen with Java2D throw new IllegalStateException("Unexpected I/O error"); diff --git a/src/java/org/apache/fop/render/pcl/PCLPainter.java b/src/java/org/apache/fop/render/pcl/PCLPainter.java index 59d9ab2b4..53e3d77da 100644 --- a/src/java/org/apache/fop/render/pcl/PCLPainter.java +++ b/src/java/org/apache/fop/render/pcl/PCLPainter.java @@ -207,13 +207,13 @@ public class PCLPainter extends AbstractIFPainter implements PCLConstants { /** {@inheritDoc} */ public void drawBorderRect(final Rectangle rect, - final BorderProps before, final BorderProps after, - final BorderProps start, final BorderProps end) throws IFException { + final BorderProps top, final BorderProps bottom, + final BorderProps left, final BorderProps right) throws IFException { if (isSpeedOptimized()) { - super.drawBorderRect(rect, before, after, start, end); + super.drawBorderRect(rect, top, bottom, left, right); return; } - if (before != null || after != null || start != null || end != null) { + if (top != null || bottom != null || left != null || right != null) { final Rectangle boundingBox = rect; final Dimension dim = boundingBox.getSize(); @@ -225,7 +225,7 @@ public class PCLPainter extends AbstractIFPainter implements PCLConstants { Java2DPainter painter = new Java2DPainter(g2d, getContext(), parent.getFontInfo(), state); try { - painter.drawBorderRect(rect, before, after, start, end); + painter.drawBorderRect(rect, top, bottom, left, right); } catch (IFException e) { //This should never happen with the Java2DPainter throw new RuntimeException("Unexpected error while painting borders", e); diff --git a/src/java/org/apache/fop/render/pdf/PDFPainter.java b/src/java/org/apache/fop/render/pdf/PDFPainter.java index b31fde0f9..82a4a1656 100644 --- a/src/java/org/apache/fop/render/pdf/PDFPainter.java +++ b/src/java/org/apache/fop/render/pdf/PDFPainter.java @@ -47,6 +47,7 @@ import org.apache.fop.render.intermediate.IFState; import org.apache.fop.render.intermediate.IFUtil; import org.apache.fop.render.pdf.PDFLogicalStructureHandler.MarkedContentInfo; import org.apache.fop.traits.BorderProps; +import org.apache.fop.traits.Direction; import org.apache.fop.traits.RuleStyle; import org.apache.fop.util.CharUtilities; @@ -256,12 +257,12 @@ public class PDFPainter extends AbstractIFPainter { } /** {@inheritDoc} */ - public void drawBorderRect(Rectangle rect, BorderProps before, BorderProps after, - BorderProps start, BorderProps end) throws IFException { - if (before != null || after != null || start != null || end != null) { + public void drawBorderRect(Rectangle rect, BorderProps top, BorderProps bottom, + BorderProps left, BorderProps right) throws IFException { + if (top != null || bottom != null || left != null || right != null) { generator.endTextObject(); try { - this.borderPainter.drawBorders(rect, before, after, start, end); + this.borderPainter.drawBorders(rect, top, bottom, left, right); } catch (IOException ioe) { throw new IFException("I/O error while drawing borders", ioe); } @@ -398,7 +399,6 @@ public class PDFPainter extends AbstractIFPainter { assert text != null; assert triplet != null; assert dp != null; - assert dp.length == text.length(); String fk = getFontInfo().getInternalFontKey(triplet); Typeface tf = getTypeface(fk); if ( tf.isMultiByte() ) { @@ -411,14 +411,16 @@ public class PDFPainter extends AbstractIFPainter { double yc = 0f; double xoLast = 0f; double yoLast = 0f; + double wox = wordSpacing; tu.writeTextMatrix ( new AffineTransform ( 1, 0, 0, -1, x / 1000f, y / 1000f ) ); tu.updateTf ( fk, fsPoints, true ); + generator.updateCharacterSpacing ( letterSpacing / 1000f ); for ( int i = 0, n = text.length(); i < n; i++ ) { char ch = text.charAt ( i ); int[] pa = ( i < dp.length ) ? dp [ i ] : paZero; double xo = xc + pa[0]; double yo = yc + pa[1]; - double xa = f.getCharWidth(ch); + double xa = f.getCharWidth(ch) + maybeWordOffsetX ( wox, ch, null ); double ya = 0; double xd = ( xo - xoLast ) / 1000f; double yd = ( yo - yoLast ) / 1000f; @@ -432,4 +434,26 @@ public class PDFPainter extends AbstractIFPainter { } } + private double maybeWordOffsetX ( double wox, char ch, Direction dir ) { + if ( ( wox != 0 ) + && CharUtilities.isAdjustableSpace ( ch ) + && ( ( dir == null ) || dir.isHorizontal() ) ) { + return wox; + } else { + return 0; + } + } + + /* + private double maybeWordOffsetY ( double woy, char ch, Direction dir ) { + if ( ( woy != 0 ) + && CharUtilities.isAdjustableSpace ( ch ) && dir.isVertical() + && ( ( dir != null ) && dir.isVertical() ) ) { + return woy; + } else { + return 0; + } + } + */ + } diff --git a/src/java/org/apache/fop/render/ps/PSPainter.java b/src/java/org/apache/fop/render/ps/PSPainter.java index c28ec7549..6396de8f7 100644 --- a/src/java/org/apache/fop/render/ps/PSPainter.java +++ b/src/java/org/apache/fop/render/ps/PSPainter.java @@ -235,16 +235,16 @@ public class PSPainter extends AbstractIFPainter { } /** {@inheritDoc} */ - public void drawBorderRect(Rectangle rect, BorderProps before, BorderProps after, - BorderProps start, BorderProps end) throws IFException { - if (before != null || after != null || start != null || end != null) { + public void drawBorderRect(Rectangle rect, BorderProps top, BorderProps bottom, + BorderProps left, BorderProps right) throws IFException { + if (top != null || bottom != null || left != null || right != null) { try { endTextObject(); if (getPSUtil().getRenderingMode() == PSRenderingMode.SIZE - && hasOnlySolidBorders(before, after, start, end)) { - super.drawBorderRect(rect, before, after, start, end); + && hasOnlySolidBorders(top, bottom, left, right)) { + super.drawBorderRect(rect, top, bottom, left, right); } else { - this.borderPainter.drawBorders(rect, before, after, start, end); + this.borderPainter.drawBorders(rect, top, bottom, left, right); } } catch (IOException ioe) { throw new IFException("I/O error in drawBorderRect()", ioe); diff --git a/src/java/org/apache/fop/traits/Direction.java b/src/java/org/apache/fop/traits/Direction.java index e3fd78b13..5eb36058e 100644 --- a/src/java/org/apache/fop/traits/Direction.java +++ b/src/java/org/apache/fop/traits/Direction.java @@ -52,6 +52,22 @@ public final class Direction extends TraitEnum { super(DIRECTION_NAMES[index], DIRECTION_VALUES[index]); } + /** + * Determine if direction is vertical or not. + * @return true if vertical + */ + public boolean isVertical() { + return ( getEnumValue() == Constants.EN_TB ) || ( getEnumValue() == Constants.EN_BT ); + } + + /** + * Determine if direction is horizontal or not. + * @return true if horizontal + */ + public boolean isHorizontal() { + return ( getEnumValue() == Constants.EN_LR ) || ( getEnumValue() == Constants.EN_RL ); + } + /** * Returns the enumeration/singleton object based on its name. * @param name the name of the enumeration value diff --git a/src/java/org/apache/fop/traits/WritingMode.java b/src/java/org/apache/fop/traits/WritingMode.java index 1bfd97ecf..5b12bb127 100644 --- a/src/java/org/apache/fop/traits/WritingMode.java +++ b/src/java/org/apache/fop/traits/WritingMode.java @@ -100,6 +100,32 @@ public final class WritingMode extends TraitEnum { wms.setWritingMode ( this ); } + /** + * Determine if WM is horizontal or not. + * @return true if horizontal + */ + public boolean isHorizontal() { + switch ( getEnumValue() ) { + case Constants.EN_LR_TB: + case Constants.EN_RL_TB: + return true; + case Constants.EN_TB_LR: + case Constants.EN_TB_RL: + return false; + default: + assert false; + return true; + } + } + + /** + * Determine if WM is vertical or not. + * @return true if vertical + */ + public boolean isVertical() { + return !isHorizontal(); + } + /** * Returns the enumeration/singleton object based on its name. * @param name the name of the enumeration value diff --git a/src/java/org/apache/fop/traits/WritingModeTraits.java b/src/java/org/apache/fop/traits/WritingModeTraits.java index 2c6b8a741..c96cd73c3 100644 --- a/src/java/org/apache/fop/traits/WritingModeTraits.java +++ b/src/java/org/apache/fop/traits/WritingModeTraits.java @@ -138,4 +138,20 @@ public class WritingModeTraits implements WritingModeTraitsSetter { writingMode.assignWritingModeTraits ( this ); } + /** + * Helper function to find the writing mode traits getter (if any) that applies for + * a given FO node. + * @param fn the node to start searching from + * @return the applicable writing mode traits getter, or null if none applies + */ + public static WritingModeTraitsGetter + getWritingModeTraitsGetter ( org.apache.fop.fo.FONode fn ) { + for ( org.apache.fop.fo.FONode n = fn; n != null; n = n.getParent() ) { + if ( n instanceof WritingModeTraitsGetter ) { + return (WritingModeTraitsGetter) n; + } + } + return null; + } + } diff --git a/src/java/org/apache/fop/util/CharUtilities.java b/src/java/org/apache/fop/util/CharUtilities.java index 3dfed12d8..4f96d0a55 100644 --- a/src/java/org/apache/fop/util/CharUtilities.java +++ b/src/java/org/apache/fop/util/CharUtilities.java @@ -28,6 +28,7 @@ import java.util.Set; // CSOFF: AvoidNestedBlocksCheck // CSOFF: InnerAssignmentCheck +// CSOFF: WhitespaceAfterCheck // CSOFF: SimplifyBooleanReturnCheck /** @@ -111,9 +112,16 @@ public class CharUtilities { public static final char MISSING_IDEOGRAPH = '\u25A1'; /** Ideogreaphic space */ public static final char IDEOGRAPHIC_SPACE = '\u3000'; + /** Object replacement character */ + public static final char OBJECT_REPLACEMENT_CHARACTER = '\uFFFC'; /** Unicode value indicating the the character is "not a character". */ public static final char NOT_A_CHARACTER = '\uFFFF'; + /** + * A static (class) parameter indicating whether V2 indic shaping + * rules apply or not, with default being true. + */ + private static final boolean useV2Indic = true; // CSOK: ConstantNameCheck /** * Utility class: Constructor prevents instantiating when subclassed. @@ -264,6 +272,12 @@ public class CharUtilities { } + // + // The following script codes are based on ISO 15924. Codes less than 1000 are + // official assignments from 15924; those equal to or greater than 1000 are FOP + // implementation specific. + // + // CSOFF: LineLengthCheck /** hebrew script constant */ public static final int SCRIPT_HEBREW = 125; // 'hebr' /** mongolian script constant */ @@ -284,22 +298,42 @@ public class CharUtilities { public static final int SCRIPT_HANGUL = 286; // 'hang' /** gurmukhi script constant */ public static final int SCRIPT_GURMUKHI = 310; // 'guru' + /** gurmukhi 2 script constant */ + public static final int SCRIPT_GURMUKHI_2 = 1310; // 'gur2' -- MSFT (pseudo) script tag for variant shaping semantics /** devanagari script constant */ public static final int SCRIPT_DEVANAGARI = 315; // 'deva' + /** devanagari 2 script constant */ + public static final int SCRIPT_DEVANAGARI_2 = 1315; // 'dev2' -- MSFT (pseudo) script tag for variant shaping semantics /** gujarati script constant */ public static final int SCRIPT_GUJARATI = 320; // 'gujr' + /** gujarati 2 script constant */ + public static final int SCRIPT_GUJARATI_2 = 1320; // 'gjr2' -- MSFT (pseudo) script tag for variant shaping semantics /** bengali script constant */ public static final int SCRIPT_BENGALI = 326; // 'beng' + /** bengali 2 script constant */ + public static final int SCRIPT_BENGALI_2 = 1326; // 'bng2' -- MSFT (pseudo) script tag for variant shaping semantics /** oriya script constant */ public static final int SCRIPT_ORIYA = 327; // 'orya' + /** oriya 2 script constant */ + public static final int SCRIPT_ORIYA_2 = 1327; // 'ory2' -- MSFT (pseudo) script tag for variant shaping semantics /** tibetan script constant */ public static final int SCRIPT_TIBETAN = 330; // 'tibt' /** telugu script constant */ public static final int SCRIPT_TELUGU = 340; // 'telu' + /** telugu 2 script constant */ + public static final int SCRIPT_TELUGU_2 = 1340; // 'tel2' -- MSFT (pseudo) script tag for variant shaping semantics + /** kannada script constant */ + public static final int SCRIPT_KANNADA = 345; // 'knda' + /** kannada 2 script constant */ + public static final int SCRIPT_KANNADA_2 = 1345; // 'knd2' -- MSFT (pseudo) script tag for variant shaping semantics /** tamil script constant */ public static final int SCRIPT_TAMIL = 346; // 'taml' + /** tamil 2 script constant */ + public static final int SCRIPT_TAMIL_2 = 1346; // 'tml2' -- MSFT (pseudo) script tag for variant shaping semantics /** malayalam script constant */ public static final int SCRIPT_MALAYALAM = 347; // 'mlym' + /** malayalam 2 script constant */ + public static final int SCRIPT_MALAYALAM_2 = 1347; // 'mlm2' -- MSFT (pseudo) script tag for variant shaping semantics /** sinhalese script constant */ public static final int SCRIPT_SINHALESE = 348; // 'sinh' /** burmese script constant */ @@ -326,6 +360,7 @@ public class CharUtilities { public static final int SCRIPT_UNDETERMINED = 998; // 'zyyy' /** uncoded script constant */ public static final int SCRIPT_UNCODED = 999; // 'zzzz' + // CSON: LineLengthCheck /** * Determine if character c is punctuation. @@ -375,7 +410,13 @@ public class CharUtilities { * @return true if character belongs to hebrew script */ public static boolean isHebrew ( int c ) { - return false; // [TBD] - implement me + if ( ( c >= 0x0590 ) && ( c <= 0x05FF ) ) { // hebrew block + return true; + } else if ( ( c >= 0xFB00 ) && ( c <= 0xFB4F ) ) { // hebrew presentation forms block + return true; + } else { + return false; + } } /** @@ -384,7 +425,11 @@ public class CharUtilities { * @return true if character belongs to mongolian script */ public static boolean isMongolian ( int c ) { - return false; // [TBD] - implement me + if ( ( c >= 0x1800 ) && ( c <= 0x18AF ) ) { // mongolian block + return true; + } else { + return false; + } } /** @@ -412,7 +457,13 @@ public class CharUtilities { * @return true if character belongs to greek script */ public static boolean isGreek ( int c ) { - return false; // [TBD] - implement me + if ( ( c >= 0x0370 ) && ( c <= 0x03FF ) ) { // greek (and coptic) block + return true; + } else if ( ( c >= 0x1F00 ) && ( c <= 0x1FFF ) ) { // greek extended block + return true; + } else { + return false; + } } /** @@ -456,7 +507,17 @@ public class CharUtilities { * @return true if character belongs to cyrillic script */ public static boolean isCyrillic ( int c ) { - return false; // [TBD] - implement me + if ( ( c >= 0x0400 ) && ( c <= 0x04FF ) ) { // cyrillic block + return true; + } else if ( ( c >= 0x0500 ) && ( c <= 0x052F ) ) { // cyrillic supplement block + return true; + } else if ( ( c >= 0x2DE0 ) && ( c <= 0x2DFF ) ) { // cyrillic extended-a block + return true; + } else if ( ( c >= 0xA640 ) && ( c <= 0xA69F ) ) { // cyrillic extended-b block + return true; + } else { + return false; + } } /** @@ -465,7 +526,13 @@ public class CharUtilities { * @return true if character belongs to georgian script */ public static boolean isGeorgian ( int c ) { - return false; // [TBD] - implement me + if ( ( c >= 0x10A0 ) && ( c <= 0x10FF ) ) { // georgian block + return true; + } else if ( ( c >= 0x2D00 ) && ( c <= 0x2D2F ) ) { // georgian supplement block + return true; + } else { + return false; + } } /** @@ -495,7 +562,11 @@ public class CharUtilities { * @return true if character belongs to gurmukhi script */ public static boolean isGurmukhi ( int c ) { - return false; // [TBD] - implement me + if ( ( c >= 0x0A00 ) && ( c <= 0x0A7F ) ) { // gurmukhi block + return true; + } else { + return false; + } } /** @@ -504,7 +575,13 @@ public class CharUtilities { * @return true if character belongs to devanagari script */ public static boolean isDevanagari ( int c ) { - return false; // [TBD] - implement me + if ( ( c >= 0x0900 ) && ( c <= 0x097F ) ) { // devangari block + return true; + } else if ( ( c >= 0xA8E0 ) && ( c <= 0xA8FF ) ) { // devangari extended block + return true; + } else { + return false; + } } /** @@ -513,7 +590,11 @@ public class CharUtilities { * @return true if character belongs to gujarati script */ public static boolean isGujarati ( int c ) { - return false; // [TBD] - implement me + if ( ( c >= 0x0A80 ) && ( c <= 0x0AFF ) ) { // gujarati block + return true; + } else { + return false; + } } /** @@ -522,7 +603,11 @@ public class CharUtilities { * @return true if character belongs to bengali script */ public static boolean isBengali ( int c ) { - return false; // [TBD] - implement me + if ( ( c >= 0x0980 ) && ( c <= 0x09FF ) ) { // bengali block + return true; + } else { + return false; + } } /** @@ -531,7 +616,11 @@ public class CharUtilities { * @return true if character belongs to oriya script */ public static boolean isOriya ( int c ) { - return false; // [TBD] - implement me + if ( ( c >= 0x0B00 ) && ( c <= 0x0B7F ) ) { // oriya block + return true; + } else { + return false; + } } /** @@ -540,7 +629,11 @@ public class CharUtilities { * @return true if character belongs to tibetan script */ public static boolean isTibetan ( int c ) { - return false; // [TBD] - implement me + if ( ( c >= 0x0F00 ) && ( c <= 0x0FFF ) ) { // tibetan block + return true; + } else { + return false; + } } /** @@ -549,7 +642,24 @@ public class CharUtilities { * @return true if character belongs to telugu script */ public static boolean isTelugu ( int c ) { - return false; // [TBD] - implement me + if ( ( c >= 0x0C00 ) && ( c <= 0x0C7F ) ) { // telugu block + return true; + } else { + return false; + } + } + + /** + * Determine if character c belong to the kannada script. + * @param c a character represented as a unicode scalar value + * @return true if character belongs to kannada script + */ + public static boolean isKannada ( int c ) { + if ( ( c >= 0x0C00 ) && ( c <= 0x0C7F ) ) { // kannada block + return true; + } else { + return false; + } } /** @@ -558,7 +668,11 @@ public class CharUtilities { * @return true if character belongs to tamil script */ public static boolean isTamil ( int c ) { - return false; // [TBD] - implement me + if ( ( c >= 0x0B80 ) && ( c <= 0x0BFF ) ) { // tamil block + return true; + } else { + return false; + } } /** @@ -567,7 +681,11 @@ public class CharUtilities { * @return true if character belongs to malayalam script */ public static boolean isMalayalam ( int c ) { - return false; // [TBD] - implement me + if ( ( c >= 0x0D00 ) && ( c <= 0x0D7F ) ) { // malayalam block + return true; + } else { + return false; + } } /** @@ -576,7 +694,11 @@ public class CharUtilities { * @return true if character belongs to sinhalese script */ public static boolean isSinhalese ( int c ) { - return false; // [TBD] - implement me + if ( ( c >= 0x0D80 ) && ( c <= 0x0DFF ) ) { // sinhala block + return true; + } else { + return false; + } } /** @@ -585,7 +707,13 @@ public class CharUtilities { * @return true if character belongs to burmese script */ public static boolean isBurmese ( int c ) { - return false; // [TBD] - implement me + if ( ( c >= 0x1000 ) && ( c <= 0x109F ) ) { // burmese (myanmar) block + return true; + } else if ( ( c >= 0xAA60 ) && ( c <= 0xAA7F ) ) { // burmese (myanmar) extended block + return true; + } else { + return false; + } } /** @@ -594,7 +722,11 @@ public class CharUtilities { * @return true if character belongs to thai script */ public static boolean isThai ( int c ) { - return false; // [TBD] - implement me + if ( ( c >= 0x0E00 ) && ( c <= 0x0E7F ) ) { // thai block + return true; + } else { + return false; + } } /** @@ -603,7 +735,13 @@ public class CharUtilities { * @return true if character belongs to khmer script */ public static boolean isKhmer ( int c ) { - return false; // [TBD] - implement me + if ( ( c >= 0x1780 ) && ( c <= 0x17FF ) ) { // khmer block + return true; + } else if ( ( c >= 0x19E0 ) && ( c <= 0x19FF ) ) { // khmer symbols block + return true; + } else { + return false; + } } /** @@ -612,7 +750,11 @@ public class CharUtilities { * @return true if character belongs to lao script */ public static boolean isLao ( int c ) { - return false; // [TBD] - implement me + if ( ( c >= 0x0E80 ) && ( c <= 0x0EFF ) ) { // lao block + return true; + } else { + return false; + } } /** @@ -621,7 +763,17 @@ public class CharUtilities { * @return true if character belongs to ethiopic (amharic) script */ public static boolean isEthiopic ( int c ) { - return false; // [TBD] - implement me + if ( ( c >= 0x1200 ) && ( c <= 0x137F ) ) { // ethiopic block + return true; + } else if ( ( c >= 0x1380 ) && ( c <= 0x139F ) ) { // ethoipic supplement block + return true; + } else if ( ( c >= 0x2D80 ) && ( c <= 0x2DDF ) ) { // ethoipic extended block + return true; + } else if ( ( c >= 0xAB00 ) && ( c <= 0xAB2F ) ) { // ethoipic extended-a block + return true; + } else { + return false; + } } /** @@ -726,23 +878,25 @@ public class CharUtilities { } else if ( isGeorgian ( c ) ) { return SCRIPT_GEORGIAN; } else if ( isGurmukhi ( c ) ) { - return SCRIPT_GURMUKHI; + return useV2IndicRules ( SCRIPT_GURMUKHI ); } else if ( isDevanagari ( c ) ) { - return SCRIPT_DEVANAGARI; + return useV2IndicRules ( SCRIPT_DEVANAGARI ); } else if ( isGujarati ( c ) ) { - return SCRIPT_GUJARATI; + return useV2IndicRules ( SCRIPT_GUJARATI ); } else if ( isBengali ( c ) ) { - return SCRIPT_BENGALI; + return useV2IndicRules ( SCRIPT_BENGALI ); } else if ( isOriya ( c ) ) { - return SCRIPT_ORIYA; + return useV2IndicRules ( SCRIPT_ORIYA ); } else if ( isTibetan ( c ) ) { return SCRIPT_TIBETAN; } else if ( isTelugu ( c ) ) { - return SCRIPT_TELUGU; + return useV2IndicRules ( SCRIPT_TELUGU ); + } else if ( isKannada ( c ) ) { + return useV2IndicRules ( SCRIPT_KANNADA ); } else if ( isTamil ( c ) ) { - return SCRIPT_TAMIL; + return useV2IndicRules ( SCRIPT_TAMIL ); } else if ( isMalayalam ( c ) ) { - return SCRIPT_MALAYALAM; + return useV2IndicRules ( SCRIPT_MALAYALAM ); } else if ( isSinhalese ( c ) ) { return SCRIPT_SINHALESE; } else if ( isBurmese ( c ) ) { @@ -760,6 +914,20 @@ public class CharUtilities { } } + /** + * Obtain the V2 indic script code corresponding to V1 indic script code SC if + * and only iff V2 indic rules apply; otherwise return SC. + * @param sc a V1 indic script code + * @return either SC or the V2 flavor of SC if V2 indic rules apply + */ + public static int useV2IndicRules ( int sc ) { + if ( useV2Indic ) { + return ( sc < 1000 ) ? ( sc + 1000 ) : sc; + } else { + return sc; + } + } + /** * Obtain the script codes of each character in a character sequence. If script * is not or cannot be determined for some character, then the script code 998 @@ -828,20 +996,75 @@ public class CharUtilities { } /** - * Determine the ISO script tag associated with an internal - * script code. + * Determine if script tag denotes an 'Indic' script, where a + * script is an 'Indic' script if it is intended to be processed by + * the generic 'Indic' Script Processor. + * @param script a script tag + * @return true if script tag is a designated 'Indic' script + */ + public static boolean isIndicScript ( String script ) { + switch ( scriptCodeFromTag ( script ) ) { + case SCRIPT_BENGALI: + case SCRIPT_BENGALI_2: + case SCRIPT_BURMESE: + case SCRIPT_DEVANAGARI: + case SCRIPT_DEVANAGARI_2: + case SCRIPT_GUJARATI: + case SCRIPT_GUJARATI_2: + case SCRIPT_GURMUKHI: + case SCRIPT_GURMUKHI_2: + case SCRIPT_KANNADA: + case SCRIPT_KANNADA_2: + case SCRIPT_MALAYALAM: + case SCRIPT_MALAYALAM_2: + case SCRIPT_ORIYA: + case SCRIPT_ORIYA_2: + case SCRIPT_TAMIL: + case SCRIPT_TAMIL_2: + case SCRIPT_TELUGU: + case SCRIPT_TELUGU_2: + return true; + default: + return false; + } + } + + /** + * Determine the script tag associated with an internal script code. * @param code the script code - * @return an ISO script tag + * @return a script tag */ public static String scriptTagFromCode ( int code ) { - String tag; - if ( scriptTagsMap == null ) { - scriptTagsMap = makeScriptTagsMap(); + Map m = getScriptTagsMap(); + if ( m != null ) { + String tag; + if ( ( tag = m.get ( Integer.valueOf ( code ) ) ) != null ) { + return tag; + } else { + return ""; + } + } else { + return ""; } - if ( ( tag = (String) scriptTagsMap.get ( Integer.valueOf ( code ) ) ) == null ) { - tag = scriptTagFromCode ( SCRIPT_UNDETERMINED ); + } + + /** + * Determine the internal script code associated with a script tag. + * @param tag the script tag + * @return a script code + */ + public static int scriptCodeFromTag ( String tag ) { + Map m = getScriptCodeMap(); + if ( m != null ) { + Integer c; + if ( ( c = m.get ( tag ) ) != null ) { + return (int) c; + } else { + return SCRIPT_UNDETERMINED; + } + } else { + return SCRIPT_UNDETERMINED; } - return tag; } /** @@ -917,46 +1140,78 @@ public class CharUtilities { } } - private static Map scriptTagsMap = null; + private static Map scriptTagsMap = null; + private static Map scriptCodeMap = null; + + private static void putScriptTag ( Map tm, Map cm, int code, String tag ) { + assert tag != null; + assert tag.length() != 0; + assert code >= 0; + assert code < 2000; + tm.put ( Integer.valueOf ( code ), tag ); + cm.put ( tag, Integer.valueOf ( code ) ); + } - private static void putScriptTag ( Map m, int code, String tag ) { - m.put ( Integer.valueOf ( code ), tag ); + private static void makeScriptMaps() { + HashMap tm = new HashMap(); + HashMap cm = new HashMap(); + putScriptTag ( tm, cm, SCRIPT_HEBREW, "hebr" ); + putScriptTag ( tm, cm, SCRIPT_MONGOLIAN, "mong" ); + putScriptTag ( tm, cm, SCRIPT_ARABIC, "arab" ); + putScriptTag ( tm, cm, SCRIPT_GREEK, "grek" ); + putScriptTag ( tm, cm, SCRIPT_LATIN, "latn" ); + putScriptTag ( tm, cm, SCRIPT_CYRILLIC, "cyrl" ); + putScriptTag ( tm, cm, SCRIPT_GEORGIAN, "geor" ); + putScriptTag ( tm, cm, SCRIPT_BOPOMOFO, "bopo" ); + putScriptTag ( tm, cm, SCRIPT_HANGUL, "hang" ); + putScriptTag ( tm, cm, SCRIPT_GURMUKHI, "guru" ); + putScriptTag ( tm, cm, SCRIPT_GURMUKHI_2, "gur2" ); + putScriptTag ( tm, cm, SCRIPT_DEVANAGARI, "deva" ); + putScriptTag ( tm, cm, SCRIPT_DEVANAGARI_2, "dev2" ); + putScriptTag ( tm, cm, SCRIPT_GUJARATI, "gujr" ); + putScriptTag ( tm, cm, SCRIPT_GUJARATI_2, "gjr2" ); + putScriptTag ( tm, cm, SCRIPT_BENGALI, "beng" ); + putScriptTag ( tm, cm, SCRIPT_BENGALI_2, "bng2" ); + putScriptTag ( tm, cm, SCRIPT_ORIYA, "orya" ); + putScriptTag ( tm, cm, SCRIPT_ORIYA_2, "ory2" ); + putScriptTag ( tm, cm, SCRIPT_TIBETAN, "tibt" ); + putScriptTag ( tm, cm, SCRIPT_TELUGU, "telu" ); + putScriptTag ( tm, cm, SCRIPT_TELUGU_2, "tel2" ); + putScriptTag ( tm, cm, SCRIPT_KANNADA, "knda" ); + putScriptTag ( tm, cm, SCRIPT_KANNADA_2, "knd2" ); + putScriptTag ( tm, cm, SCRIPT_TAMIL, "taml" ); + putScriptTag ( tm, cm, SCRIPT_TAMIL_2, "tml2" ); + putScriptTag ( tm, cm, SCRIPT_MALAYALAM, "mlym" ); + putScriptTag ( tm, cm, SCRIPT_MALAYALAM_2, "mlm2" ); + putScriptTag ( tm, cm, SCRIPT_SINHALESE, "sinh" ); + putScriptTag ( tm, cm, SCRIPT_BURMESE, "mymr" ); + putScriptTag ( tm, cm, SCRIPT_THAI, "thai" ); + putScriptTag ( tm, cm, SCRIPT_KHMER, "khmr" ); + putScriptTag ( tm, cm, SCRIPT_LAO, "laoo" ); + putScriptTag ( tm, cm, SCRIPT_HIRAGANA, "hira" ); + putScriptTag ( tm, cm, SCRIPT_ETHIOPIC, "ethi" ); + putScriptTag ( tm, cm, SCRIPT_HAN, "hani" ); + putScriptTag ( tm, cm, SCRIPT_KATAKANA, "kana" ); + putScriptTag ( tm, cm, SCRIPT_MATH, "zmth" ); + putScriptTag ( tm, cm, SCRIPT_SYMBOL, "zsym" ); + putScriptTag ( tm, cm, SCRIPT_UNDETERMINED, "zyyy" ); + putScriptTag ( tm, cm, SCRIPT_UNCODED, "zzzz" ); + scriptTagsMap = tm; + scriptCodeMap = cm; } - private static Map makeScriptTagsMap() { - HashMap m = new HashMap(); - putScriptTag ( m, SCRIPT_HEBREW, "hebr" ); - putScriptTag ( m, SCRIPT_MONGOLIAN, "mong" ); - putScriptTag ( m, SCRIPT_ARABIC, "arab" ); - putScriptTag ( m, SCRIPT_GREEK, "grek" ); - putScriptTag ( m, SCRIPT_LATIN, "latn" ); - putScriptTag ( m, SCRIPT_CYRILLIC, "cyrl" ); - putScriptTag ( m, SCRIPT_GEORGIAN, "geor" ); - putScriptTag ( m, SCRIPT_BOPOMOFO, "bopo" ); - putScriptTag ( m, SCRIPT_HANGUL, "hang" ); - putScriptTag ( m, SCRIPT_GURMUKHI, "guru" ); - putScriptTag ( m, SCRIPT_DEVANAGARI, "deva" ); - putScriptTag ( m, SCRIPT_GUJARATI, "gujr" ); - putScriptTag ( m, SCRIPT_BENGALI, "beng" ); - putScriptTag ( m, SCRIPT_ORIYA, "orya" ); - putScriptTag ( m, SCRIPT_TIBETAN, "tibt" ); - putScriptTag ( m, SCRIPT_TELUGU, "telu" ); - putScriptTag ( m, SCRIPT_TAMIL, "taml" ); - putScriptTag ( m, SCRIPT_MALAYALAM, "mlym" ); - putScriptTag ( m, SCRIPT_SINHALESE, "sinh" ); - putScriptTag ( m, SCRIPT_BURMESE, "mymr" ); - putScriptTag ( m, SCRIPT_THAI, "thai" ); - putScriptTag ( m, SCRIPT_KHMER, "khmr" ); - putScriptTag ( m, SCRIPT_LAO, "laoo" ); - putScriptTag ( m, SCRIPT_HIRAGANA, "hira" ); - putScriptTag ( m, SCRIPT_ETHIOPIC, "ethi" ); - putScriptTag ( m, SCRIPT_HAN, "hani" ); - putScriptTag ( m, SCRIPT_KATAKANA, "kana" ); - putScriptTag ( m, SCRIPT_MATH, "zmth" ); - putScriptTag ( m, SCRIPT_SYMBOL, "zsym" ); - putScriptTag ( m, SCRIPT_UNDETERMINED, "zyyy" ); - putScriptTag ( m, SCRIPT_UNCODED, "zzzz" ); - return m; + private static Map getScriptTagsMap() { + if ( scriptTagsMap == null ) { + makeScriptMaps(); + } + return scriptTagsMap; + } + + private static Map getScriptCodeMap() { + if ( scriptCodeMap == null ) { + makeScriptMaps(); + } + return scriptCodeMap; } /** @@ -1641,4 +1896,25 @@ public class CharUtilities { } } + /** + * Determine if two character sequences contain the same characters. + * @param cs1 first character sequence + * @param cs2 second character sequence + * @return true if both sequences have same length and same character sequence + */ + public static boolean isSameSequence ( CharSequence cs1, CharSequence cs2 ) { + assert cs1 != null; + assert cs2 != null; + if ( cs1.length() != cs2.length() ) { + return false; + } else { + for ( int i = 0, n = cs1.length(); i < n; i++ ) { + if ( cs1.charAt(i) != cs2.charAt(i) ) { + return false; + } + } + return true; + } + } + } diff --git a/test/java/org/apache/fop/complexscripts/ComplexScriptsTestSuite.java b/test/java/org/apache/fop/complexscripts/ComplexScriptsTestSuite.java index fd6c99b01..72bdd0765 100644 --- a/test/java/org/apache/fop/complexscripts/ComplexScriptsTestSuite.java +++ b/test/java/org/apache/fop/complexscripts/ComplexScriptsTestSuite.java @@ -20,6 +20,8 @@ package org.apache.fop.complexscripts; import org.apache.fop.complexscripts.bidi.BidiTestSuite; +import org.apache.fop.complexscripts.fonts.FontsTestSuite; +import org.apache.fop.complexscripts.scripts.ScriptsTestSuite; import junit.framework.Test; import junit.framework.TestSuite; @@ -38,6 +40,8 @@ public class ComplexScriptsTestSuite { "Test suite for complex scripts functionality"); //$JUnit-BEGIN$ suite.addTest(BidiTestSuite.suite()); + suite.addTest(FontsTestSuite.suite()); + suite.addTest(ScriptsTestSuite.suite()); //$JUnit-END$ return suite; } diff --git a/test/layoutengine/standard-testcases/simple-page-master_reference-orientation_0_writing-mode_rl_region-body_margin_relative.xml b/test/layoutengine/standard-testcases/simple-page-master_reference-orientation_0_writing-mode_rl_region-body_margin_relative.xml index e594a211e..bfbc90565 100644 --- a/test/layoutengine/standard-testcases/simple-page-master_reference-orientation_0_writing-mode_rl_region-body_margin_relative.xml +++ b/test/layoutengine/standard-testcases/simple-page-master_reference-orientation_0_writing-mode_rl_region-body_margin_relative.xml @@ -19,12 +19,10 @@

- This test checks relative margin on region (region-body). + This test checks relative margin on regions when writing mode is right to left, + with reference orientation 0.

- - ../../resources/images/bgimg300dpi.jpg - @@ -32,8 +30,8 @@ - - + + @@ -60,7 +58,7 @@ Demonstrates relative 5% margin on the page master, 10% margin on regions before - and end and 5% margin on regions start and after. Corresponding margins are set + and start and 5% margin on regions end and after. Corresponding margins are set on the region body. The page size is 5in x 3in with the page reference not rotated and a rl writing-mode. @@ -76,23 +74,23 @@ - + - + - - + + - + - + - - + + - + @@ -100,14 +98,14 @@ - + - + - +
diff --git a/test/layoutengine/standard-testcases/simple-page-master_reference-orientation_180_writing-mode_rl_region-body_margin_relative.xml b/test/layoutengine/standard-testcases/simple-page-master_reference-orientation_180_writing-mode_rl_region-body_margin_relative.xml index b9a26ce7c..284fdf02c 100644 --- a/test/layoutengine/standard-testcases/simple-page-master_reference-orientation_180_writing-mode_rl_region-body_margin_relative.xml +++ b/test/layoutengine/standard-testcases/simple-page-master_reference-orientation_180_writing-mode_rl_region-body_margin_relative.xml @@ -19,12 +19,10 @@

- This test checks relative margin on region (region-body). + This test checks relative margin on regions when writing mode is right to left, + with reference orientation 180.

- - ../../resources/images/bgimg300dpi.jpg - @@ -32,8 +30,8 @@ - - + + @@ -60,9 +58,9 @@ Demonstrates relative 5% margin on the page master, 10% margin on regions before - and end and 5% margin on regions start and after. Corresponding margins are set - on the region body. The page size is 5in x 3in with the page reference rotated by - 180 degrees and a rl writing-mode. + and start and 5% margin on regions end and after. Corresponding margins are set + on the region body. The page size is 5in x 3in with the page reference rotated + by 180 degrees and a rl writing-mode. @@ -76,23 +74,23 @@ - + - + - - + + - + - + - - + + - + @@ -100,14 +98,14 @@ - + - + - +
diff --git a/test/layoutengine/standard-testcases/simple-page-master_reference-orientation_270_writing-mode_rl_region-body_margin_relative.xml b/test/layoutengine/standard-testcases/simple-page-master_reference-orientation_270_writing-mode_rl_region-body_margin_relative.xml index 93b37467b..550785af6 100644 --- a/test/layoutengine/standard-testcases/simple-page-master_reference-orientation_270_writing-mode_rl_region-body_margin_relative.xml +++ b/test/layoutengine/standard-testcases/simple-page-master_reference-orientation_270_writing-mode_rl_region-body_margin_relative.xml @@ -19,21 +19,19 @@

- This test checks relative margin on region (region-body). + This test checks relative margin on regions when writing mode is right to left, + with reference orientation 270.

- - ../../resources/images/bgimg300dpi.jpg - - + - - + + @@ -60,9 +58,9 @@ Demonstrates relative 5% margin on the page master, 10% margin on regions before - and end and 5% margin on regions start and after. Corresponding margins are set - on the region body. The page size is 5in x 3in with the page reference rotated by - 270 degrees and a rl writing-mode. + and start and 5% margin on regions end and after. Corresponding margins are set + on the region body. The page size is 5in x 3in with the page reference rotated + by 270 degrees and a rl writing-mode. @@ -71,43 +69,43 @@ - - - - - - + + + + + + - - - - - - + + + + + + - - - - - - + + + + + + - - - - - - + + + + + + - - - - - - + + + + + +
diff --git a/test/layoutengine/standard-testcases/simple-page-master_reference-orientation_90_writing-mode_rl_region-body_margin_relative.xml b/test/layoutengine/standard-testcases/simple-page-master_reference-orientation_90_writing-mode_rl_region-body_margin_relative.xml index 8c98329f7..cdb3140d1 100644 --- a/test/layoutengine/standard-testcases/simple-page-master_reference-orientation_90_writing-mode_rl_region-body_margin_relative.xml +++ b/test/layoutengine/standard-testcases/simple-page-master_reference-orientation_90_writing-mode_rl_region-body_margin_relative.xml @@ -19,21 +19,19 @@

- This test checks relative margin on region (region-body). + This test checks relative margin on regions when writing mode is right to left, + with reference orientation 90.

- - ../../resources/images/bgimg300dpi.jpg - - + - - + + @@ -60,9 +58,9 @@ Demonstrates relative 5% margin on the page master, 10% margin on regions before - and end and 5% margin on regions start and after. Corresponding margins are set - on the region body. The page size is 5in x 3in with the page reference rotated by - 90 degrees and a rl writing-mode. + and start and 5% margin on regions end and after. Corresponding margins are set + on the region body. The page size is 5in x 3in with the page reference rotated + by 90 degrees and a rl writing-mode. @@ -71,43 +69,43 @@ - - - - - - + + + + + + - - - - - - + + + + + + - - - - - - + + + + + + - - - - - - + + + + + + - - - - - - + + + + + +
-- 2.39.5