aboutsummaryrefslogtreecommitdiffstats
path: root/src/java/org/apache
diff options
context:
space:
mode:
Diffstat (limited to 'src/java/org/apache')
-rw-r--r--src/java/org/apache/fop/apps/FOUserAgent.java18
-rw-r--r--src/java/org/apache/fop/apps/FopFactory.java17
-rw-r--r--src/java/org/apache/fop/apps/FopFactoryConfigurator.java10
-rw-r--r--src/java/org/apache/fop/area/Area.java90
-rw-r--r--src/java/org/apache/fop/area/AreaTreeHandler.java14
-rw-r--r--src/java/org/apache/fop/area/AreaTreeParser.java31
-rw-r--r--src/java/org/apache/fop/area/Block.java9
-rw-r--r--src/java/org/apache/fop/area/BodyRegion.java12
-rw-r--r--src/java/org/apache/fop/area/CTM.java29
-rw-r--r--src/java/org/apache/fop/area/LineArea.java52
-rw-r--r--src/java/org/apache/fop/area/LinkResolver.java33
-rw-r--r--src/java/org/apache/fop/area/MainReference.java13
-rw-r--r--src/java/org/apache/fop/area/Page.java24
-rw-r--r--src/java/org/apache/fop/area/PageViewport.java12
-rw-r--r--src/java/org/apache/fop/area/RegionViewport.java22
-rw-r--r--src/java/org/apache/fop/area/Span.java26
-rw-r--r--src/java/org/apache/fop/area/Trait.java23
-rw-r--r--src/java/org/apache/fop/area/inline/AbstractTextArea.java2
-rw-r--r--src/java/org/apache/fop/area/inline/BasicLinkArea.java23
-rw-r--r--src/java/org/apache/fop/area/inline/FilledArea.java7
-rw-r--r--src/java/org/apache/fop/area/inline/InlineArea.java81
-rw-r--r--src/java/org/apache/fop/area/inline/InlineParent.java38
-rw-r--r--src/java/org/apache/fop/area/inline/InlineViewport.java15
-rw-r--r--src/java/org/apache/fop/area/inline/Space.java6
-rw-r--r--src/java/org/apache/fop/area/inline/SpaceArea.java16
-rw-r--r--src/java/org/apache/fop/area/inline/TextArea.java94
-rw-r--r--src/java/org/apache/fop/area/inline/UnresolvedPageNumber.java36
-rw-r--r--src/java/org/apache/fop/area/inline/WordArea.java278
-rw-r--r--src/java/org/apache/fop/cli/CommandLineOptions.java8
-rw-r--r--src/java/org/apache/fop/complexscripts/bidi/BidiClass.java271
-rw-r--r--src/java/org/apache/fop/complexscripts/bidi/BidiConstants.java91
-rw-r--r--src/java/org/apache/fop/complexscripts/bidi/BidiResolver.java242
-rw-r--r--src/java/org/apache/fop/complexscripts/bidi/DelimitedTextRange.java228
-rw-r--r--src/java/org/apache/fop/complexscripts/bidi/InlineRun.java310
-rw-r--r--src/java/org/apache/fop/complexscripts/bidi/TextInterval.java143
-rw-r--r--src/java/org/apache/fop/complexscripts/bidi/UnflattenProcessor.java361
-rw-r--r--src/java/org/apache/fop/complexscripts/bidi/UnicodeBidiAlgorithm.java839
-rw-r--r--src/java/org/apache/fop/complexscripts/fonts/AdvancedTypographicTableFormatException.java49
-rw-r--r--src/java/org/apache/fop/complexscripts/fonts/GlyphClassMapping.java48
-rw-r--r--src/java/org/apache/fop/complexscripts/fonts/GlyphClassTable.java277
-rw-r--r--src/java/org/apache/fop/complexscripts/fonts/GlyphCoverageMapping.java46
-rw-r--r--src/java/org/apache/fop/complexscripts/fonts/GlyphCoverageTable.java233
-rw-r--r--src/java/org/apache/fop/complexscripts/fonts/GlyphDefinition.java38
-rw-r--r--src/java/org/apache/fop/complexscripts/fonts/GlyphDefinitionSubtable.java76
-rw-r--r--src/java/org/apache/fop/complexscripts/fonts/GlyphDefinitionTable.java451
-rw-r--r--src/java/org/apache/fop/complexscripts/fonts/GlyphMappingTable.java322
-rw-r--r--src/java/org/apache/fop/complexscripts/fonts/GlyphPositioning.java43
-rw-r--r--src/java/org/apache/fop/complexscripts/fonts/GlyphPositioningState.java208
-rw-r--r--src/java/org/apache/fop/complexscripts/fonts/GlyphPositioningSubtable.java129
-rw-r--r--src/java/org/apache/fop/complexscripts/fonts/GlyphPositioningTable.java2264
-rw-r--r--src/java/org/apache/fop/complexscripts/fonts/GlyphProcessingState.java1135
-rw-r--r--src/java/org/apache/fop/complexscripts/fonts/GlyphSubstitution.java41
-rw-r--r--src/java/org/apache/fop/complexscripts/fonts/GlyphSubstitutionState.java230
-rw-r--r--src/java/org/apache/fop/complexscripts/fonts/GlyphSubstitutionSubtable.java124
-rw-r--r--src/java/org/apache/fop/complexscripts/fonts/GlyphSubstitutionTable.java1474
-rw-r--r--src/java/org/apache/fop/complexscripts/fonts/GlyphSubtable.java314
-rw-r--r--src/java/org/apache/fop/complexscripts/fonts/GlyphTable.java1300
-rw-r--r--src/java/org/apache/fop/complexscripts/fonts/IncompatibleSubtableException.java41
-rw-r--r--src/java/org/apache/fop/complexscripts/fonts/OTFAdvancedTypographicTableReader.java3797
-rw-r--r--src/java/org/apache/fop/complexscripts/fonts/Positionable.java58
-rw-r--r--src/java/org/apache/fop/complexscripts/fonts/Substitutable.java63
-rw-r--r--src/java/org/apache/fop/complexscripts/scripts/ArabicScriptProcessor.java522
-rw-r--r--src/java/org/apache/fop/complexscripts/scripts/DefaultScriptProcessor.java144
-rw-r--r--src/java/org/apache/fop/complexscripts/scripts/DevanagariScriptProcessor.java537
-rw-r--r--src/java/org/apache/fop/complexscripts/scripts/GujaratiScriptProcessor.java537
-rw-r--r--src/java/org/apache/fop/complexscripts/scripts/GurmukhiScriptProcessor.java543
-rw-r--r--src/java/org/apache/fop/complexscripts/scripts/IndicScriptProcessor.java589
-rw-r--r--src/java/org/apache/fop/complexscripts/scripts/ScriptProcessor.java234
-rw-r--r--src/java/org/apache/fop/complexscripts/util/CharMirror.java715
-rw-r--r--src/java/org/apache/fop/complexscripts/util/CharScript.java930
-rw-r--r--src/java/org/apache/fop/complexscripts/util/DiscontinuousAssociationException.java41
-rw-r--r--src/java/org/apache/fop/complexscripts/util/GlyphContextTester.java42
-rw-r--r--src/java/org/apache/fop/complexscripts/util/GlyphSequence.java1075
-rw-r--r--src/java/org/apache/fop/complexscripts/util/GlyphTester.java36
-rw-r--r--src/java/org/apache/fop/complexscripts/util/NumberConverter.java1616
-rw-r--r--src/java/org/apache/fop/complexscripts/util/ScriptContextTester.java35
-rw-r--r--src/java/org/apache/fop/complexscripts/util/UTF32.java128
-rw-r--r--src/java/org/apache/fop/fo/Constants.java22
-rw-r--r--src/java/org/apache/fop/fo/FONode.java119
-rw-r--r--src/java/org/apache/fop/fo/FOPropertyMapping.java162
-rw-r--r--src/java/org/apache/fop/fo/FOText.java343
-rw-r--r--src/java/org/apache/fop/fo/FObj.java63
-rw-r--r--src/java/org/apache/fop/fo/PropertyList.java84
-rw-r--r--src/java/org/apache/fop/fo/flow/AbstractGraphics.java20
-rw-r--r--src/java/org/apache/fop/fo/flow/AbstractPageNumberCitation.java16
-rw-r--r--src/java/org/apache/fop/fo/flow/BidiOverride.java138
-rw-r--r--src/java/org/apache/fop/fo/flow/Block.java64
-rw-r--r--src/java/org/apache/fop/fo/flow/BlockContainer.java85
-rw-r--r--src/java/org/apache/fop/fo/flow/Character.java15
-rw-r--r--src/java/org/apache/fop/fo/flow/InlineContainer.java89
-rw-r--r--src/java/org/apache/fop/fo/flow/InlineLevel.java17
-rw-r--r--src/java/org/apache/fop/fo/flow/Leader.java43
-rw-r--r--src/java/org/apache/fop/fo/flow/ListItem.java17
-rw-r--r--src/java/org/apache/fop/fo/flow/PageNumber.java6
-rw-r--r--src/java/org/apache/fop/fo/flow/Wrapper.java6
-rw-r--r--src/java/org/apache/fop/fo/flow/table/Table.java34
-rw-r--r--src/java/org/apache/fop/fo/pagination/AbstractPageSequence.java9
-rw-r--r--src/java/org/apache/fop/fo/pagination/PageNumberGenerator.java152
-rw-r--r--src/java/org/apache/fop/fo/pagination/PageSequence.java132
-rw-r--r--src/java/org/apache/fop/fo/pagination/Region.java27
-rw-r--r--src/java/org/apache/fop/fo/pagination/RegionAfter.java14
-rw-r--r--src/java/org/apache/fop/fo/pagination/RegionBA.java7
-rw-r--r--src/java/org/apache/fop/fo/pagination/RegionBefore.java13
-rw-r--r--src/java/org/apache/fop/fo/pagination/RegionBody.java16
-rw-r--r--src/java/org/apache/fop/fo/pagination/RegionEnd.java18
-rw-r--r--src/java/org/apache/fop/fo/pagination/RegionSE.java7
-rw-r--r--src/java/org/apache/fop/fo/pagination/RegionStart.java17
-rw-r--r--src/java/org/apache/fop/fo/pagination/SimplePageMaster.java27
-rw-r--r--src/java/org/apache/fop/fo/properties/CorrespondingPropertyMaker.java19
-rw-r--r--src/java/org/apache/fop/fo/properties/DimensionPropertyMaker.java23
-rw-r--r--src/java/org/apache/fop/fo/properties/IndentPropertyMaker.java13
-rw-r--r--src/java/org/apache/fop/fonts/BFEntry.java12
-rw-r--r--src/java/org/apache/fop/fonts/CustomFont.java17
-rw-r--r--src/java/org/apache/fop/fonts/CustomFontCollection.java5
-rw-r--r--src/java/org/apache/fop/fonts/EmbedFontInfo.java27
-rw-r--r--src/java/org/apache/fop/fonts/Font.java111
-rw-r--r--src/java/org/apache/fop/fonts/FontInfoConfigurator.java10
-rw-r--r--src/java/org/apache/fop/fonts/FontLoader.java29
-rw-r--r--src/java/org/apache/fop/fonts/FontManager.java36
-rw-r--r--src/java/org/apache/fop/fonts/FontManagerConfigurator.java12
-rw-r--r--src/java/org/apache/fop/fonts/FontReader.java17
-rw-r--r--src/java/org/apache/fop/fonts/FontResolver.java6
-rw-r--r--src/java/org/apache/fop/fonts/FontSetup.java52
-rw-r--r--src/java/org/apache/fop/fonts/LazyFont.java124
-rw-r--r--src/java/org/apache/fop/fonts/MultiByteFont.java351
-rw-r--r--src/java/org/apache/fop/fonts/MutableFont.java6
-rw-r--r--src/java/org/apache/fop/fonts/Typeface.java6
-rw-r--r--src/java/org/apache/fop/fonts/apps/AbstractFontReader.java6
-rw-r--r--src/java/org/apache/fop/fonts/apps/TTFReader.java22
-rw-r--r--src/java/org/apache/fop/fonts/autodetect/FontInfoFinder.java12
-rw-r--r--src/java/org/apache/fop/fonts/truetype/TTFDirTabEntry.java8
-rw-r--r--src/java/org/apache/fop/fonts/truetype/TTFFile.java214
-rw-r--r--src/java/org/apache/fop/fonts/truetype/TTFFontLoader.java25
-rw-r--r--src/java/org/apache/fop/fonts/truetype/TTFSubSetFile.java16
-rw-r--r--src/java/org/apache/fop/fonts/type1/Type1FontLoader.java2
-rw-r--r--src/java/org/apache/fop/layoutmgr/AbstractLayoutManager.java2
-rw-r--r--src/java/org/apache/fop/layoutmgr/BlockContainerLayoutManager.java15
-rw-r--r--src/java/org/apache/fop/layoutmgr/BlockLayoutManager.java3
-rw-r--r--src/java/org/apache/fop/layoutmgr/ExternalDocumentLayoutManager.java8
-rw-r--r--src/java/org/apache/fop/layoutmgr/LayoutContext.java7
-rw-r--r--src/java/org/apache/fop/layoutmgr/LayoutManagerMapping.java24
-rw-r--r--src/java/org/apache/fop/layoutmgr/PageProvider.java1
-rw-r--r--src/java/org/apache/fop/layoutmgr/PageSequenceLayoutManager.java7
-rw-r--r--src/java/org/apache/fop/layoutmgr/inline/AbstractGraphicsLayoutManager.java25
-rw-r--r--src/java/org/apache/fop/layoutmgr/inline/AbstractPageNumberCitationLayoutManager.java41
-rw-r--r--src/java/org/apache/fop/layoutmgr/inline/AlignmentContext.java15
-rw-r--r--src/java/org/apache/fop/layoutmgr/inline/BasicLinkLayoutManager.java6
-rw-r--r--src/java/org/apache/fop/layoutmgr/inline/BidiLayoutManager.java45
-rw-r--r--src/java/org/apache/fop/layoutmgr/inline/CharacterLayoutManager.java13
-rw-r--r--src/java/org/apache/fop/layoutmgr/inline/InlineLayoutManager.java6
-rw-r--r--src/java/org/apache/fop/layoutmgr/inline/InlineStackingLayoutManager.java4
-rw-r--r--src/java/org/apache/fop/layoutmgr/inline/LeaderLayoutManager.java30
-rw-r--r--src/java/org/apache/fop/layoutmgr/inline/LeafNodeLayoutManager.java2
-rw-r--r--src/java/org/apache/fop/layoutmgr/inline/LineLayoutManager.java63
-rw-r--r--src/java/org/apache/fop/layoutmgr/inline/PageNumberCitationLastLayoutManager.java8
-rw-r--r--src/java/org/apache/fop/layoutmgr/inline/PageNumberCitationLayoutManager.java19
-rw-r--r--src/java/org/apache/fop/layoutmgr/inline/PageNumberLayoutManager.java2
-rw-r--r--src/java/org/apache/fop/layoutmgr/inline/ScaledBaselineTable.java9
-rw-r--r--src/java/org/apache/fop/layoutmgr/inline/TextLayoutManager.java607
-rw-r--r--src/java/org/apache/fop/layoutmgr/list/ListBlockLayoutManager.java4
-rw-r--r--src/java/org/apache/fop/layoutmgr/list/ListItemContentLayoutManager.java6
-rw-r--r--src/java/org/apache/fop/layoutmgr/list/ListItemLayoutManager.java5
-rw-r--r--src/java/org/apache/fop/layoutmgr/table/ColumnSetup.java42
-rw-r--r--src/java/org/apache/fop/pdf/PDFTextUtil.java57
-rw-r--r--src/java/org/apache/fop/render/AbstractPathOrientedRenderer.java328
-rw-r--r--src/java/org/apache/fop/render/AbstractRenderer.java129
-rw-r--r--src/java/org/apache/fop/render/DefaultFontResolver.java5
-rw-r--r--src/java/org/apache/fop/render/PrintRenderer.java3
-rw-r--r--src/java/org/apache/fop/render/PrintRendererConfigurator.java7
-rw-r--r--src/java/org/apache/fop/render/afp/AFPPainter.java14
-rw-r--r--src/java/org/apache/fop/render/bitmap/BitmapRendererConfigurator.java3
-rw-r--r--src/java/org/apache/fop/render/intermediate/AbstractIFPainter.java52
-rw-r--r--src/java/org/apache/fop/render/intermediate/BorderPainter.java74
-rw-r--r--src/java/org/apache/fop/render/intermediate/IFPainter.java18
-rw-r--r--src/java/org/apache/fop/render/intermediate/IFParser.java10
-rw-r--r--src/java/org/apache/fop/render/intermediate/IFRenderer.java172
-rw-r--r--src/java/org/apache/fop/render/intermediate/IFSerializer.java37
-rw-r--r--src/java/org/apache/fop/render/intermediate/IFUtil.java178
-rw-r--r--src/java/org/apache/fop/render/java2d/ConfiguredFontCollection.java8
-rw-r--r--src/java/org/apache/fop/render/java2d/Java2DPainter.java12
-rw-r--r--src/java/org/apache/fop/render/java2d/Java2DRenderer.java7
-rw-r--r--src/java/org/apache/fop/render/pcl/PCLPainter.java31
-rw-r--r--src/java/org/apache/fop/render/pcl/PCLRendererConfigurator.java3
-rw-r--r--src/java/org/apache/fop/render/pdf/PDFPainter.java89
-rw-r--r--src/java/org/apache/fop/render/ps/AbstractPSTranscoder.java3
-rw-r--r--src/java/org/apache/fop/render/ps/NativeTextHandler.java3
-rw-r--r--src/java/org/apache/fop/render/ps/PSPainter.java25
-rw-r--r--src/java/org/apache/fop/render/rtf/RTFHandler.java3
-rw-r--r--src/java/org/apache/fop/render/xml/XMLRenderer.java46
-rw-r--r--src/java/org/apache/fop/svg/PDFDocumentGraphics2D.java3
-rw-r--r--src/java/org/apache/fop/svg/PDFDocumentGraphics2DConfigurator.java15
-rw-r--r--src/java/org/apache/fop/svg/PDFGraphics2D.java3
-rw-r--r--src/java/org/apache/fop/svg/PDFTranscoder.java3
-rw-r--r--src/java/org/apache/fop/text/linebreak/LineBreakUtils.java46
-rw-r--r--src/java/org/apache/fop/traits/Direction.java108
-rw-r--r--src/java/org/apache/fop/traits/WritingMode.java166
-rw-r--r--src/java/org/apache/fop/traits/WritingModeTraits.java157
-rw-r--r--src/java/org/apache/fop/traits/WritingModeTraitsGetter.java58
-rw-r--r--src/java/org/apache/fop/traits/WritingModeTraitsSetter.java70
-rw-r--r--src/java/org/apache/fop/util/CharUtilities.java158
-rw-r--r--src/java/org/apache/fop/util/XMLUtil.java122
201 files changed, 28751 insertions, 1466 deletions
diff --git a/src/java/org/apache/fop/apps/FOUserAgent.java b/src/java/org/apache/fop/apps/FOUserAgent.java
index 7d1736586..c13a624cf 100644
--- a/src/java/org/apache/fop/apps/FOUserAgent.java
+++ b/src/java/org/apache/fop/apps/FOUserAgent.java
@@ -654,6 +654,24 @@ public class FOUserAgent {
}
/**
+ * Check whether complex script features are enabled.
+ *
+ * @return true if FOP is to use complex script features
+ */
+ public boolean isComplexScriptFeaturesEnabled() {
+ return factory.isComplexScriptFeaturesEnabled();
+ }
+
+ /**
+ * Control whether complex script features should be enabled
+ *
+ * @param useComplexScriptFeatures true if FOP is to use complex script features
+ */
+ public void setComplexScriptFeaturesEnabled(boolean useComplexScriptFeatures) {
+ factory.setComplexScriptFeaturesEnabled ( useComplexScriptFeatures );
+ }
+
+ /**
* Activates accessibility (for output formats that support it).
*
* @param accessibility <code>true</code> to enable accessibility support
diff --git a/src/java/org/apache/fop/apps/FopFactory.java b/src/java/org/apache/fop/apps/FopFactory.java
index 2aca8bf99..18c84a036 100644
--- a/src/java/org/apache/fop/apps/FopFactory.java
+++ b/src/java/org/apache/fop/apps/FopFactory.java
@@ -143,6 +143,10 @@ public class FopFactory implements ImageContext {
/** Page width */
private String pageWidth = FopFactoryConfigurator.DEFAULT_PAGE_WIDTH;
+ /** Complex scripts support enabled */
+ private boolean useComplexScriptFeatures
+ = FopFactoryConfigurator.DEFAULT_COMPLEX_SCRIPT_FEATURES;
+
/** @see #setBreakIndentInheritanceOnReferenceAreaBoundary(boolean) */
private boolean breakIndentInheritanceOnReferenceAreaBoundary
= FopFactoryConfigurator.DEFAULT_BREAK_INDENT_INHERITANCE;
@@ -212,6 +216,19 @@ public class FopFactory implements ImageContext {
}
/**
+ * Sets complex script support.
+ * @param value <code>true</code> to enable complex script features,
+ * <code>false</code> otherwise
+ */
+ void setComplexScriptFeaturesEnabled(boolean value) {
+ this.useComplexScriptFeatures = value;
+ }
+
+ boolean isComplexScriptFeaturesEnabled() {
+ return useComplexScriptFeatures;
+ }
+
+ /**
* Returns a new {@link Fop} instance. FOP will be configured with a default user agent
* instance.
* <p>
diff --git a/src/java/org/apache/fop/apps/FopFactoryConfigurator.java b/src/java/org/apache/fop/apps/FopFactoryConfigurator.java
index 9097f23e3..4ed59d0fe 100644
--- a/src/java/org/apache/fop/apps/FopFactoryConfigurator.java
+++ b/src/java/org/apache/fop/apps/FopFactoryConfigurator.java
@@ -69,6 +69,9 @@ public class FopFactoryConfigurator {
/** Defines the default target resolution (72dpi) for FOP */
public static final float DEFAULT_TARGET_RESOLUTION = GraphicsConstants.DEFAULT_DPI; //dpi
+ /** Defines the default complex script support */
+ public static final boolean DEFAULT_COMPLEX_SCRIPT_FEATURES = true;
+
private static final String PREFER_RENDERER = "prefer-renderer";
/** logger instance */
@@ -269,6 +272,13 @@ public class FopFactoryConfigurator {
}
}
+ // configure complex script support
+ Configuration csConfig = cfg.getChild("complex-scripts");
+ if (csConfig != null) {
+ this.factory.setComplexScriptFeaturesEnabled
+ (!csConfig.getAttributeAsBoolean ( "disabled", false ));
+ }
+
// configure font manager
new FontManagerConfigurator(cfg, baseURI).configure(factory.getFontManager(), strict);
diff --git a/src/java/org/apache/fop/area/Area.java b/src/java/org/apache/fop/area/Area.java
index 1f14ed740..435876d65 100644
--- a/src/java/org/apache/fop/area/Area.java
+++ b/src/java/org/apache/fop/area/Area.java
@@ -25,6 +25,7 @@ import java.util.Map;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.fop.traits.BorderProps;
+import org.apache.fop.traits.WritingModeTraitsGetter;
// If the area appears more than once in the output
// or if the area has external data it is cached
@@ -41,27 +42,6 @@ public class Area extends AreaTreeObject implements Serializable {
private static final long serialVersionUID = 6342888466142626492L;
- // stacking directions
- /**
- * Stacking left to right
- */
- public static final int LR = 0;
-
- /**
- * Stacking right to left
- */
- public static final int RL = 1;
-
- /**
- * Stacking top to bottom
- */
- public static final int TB = 2;
-
- /**
- * Stacking bottom to top
- */
- public static final int BT = 3;
-
// orientations for reference areas
/**
* Normal orientation
@@ -130,16 +110,20 @@ public class Area extends AreaTreeObject implements Serializable {
protected int bpd;
/**
+ * Resolved bidirectional level for area.
+ */
+ protected int bidiLevel = -1;
+
+ /**
* Traits for this area stored in a HashMap
*/
- protected Map<Integer, Object> props = null;
+ protected transient Map<Integer, Object> traits = null;
/**
* logging instance
*/
protected static final Log log = LogFactory.getLog(Area.class);
-
/**
* Get the area class of this area.
*
@@ -226,6 +210,32 @@ public class Area extends AreaTreeObject implements Serializable {
}
/**
+ * Set the bidirectional embedding level.
+ *
+ * @param bidiLevel the bidirectional embedding level
+ */
+ public void setBidiLevel ( int bidiLevel ) {
+ this.bidiLevel = bidiLevel;
+ }
+
+ /**
+ * Reset the bidirectional embedding level to default
+ * value (-1).
+ */
+ public void resetBidiLevel() {
+ setBidiLevel(-1);
+ }
+
+ /**
+ * Get the bidirectional embedding level.
+ *
+ * @return the bidirectional embedding level
+ */
+ public int getBidiLevel() {
+ return bidiLevel;
+ }
+
+ /**
* Return the sum of region border- and padding-before
*
* @return width in millipoints
@@ -379,10 +389,23 @@ public class Area extends AreaTreeObject implements Serializable {
* @param prop the value of the trait
*/
public void addTrait(Integer traitCode, Object prop) {
- if (props == null) {
- props = new java.util.HashMap<Integer, Object>(20);
+ if (traits == null) {
+ traits = new java.util.HashMap<Integer, Object>(20);
+ }
+ traits.put(traitCode, prop);
+ }
+
+ /**
+ * Set traits on this area, copying from an existing traits map.
+ *
+ * @param traits the map of traits
+ */
+ public void setTraits ( Map traits ) {
+ if ( traits != null ) {
+ this.traits = new java.util.HashMap ( traits );
+ } else {
+ this.traits = null;
}
- props.put(traitCode, prop);
}
/**
@@ -391,12 +414,12 @@ public class Area extends AreaTreeObject implements Serializable {
* @return the map of traits
*/
public Map<Integer, Object> getTraits() {
- return this.props;
+ return this.traits;
}
/** @return true if the area has traits */
public boolean hasTraits() {
- return (this.props != null);
+ return (this.traits != null);
}
/**
@@ -406,7 +429,7 @@ public class Area extends AreaTreeObject implements Serializable {
* @return the trait value
*/
public Object getTrait(Integer traitCode) {
- return (props != null ? props.get(traitCode) : null);
+ return (traits != null ? traits.get(traitCode) : null);
}
/**
@@ -445,6 +468,14 @@ public class Area extends AreaTreeObject implements Serializable {
}
/**
+ * Sets the writing mode traits for this area. Default implementation
+ * does nothing.
+ * @param wmtg a WM traits getter
+ */
+ public void setWritingModeTraits(WritingModeTraitsGetter wmtg) {
+ }
+
+ /**
* {@inheritDoc}
* @return ipd and bpd of area
*/
@@ -457,4 +488,3 @@ public class Area extends AreaTreeObject implements Serializable {
return sb.toString();
}
}
-
diff --git a/src/java/org/apache/fop/area/AreaTreeHandler.java b/src/java/org/apache/fop/area/AreaTreeHandler.java
index 6448e7c5d..c87a728e6 100644
--- a/src/java/org/apache/fop/area/AreaTreeHandler.java
+++ b/src/java/org/apache/fop/area/AreaTreeHandler.java
@@ -73,6 +73,9 @@ public class AreaTreeHandler extends FOEventHandler {
/** The AreaTreeModel in use */
protected AreaTreeModel model;
+ // Flag for controlling complex script features (default: true).
+ private boolean useComplexScriptFeatures = true;
+
// Keeps track of all meaningful id references
private IDTracker idTracker;
@@ -108,6 +111,8 @@ public class AreaTreeHandler extends FOEventHandler {
this.idTracker = new IDTracker();
+ this.useComplexScriptFeatures = userAgent.isComplexScriptFeaturesEnabled();
+
if (log.isDebugEnabled()) {
statistics = new Statistics();
}
@@ -169,6 +174,15 @@ public class AreaTreeHandler extends FOEventHandler {
}
/**
+ * Check whether complex script features are enabled.
+ *
+ * @return true if using complex script features
+ */
+ public boolean isComplexScriptFeaturesEnabled() {
+ return useComplexScriptFeatures;
+ }
+
+ /**
* Prepare AreaTreeHandler for document processing This is called from
* FOTreeBuilder.startDocument()
*
diff --git a/src/java/org/apache/fop/area/AreaTreeParser.java b/src/java/org/apache/fop/area/AreaTreeParser.java
index d8c22f2e8..961160a55 100644
--- a/src/java/org/apache/fop/area/AreaTreeParser.java
+++ b/src/java/org/apache/fop/area/AreaTreeParser.java
@@ -700,7 +700,7 @@ public class AreaTreeParser {
public void startElement(Attributes attributes) {
InlineArea inl = new InlineArea();
transferForeignObjects(attributes, inl);
- inl.setOffset(XMLUtil.getAttributeAsInt(attributes, "offset", 0));
+ inl.setBlockProgressionOffset(XMLUtil.getAttributeAsInt(attributes, "offset", 0));
setAreaAttributes(attributes, inl);
setTraits(attributes, inl, SUBSET_COMMON);
setTraits(attributes, inl, SUBSET_BOX);
@@ -720,7 +720,7 @@ public class AreaTreeParser {
public void startElement(Attributes attributes) {
InlineParent ip = new InlineParent();
transferForeignObjects(attributes, ip);
- ip.setOffset(XMLUtil.getAttributeAsInt(attributes, "offset", 0));
+ ip.setBlockProgressionOffset(XMLUtil.getAttributeAsInt(attributes, "offset", 0));
setAreaAttributes(attributes, ip);
setTraits(attributes, ip, SUBSET_COMMON);
setTraits(attributes, ip, SUBSET_BOX);
@@ -741,7 +741,7 @@ public class AreaTreeParser {
public void startElement(Attributes attributes) {
InlineBlockParent ibp = new InlineBlockParent();
transferForeignObjects(attributes, ibp);
- ibp.setOffset(XMLUtil.getAttributeAsInt(attributes, "offset", 0));
+ ibp.setBlockProgressionOffset(XMLUtil.getAttributeAsInt(attributes, "offset", 0));
setAreaAttributes(attributes, ibp);
setTraits(attributes, ibp, SUBSET_COMMON);
setTraits(attributes, ibp, SUBSET_BOX);
@@ -769,7 +769,7 @@ public class AreaTreeParser {
setTraits(attributes, text, SUBSET_COLOR);
setTraits(attributes, text, SUBSET_FONT);
text.setBaselineOffset(XMLUtil.getAttributeAsInt(attributes, "baseline", 0));
- text.setOffset(XMLUtil.getAttributeAsInt(attributes, "offset", 0));
+ text.setBlockProgressionOffset(XMLUtil.getAttributeAsInt(attributes, "offset", 0));
text.setTextLetterSpaceAdjust(XMLUtil.getAttributeAsInt(attributes,
"tlsadjust", 0));
text.setTextWordSpaceAdjust(XMLUtil.getAttributeAsInt(attributes,
@@ -791,8 +791,14 @@ public class AreaTreeParser {
int[] letterAdjust
= 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(content.toString().trim(), offset, letterAdjust);
+ WordArea word = new WordArea
+ ( offset, level, content.toString().trim(), letterAdjust,
+ null, gposAdjustments, reversed );
AbstractTextArea text = getCurrentText();
word.setParentArea(text);
text.addChildArea(word);
@@ -811,7 +817,8 @@ public class AreaTreeParser {
if (content.position() > 0) {
content.flip();
boolean adjustable = XMLUtil.getAttributeAsBoolean(lastAttributes, "adj", true);
- SpaceArea space = new SpaceArea(content.charAt(0), offset, adjustable);
+ int level = XMLUtil.getAttributeAsInt(lastAttributes, "level", -1);
+ SpaceArea space = new SpaceArea(offset, level, content.charAt(0), adjustable);
AbstractTextArea text = getCurrentText();
space.setParentArea(text);
text.addChildArea(space);
@@ -821,7 +828,7 @@ public class AreaTreeParser {
setTraits(lastAttributes, space, SUBSET_COMMON);
setTraits(lastAttributes, space, SUBSET_BOX);
setTraits(lastAttributes, space, SUBSET_COLOR);
- space.setOffset(offset);
+ space.setBlockProgressionOffset(offset);
Area parent = (Area)areaStack.peek();
parent.addChildArea(space);
}
@@ -842,7 +849,8 @@ public class AreaTreeParser {
setTraits(attributes, leader, SUBSET_BOX);
setTraits(attributes, leader, SUBSET_COLOR);
setTraits(attributes, leader, SUBSET_FONT);
- leader.setOffset(XMLUtil.getAttributeAsInt(attributes, "offset", 0));
+ leader.setBlockProgressionOffset
+ ( XMLUtil.getAttributeAsInt(attributes, "offset", 0) );
String ruleStyle = attributes.getValue("ruleStyle");
if (ruleStyle != null) {
leader.setRuleStyle(ruleStyle);
@@ -857,7 +865,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);
@@ -865,7 +874,8 @@ public class AreaTreeParser {
setTraits(attributes, viewport, SUBSET_COLOR);
viewport.setContentPosition(XMLUtil.getAttributeAsRectangle2D(attributes, "pos"));
viewport.setClip(XMLUtil.getAttributeAsBoolean(attributes, "clip", false));
- viewport.setOffset(XMLUtil.getAttributeAsInt(attributes, "offset", 0));
+ viewport.setBlockProgressionOffset
+ ( XMLUtil.getAttributeAsInt(attributes, "offset", 0) );
Area parent = (Area)areaStack.peek();
parent.addChildArea(viewport);
areaStack.push(viewport);
@@ -1025,6 +1035,7 @@ public class AreaTreeParser {
private void setAreaAttributes(Attributes attributes, Area area) {
area.setIPD(Integer.parseInt(attributes.getValue("ipd")));
area.setBPD(Integer.parseInt(attributes.getValue("bpd")));
+ area.setBidiLevel(XMLUtil.getAttributeAsInt(attributes, "level", -1));
}
private static final Object[] SUBSET_COMMON = new Object[] {
diff --git a/src/java/org/apache/fop/area/Block.java b/src/java/org/apache/fop/area/Block.java
index 423dcfafa..c6e31f71d 100644
--- a/src/java/org/apache/fop/area/Block.java
+++ b/src/java/org/apache/fop/area/Block.java
@@ -58,7 +58,6 @@ public class Block extends BlockParent {
*/
public static final int FIXED = 3;
- private int stacking = TB;
private int positioning = STACK;
/** if true, allow BPD update */
@@ -133,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/BodyRegion.java b/src/java/org/apache/fop/area/BodyRegion.java
index 2dd8a9a7c..2fd0b014b 100644
--- a/src/java/org/apache/fop/area/BodyRegion.java
+++ b/src/java/org/apache/fop/area/BodyRegion.java
@@ -22,6 +22,7 @@ package org.apache.fop.area;
import java.util.List;
import org.apache.fop.fo.pagination.RegionBody;
+import org.apache.fop.traits.WritingModeTraitsGetter;
/**
* This class is a container for the areas that may be generated by
@@ -140,6 +141,17 @@ public class BodyRegion extends RegionReference {
}
/**
+ * Sets the writing mode traits for the main reference area of
+ * this body region area.
+ * @param wmtg a WM traits getter
+ */
+ public void setWritingModeTraits(WritingModeTraitsGetter wmtg) {
+ if ( getMainReference() != null ) {
+ getMainReference().setWritingModeTraits ( wmtg );
+ }
+ }
+
+ /**
* Clone this object.
*
* @return a shallow copy of this object
diff --git a/src/java/org/apache/fop/area/CTM.java b/src/java/org/apache/fop/area/CTM.java
index 07c3bbc02..73943ed65 100644
--- a/src/java/org/apache/fop/area/CTM.java
+++ b/src/java/org/apache/fop/area/CTM.java
@@ -25,10 +25,12 @@ import java.awt.geom.Rectangle2D;
import java.io.Serializable;
import org.apache.fop.datatypes.FODimension;
+import org.apache.fop.traits.WritingMode;
import static org.apache.fop.fo.Constants.EN_LR_TB;
import static org.apache.fop.fo.Constants.EN_RL_TB;
import static org.apache.fop.fo.Constants.EN_TB_RL;
+import static org.apache.fop.fo.Constants.EN_TB_LR;
/**
* Describe a PDF or PostScript style coordinate transformation matrix (CTM).
@@ -42,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);
/**
@@ -126,28 +128,25 @@ public class CTM implements Serializable {
* Return a CTM which will transform coordinates for a particular writing-mode
* into normalized first quandrant coordinates.
* @param wm A writing mode constant from fo.properties.WritingMode, ie.
- * one of LR_TB, RL_TB, TB_RL.
+ * one of LR_TB, RL_TB, TB_RL, TB_LR.
* @param ipd The inline-progression dimension of the reference area whose
* CTM is being set..
* @param bpd The block-progression dimension of the reference area whose
* CTM is being set.
* @return a new CTM with the required transform
*/
- public static CTM getWMctm(int wm, int ipd, int bpd) {
+ public static CTM getWMctm(WritingMode wm, int ipd, int bpd) {
CTM wmctm;
- switch (wm) {
+ switch (wm.getEnumValue()) {
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;
}
@@ -284,7 +283,7 @@ public class CTM implements Serializable {
* @return CTM the coordinate transformation matrix (CTM)
*/
public static CTM getCTMandRelDims(int absRefOrient,
- int writingMode,
+ WritingMode writingMode,
Rectangle2D absVPrect,
FODimension reldims) {
int width, height;
@@ -335,12 +334,18 @@ public class CTM implements Serializable {
* can set ipd and bpd appropriately based on the writing mode.
*/
- if (writingMode == EN_LR_TB || writingMode == EN_RL_TB) {
+ switch ( writingMode.getEnumValue() ) {
+ default:
+ case EN_LR_TB:
+ case EN_RL_TB:
reldims.ipd = width;
reldims.bpd = height;
- } else {
+ break;
+ case EN_TB_LR:
+ case EN_TB_RL:
reldims.ipd = height;
reldims.bpd = width;
+ break;
}
// Set a rectangle to be the writing-mode relative version???
// Now transform for writing mode
diff --git a/src/java/org/apache/fop/area/LineArea.java b/src/java/org/apache/fop/area/LineArea.java
index edf60578a..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;
@@ -125,6 +126,24 @@ public class LineArea extends Area {
}
/**
+ * <p>Set (en masse) the inline child areas of this line area.</p>
+ * <p> Used by bidirectional processing after line area consituent reordering.</p>
+ * @param inlineAreas the list of inline areas
+ */
+ public void setInlineAreas ( List inlineAreas ) {
+ for ( Iterator<InlineArea> 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;
+ }
+
+ /**
* Get the inline child areas of this line area.
*
* @return the list of inline areas
@@ -149,6 +168,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.
*/
public void updateExtentsFromChildren() {
@@ -178,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
@@ -198,7 +236,7 @@ public class LineArea extends Area {
// if the LineArea has already been added to the area tree,
// call finalize(); otherwise, wait for the LineLM to call it
if (adjustingInfo.bAddedToAreaTree) {
- finalise();
+ finish();
}
break;
default:
@@ -211,7 +249,7 @@ public class LineArea extends Area {
* and destroy the AdjustingInfo object if there are
* no UnresolvedAreas left
*/
- public void finalise() {
+ public void finish() {
if (adjustingInfo.lineAlignment == EN_JUSTIFY) {
if (log.isTraceEnabled()) {
log.trace("Applying variation factor to justified line: " + adjustingInfo);
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<Resolvable> 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<Resolvable>();
+ }
+ dependents.add(dependent);
+ }
+
+ private void resolveDependents(String id, PageViewport pv) {
+ if ( dependents != null ) {
+ List<PageViewport> pages = new ArrayList<PageViewport>();
+ pages.add(pv);
+ for ( Resolvable r : dependents ) {
+ r.resolveIDRef(id, pages);
+ }
+ }
+ }
+
}
diff --git a/src/java/org/apache/fop/area/MainReference.java b/src/java/org/apache/fop/area/MainReference.java
index a6112011d..44f4cc8c8 100644
--- a/src/java/org/apache/fop/area/MainReference.java
+++ b/src/java/org/apache/fop/area/MainReference.java
@@ -19,6 +19,8 @@
package org.apache.fop.area;
+import org.apache.fop.traits.WritingModeTraitsGetter;
+
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
@@ -124,5 +126,16 @@ public class MainReference extends Area {
return parent.getColumnGap();
}
+ /**
+ * Sets the writing mode traits for the spans of this main
+ * reference area.
+ * @param wmtg a WM traits getter
+ */
+ public void setWritingModeTraits(WritingModeTraitsGetter wmtg) {
+ for ( Span s : (List<Span>) getSpans() ) {
+ s.setWritingModeTraits ( wmtg );
+ }
+ }
+
}
diff --git a/src/java/org/apache/fop/area/Page.java b/src/java/org/apache/fop/area/Page.java
index a1d9e389f..3a08809dc 100644
--- a/src/java/org/apache/fop/area/Page.java
+++ b/src/java/org/apache/fop/area/Page.java
@@ -33,6 +33,7 @@ import org.apache.fop.fo.pagination.RegionBody;
import org.apache.fop.fo.pagination.SimplePageMaster;
import org.apache.fop.fo.properties.CommonMarginBlock;
import org.apache.fop.layoutmgr.TraitSetter;
+import org.apache.fop.traits.WritingModeTraitsGetter;
import static org.apache.fop.fo.Constants.FO_REGION_AFTER;
import static org.apache.fop.fo.Constants.FO_REGION_BEFORE;
@@ -304,6 +305,29 @@ public class Page extends AreaTreeObject implements Serializable, Cloneable {
return unresolved;
}
+ /**
+ * Sets the writing mode traits for the region viewports of
+ * this page.
+ * @param wmtg a WM traits getter
+ */
+ public void setWritingModeTraits(WritingModeTraitsGetter wmtg) {
+ if (regionBefore != null) {
+ regionBefore.setWritingModeTraits(wmtg);
+ }
+ if (regionStart != null) {
+ regionStart.setWritingModeTraits(wmtg);
+ }
+ if (regionBody != null) {
+ regionBody.setWritingModeTraits(wmtg);
+ }
+ if (regionEnd != null) {
+ regionEnd.setWritingModeTraits(wmtg);
+ }
+ if (regionAfter != null) {
+ regionAfter.setWritingModeTraits(wmtg);
+ }
+ }
+
}
diff --git a/src/java/org/apache/fop/area/PageViewport.java b/src/java/org/apache/fop/area/PageViewport.java
index ff4b2fdaa..f335fa128 100644
--- a/src/java/org/apache/fop/area/PageViewport.java
+++ b/src/java/org/apache/fop/area/PageViewport.java
@@ -34,6 +34,7 @@ import org.apache.commons.logging.LogFactory;
import org.apache.fop.fo.flow.Marker;
import org.apache.fop.fo.pagination.SimplePageMaster;
+import org.apache.fop.traits.WritingModeTraitsGetter;
import static org.apache.fop.fo.Constants.FO_REGION_BODY;
import static org.apache.fop.fo.Constants.EN_FSWP;
@@ -654,4 +655,15 @@ public class PageViewport extends AreaTreeObject implements Resolvable, Cloneabl
public RegionReference getRegionReference(int id) {
return getPage().getRegionViewport(id).getRegionReference();
}
+
+ /**
+ * Sets the writing mode traits for the page associated with this viewport.
+ * @param wmtg a WM traits getter
+ */
+ public void setWritingModeTraits(WritingModeTraitsGetter wmtg) {
+ if ( page != null ) {
+ page.setWritingModeTraits(wmtg);
+ }
+ }
+
}
diff --git a/src/java/org/apache/fop/area/RegionViewport.java b/src/java/org/apache/fop/area/RegionViewport.java
index 6cc700a7b..0104b53ad 100644
--- a/src/java/org/apache/fop/area/RegionViewport.java
+++ b/src/java/org/apache/fop/area/RegionViewport.java
@@ -19,6 +19,8 @@
package org.apache.fop.area;
+import org.apache.fop.traits.WritingModeTraitsGetter;
+
import java.awt.Rectangle;
import java.awt.geom.Rectangle2D;
import java.io.IOException;
@@ -106,7 +108,7 @@ public class RegionViewport extends Area implements Cloneable, Viewport {
out.writeFloat((float) viewArea.getWidth());
out.writeFloat((float) viewArea.getHeight());
out.writeBoolean(clip);
- out.writeObject(props);
+ out.writeObject(traits);
out.writeObject(regionReference);
}
@@ -115,7 +117,7 @@ public class RegionViewport extends Area implements Cloneable, Viewport {
viewArea = new Rectangle2D.Float(in.readFloat(), in.readFloat(),
in.readFloat(), in.readFloat());
clip = in.readBoolean();
- props = (HashMap)in.readObject();
+ traits = (HashMap)in.readObject();
setRegionReference((RegionReference) in.readObject());
}
@@ -128,13 +130,25 @@ public class RegionViewport extends Area implements Cloneable, Viewport {
public Object clone() {
RegionViewport rv = new RegionViewport((Rectangle2D)viewArea.clone());
rv.regionReference = (RegionReference)regionReference.clone();
- if (props != null) {
- rv.props = new HashMap(props);
+ if (traits != null) {
+ rv.traits = new HashMap(traits);
}
if (foreignAttributes != null) {
rv.foreignAttributes = new HashMap(foreignAttributes);
}
return rv;
}
+
+ /**
+ * Sets the writing mode traits for the region reference of
+ * this region viewport
+ * @param wmtg a WM traits getter
+ */
+ public void setWritingModeTraits(WritingModeTraitsGetter wmtg) {
+ if (regionReference != null) {
+ regionReference.setWritingModeTraits(wmtg);
+ }
+ }
+
}
diff --git a/src/java/org/apache/fop/area/Span.java b/src/java/org/apache/fop/area/Span.java
index c2fd4679c..f25140097 100644
--- a/src/java/org/apache/fop/area/Span.java
+++ b/src/java/org/apache/fop/area/Span.java
@@ -19,8 +19,12 @@
package org.apache.fop.area;
+import java.util.Iterator;
import java.util.List;
+import org.apache.fop.fo.Constants;
+import org.apache.fop.traits.WritingModeTraitsGetter;
+
/**
* The span-reference-area.
* This is a block-area with 0 border and padding that is stacked
@@ -183,6 +187,28 @@ public class Span extends Area {
return (areaCount == 0);
}
+ /**
+ * Sets the writing mode traits for the main reference area of
+ * this span area.
+ * @param wmtg a WM traits getter
+ */
+ public void setWritingModeTraits(WritingModeTraitsGetter wmtg) {
+ switch ( wmtg.getColumnProgressionDirection().getEnumValue() ) {
+ case Constants.EN_RL:
+ setBidiLevel(1);
+ for ( Iterator<NormalFlow> it = flowAreas.iterator(); it.hasNext();) {
+ it.next().setBidiLevel(1);
+ }
+ break;
+ default:
+ resetBidiLevel();
+ for ( Iterator<NormalFlow> it = flowAreas.iterator(); it.hasNext();) {
+ it.next().resetBidiLevel();
+ }
+ break;
+ }
+ }
+
/** {@inheritDoc} */
@Override
public String toString() {
diff --git a/src/java/org/apache/fop/area/Trait.java b/src/java/org/apache/fop/area/Trait.java
index d9194559d..9942a1370 100644
--- a/src/java/org/apache/fop/area/Trait.java
+++ b/src/java/org/apache/fop/area/Trait.java
@@ -26,6 +26,8 @@ import org.apache.xmlgraphics.image.loader.ImageInfo;
import org.apache.fop.fonts.FontTriplet;
import org.apache.fop.traits.BorderProps;
+import org.apache.fop.traits.Direction;
+import org.apache.fop.traits.WritingMode;
import org.apache.fop.util.ColorUtil;
import static org.apache.fop.fo.Constants.EN_REPEAT;
@@ -156,8 +158,19 @@ public final class Trait implements Serializable {
/** For navigation in the document structure. */
public static final Integer STRUCTURE_TREE_ELEMENT = 37;
+ /** writing mode trait */
+ public static final Integer WRITING_MODE = 38;
+ /** inline progression direction trait */
+ public static final Integer INLINE_PROGRESSION_DIRECTION = 39;
+ /** block progression direction trait */
+ public static final Integer BLOCK_PROGRESSION_DIRECTION = 40;
+ /** column progression direction trait */
+ public static final Integer COLUMN_PROGRESSION_DIRECTION = 41;
+ /** shift direction trait */
+ public static final Integer SHIFT_DIRECTION = 42;
+
/** Maximum value used by trait keys */
- public static final int MAX_TRAIT_KEY = 37;
+ public static final int MAX_TRAIT_KEY = 42;
private static final TraitInfo[] TRAIT_INFO = new TraitInfo[MAX_TRAIT_KEY + 1];
@@ -221,6 +234,14 @@ public final class Trait implements Serializable {
put(SPACE_AFTER, new TraitInfo("space-after", Integer.class));
put(IS_REFERENCE_AREA, new TraitInfo("is-reference-area", Boolean.class));
put(IS_VIEWPORT_AREA, new TraitInfo("is-viewport-area", Boolean.class));
+ put(WRITING_MODE,
+ new TraitInfo("writing-mode", WritingMode.class));
+ put(INLINE_PROGRESSION_DIRECTION,
+ new TraitInfo("inline-progression-direction", Direction.class));
+ put(BLOCK_PROGRESSION_DIRECTION,
+ new TraitInfo("block-progression-direction", Direction.class));
+ put(SHIFT_DIRECTION,
+ new TraitInfo("shift-direction", Direction.class));
}
diff --git a/src/java/org/apache/fop/area/inline/AbstractTextArea.java b/src/java/org/apache/fop/area/inline/AbstractTextArea.java
index 1558e8160..348820939 100644
--- a/src/java/org/apache/fop/area/inline/AbstractTextArea.java
+++ b/src/java/org/apache/fop/area/inline/AbstractTextArea.java
@@ -188,7 +188,7 @@ public abstract class AbstractTextArea extends InlineParent {
@Override
int getVirtualOffset() {
- return getOffset();
+ return getBlockProgressionOffset();
}
@Override
diff --git a/src/java/org/apache/fop/area/inline/BasicLinkArea.java b/src/java/org/apache/fop/area/inline/BasicLinkArea.java
index fce913944..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);
@@ -42,12 +45,28 @@ public class BasicLinkArea extends InlineParent {
* called after all of the children areas have been added to this area.
*/
/* Make this area start at its beforest child. */
- setOffset(getOffset() + minChildOffset);
+ setBlockProgressionOffset(getBlockProgressionOffset() + minChildOffset);
/* Update children offsets accordingly. */
for (InlineArea inline : inlines) {
- inline.setOffset(inline.getOffset() - minChildOffset);
+ inline.setBlockProgressionOffset(inline.getBlockProgressionOffset() - minChildOffset);
}
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/FilledArea.java b/src/java/org/apache/fop/area/inline/FilledArea.java
index e0207ada6..dfb3af58b 100644
--- a/src/java/org/apache/fop/area/inline/FilledArea.java
+++ b/src/java/org/apache/fop/area/inline/FilledArea.java
@@ -50,10 +50,9 @@ public class FilledArea extends InlineParent {
* @param v the offset
*/
/*
- public void setOffset(int v) {
+ public void setBlockProgressionOffset(int v) {
setChildOffset(inlines.listIterator(), v);
}
- */
private void setChildOffset(ListIterator childrenIterator, int v) {
while (childrenIterator.hasNext()) {
@@ -63,10 +62,11 @@ public class FilledArea extends InlineParent {
} else if (child instanceof InlineViewport) {
// nothing
} else {
- child.setOffset(v);
+ child.setBlockProgressionOffset(v);
}
}
}
+ */
/**
* Set the unit width for the areas to fill the full width.
@@ -129,5 +129,6 @@ public class FilledArea extends InlineParent {
setIPD(getIPD() + adjustingInfo.applyVariationFactor(variationFactor));
return false;
}
+
}
diff --git a/src/java/org/apache/fop/area/inline/InlineArea.java b/src/java/org/apache/fop/area/inline/InlineArea.java
index d62e6f721..5fa156ac8 100644
--- a/src/java/org/apache/fop/area/inline/InlineArea.java
+++ b/src/java/org/apache/fop/area/inline/InlineArea.java
@@ -20,10 +20,12 @@
package org.apache.fop.area.inline;
import java.io.Serializable;
+import java.util.List;
import org.apache.fop.area.Area;
import org.apache.fop.area.LineArea;
import org.apache.fop.area.Trait;
+import org.apache.fop.complexscripts.bidi.InlineRun;
/**
* Inline Area
@@ -79,7 +81,7 @@ public class InlineArea extends Area {
/**
* offset position from before edge of parent area
*/
- protected int offset = 0;
+ protected int blockProgressionOffset = 0;
/**
* parent area
@@ -101,6 +103,23 @@ public class InlineArea extends Area {
protected InlineAdjustingInfo adjustingInfo = null;
/**
+ * Default constructor for inline area.
+ */
+ public InlineArea() {
+ this ( 0, -1 );
+ }
+
+ /**
+ * Instantiate inline area.
+ * @param blockProgressionOffset a block progression offset or zero
+ * @param bidiLevel a resolved bidi level or -1
+ */
+ protected InlineArea ( int blockProgressionOffset, int bidiLevel ) {
+ this.blockProgressionOffset = blockProgressionOffset;
+ setBidiLevel(bidiLevel);
+ }
+
+ /**
* @return the adjustment information object
*/
public InlineAdjustingInfo getAdjustingInfo() {
@@ -138,25 +157,25 @@ public class InlineArea extends Area {
}
/**
- * Set the offset of this inline area.
+ * Set the block progression offset of this inline area.
* This is used to set the offset of the inline area
* which is relative to the before edge of the parent area.
*
- * @param offset the offset
+ * @param blockProgressionOffset the offset
*/
- public void setOffset(int offset) {
- this.offset = offset;
+ public void setBlockProgressionOffset(int blockProgressionOffset) {
+ this.blockProgressionOffset = blockProgressionOffset;
}
/**
- * Get the offset of this inline area.
+ * Get the block progression offset of this inline area.
* This returns the offset of the inline area
- * which is relative to the before edge of the parent area.
+ * relative to the before edge of the parent area.
*
- * @return the offset
+ * @return the blockProgressionOffset
*/
- public int getOffset() {
- return offset;
+ public int getBlockProgressionOffset() {
+ return blockProgressionOffset;
}
/**
@@ -266,7 +285,7 @@ public class InlineArea extends Area {
* @see BasicLinkArea
*/
int getVirtualOffset() {
- return getOffset();
+ return getBlockProgressionOffset();
}
/**
@@ -278,5 +297,43 @@ public class InlineArea extends Area {
int getVirtualBPD() {
return getBPD();
}
-}
+ /**
+ * Collection bidi inline runs.
+ * @param runs current list of inline runs
+ * @return modified list of inline runs, having appended new run
+ */
+ public List collectInlineRuns ( List runs ) {
+ assert runs != null;
+ runs.add ( new InlineRun ( this, new int[] {getBidiLevel()}) );
+ return runs;
+ }
+
+ /**
+ * Determine if inline area IA is an ancestor inline area or same as this area.
+ * @param ia inline area to test
+ * @return true if specified inline area is an ancestor or same as this area
+ */
+ public boolean isAncestorOrSelf ( InlineArea ia ) {
+ return ( ia == this ) || isAncestor ( ia );
+ }
+
+ /**
+ * Determine if inline area IA is an ancestor inline area of this area.
+ * @param ia inline area to test
+ * @return true if specified inline area is an ancestor of this area
+ */
+ public boolean isAncestor ( InlineArea ia ) {
+ for ( Area p = getParentArea(); p != null;) {
+ if ( p == ia ) {
+ return true;
+ } else if ( p instanceof InlineArea ) {
+ p = ( (InlineArea) p ).getParentArea();
+ } else {
+ p = null;
+ }
+ }
+ return false;
+ }
+
+}
diff --git a/src/java/org/apache/fop/area/inline/InlineParent.java b/src/java/org/apache/fop/area/inline/InlineParent.java
index 521080469..0e1f32dff 100644
--- a/src/java/org/apache/fop/area/inline/InlineParent.java
+++ b/src/java/org/apache/fop/area/inline/InlineParent.java
@@ -20,6 +20,7 @@
package org.apache.fop.area.inline;
import java.util.List;
+import java.util.Iterator;
import org.apache.fop.area.Area;
@@ -62,6 +63,7 @@ public class InlineParent extends InlineArea {
if (autoSize) {
increaseIPD(childArea.getAllocIPD());
}
+ updateLevel ( childArea.getBidiLevel() );
int childOffset = childArea.getVirtualOffset();
minChildOffset = Math.min(minChildOffset, childOffset);
maxAfterEdge = Math.max(maxAfterEdge, childOffset + childArea.getVirtualBPD());
@@ -69,7 +71,7 @@ public class InlineParent extends InlineArea {
@Override
int getVirtualOffset() {
- return getOffset() + minChildOffset;
+ return getBlockProgressionOffset() + minChildOffset;
}
@Override
@@ -110,5 +112,37 @@ public class InlineParent extends InlineArea {
return hasUnresolvedAreas;
}
-}
+ @Override
+ public List collectInlineRuns ( List runs ) {
+ for ( Iterator<InlineArea> it = getChildAreas().iterator(); it.hasNext();) {
+ InlineArea ia = it.next();
+ runs = ia.collectInlineRuns ( runs );
+ }
+ return runs;
+ }
+
+ /**
+ * Reset bidirectionality level of all children to default (-1),
+ * signalling that they will inherit the level of their parent text area.
+ */
+ public void resetChildrenLevel() {
+ for ( Iterator it = inlines.iterator(); it.hasNext();) {
+ ( (InlineArea) it.next() ) .resetBidiLevel();
+ }
+ }
+ private void updateLevel ( int newLevel ) {
+ if ( newLevel >= 0 ) {
+ int curLevel = getBidiLevel();
+ if ( curLevel >= 0 ) {
+ if ( newLevel < curLevel ) {
+ setBidiLevel ( newLevel );
+ }
+ } else {
+ setBidiLevel ( newLevel );
+ }
+ }
+ }
+
+
+}
diff --git a/src/java/org/apache/fop/area/inline/InlineViewport.java b/src/java/org/apache/fop/area/inline/InlineViewport.java
index 68cc9a797..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;
}
@@ -121,7 +132,7 @@ public class InlineViewport extends InlineArea implements Viewport {
out.writeFloat((float) contentPosition.getHeight());
}
out.writeBoolean(clip);
- out.writeObject(props);
+ out.writeObject(traits);
out.writeObject(content);
}
@@ -134,7 +145,7 @@ public class InlineViewport extends InlineArea implements Viewport {
in.readFloat());
}
this.clip = in.readBoolean();
- this.props = (HashMap) in.readObject();
+ this.traits = (HashMap) in.readObject();
this.content = (Area) in.readObject();
}
diff --git a/src/java/org/apache/fop/area/inline/Space.java b/src/java/org/apache/fop/area/inline/Space.java
index bf683bb22..b097e4349 100644
--- a/src/java/org/apache/fop/area/inline/Space.java
+++ b/src/java/org/apache/fop/area/inline/Space.java
@@ -27,4 +27,10 @@ public class Space extends InlineArea {
private static final long serialVersionUID = -8748265505356839796L;
+ /**
+ * Default constructor.
+ */
+ public Space() {
+ }
+
}
diff --git a/src/java/org/apache/fop/area/inline/SpaceArea.java b/src/java/org/apache/fop/area/inline/SpaceArea.java
index ebfcc5ec8..c3786ec0d 100644
--- a/src/java/org/apache/fop/area/inline/SpaceArea.java
+++ b/src/java/org/apache/fop/area/inline/SpaceArea.java
@@ -38,14 +38,15 @@ public class SpaceArea extends InlineArea {
/**
* Create a space area
- * @param s the space character
- * @param o the offset for the next area
- * @param a is this space adjustable?
+ * @param space the space character
+ * @param blockProgressionOffset the offset for the next area
+ * @param adjustable is this space adjustable?
+ * @param bidiLevel the bidirectional embedding level (or -1 if not defined)
*/
- public SpaceArea(char s, int o, boolean a) {
- space = s;
- offset = o;
- isAdjustable = a;
+ public SpaceArea(int blockProgressionOffset, int bidiLevel, char space, boolean adjustable) {
+ super ( blockProgressionOffset, bidiLevel );
+ this.space = space;
+ this.isAdjustable = adjustable;
}
/** @return Returns the space. */
@@ -57,4 +58,5 @@ public class SpaceArea extends InlineArea {
public boolean isAdjustable() {
return this.isAdjustable;
}
+
}
diff --git a/src/java/org/apache/fop/area/inline/TextArea.java b/src/java/org/apache/fop/area/inline/TextArea.java
index 15d005025..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,10 @@
package org.apache.fop.area.inline;
+import java.util.Arrays;
+
+import org.apache.fop.util.CharUtilities;
+
/**
* A text inline area.
*/
@@ -57,7 +61,7 @@ public class TextArea extends AbstractTextArea {
* @param offset the offset for the next area
*/
public void addWord(String word, int offset) {
- addWord(word, offset, null);
+ addWord(word, 0, null, null, null, offset);
}
/**
@@ -65,25 +69,51 @@ public class TextArea extends AbstractTextArea {
*
* @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 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
*/
- public void addWord(String word, int offset, int[] letterAdjust) {
- WordArea wordArea = new WordArea(word, offset, letterAdjust);
+ public void addWord
+ ( String word, int ipd, int[] letterAdjust, int[] levels,
+ int[][] gposAdjustments, int blockProgressionOffset ) {
+ int minWordLevel = findMinLevel ( levels );
+ WordArea wordArea = new WordArea
+ ( blockProgressionOffset, minWordLevel, word, letterAdjust, levels, gposAdjustments );
+ wordArea.setIPD ( ipd );
addChildArea(wordArea);
wordArea.setParentArea(this);
+ updateLevel(minWordLevel);
}
/**
* Create and add a SpaceArea child to this TextArea
*
- * @param space the space character
- * @param offset the offset for the next area
+ * @param space the space character
+ * @param ipd the space's ipd
+ * @param blockProgressionOffset the offset for the next area
* @param adjustable is this space adjustable?
+ * @param level resolved bidirection level of space character
*/
- public void addSpace(char space, int offset, boolean adjustable) {
- SpaceArea spaceArea = new SpaceArea(space, offset, adjustable);
+ public void addSpace
+ ( char space, int ipd, boolean adjustable, int blockProgressionOffset, int level ) {
+ SpaceArea spaceArea = new SpaceArea(blockProgressionOffset, level, space, adjustable);
+ spaceArea.setIPD ( ipd );
addChildArea(spaceArea);
spaceArea.setParentArea(this);
+ updateLevel(level);
}
/**
@@ -113,7 +143,55 @@ public class TextArea extends AbstractTextArea {
/** {@inheritDoc} */
@Override
public String toString() {
- return "TextArea{text=" + getText() + "}";
+ StringBuffer sb = new StringBuffer(super.toString());
+ sb.append(" {text=\"");
+ sb.append(CharUtilities.toNCRefs(getText()));
+ sb.append("\"");
+ sb.append("}");
+ return sb.toString();
}
+
+ private void updateLevel ( int newLevel ) {
+ if ( newLevel >= 0 ) {
+ int curLevel = getBidiLevel();
+ if ( curLevel >= 0 ) {
+ if ( newLevel < curLevel ) {
+ setBidiLevel ( newLevel );
+ }
+ } else {
+ setBidiLevel ( newLevel );
+ }
+ }
+ }
+
+ private static int findMinLevel ( int[] levels ) {
+ if ( levels != null ) {
+ int lMin = Integer.MAX_VALUE;
+ for ( int i = 0, n = levels.length; i < n; i++ ) {
+ int l = levels [ i ];
+ if ( ( l >= 0 ) && ( l < lMin ) ) {
+ lMin = l;
+ }
+ }
+ if ( lMin == Integer.MAX_VALUE ) {
+ return -1;
+ } else {
+ return lMin;
+ }
+ } else {
+ return -1;
+ }
+ }
+
+ 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..71a3dfa62 100644
--- a/src/java/org/apache/fop/area/inline/UnresolvedPageNumber.java
+++ b/src/java/org/apache/fop/area/inline/UnresolvedPageNumber.java
@@ -19,12 +19,13 @@
package org.apache.fop.area.inline;
+import java.util.List;
+
import org.apache.fop.area.PageViewport;
import org.apache.fop.area.Resolvable;
+import org.apache.fop.complexscripts.bidi.InlineRun;
import org.apache.fop.fonts.Font;
-import java.util.List;
-
/**
* Unresolvable page number area.
* This is a word area that resolves itself to a page number
@@ -83,12 +84,24 @@ public class UnresolvedPageNumber extends TextArea implements Resolvable {
}
/**
+ * 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
* string from the first page in the list of pages that apply
* 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 +116,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());
@@ -137,4 +150,21 @@ public class UnresolvedPageNumber extends TextArea implements Resolvable {
int lineStretch, int lineShrink) {
return true;
}
+
+ /**
+ * Collection bidi inline runs.
+ * Override of @{link InlineParent} implementation.
+ *
+ * N.B. [GA] without this override, the page-number-citation_writing_mode_rl
+ * layout engine test will fail. It may be that the test needs to
+ * be updated rather than using this override.
+ * @param runs current list of inline runs
+ * @return modified list of inline runs, having appended new run
+ */
+ @Override
+ public List collectInlineRuns ( List runs ) {
+ assert runs != null;
+ runs.add ( new InlineRun ( this, new int[] {getBidiLevel()}) );
+ return runs;
+ }
}
diff --git a/src/java/org/apache/fop/area/inline/WordArea.java b/src/java/org/apache/fop/area/inline/WordArea.java
index 7f62fe8d7..00ddc9e1d 100644
--- a/src/java/org/apache/fop/area/inline/WordArea.java
+++ b/src/java/org/apache/fop/area/inline/WordArea.java
@@ -19,6 +19,12 @@
package org.apache.fop.area.inline;
+import java.util.Arrays;
+import java.util.List;
+
+import org.apache.fop.complexscripts.bidi.InlineRun;
+import org.apache.fop.complexscripts.util.CharMirror;
+
/**
* A string of characters without spaces
*/
@@ -29,22 +35,63 @@ public class WordArea extends InlineArea {
/** The text for this word area */
protected String word;
- /** The correction offset for the next area */
- protected int offset = 0;
-
/** An array of width for adjusting the individual letters (optional) */
protected int[] letterAdjust;
/**
+ * An array of resolved bidirectional levels corresponding to each character
+ * in word (optional)
+ */
+ protected int[] levels;
+
+ /**
+ * An array of glyph positioning adjustments to apply to each glyph 'char' in word (optional)
+ */
+ protected int[][] gposAdjustments;
+
+ /**
+ * A flag indicating whether the content of word is reversed in relation to
+ * its original logical order.
+ */
+ protected boolean 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
+ * @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, 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 = reversed;
+ }
+
+ /**
* Create a word area
- * @param w the word string
- * @param o the offset for the next area
- * @param la the letter adjust array (may be null)
+ * @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(String w, int o, int[] la) {
- word = w;
- offset = o;
- this.letterAdjust = la;
+ 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. */
@@ -52,20 +99,211 @@ public class WordArea extends InlineArea {
return word;
}
- /** @return Returns the offset. */
- @Override
- public int getOffset() {
- return offset;
+ /** @return the array of letter adjust widths */
+ public int[] getLetterAdjustArray() {
+ return this.letterAdjust;
}
- /** @param o The offset to set. */
+
+ /**
+ * Obtain per-character (glyph) bidi levels.
+ * @return a (possibly empty) array of levels or null (if none resolved)
+ */
+ public int[] getBidiLevels() {
+ return levels;
+ }
+
+ /**
+ * <p>Obtain per-character (glyph) bidi levels over a specified subsequence.</p>
+ * <p>If word has been reversed, then the subsequence is over the reversed word.</p>
+ * @param start starting (inclusive) index of subsequence
+ * @param end ending (exclusive) index of subsequence
+ * @return a (possibly null) array of per-character (glyph) levels over the specified
+ * sequence
+ */
+ public int[] getBidiLevels ( int start, int end ) {
+ assert start <= end;
+ if ( this.levels != null ) {
+ int n = end - start;
+ int[] levels = new int [ n ];
+ for ( int i = 0; i < n; i++ ) {
+ levels[i] = this.levels [ start + i ];
+ }
+ return levels;
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * <p>Obtain per-character (glyph) level at a specified index position.</p>
+ * <p>If word has been reversed, then the position is relative to the reversed word.</p>
+ * @param position the index of the (possibly reversed) character from which to obtain the
+ * level
+ * @return a resolved bidirectional level or, if not specified, then -1
+ */
+ public int bidiLevelAt ( int position ) {
+ if ( position > word.length() ) {
+ throw new IndexOutOfBoundsException();
+ } else if ( levels != null ) {
+ return levels [ position ];
+ } else {
+ return -1;
+ }
+ }
+
@Override
- public void setOffset(int o) {
- offset = o;
+ public List collectInlineRuns ( List runs ) {
+ assert runs != null;
+ InlineRun r;
+ if ( getBidiLevels() != null ) {
+ r = new InlineRun ( this, getBidiLevels() );
+ } else {
+ r = new InlineRun ( this, -1, word.length() );
+ }
+ runs.add ( r );
+ return runs;
}
- /** @return the array of letter adjust widths */
- public int[] getLetterAdjustArray() {
- return this.letterAdjust;
+ /**
+ * Obtain per-character (glyph) position adjustments.
+ * @return a (possibly empty) array of adjustments, each having four elements, or null
+ * if no adjustments apply
+ */
+ public int[][] getGlyphPositionAdjustments() {
+ return gposAdjustments;
+ }
+
+ /**
+ * <p>Obtain per-character (glyph) position adjustments at a specified index position.</p>
+ * <p>If word has been reversed, then the position is relative to the reversed word.</p>
+ * @param position the index of the (possibly reversed) character from which to obtain the
+ * level
+ * @return an array of adjustments or null if none applies
+ */
+ public int[] glyphPositionAdjustmentsAt ( int position ) {
+ if ( position > word.length() ) {
+ throw new IndexOutOfBoundsException();
+ } else if ( gposAdjustments != null ) {
+ return gposAdjustments [ position ];
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * <p>Reverse characters and corresponding per-character levels and glyph position
+ * adjustments.</p>
+ * @param mirror if true, then perform mirroring if mirrorred characters
+ */
+ public void reverse ( boolean mirror ) {
+ if ( word.length() > 0 ) {
+ word = ( ( new StringBuffer ( word ) ) .reverse() ) .toString();
+ if ( levels != null ) {
+ reverse ( levels );
+ }
+ if ( gposAdjustments != null ) {
+ reverse ( gposAdjustments );
+ }
+ reversed = !reversed;
+ if ( mirror ) {
+ word = CharMirror.mirror ( word );
+ }
+ }
+ }
+
+ /**
+ * <p>Perform mirroring on mirrorable characters.</p>
+ */
+ public void mirror() {
+ if ( word.length() > 0 ) {
+ word = CharMirror.mirror ( word );
+ }
+ }
+
+ /**
+ * <p>Determined if word has been reversed (in relation to original logical order).</p>
+ * <p>If a word is reversed, then both its characters (glyphs) and corresponding per-character
+ * levels are in reverse order.</p>
+ * <p>Note: this information is used in order to process non-spacing marks during rendering as
+ * well as provide hints for caret direction.</p>
+ * @return true if word is reversed
+ */
+ public boolean isReversed() {
+ return reversed;
+ }
+
+ /*
+ * If int[] array is not of specified length, then create
+ * a new copy of the first length entries.
+ */
+ private static int[] maybeAdjustLength ( int[] ia, int length ) {
+ if ( ia != null ) {
+ if ( ia.length == length ) {
+ return ia;
+ } else {
+ int[] iaNew = new int [ length ];
+ for ( int i = 0, n = ia.length; i < n; i++ ) {
+ if ( i < length ) {
+ iaNew [ i ] = ia [ i ];
+ } else {
+ break;
+ }
+ }
+ return iaNew;
+ }
+ } else {
+ return ia;
+ }
+ }
+
+ /*
+ * If int[][] matrix is not of specified length, then create
+ * a new shallow copy of the first length entries.
+ */
+ private static int[][] maybeAdjustLength ( int[][] im, int length ) {
+ if ( im != null ) {
+ if ( im.length == length ) {
+ return im;
+ } else {
+ int[][] imNew = new int [ length ][];
+ for ( int i = 0, n = im.length; i < n; i++ ) {
+ if ( i < length ) {
+ imNew [ i ] = im [ i ];
+ } else {
+ break;
+ }
+ }
+ return imNew;
+ }
+ } else {
+ return im;
+ }
+ }
+
+ private static int[] maybePopulateLevels ( int[] levels, int level, int count ) {
+ if ( ( levels == null ) && ( level >= 0 ) ) {
+ levels = new int[count];
+ Arrays.fill ( levels, level );
+ }
+ return maybeAdjustLength ( levels, count );
+ }
+
+ private static void reverse ( int[] a ) {
+ for ( int i = 0, n = a.length, m = n / 2; i < m; i++ ) {
+ int k = n - i - 1;
+ int t = a [ k ];
+ a [ k ] = a [ i ];
+ a [ i ] = t;
+ }
+ }
+
+ private static void reverse ( int[][] aa ) {
+ for ( int i = 0, n = aa.length, m = n / 2; i < m; i++ ) {
+ int k = n - i - 1;
+ int[] t = aa [ k ];
+ aa [ k ] = aa [ i ];
+ aa [ i ] = t;
+ }
}
}
diff --git a/src/java/org/apache/fop/cli/CommandLineOptions.java b/src/java/org/apache/fop/cli/CommandLineOptions.java
index 845c11a0d..bfcdb163e 100644
--- a/src/java/org/apache/fop/cli/CommandLineOptions.java
+++ b/src/java/org/apache/fop/cli/CommandLineOptions.java
@@ -118,6 +118,8 @@ public class CommandLineOptions {
private int targetResolution = 0;
/* control memory-conservation policy */
private boolean conserveMemoryPolicy = false;
+ /* true if a complex script features are enabled */
+ private boolean useComplexScriptFeatures = true;
private FopFactory factory = FopFactory.newInstance();
private FOUserAgent foUserAgent;
@@ -181,6 +183,9 @@ public class CommandLineOptions {
addXSLTParameter("fop-output-format", getOutputFormat());
addXSLTParameter("fop-version", Version.getVersion());
foUserAgent.setConserveMemoryPolicy(conserveMemoryPolicy);
+ if (!useComplexScriptFeatures) {
+ foUserAgent.setComplexScriptFeaturesEnabled(false);
+ }
} else {
return false;
}
@@ -378,6 +383,8 @@ public class CommandLineOptions {
getPDFEncryptionParams().setAllowEditContent(false);
} else if (args[i].equals("-noannotations")) {
getPDFEncryptionParams().setAllowEditAnnotations(false);
+ } else if (args[i].equals("-nocs")) {
+ useComplexScriptFeatures = false;
} else if (args[i].equals("-nofillinforms")) {
getPDFEncryptionParams().setAllowFillInForms(false);
} else if (args[i].equals("-noaccesscontent")) {
@@ -1175,6 +1182,7 @@ public class CommandLineOptions {
+ " -q quiet mode \n"
+ " -c cfg.xml use additional configuration file cfg.xml\n"
+ " -l lang the language to use for user information \n"
+ + " -nocs disable complex script features\n"
+ " -r relaxed/less strict validation (where available)\n"
+ " -dpi xxx target resolution in dots per inch (dpi) where xxx is a number\n"
+ " -s for area tree XML, down to block areas only\n"
diff --git a/src/java/org/apache/fop/complexscripts/bidi/BidiClass.java b/src/java/org/apache/fop/complexscripts/bidi/BidiClass.java
new file mode 100644
index 000000000..6e462eadc
--- /dev/null
+++ b/src/java/org/apache/fop/complexscripts/bidi/BidiClass.java
@@ -0,0 +1,271 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id: License.java 1039179 2010-11-25 21:04:09Z vhennebert $ */
+
+package org.apache.fop.complexscripts.bidi;
+
+import java.util.Arrays;
+import org.apache.fop.complexscripts.bidi.BidiConstants;
+
+// CSOFF: WhitespaceAfterCheck
+// CSOFF: LineLengthCheck
+
+/*
+ * !!! THIS IS A GENERATED FILE !!!
+ * If updates to the source are needed, then:
+ * - apply the necessary modifications to
+ * 'src/codegen/unicode/java/org/apache/fop/complexscripts/bidi/GenerateBidiClass.java'
+ * - run 'ant codegen-unicode', which will generate a new BidiClass.java
+ * in 'src/java/org/apache/fop/complexscripts/bidi'
+ * - commit BOTH changed files
+ */
+
+/** Bidirectional class utilities. */
+public final class BidiClass {
+
+private BidiClass() {
+}
+
+private static byte[] bcL1 = {
+15,15,15,15,15,15,15,15,15,17,16,17,18,16,15,15,15,15,15,15,15,15,15,15,15,15,15,15,16,16,16,17,18,19,19,11,11,11,19,19,19,
+19,19,10,13,10,13,13,9,9,9,9,9,9,9,9,9,9,13,19,19,19,19,19,19,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,19,19,19,
+19,19,19,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,19,19,19,19,15,15,15,15,15,15,16,15,15,15,15,15,15,15,15,15,15,
+15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,13,19,11,11,11,11,19,19,19,19,1,19,19,15,19,19,11,11,9,9,19,1,19,19,19,9,1,
+19,19,19,19,19,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,19,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
+1,1,19,1,1,1,1,1,1,1,1
+};
+
+private static byte[] bcR1 = {
+4,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,
+14,14,14,14,14,4,14,4,14,14,4,14,14,4,14,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,
+4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,12,12,12,12,5,5,19,19,5,11,11,5,13,5,19,19,14,14,14,14,14,14,14,14,14,14,14,5,5,5,5,5,5,5,5,
+5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,14,14,14,14,14,14,14,14,14,14,14,14,14,14,
+14,14,14,14,14,14,14,12,12,12,12,12,12,12,12,12,12,11,12,12,5,5,5,14,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,
+5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,
+5,5,5,5,5,5,5,5,5,5,5,5,5,14,14,14,14,14,14,14,12,19,14,14,14,14,14,14,5,5,14,14,19,14,14,14,14,5,5,9,9,9,9,9,9,9,9,9,9,5,
+5,5,5,5,5
+};
+
+private static int[] bcS1 = {
+256,443,444,448,452,660,661,688,697,699,706,710,720,722,736,741,748,749,750,751,768,880,884,885,886,890,891,894,900,902,903,
+904,908,910,931,1014,1015,1154,1155,1160,1162,1329,1369,1370,1377,1417,1418,1792,1806,1807,1808,1809,1810,1840,1867,1869,1958,
+1969,1970,1984,1994,2027,2036,2038,2039,2042,2043,2048,2070,2074,2075,2084,2085,2088,2089,2094,2096,2111,2112,2137,2140,2142,
+2143,2304,2307,2308,2362,2363,2364,2365,2366,2369,2377,2381,2382,2384,2385,2392,2402,2404,2406,2416,2417,2418,2425,2433,2434,
+2437,2447,2451,2474,2482,2486,2492,2493,2494,2497,2503,2507,2509,2510,2519,2524,2527,2530,2534,2544,2546,2548,2554,2555,2561,
+2563,2565,2575,2579,2602,2610,2613,2616,2620,2622,2625,2631,2635,2641,2649,2654,2662,2672,2674,2677,2689,2691,2693,2703,2707,
+2730,2738,2741,2748,2749,2750,2753,2759,2761,2763,2765,2768,2784,2786,2790,2801,2817,2818,2821,2831,2835,2858,2866,2869,2876,
+2877,2878,2879,2880,2881,2887,2891,2893,2902,2903,2908,2911,2914,2918,2928,2929,2930,2946,2947,2949,2958,2962,2969,2972,2974,
+2979,2984,2990,3006,3008,3009,3014,3018,3021,3024,3031,3046,3056,3059,3065,3066,3073,3077,3086,3090,3114,3125,3133,3134,3137,
+3142,3146,3157,3160,3168,3170,3174,3192,3199,3202,3205,3214,3218,3242,3253,3260,3261,3262,3263,3264,3270,3271,3274,3276,3285,
+3294,3296,3298,3302,3313,3330,3333,3342,3346,3389,3390,3393,3398,3402,3405,3406,3415,3424,3426,3430,3440,3449,3450,3458,3461,
+3482,3507,3517,3520,3530,3535,3538,3542,3544,3570,3572,3585,3633,3634,3636,3647,3648,3654,3655,3663,3664,3674,3713,3716,3719,
+3722,3725,3732,3737,3745,3749,3751,3754,3757,3761,3762,3764,3771,3773,3776,3782,3784,3792,3804,3840,3841,3844,3859,3864,3866,
+3872,3882,3892,3893,3894,3895,3896,3897,3898,3899,3900,3901,3902,3904,3913,3953,3967,3968,3973,3974,3976,3981,3993,4030,4038,
+4039,4046,4048,4053,4057,4096,4139,4141,4145,4146,4152,4153,4155,4157,4159,4160,4170,4176,4182,4184,4186,4190,4193,4194,4197,
+4199,4206,4209,4213,4226,4227,4229,4231,4237,4238,4239,4240,4250,4253,4254,4256,4304,4347,4348,4352,4682,4688,4696,4698,4704,
+4746,4752,4786,4792,4800,4802,4808,4824,4882,4888,4957,4960,4961,4969,4992,5008,5024,5120,5121,5741,5743,5760,5761,5787,5788,
+5792,5867,5870,5888,5902,5906,5920,5938,5941,5952,5970,5984,5998,6002,6016,6068,6070,6071,6078,6086,6087,6089,6100,6103,6104,
+6107,6108,6109,6112,6128,6144,6150,6151,6155,6158,6160,6176,6211,6212,6272,6313,6314,6320,6400,6432,6435,6439,6441,6448,6450,
+6451,6457,6464,6468,6470,6480,6512,6528,6576,6593,6600,6608,6618,6622,6656,6679,6681,6686,6688,6741,6742,6743,6744,6752,6753,
+6754,6755,6757,6765,6771,6783,6784,6800,6816,6823,6824,6912,6916,6917,6964,6965,6966,6971,6972,6973,6978,6979,6981,6992,7002,
+7009,7019,7028,7040,7042,7043,7073,7074,7078,7080,7082,7086,7088,7104,7142,7143,7144,7146,7149,7150,7151,7154,7164,7168,7204,
+7212,7220,7222,7227,7232,7245,7248,7258,7288,7294,7376,7379,7380,7393,7394,7401,7405,7406,7410,7424,7468,7522,7544,7545,7579,
+7616,7676,7680,7960,7968,8008,8016,8025,8027,8029,8031,8064,8118,8125,8126,8127,8130,8134,8141,8144,8150,8157,8160,8173,8178,
+8182,8189,8192,8203,8206,8207,8208,8214,8216,8217,8218,8219,8221,8222,8223,8224,8232,8233,8234,8235,8236,8237,8238,8239,8240,
+8245,8249,8250,8251,8255,8257,8260,8261,8262,8263,8274,8275,8276,8277,8287,8288,8293,8298,8304,8305,8308,8314,8316,8317,8318,
+8319,8320,8330,8332,8333,8334,8336,8352,8400,8413,8417,8418,8421,8448,8450,8451,8455,8456,8458,8468,8469,8470,8472,8473,8478,
+8484,8485,8486,8487,8488,8489,8490,8494,8495,8501,8505,8506,8508,8512,8517,8522,8523,8524,8526,8527,8528,8544,8579,8581,8585,
+8592,8597,8602,8604,8608,8609,8611,8612,8614,8615,8622,8623,8654,8656,8658,8659,8660,8661,8692,8722,8723,8724,8960,8968,8972,
+8992,8994,9001,9002,9003,9014,9083,9084,9085,9109,9110,9115,9140,9180,9186,9216,9280,9312,9352,9372,9450,9472,9655,9656,9665,
+9666,9720,9728,9839,9840,9900,9901,9985,10088,10089,10090,10091,10092,10093,10094,10095,10096,10097,10098,10099,10100,10101,
+10102,10132,10176,10181,10182,10183,10188,10190,10214,10215,10216,10217,10218,10219,10220,10221,10222,10223,10224,10240,10496,
+10627,10628,10629,10630,10631,10632,10633,10634,10635,10636,10637,10638,10639,10640,10641,10642,10643,10644,10645,10646,10647,
+10648,10649,10712,10713,10714,10715,10716,10748,10749,10750,11008,11056,11077,11079,11088,11264,11312,11360,11389,11390,11493,
+11499,11503,11513,11517,11518,11520,11568,11631,11632,11647,11648,11680,11688,11696,11704,11712,11720,11728,11736,11744,11776,
+11778,11779,11780,11781,11782,11785,11786,11787,11788,11789,11790,11799,11800,11802,11803,11804,11805,11806,11808,11809,11810,
+11811,11812,11813,11814,11815,11816,11817,11818,11823,11824,11904,11931,12032,12272,12288,12289,12292,12293,12294,12295,12296,
+12297,12298,12299,12300,12301,12302,12303,12304,12305,12306,12308,12309,12310,12311,12312,12313,12314,12315,12316,12317,12318,
+12320,12321,12330,12336,12337,12342,12344,12347,12348,12349,12350,12353,12441,12443,12445,12447,12448,12449,12539,12540,12543,
+12549,12593,12688,12690,12694,12704,12736,12784,12800,12829,12832,12842,12880,12881,12896,12924,12927,12928,12938,12977,12992,
+13004,13008,13056,13175,13179,13278,13280,13311,13312,19904,19968,40960,40981,40982,42128,42192,42232,42238,42240,42508,42509,
+42512,42528,42538,42560,42606,42607,42608,42611,42620,42622,42623,42624,42656,42726,42736,42738,42752,42775,42784,42786,42864,
+42865,42888,42889,42891,42896,42912,43002,43003,43010,43011,43014,43015,43019,43020,43043,43045,43047,43048,43056,43062,43064,
+43065,43072,43124,43136,43138,43188,43204,43214,43216,43232,43250,43256,43259,43264,43274,43302,43310,43312,43335,43346,43359,
+43360,43392,43395,43396,43443,43444,43446,43450,43452,43453,43457,43471,43472,43486,43520,43561,43567,43569,43571,43573,43584,
+43587,43588,43596,43597,43600,43612,43616,43632,43633,43639,43642,43643,43648,43696,43697,43698,43701,43703,43705,43710,43712,
+43713,43714,43739,43741,43742,43777,43785,43793,43808,43816,43968,44003,44005,44006,44008,44009,44011,44012,44013,44016,44032,
+55216,55243,57344,63744,64048,64112,64256,64275,64285,64286,64287,64297,64298,64311,64312,64317,64318,64319,64320,64322,64323,
+64325,64326,64336,64434,64450,64467,64830,64831,64832,64848,64912,64914,64968,64976,65008,65020,65021,65022,65024,65040,65047,
+65048,65049,65056,65072,65073,65075,65077,65078,65079,65080,65081,65082,65083,65084,65085,65086,65087,65088,65089,65090,65091,
+65092,65093,65095,65096,65097,65101,65104,65105,65106,65108,65109,65110,65112,65113,65114,65115,65116,65117,65118,65119,65120,
+65122,65123,65124,65128,65129,65130,65131,65136,65141,65142,65277,65279,65281,65283,65284,65285,65286,65288,65289,65290,65291,
+65292,65293,65294,65296,65306,65307,65308,65311,65313,65339,65340,65341,65342,65343,65344,65345,65371,65372,65373,65374,65375,
+65376,65377,65378,65379,65380,65382,65392,65393,65438,65440,65474,65482,65490,65498,65504,65506,65507,65508,65509,65512,65513,
+65517,65520,65529,65532,65534,65536,65549,65576,65596,65599,65616,65664,65792,65793,65794,65799,65847,65856,65909,65913,65930,
+65936,66000,66045,66176,66208,66304,66336,66352,66369,66370,66378,66432,66463,66464,66504,66512,66513,66560,66640,66720,67584,
+67590,67592,67593,67594,67638,67639,67641,67644,67645,67647,67670,67671,67672,67680,67840,67862,67868,67871,67872,67898,67903,
+67904,68096,68097,68100,68101,68103,68108,68112,68116,68117,68120,68121,68148,68152,68155,68159,68160,68168,68176,68185,68192,
+68221,68223,68224,68352,68406,68409,68416,68438,68440,68448,68467,68472,68480,68608,68681,69216,69247,69632,69633,69634,69635,
+69688,69703,69714,69734,69760,69762,69763,69808,69811,69815,69817,69819,69821,69822,73728,74752,74864,77824,92160,110592,118784,
+119040,119081,119141,119143,119146,119149,119155,119163,119171,119173,119180,119210,119214,119296,119362,119365,119552,119648,
+119808,119894,119966,119970,119973,119977,119982,119995,119997,120005,120071,120077,120086,120094,120123,120128,120134,120138,
+120146,120488,120513,120514,120539,120540,120571,120572,120597,120598,120629,120630,120655,120656,120687,120688,120713,120714,
+120745,120746,120771,120772,120782,124928,126976,127024,127136,127153,127169,127185,127232,127248,127280,127344,127462,127504,
+127552,127568,127744,127792,127799,127872,127904,127942,127968,128000,128064,128066,128140,128141,128249,128256,128292,128293,
+128336,128507,128513,128530,128534,128536,128538,128540,128544,128552,128557,128560,128565,128581,128640,128768,131070,131072,
+173824,177984,194560,196606,262142,327678,393214,458750,524286,589822,655358,720894,786430,851966,917502,917505,917506,917536,
+917632,917760,918000,983038,983040,1048574,1048576,1114110
+};
+
+private static int[] bcE1 = {
+442,443,447,451,659,660,687,696,698,705,709,719,721,735,740,747,748,749,750,767,879,883,884,885,887,890,893,894,901,902,903,
+906,908,929,1013,1014,1153,1154,1159,1161,1319,1366,1369,1375,1415,1417,1418,1805,1806,1807,1808,1809,1839,1866,1868,1957,
+1968,1969,1983,1993,2026,2035,2037,2038,2041,2042,2047,2069,2073,2074,2083,2084,2087,2088,2093,2095,2110,2111,2136,2139,2141,
+2142,2303,2306,2307,2361,2362,2363,2364,2365,2368,2376,2380,2381,2383,2384,2391,2401,2403,2405,2415,2416,2417,2423,2431,2433,
+2435,2444,2448,2472,2480,2482,2489,2492,2493,2496,2500,2504,2508,2509,2510,2519,2525,2529,2531,2543,2545,2547,2553,2554,2555,
+2562,2563,2570,2576,2600,2608,2611,2614,2617,2620,2624,2626,2632,2637,2641,2652,2654,2671,2673,2676,2677,2690,2691,2701,2705,
+2728,2736,2739,2745,2748,2749,2752,2757,2760,2761,2764,2765,2768,2785,2787,2799,2801,2817,2819,2828,2832,2856,2864,2867,2873,
+2876,2877,2878,2879,2880,2884,2888,2892,2893,2902,2903,2909,2913,2915,2927,2928,2929,2935,2946,2947,2954,2960,2965,2970,2972,
+2975,2980,2986,3001,3007,3008,3010,3016,3020,3021,3024,3031,3055,3058,3064,3065,3066,3075,3084,3088,3112,3123,3129,3133,3136,
+3140,3144,3149,3158,3161,3169,3171,3183,3198,3199,3203,3212,3216,3240,3251,3257,3260,3261,3262,3263,3268,3270,3272,3275,3277,
+3286,3294,3297,3299,3311,3314,3331,3340,3344,3386,3389,3392,3396,3400,3404,3405,3406,3415,3425,3427,3439,3445,3449,3455,3459,
+3478,3505,3515,3517,3526,3530,3537,3540,3542,3551,3571,3572,3632,3633,3635,3642,3647,3653,3654,3662,3663,3673,3675,3714,3716,
+3720,3722,3725,3735,3743,3747,3749,3751,3755,3760,3761,3763,3769,3772,3773,3780,3782,3789,3801,3805,3840,3843,3858,3863,3865,
+3871,3881,3891,3892,3893,3894,3895,3896,3897,3898,3899,3900,3901,3903,3911,3948,3966,3967,3972,3973,3975,3980,3991,4028,4037,
+4038,4044,4047,4052,4056,4058,4138,4140,4144,4145,4151,4152,4154,4156,4158,4159,4169,4175,4181,4183,4185,4189,4192,4193,4196,
+4198,4205,4208,4212,4225,4226,4228,4230,4236,4237,4238,4239,4249,4252,4253,4255,4293,4346,4347,4348,4680,4685,4694,4696,4701,
+4744,4749,4784,4789,4798,4800,4805,4822,4880,4885,4954,4959,4960,4968,4988,5007,5017,5108,5120,5740,5742,5759,5760,5786,5787,
+5788,5866,5869,5872,5900,5905,5908,5937,5940,5942,5969,5971,5996,6000,6003,6067,6069,6070,6077,6085,6086,6088,6099,6102,6103,
+6106,6107,6108,6109,6121,6137,6149,6150,6154,6157,6158,6169,6210,6211,6263,6312,6313,6314,6389,6428,6434,6438,6440,6443,6449,
+6450,6456,6459,6464,6469,6479,6509,6516,6571,6592,6599,6601,6617,6618,6655,6678,6680,6683,6687,6740,6741,6742,6743,6750,6752,
+6753,6754,6756,6764,6770,6780,6783,6793,6809,6822,6823,6829,6915,6916,6963,6964,6965,6970,6971,6972,6977,6978,6980,6987,7001,
+7008,7018,7027,7036,7041,7042,7072,7073,7077,7079,7081,7082,7087,7097,7141,7142,7143,7145,7148,7149,7150,7153,7155,7167,7203,
+7211,7219,7221,7223,7231,7241,7247,7257,7287,7293,7295,7378,7379,7392,7393,7400,7404,7405,7409,7410,7467,7521,7543,7544,7578,
+7615,7654,7679,7957,7965,8005,8013,8023,8025,8027,8029,8061,8116,8124,8125,8126,8129,8132,8140,8143,8147,8155,8159,8172,8175,
+8180,8188,8190,8202,8205,8206,8207,8213,8215,8216,8217,8218,8220,8221,8222,8223,8231,8232,8233,8234,8235,8236,8237,8238,8239,
+8244,8248,8249,8250,8254,8256,8259,8260,8261,8262,8273,8274,8275,8276,8286,8287,8292,8297,8303,8304,8305,8313,8315,8316,8317,
+8318,8319,8329,8331,8332,8333,8334,8348,8377,8412,8416,8417,8420,8432,8449,8450,8454,8455,8457,8467,8468,8469,8471,8472,8477,
+8483,8484,8485,8486,8487,8488,8489,8493,8494,8500,8504,8505,8507,8511,8516,8521,8522,8523,8525,8526,8527,8543,8578,8580,8584,
+8585,8596,8601,8603,8607,8608,8610,8611,8613,8614,8621,8622,8653,8655,8657,8658,8659,8660,8691,8721,8722,8723,8959,8967,8971,
+8991,8993,9000,9001,9002,9013,9082,9083,9084,9108,9109,9114,9139,9179,9185,9203,9254,9290,9351,9371,9449,9471,9654,9655,9664,
+9665,9719,9727,9838,9839,9899,9900,9983,10087,10088,10089,10090,10091,10092,10093,10094,10095,10096,10097,10098,10099,10100,
+10101,10131,10175,10180,10181,10182,10186,10188,10213,10214,10215,10216,10217,10218,10219,10220,10221,10222,10223,10239,10495,
+10626,10627,10628,10629,10630,10631,10632,10633,10634,10635,10636,10637,10638,10639,10640,10641,10642,10643,10644,10645,10646,
+10647,10648,10711,10712,10713,10714,10715,10747,10748,10749,11007,11055,11076,11078,11084,11097,11310,11358,11388,11389,11492,
+11498,11502,11505,11516,11517,11519,11557,11621,11631,11632,11647,11670,11686,11694,11702,11710,11718,11726,11734,11742,11775,
+11777,11778,11779,11780,11781,11784,11785,11786,11787,11788,11789,11798,11799,11801,11802,11803,11804,11805,11807,11808,11809,
+11810,11811,11812,11813,11814,11815,11816,11817,11822,11823,11825,11929,12019,12245,12283,12288,12291,12292,12293,12294,12295,
+12296,12297,12298,12299,12300,12301,12302,12303,12304,12305,12307,12308,12309,12310,12311,12312,12313,12314,12315,12316,12317,
+12319,12320,12329,12335,12336,12341,12343,12346,12347,12348,12349,12351,12438,12442,12444,12446,12447,12448,12538,12539,12542,
+12543,12589,12686,12689,12693,12703,12730,12771,12799,12828,12830,12841,12879,12880,12895,12923,12926,12927,12937,12976,12991,
+13003,13007,13054,13174,13178,13277,13279,13310,13311,19893,19967,40907,40980,40981,42124,42182,42231,42237,42239,42507,42508,
+42511,42527,42537,42539,42605,42606,42607,42610,42611,42621,42622,42623,42647,42725,42735,42737,42743,42774,42783,42785,42863,
+42864,42887,42888,42890,42894,42897,42921,43002,43009,43010,43013,43014,43018,43019,43042,43044,43046,43047,43051,43061,43063,
+43064,43065,43123,43127,43137,43187,43203,43204,43215,43225,43249,43255,43258,43259,43273,43301,43309,43311,43334,43345,43347,
+43359,43388,43394,43395,43442,43443,43445,43449,43451,43452,43456,43469,43471,43481,43487,43560,43566,43568,43570,43572,43574,
+43586,43587,43595,43596,43597,43609,43615,43631,43632,43638,43641,43642,43643,43695,43696,43697,43700,43702,43704,43709,43711,
+43712,43713,43714,43740,43741,43743,43782,43790,43798,43814,43822,44002,44004,44005,44007,44008,44010,44011,44012,44013,44025,
+55203,55238,55291,63743,64045,64109,64217,64262,64279,64285,64286,64296,64297,64310,64311,64316,64317,64318,64319,64321,64322,
+64324,64325,64335,64433,64449,64466,64829,64830,64831,64847,64911,64913,64967,64975,65007,65019,65020,65021,65023,65039,65046,
+65047,65048,65049,65062,65072,65074,65076,65077,65078,65079,65080,65081,65082,65083,65084,65085,65086,65087,65088,65089,65090,
+65091,65092,65094,65095,65096,65100,65103,65104,65105,65106,65108,65109,65111,65112,65113,65114,65115,65116,65117,65118,65119,
+65121,65122,65123,65126,65128,65129,65130,65131,65140,65141,65276,65278,65279,65282,65283,65284,65285,65287,65288,65289,65290,
+65291,65292,65293,65295,65305,65306,65307,65310,65312,65338,65339,65340,65341,65342,65343,65344,65370,65371,65372,65373,65374,
+65375,65376,65377,65378,65379,65381,65391,65392,65437,65439,65470,65479,65487,65495,65500,65505,65506,65507,65508,65510,65512,
+65516,65518,65528,65531,65533,65535,65547,65574,65594,65597,65613,65629,65786,65792,65793,65794,65843,65855,65908,65912,65929,
+65930,65947,66044,66045,66204,66256,66334,66339,66368,66369,66377,66378,66461,66463,66499,66511,66512,66517,66639,66717,66729,
+67589,67591,67592,67593,67637,67638,67640,67643,67644,67646,67669,67670,67671,67679,67839,67861,67867,67870,67871,67897,67902,
+67903,68095,68096,68099,68100,68102,68107,68111,68115,68116,68119,68120,68147,68151,68154,68158,68159,68167,68175,68184,68191,
+68220,68222,68223,68351,68405,68408,68415,68437,68439,68447,68466,68471,68479,68607,68680,69215,69246,69631,69632,69633,69634,
+69687,69702,69709,69733,69743,69761,69762,69807,69810,69814,69816,69818,69820,69821,69825,74606,74850,74867,78894,92728,110593,
+119029,119078,119140,119142,119145,119148,119154,119162,119170,119172,119179,119209,119213,119261,119361,119364,119365,119638,
+119665,119892,119964,119967,119970,119974,119980,119993,119995,120003,120069,120074,120084,120092,120121,120126,120132,120134,
+120144,120485,120512,120513,120538,120539,120570,120571,120596,120597,120628,120629,120654,120655,120686,120687,120712,120713,
+120744,120745,120770,120771,120779,120831,126975,127019,127123,127150,127166,127183,127199,127242,127278,127337,127386,127490,
+127546,127560,127569,127776,127797,127868,127891,127940,127946,127984,128062,128064,128139,128140,128247,128252,128291,128292,
+128317,128359,128511,128528,128532,128534,128536,128538,128542,128549,128555,128557,128563,128576,128591,128709,128883,131071,
+173782,177972,178205,195101,196607,262143,327679,393215,458751,524287,589823,655359,720895,786431,851967,917504,917505,917535,
+917631,917759,917999,921599,983039,1048573,1048575,1114109,1114111
+};
+
+private static byte[] bcC1 = {
+1,1,1,1,1,1,1,1,19,1,19,19,1,19,1,19,19,19,1,19,14,1,19,19,1,1,1,19,19,1,19,1,1,1,1,19,1,1,14,14,1,1,1,1,1,1,19,5,5,12,5,14,
+5,14,5,5,14,5,5,4,4,14,4,19,19,4,4,4,14,4,14,4,14,4,14,4,4,4,4,14,4,4,4,14,1,1,14,1,14,1,1,14,1,14,1,1,14,1,14,1,1,1,1,1,1,
+14,1,1,1,1,1,1,1,14,1,1,14,1,1,14,1,1,1,1,14,1,1,11,1,1,11,14,1,1,1,1,1,1,1,1,14,1,14,14,14,14,1,1,1,14,1,14,14,1,1,1,1,1,
+1,1,14,1,1,14,14,1,1,14,1,1,14,1,11,14,1,1,1,1,1,1,1,14,1,1,14,1,14,1,1,14,14,1,1,1,14,1,1,1,1,14,1,1,1,1,1,1,1,1,1,1,1,14,
+1,1,1,14,1,1,1,1,19,11,19,1,1,1,1,1,1,1,14,1,14,14,14,1,1,14,1,19,1,1,1,1,1,1,1,14,1,1,1,1,1,1,1,14,1,1,1,14,1,1,1,1,1,1,1,
+1,14,1,1,14,1,1,1,14,1,1,1,1,1,1,1,1,1,1,14,1,14,14,1,1,1,1,14,1,14,11,1,1,14,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,14,1,14,14,1,1,
+1,14,1,1,1,1,1,1,14,1,1,1,1,14,1,14,1,14,19,19,19,19,1,1,1,14,1,14,1,14,1,14,14,1,14,1,1,1,1,1,1,1,14,1,14,1,14,1,14,1,1,1,
+1,1,14,1,14,1,1,1,1,1,14,1,14,1,14,1,14,1,1,1,1,14,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,14,1,1,1,1,19,1,19,1,1,1,18,1,
+19,19,1,1,1,1,1,14,1,14,1,1,14,1,1,14,1,1,1,14,1,14,1,14,1,1,1,11,1,14,1,19,19,19,19,14,18,1,1,1,1,1,14,1,1,1,14,1,14,1,1,
+14,1,14,19,19,1,1,1,1,1,1,1,1,1,19,1,14,1,1,1,1,14,1,14,14,1,14,1,14,1,14,14,1,1,1,1,1,14,1,1,14,1,14,1,14,1,14,1,1,1,1,1,
+14,1,14,1,1,1,14,1,14,1,1,1,1,14,1,14,1,14,1,14,1,1,1,1,14,1,14,1,1,1,1,1,1,1,14,1,14,1,14,1,14,1,1,1,1,1,1,1,1,14,14,1,1,
+1,1,1,1,1,1,1,1,1,19,1,19,1,1,19,1,1,19,1,19,1,1,19,18,15,1,4,19,19,19,19,19,19,19,19,19,19,18,16,2,6,8,3,7,13,11,19,19,19,
+19,19,19,13,19,19,19,19,19,19,19,18,15,15,15,9,1,9,10,19,19,19,1,9,10,19,19,19,1,11,14,14,14,14,14,19,1,19,1,19,1,19,1,19,
+19,1,19,1,19,1,19,1,19,1,11,1,1,1,19,1,19,1,19,19,19,1,1,19,1,1,1,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,
+19,10,11,19,19,19,19,19,19,19,19,19,1,19,19,19,1,19,19,19,19,19,19,19,19,9,1,19,19,19,19,19,19,19,19,19,19,1,19,19,19,19,19,
+19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,1,19,19,19,19,19,19,19,19,19,19,
+19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,1,1,1,1,1,19,1,14,19,19,19,1,1,1,1,14,1,1,
+1,1,1,1,1,1,1,14,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,
+19,18,19,19,1,1,1,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,1,14,19,1,19,1,1,1,19,19,1,14,19,1,
+1,19,1,19,1,1,1,1,1,1,1,1,19,1,1,19,1,1,19,19,1,19,1,1,1,19,1,19,1,1,19,1,19,1,19,1,19,1,1,1,1,19,1,1,1,1,1,19,1,1,1,1,1,14,
+14,19,14,19,19,1,1,1,14,1,19,19,19,1,1,1,19,1,1,1,1,1,1,14,1,14,1,14,1,1,14,1,19,1,1,11,11,1,19,1,1,1,14,1,1,14,1,1,1,1,1,
+14,1,1,14,1,1,1,14,1,1,14,1,14,1,14,1,1,1,1,1,1,14,1,14,1,14,1,14,1,14,1,1,1,1,1,1,1,1,1,1,14,1,14,1,14,1,14,1,14,1,1,1,1,
+1,1,1,1,1,1,1,14,1,14,1,1,1,14,1,1,1,1,1,1,1,1,1,1,4,14,4,10,4,4,4,4,4,4,4,4,4,4,4,5,5,5,5,19,19,5,5,5,5,5,15,5,5,19,5,14,
+19,19,19,19,14,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,13,19,13,19,13,19,19,19,19,19,19,19,
+19,11,19,10,10,19,19,11,11,19,5,5,5,5,15,19,11,11,11,19,19,19,19,10,13,10,13,9,13,19,19,19,1,19,19,19,19,19,19,1,19,19,19,
+19,19,19,19,19,19,19,1,1,1,1,1,1,1,1,1,11,19,19,19,11,19,19,19,15,19,19,15,1,1,1,1,1,1,1,1,19,1,1,1,19,19,19,19,19,1,14,1,
+1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,19,4,4,4,4,4,14,4,14,4,14,4,4,4,4,4,4,14,4,14,4,4,4,4,
+4,4,4,4,4,4,19,4,4,4,4,4,4,4,4,4,12,4,1,14,1,1,14,1,19,1,14,1,1,1,14,1,14,1,1,1,1,1,1,1,1,1,1,1,1,1,14,1,1,15,14,1,14,1,14,
+1,19,14,19,19,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,19,1,1,1,19,1,1,1,19,1,1,1,19,1,1,1,19,1,9,4,19,19,19,19,19,19,
+9,1,1,1,1,1,1,1,19,19,19,19,19,19,19,19,19,19,1,19,19,19,1,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,15,1,1,1,1,15,
+15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,14,15,15,1,15,1,15
+};
+
+/**
+ * Lookup bidi class for character expressed as unicode scalar value.
+ * @param ch a unicode scalar value
+ * @return bidi class
+ */
+public static int getBidiClass ( int ch ) {
+ if ( ch <= 0x00FF ) {
+ return bcL1 [ ch - 0x0000 ];
+ } else if ( ( ch >= 0x0590 ) && ( ch <= 0x06FF ) ) {
+ return bcR1 [ ch - 0x0590 ];
+ } else {
+ return getBidiClass ( ch, bcS1, bcE1, bcC1 );
+ }
+}
+
+private static int getBidiClass ( int ch, int[] sa, int[] ea, byte[] ca ) {
+ int k = Arrays.binarySearch ( sa, ch );
+ if ( k >= 0 ) {
+ return ca [ k ];
+ } else {
+ k = - ( k + 1 );
+ if ( k == 0 ) {
+ return BidiConstants.L;
+ } else if ( ch <= ea [ k - 1 ] ) {
+ return ca [ k - 1 ];
+ } else {
+ return BidiConstants.L;
+ }
+ }
+}
+
+}
diff --git a/src/java/org/apache/fop/complexscripts/bidi/BidiConstants.java b/src/java/org/apache/fop/complexscripts/bidi/BidiConstants.java
new file mode 100644
index 000000000..ef29223be
--- /dev/null
+++ b/src/java/org/apache/fop/complexscripts/bidi/BidiConstants.java
@@ -0,0 +1,91 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.complexscripts.bidi;
+
+
+/**
+ * Constants used for bidirectional processing.
+ * @author Glenn Adams
+ */
+public interface BidiConstants {
+
+ // bidi character class
+
+ /** first external (official) category */
+ int FIRST = 1;
+
+ // strong category
+ /** left-to-right class */
+ int L = 1;
+ /** left-to-right embedding class */
+ int LRE = 2;
+ /** left-to-right override class */
+ int LRO = 3;
+ /** right-to-left class */
+ int R = 4;
+ /** right-to-left arabic class */
+ int AL = 5;
+ /** right-to-left embedding class */
+ int RLE = 6;
+ /** right-to-left override class */
+ int RLO = 7;
+
+ // weak category
+ /** pop directional formatting class */
+ int PDF = 8;
+ /** european number class */
+ int EN = 9;
+ /** european number separator class */
+ int ES = 10;
+ /** european number terminator class */
+ int ET = 11;
+ /** arabic number class */
+ int AN = 12;
+ /** common number separator class */
+ int CS = 13;
+ /** non-spacing mark class */
+ int NSM = 14;
+ /** boundary neutral class */
+ int BN = 15;
+
+ // neutral category
+ /** paragraph separator class */
+ int B = 16;
+ /** segment separator class */
+ int S = 17;
+ /** whitespace class */
+ int WS = 18;
+ /** other neutrals class */
+ int ON = 19;
+
+ /** last external (official) category */
+ int LAST = 19;
+
+ // implementation specific categories
+ /** placeholder for low surrogate */
+ int SURROGATE = 20;
+
+ // other constants
+ /** last
+ /** maximum bidirectional levels */
+ int MAX_LEVELS = 61;
+ /** override flag */
+ int OVERRIDE = 128;
+}
diff --git a/src/java/org/apache/fop/complexscripts/bidi/BidiResolver.java b/src/java/org/apache/fop/complexscripts/bidi/BidiResolver.java
new file mode 100644
index 000000000..70bbe857a
--- /dev/null
+++ b/src/java/org/apache/fop/complexscripts/bidi/BidiResolver.java
@@ -0,0 +1,242 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.complexscripts.bidi;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Stack;
+import java.util.Vector;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import org.apache.fop.area.LineArea;
+import org.apache.fop.area.inline.InlineArea;
+import org.apache.fop.fo.pagination.PageSequence;
+
+// CSOFF: EmptyForIteratorPadCheck
+// CSOFF: InnerAssignmentCheck
+// CSOFF: LineLengthCheck
+// CSOFF: NoWhitespaceAfterCheck
+// CSOFF: SimplifyBooleanReturnCheck
+
+/**
+ * <p>A utility class for performing bidirectional resolution processing.</p>
+ *
+ * @author Glenn Adams
+ */
+public final class BidiResolver {
+
+ /**
+ * logging instance
+ */
+ private static final Log log = LogFactory.getLog(BidiResolver.class); // CSOK: ConstantNameCheck
+
+ private BidiResolver() {
+ }
+
+ /**
+ * Resolve inline directionality.
+ * @param ps a page sequence FO instance
+ */
+ public static void resolveInlineDirectionality ( PageSequence ps ) {
+ if (log.isDebugEnabled()) {
+ log.debug ( "BD: RESOLVE: " + ps );
+ }
+ List ranges = pruneEmptyRanges ( ps.collectDelimitedTextRanges ( new Stack() ) );
+ resolveInlineDirectionality ( ranges );
+ }
+
+ /**
+ * Reorder line area.
+ * @param la a line area instance
+ */
+ public static void reorder ( LineArea la ) {
+
+ // 1. collect inline levels
+ List runs = collectRuns ( la.getInlineAreas(), new Vector() );
+ if (log.isDebugEnabled()) {
+ dumpRuns ( "BD: REORDER: INPUT:", runs );
+ }
+
+ // 2. split heterogeneous inlines
+ runs = splitRuns ( runs );
+ if (log.isDebugEnabled()) {
+ dumpRuns ( "BD: REORDER: SPLIT INLINES:", runs );
+ }
+
+ // 3. determine minimum and maximum levels
+ int[] mm = computeMinMaxLevel ( runs, null );
+ if (log.isDebugEnabled()) {
+ log.debug( "BD: REORDER: { min = " + mm[0] + ", max = " + mm[1] + "}" );
+ }
+
+ // 4. reorder from maximum level to minimum odd level
+ int mn = mm[0];
+ int mx = mm[1];
+ if ( mx > 0 ) {
+ for ( int l1 = mx, l2 = ( ( mn & 1 ) == 0 ) ? ( mn + 1 ) : mn; l1 >= l2; l1-- ) {
+ runs = reorderRuns ( runs, l1 );
+ }
+ }
+ if (log.isDebugEnabled()) {
+ dumpRuns ( "BD: REORDER: REORDERED RUNS:", runs );
+ }
+
+ // 5. reverse word consituents (characters and glyphs) while mirroring
+ boolean mirror = true;
+ reverseWords ( runs, mirror );
+ if (log.isDebugEnabled()) {
+ dumpRuns ( "BD: REORDER: REORDERED WORDS:", runs );
+ }
+
+ // 6. replace line area's inline areas with reordered runs' inline areas
+ replaceInlines ( la, replicateSplitWords ( runs ) );
+ }
+
+ private static void resolveInlineDirectionality ( List ranges ) {
+ for ( Iterator it = ranges.iterator(); it.hasNext(); ) {
+ DelimitedTextRange r = (DelimitedTextRange) it.next();
+ r.resolve();
+ if (log.isDebugEnabled()) {
+ log.debug ( r );
+ }
+ }
+ }
+
+ private static List collectRuns ( List inlines, List runs ) {
+ for ( Iterator it = inlines.iterator(); it.hasNext(); ) {
+ InlineArea ia = (InlineArea) it.next();
+ runs = ia.collectInlineRuns ( runs );
+ }
+ return runs;
+ }
+
+ private static List splitRuns ( List runs ) {
+ List runsNew = new Vector();
+ for ( Iterator it = runs.iterator(); it.hasNext(); ) {
+ InlineRun ir = (InlineRun) it.next();
+ if ( ir.isHomogenous() ) {
+ runsNew.add ( ir );
+ } else {
+ runsNew.addAll ( ir.split() );
+ }
+ }
+ if ( ! runsNew.equals ( runs ) ) {
+ runs = runsNew;
+ }
+ return runs;
+ }
+
+ private static int[] computeMinMaxLevel ( List runs, int[] mm ) {
+ if ( mm == null ) {
+ mm = new int[] {Integer.MAX_VALUE, Integer.MIN_VALUE};
+ }
+ for ( Iterator it = runs.iterator(); it.hasNext(); ) {
+ InlineRun ir = (InlineRun) it.next();
+ ir.updateMinMax ( mm );
+ }
+ return mm;
+ }
+ private static List reorderRuns ( List runs, int level ) {
+ assert level >= 0;
+ List runsNew = new Vector();
+ for ( int i = 0, n = runs.size(); i < n; i++ ) {
+ InlineRun iri = (InlineRun) runs.get(i);
+ if ( iri.getMinLevel() < level ) {
+ runsNew.add ( iri );
+ } else {
+ int s = i;
+ int e = s;
+ while ( e < n ) {
+ InlineRun ire = (InlineRun) runs.get(e);
+ if ( ire.getMinLevel() < level ) {
+ break;
+ } else {
+ e++;
+ }
+ }
+ if ( s < e ) {
+ runsNew.addAll ( reverseRuns ( runs, s, e ) );
+ }
+ i = e - 1;
+ }
+ }
+ if ( ! runsNew.equals ( runs ) ) {
+ runs = runsNew;
+ }
+ return runs;
+ }
+ private static List reverseRuns ( List runs, int s, int e ) {
+ int n = e - s;
+ Vector runsNew = new Vector ( n );
+ if ( n > 0 ) {
+ for ( int i = 0; i < n; i++ ) {
+ int k = ( n - i - 1 );
+ InlineRun ir = (InlineRun) runs.get(s + k);
+ ir.reverse();
+ runsNew.add ( ir );
+ }
+ }
+ return runsNew;
+ }
+ private static void reverseWords ( List runs, boolean mirror ) {
+ for ( Iterator it = runs.iterator(); it.hasNext(); ) {
+ InlineRun ir = (InlineRun) it.next();
+ ir.maybeReverseWord ( mirror );
+ }
+ }
+ private static List replicateSplitWords ( List runs ) {
+ // [TBD] for each run which inline word area appears multiple times in
+ // runs, replicate that word
+ return runs;
+ }
+ private static void replaceInlines ( LineArea la, List runs ) {
+ List<InlineArea> inlines = new ArrayList<InlineArea>();
+ for ( Iterator it = runs.iterator(); it.hasNext(); ) {
+ InlineRun ir = (InlineRun) it.next();
+ inlines.add ( ir.getInline() );
+ }
+ la.setInlineAreas ( unflattenInlines ( inlines ) );
+ }
+ private static List unflattenInlines ( List<InlineArea> inlines ) {
+ return new UnflattenProcessor ( inlines ) .unflatten();
+ }
+ private static void dumpRuns ( String header, List runs ) {
+ log.debug ( header );
+ for ( Iterator it = runs.iterator(); it.hasNext(); ) {
+ InlineRun ir = (InlineRun) it.next();
+ log.debug ( ir );
+ }
+ }
+
+ private static List pruneEmptyRanges ( Stack ranges ) {
+ Vector rv = new Vector();
+ for ( Iterator it = ranges.iterator(); it.hasNext(); ) {
+ DelimitedTextRange r = (DelimitedTextRange) it.next();
+ if ( ! r.isEmpty() ) {
+ rv.add ( r );
+ }
+ }
+ return rv;
+ }
+
+}
diff --git a/src/java/org/apache/fop/complexscripts/bidi/DelimitedTextRange.java b/src/java/org/apache/fop/complexscripts/bidi/DelimitedTextRange.java
new file mode 100644
index 000000000..9939db971
--- /dev/null
+++ b/src/java/org/apache/fop/complexscripts/bidi/DelimitedTextRange.java
@@ -0,0 +1,228 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.complexscripts.bidi;
+
+import java.util.Iterator;
+import java.util.List;
+import java.util.Vector;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import org.apache.fop.fo.CharIterator;
+import org.apache.fop.fo.FONode;
+import org.apache.fop.fo.FObj;
+import org.apache.fop.traits.Direction;
+import org.apache.fop.traits.WritingModeTraits;
+import org.apache.fop.traits.WritingModeTraitsGetter;
+import org.apache.fop.util.CharUtilities;
+
+// CSOFF: EmptyForIteratorPadCheck
+// CSOFF: InnerAssignmentCheck
+// CSOFF: LineLengthCheck
+// CSOFF: NoWhitespaceAfterCheck
+
+/**
+ * The <code>DelimitedTextRange</code> class implements the "delimited text range" as described
+ * by XML-FO 1.1 §5.8, which contains a flattened sequence of characters. Any FO that generates
+ * block areas serves as a delimiter.
+ *
+ * @author Glenn Adams
+ */
+public class DelimitedTextRange {
+ private FONode fn; // node that generates this text range
+ private StringBuffer buffer; // flattened character sequence of generating FO nodes
+ private List intervals; // list of intervals over buffer of generating FO nodes
+ /**
+ * Primary constructor.
+ * @param fn node that generates this text range
+ */
+ public DelimitedTextRange ( FONode fn ) {
+ this.fn = fn;
+ this.buffer = new StringBuffer();
+ this.intervals = new Vector();
+ }
+ /**
+ * Obtain node that generated this text range.
+ * @return node that generated this text range
+ */
+ public FONode getNode() {
+ return fn;
+ }
+ /**
+ * Append interval using characters from character iterator IT.
+ * @param it character iterator
+ * @param fn node that generates interval being appended
+ */
+ public void append ( CharIterator it, FONode fn ) {
+ if ( it != null ) {
+ int s = buffer.length();
+ int e = s;
+ while ( it.hasNext() ) {
+ char c = it.nextChar();
+ buffer.append ( c );
+ e++;
+ }
+ intervals.add ( new TextInterval ( fn, s, e ) );
+ }
+ }
+ /**
+ * Append interval using character C.
+ * @param c character
+ * @param fn node that generates interval being appended
+ */
+ public void append ( char c, FONode fn ) {
+ if ( c != 0 ) {
+ int s = buffer.length();
+ int e = s + 1;
+ buffer.append ( c );
+ intervals.add ( new TextInterval ( fn, s, e ) );
+ }
+ }
+ /**
+ * Determine if range is empty.
+ * @return true if range is empty
+ */
+ public boolean isEmpty() {
+ return buffer.length() == 0;
+ }
+ /**
+ * Resolve bidirectional levels for this range.
+ */
+ public void resolve() {
+ WritingModeTraitsGetter tg;
+ if ( ( tg = WritingModeTraits.getWritingModeTraitsGetter ( getNode() ) ) != null ) {
+ resolve ( tg.getInlineProgressionDirection() );
+ }
+ }
+ @Override
+ public String 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(); ) {
+ TextInterval ti = (TextInterval) it.next();
+ if ( first ) {
+ first = false;
+ } else {
+ sb.append(',');
+ }
+ sb.append ( ti.toString() );
+ }
+ sb.append("> }");
+ return sb.toString();
+ }
+ private void resolve ( Direction paragraphEmbeddingLevel ) {
+ int [] levels;
+ if ( ( levels = UnicodeBidiAlgorithm.resolveLevels ( buffer, paragraphEmbeddingLevel ) ) != null ) {
+ assignLevels ( levels );
+ assignBlockLevel ( paragraphEmbeddingLevel );
+ assignTextLevels();
+ }
+ }
+ /**
+ * <p>Assign resolved levels to all text intervals of this delimited text range.</p>
+ * <p>Has a possible side effect of replacing the intervals array with a new array
+ * containing new text intervals, such that each text interval is associated with
+ * a single level run.</p>
+ * @param levels array of levels each corresponding to each index of the delimited
+ * text range
+ */
+ private void assignLevels ( int[] levels ) {
+ Vector intervalsNew = new Vector ( intervals.size() );
+ for ( Iterator it = intervals.iterator(); it.hasNext(); ) {
+ TextInterval ti = (TextInterval) it.next();
+ intervalsNew.addAll ( assignLevels ( ti, levels ) );
+ }
+ if ( ! intervalsNew.equals ( intervals ) ) {
+ intervals = intervalsNew;
+ }
+ }
+ /**
+ * <p>Assign resolved levels to a specified text interval over this delimited text
+ * range.</p>
+ * <p>Returns a list of text intervals containing either (1) the single, input text
+ * interval or (2) two or more new text intervals obtained from sub-dividing the input
+ * text range into level runs, i.e., runs of text assigned to a single level.</p>
+ * @param ti a text interval to which levels are to be assigned
+ * @param levels array of levels each corresponding to each index of the delimited
+ * text range
+ * @return a list of text intervals as described above
+ */
+ private static final Log log = LogFactory.getLog(BidiResolver.class); // CSOK: ConstantNameCheck
+ private List assignLevels ( TextInterval ti, int[] levels ) {
+ Vector tiv = new Vector();
+ FONode fn = ti.getNode();
+ int fnStart = ti.getStart(); // start of node's text in delimited text range
+ for ( int i = fnStart, n = ti.getEnd(); i < n; ) {
+ int s = i; // inclusive start index of interval in delimited text range
+ int e = s; // exclusive end index of interval in delimited text range
+ int l = levels [ s ]; // current run level
+ while ( e < n ) { // skip to end of run level or end of interval
+ if ( levels [ e ] != l ) {
+ break;
+ } else {
+ e++;
+ }
+ }
+ if ( ( ti.getStart() == s ) && ( ti.getEnd() == e ) ) {
+ ti.setLevel ( l ); // reuse interval, assigning it single level
+ } else {
+ ti = new TextInterval ( fn, fnStart, s, e, l ); // subdivide interval
+ }
+ if (log.isDebugEnabled()) {
+ log.debug ( "AL(" + l + "): " + ti );
+ }
+ tiv.add ( ti );
+ i = e;
+ }
+ return tiv;
+ }
+ /**
+ * <p>Assign resolved levels for each interval to source #PCDATA in the associated FOText.</p>
+ */
+ private void assignTextLevels() {
+ for ( Iterator it = intervals.iterator(); it.hasNext(); ) {
+ TextInterval ti = (TextInterval) it.next();
+ ti.assignTextLevels();
+ }
+ }
+ private void assignBlockLevel ( Direction paragraphEmbeddingLevel ) {
+ int defaultLevel = ( paragraphEmbeddingLevel == Direction.RL ) ? 1 : 0;
+ for ( Iterator it = intervals.iterator(); it.hasNext(); ) {
+ TextInterval ti = (TextInterval) it.next();
+ assignBlockLevel ( ti.getNode(), defaultLevel );
+ }
+ }
+ private void assignBlockLevel ( FONode node, int defaultLevel ) {
+ for ( FONode fn = node; fn != null; fn = fn.getParent() ) {
+ if ( fn instanceof FObj ) {
+ FObj fo = (FObj) fn;
+ if ( fo.isBidiRangeBlockItem() ) {
+ if ( fo.getBidiLevel() < 0 ) {
+ fo.setBidiLevel ( defaultLevel );
+ }
+ break;
+ }
+ }
+ }
+ }
+}
+
diff --git a/src/java/org/apache/fop/complexscripts/bidi/InlineRun.java b/src/java/org/apache/fop/complexscripts/bidi/InlineRun.java
new file mode 100644
index 000000000..6ea62494a
--- /dev/null
+++ b/src/java/org/apache/fop/complexscripts/bidi/InlineRun.java
@@ -0,0 +1,310 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.complexscripts.bidi;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Vector;
+
+import org.apache.fop.area.inline.Anchor;
+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.traits.Direction;
+import org.apache.fop.util.CharUtilities;
+
+// CSOFF: EmptyForIteratorPadCheck
+// CSOFF: InnerAssignmentCheck
+// CSOFF: NoWhitespaceAfterCheck
+// CSOFF: SimplifyBooleanReturnCheck
+
+/**
+ * The <code>InlineRun</code> class is a utility class, the instances of which are used
+ * to capture a sequence of reordering levels associated with an inline area.
+ *
+ * @author Glenn Adams
+ */
+public class InlineRun {
+ private InlineArea inline;
+ private int[] levels;
+ private int minLevel;
+ private int maxLevel;
+ private int reversals;
+ /**
+ * Primary constructor.
+ * @param inline which generated this inline run
+ * @param levels levels array
+ */
+ public InlineRun ( InlineArea inline, int[] levels ) {
+ assert inline != null;
+ assert levels != null;
+ this.inline = inline;
+ this.levels = levels;
+ setMinMax ( levels );
+ }
+ /**
+ * Alternate constructor.
+ * @param inline which generated this inline run
+ * @param level for each index
+ * @param count of indices
+ */
+ public InlineRun ( InlineArea inline, int level, int count ) {
+ this ( inline, makeLevels ( level, count ) );
+ }
+ /**
+ * Obtain inline area that generated this inline run.
+ * @return inline area that generated this inline run.
+ */
+ public InlineArea getInline() {
+ return inline;
+ }
+ /**
+ * Obtain minimum bidi level for this run.
+ * @return minimum bidi level
+ */
+ public int getMinLevel() {
+ return minLevel;
+ }
+ /**
+ * Obtain maximum bidi level for this run.
+ * @return maximum bidi level
+ */
+ public int getMaxLevel() {
+ return maxLevel;
+ }
+ private void setMinMax ( int[] levels ) {
+ int mn = Integer.MAX_VALUE;
+ int mx = Integer.MIN_VALUE;
+ if ( ( levels != null ) && ( levels.length > 0 ) ) {
+ for ( int i = 0, n = levels.length; i < n; i++ ) {
+ int l = levels [ i ];
+ if ( l < mn ) {
+ mn = l;
+ }
+ if ( l > mx ) {
+ mx = l;
+ }
+ }
+ } else {
+ mn = mx = -1;
+ }
+ this.minLevel = mn;
+ this.maxLevel = mx;
+ }
+ /**
+ * Determine if this run has homogenous (same valued) bidi levels.
+ * @return true if homogenous
+ */
+ public boolean isHomogenous() {
+ return minLevel == maxLevel;
+ }
+ /**
+ * Split this inline run into homogenous runs.
+ * @return list of new runs
+ */
+ public List split() {
+ List runs = new Vector();
+ for ( int i = 0, n = levels.length; i < n; ) {
+ int l = levels [ i ];
+ int s = i;
+ int e = s;
+ while ( e < n ) {
+ if ( levels [ e ] != l ) {
+ break;
+ } else {
+ e++;
+ }
+ }
+ if ( s < e ) {
+ runs.add ( new InlineRun ( inline, l, e - s ) );
+ }
+ i = e;
+ }
+ assert runs.size() < 2 : "heterogeneous inlines not yet supported!!";
+ return runs;
+ }
+ /**
+ * Update a min/max array to correspond with this run's min/max values.
+ * @param mm reference to min/max array
+ */
+ public void updateMinMax ( int[] mm ) {
+ if ( minLevel < mm[0] ) {
+ mm[0] = minLevel;
+ }
+ if ( maxLevel > mm[1] ) {
+ mm[1] = maxLevel;
+ }
+ }
+ /**
+ * Determine if run needs mirroring.
+ * @return true if run is homogenous and odd (i.e., right to left)
+ */
+ public boolean maybeNeedsMirroring() {
+ return ( minLevel == maxLevel ) && ( ( minLevel & 1 ) != 0 );
+ }
+ /**
+ * Reverse run (by incrementing reversal count, not actually reversing).
+ */
+ public void reverse() {
+ reversals++;
+ }
+ /**
+ * Reverse inline area if it is a word area and it requires
+ * reversal.
+ * @param mirror if true then also mirror characters
+ */
+ public void maybeReverseWord ( boolean mirror ) {
+ if ( inline instanceof WordArea ) {
+ WordArea w = (WordArea) inline;
+ // if not already reversed, then reverse now
+ if ( ! w.isReversed() ) {
+ if ( ( reversals & 1 ) != 0 ) {
+ w.reverse ( mirror );
+ } else if ( mirror && maybeNeedsMirroring() ) {
+ w.mirror();
+ }
+ }
+ }
+ }
+ @Override
+ public boolean equals ( Object o ) {
+ if ( o instanceof InlineRun ) {
+ InlineRun ir = (InlineRun) o;
+ if ( ir.inline != inline ) {
+ return false;
+ } else if ( ir.minLevel != minLevel ) {
+ return false;
+ } else if ( ir.maxLevel != maxLevel ) {
+ return false;
+ } else if ( ( ir.levels != null ) && ( levels != null ) ) {
+ if ( ir.levels.length != levels.length ) {
+ return false;
+ } else {
+ for ( int i = 0, n = levels.length; i < n; i++ ) {
+ if ( ir.levels[i] != levels[i] ) {
+ return false;
+ }
+ }
+ return true;
+ }
+ } else if ( ( ir.levels == null ) && ( levels == null ) ) {
+ return true;
+ } else {
+ return false;
+ }
+ } else {
+ return false;
+ }
+ }
+ @Override
+ public int hashCode() {
+ int l = ( inline != null ) ? inline.hashCode() : 0;
+ l = ( l ^ minLevel ) + ( l << 19 );
+ l = ( l ^ maxLevel ) + ( l << 11 );
+ return l;
+ }
+ @Override
+ public String toString() {
+ StringBuffer sb = new StringBuffer( "RR: { type = \'" );
+ char c;
+ String content = null;
+ if ( inline instanceof WordArea ) {
+ c = 'W';
+ content = ( (WordArea) inline ) .getWord();
+ } else if ( inline instanceof SpaceArea ) {
+ c = 'S';
+ content = ( (SpaceArea) inline ) .getSpace();
+ } else if ( inline instanceof Anchor ) {
+ c = 'A';
+ } else if ( inline instanceof Leader ) {
+ c = 'L';
+ } else if ( inline instanceof Space ) {
+ c = 'S';
+ } 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 = '?';
+ }
+ sb.append ( c );
+ sb.append ( "\', levels = \'" );
+ sb.append ( generateLevels ( levels ) );
+ sb.append ( "\', min = " );
+ sb.append ( minLevel );
+ sb.append ( ", max = " );
+ sb.append ( maxLevel );
+ sb.append ( ", reversals = " );
+ sb.append ( reversals );
+ sb.append ( ", content = <" );
+ sb.append ( CharUtilities.toNCRefs ( content ) );
+ sb.append ( "> }" );
+ return sb.toString();
+ }
+ private String generateLevels ( int[] levels ) {
+ StringBuffer lb = new StringBuffer();
+ int maxLevel = -1;
+ int numLevels = levels.length;
+ for ( int i = 0; i < numLevels; i++ ) {
+ int l = levels [ i ];
+ if ( l > maxLevel ) {
+ maxLevel = l;
+ }
+ }
+ if ( maxLevel < 0 ) {
+ // leave level buffer empty
+ } else if ( maxLevel < 10 ) {
+ // use string of decimal digits
+ for ( int i = 0; i < numLevels; i++ ) {
+ lb.append ( (char) ( '0' + levels [ i ] ) );
+ }
+ } else {
+ // use comma separated list
+ boolean first = true;
+ for ( int i = 0; i < numLevels; i++ ) {
+ if ( first ) {
+ first = false;
+ } else {
+ lb.append(',');
+ }
+ lb.append ( levels [ i ] );
+ }
+ }
+ return lb.toString();
+ }
+ private static int[] makeLevels ( int level, int count ) {
+ int[] levels = new int [ count ];
+ Arrays.fill ( levels, level );
+ return levels;
+ }
+}
+
diff --git a/src/java/org/apache/fop/complexscripts/bidi/TextInterval.java b/src/java/org/apache/fop/complexscripts/bidi/TextInterval.java
new file mode 100644
index 000000000..7d4ab105b
--- /dev/null
+++ b/src/java/org/apache/fop/complexscripts/bidi/TextInterval.java
@@ -0,0 +1,143 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.complexscripts.bidi;
+
+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.Character;
+import org.apache.fop.fo.flow.Leader;
+
+// CSOFF: LineLengthCheck
+// CSOFF: SimplifyBooleanReturnCheck
+
+/**
+ * The <code>TextInterval</code> class is a utility class, the instances of which are used
+ * to record backpointers to associated nodes over sub-intervals of a delimited text range.
+ *
+ * @author Glenn Adams
+ */
+class TextInterval {
+ private FONode fn; // associated node
+ private int textStart; // starting index within delimited text range of associated node's text
+ private int start; // starting index within delimited text range
+ private int end; // ending index within delimited text range
+ private int level; // resolved level or default (-1)
+ TextInterval ( FONode fn, int start, int end ) {
+ this ( fn, start, start, end, -1 );
+ }
+ TextInterval ( FONode fn, int textStart, int start, int end, int level ) {
+ this.fn = fn;
+ this.textStart = textStart;
+ this.start = start;
+ this.end = end;
+ this.level = level;
+ }
+ FONode getNode() {
+ return fn;
+ }
+ int getTextStart() {
+ return textStart;
+ }
+ int getStart() {
+ return start;
+ }
+ int getEnd() {
+ return end;
+ }
+ int getLevel() {
+ return level;
+ }
+ void setLevel ( int level ) {
+ this.level = level;
+ }
+ public int length() {
+ return end - start;
+ }
+ public String getText() {
+ if ( fn instanceof FOText ) {
+ return ( (FOText) fn ) .getCharSequence() .toString();
+ } else if ( fn instanceof Character ) {
+ return new String ( new char[] {( (Character) fn ) .getCharacter()} );
+ } else {
+ return null;
+ }
+ }
+ public void assignTextLevels() {
+ if ( fn instanceof FOText ) {
+ ( (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 );
+ } else if ( fn instanceof Leader ) {
+ ( (Leader) fn ) .setBidiLevel ( level );
+ }
+ }
+ public boolean equals ( Object o ) {
+ if ( o instanceof TextInterval ) {
+ TextInterval ti = (TextInterval) o;
+ if ( ti.getNode() != fn ) {
+ return false;
+ } else if ( ti.getStart() != start ) {
+ return false;
+ } else if ( ti.getEnd() != end ) {
+ return false;
+ } else {
+ return true;
+ }
+ } else {
+ return false;
+ }
+ }
+ public int hashCode() {
+ int l = ( fn != null ) ? fn.hashCode() : 0;
+ l = ( l ^ start ) + ( l << 19 );
+ l = ( l ^ end ) + ( l << 11 );
+ return l;
+ }
+ public String toString() {
+ StringBuffer sb = new StringBuffer();
+ char c;
+ if ( fn instanceof FOText ) {
+ c = 'T';
+ } else if ( fn instanceof Character ) {
+ c = 'C';
+ } else if ( fn instanceof BidiOverride ) {
+ c = 'B';
+ } else if ( fn instanceof AbstractPageNumberCitation ) {
+ c = '#';
+ } else if ( fn instanceof AbstractGraphics ) {
+ c = 'G';
+ } else if ( fn instanceof Leader ) {
+ c = 'L';
+ } else {
+ c = '?';
+ }
+ sb.append ( c );
+ sb.append ( "[" + start + "," + end + "][" + textStart + "](" + level + ")" );
+ return sb.toString();
+ }
+}
+
diff --git a/src/java/org/apache/fop/complexscripts/bidi/UnflattenProcessor.java b/src/java/org/apache/fop/complexscripts/bidi/UnflattenProcessor.java
new file mode 100644
index 000000000..d91bed572
--- /dev/null
+++ b/src/java/org/apache/fop/complexscripts/bidi/UnflattenProcessor.java
@@ -0,0 +1,361 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.complexscripts.bidi;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Stack;
+
+import org.apache.fop.area.Area;
+import org.apache.fop.area.LinkResolver;
+import org.apache.fop.area.inline.BasicLinkArea;
+import org.apache.fop.area.inline.FilledArea;
+import org.apache.fop.area.inline.InlineArea;
+import org.apache.fop.area.inline.InlineParent;
+import org.apache.fop.area.inline.SpaceArea;
+import org.apache.fop.area.inline.TextArea;
+import org.apache.fop.area.inline.UnresolvedPageNumber;
+
+// CSOFF: EmptyForIteratorPadCheck
+// CSOFF: LineLengthCheck
+// CSOFF: NoWhitespaceAfterCheck
+// CSOFF: SimplifyBooleanReturnCheck
+
+/**
+ * The <code>UnflattenProcessor</code> class is used to reconstruct (by unflattening) a line
+ * area's internal area hierarachy after leaf inline area reordering is completed.
+ *
+ * @author Glenn Adams
+ */
+class UnflattenProcessor {
+ private List<InlineArea> il; // list of flattened inline areas being unflattened
+ private List<InlineArea> 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<InlineParent> icOrig; // stack of original inline parent containers
+ private Stack<InlineParent> icNew; // stack of new inline parent containers being constructed
+ UnflattenProcessor ( List<InlineArea> inlines ) {
+ this.il = inlines;
+ this.ilNew = new ArrayList<InlineArea>();
+ this.iaLevelLast = -1;
+ this.icOrig = new Stack<InlineParent>();
+ this.icNew = new Stack<InlineParent>();
+ }
+ List unflatten() {
+ if ( il != null ) {
+ for ( Iterator<InlineArea> 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<InlineParent> ich, TextArea tc, InlineArea ia ) {
+ if ( ( tcNew == null ) || ( tc != tcNew ) ) {
+ maybeFinishTextContainer ( tc, ia );
+ maybeFinishInlineContainers ( ich, tc, ia );
+ update ( ich, tc, ia );
+ } else {
+ // skip inline area whose text container is the current new text container,
+ // which occurs in the context of the inline runs produced by a filled area
+ }
+ }
+ 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<InlineParent> 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<InlineParent> ich, TextArea tc, InlineArea ia ) {
+ if ( ( ich != null ) && ! ich.isEmpty() ) { // finish non-matching inner inline container(s)
+ for ( Iterator<InlineParent> 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<InlineParent> ich, TextArea tc, InlineArea ia ) {
+ if ( shouldFinishInlineContainer ( ich, tc, ia ) ) {
+ finishInlineContainer ( ich, tc, ia );
+ }
+ }
+ private void finishAll() {
+ finishTextContainer();
+ finishInlineContainer();
+ }
+ private void update ( List<InlineParent> ich, TextArea tc, InlineArea ia ) {
+ if ( ! alreadyUnflattened ( ia ) ) {
+ if ( ( ich != null ) && ! ich.isEmpty() ) {
+ pushInlineContainers ( ich );
+ }
+ if ( tc != null ) {
+ pushTextContainer ( tc, ia );
+ } else {
+ pushNonTextInline ( ia );
+ }
+ iaLevelLast = ia.getBidiLevel();
+ tcOrig = tc;
+ } else if ( tcNew != null ) {
+ finishTextContainer();
+ tcOrig = null;
+ } else {
+ tcOrig = null;
+ }
+ }
+ private boolean alreadyUnflattened ( InlineArea ia ) {
+ for ( Iterator<InlineArea> it = ilNew.iterator(); it.hasNext(); ) {
+ if ( ia.isAncestorOrSelf ( it.next() ) ) {
+ return true;
+ }
+ }
+ return false;
+ }
+ private void pushInlineContainers ( List<InlineParent> ich ) {
+ LinkedList<InlineParent> icl = new LinkedList<InlineParent>();
+ for ( Iterator<InlineParent> it = ich.iterator(); it.hasNext(); ) {
+ InlineParent ic = it.next();
+ if ( icOrig.search ( ic ) >= 0 ) {
+ break;
+ } else {
+ icl.addFirst ( ic );
+ }
+ }
+ for ( Iterator<InlineParent> 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 if ( i instanceof FilledArea ) {
+ return generateFilledArea ( (FilledArea) 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 generateFilledArea ( FilledArea f ) {
+ FilledArea fc = new FilledArea();
+ if ( f != null ) {
+ initializeInlineContainer ( fc, f );
+ initializeFilledArea ( fc, f );
+ }
+ return fc;
+ }
+ private void initializeFilledArea ( FilledArea fc, FilledArea f ) {
+ assert fc != null;
+ assert f != null;
+ fc.setIPD ( f.getIPD() );
+ fc.setUnitWidth ( f.getUnitWidth() );
+ }
+ 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<InlineParent> findInlineContainers ( InlineArea ia ) {
+ assert ia != null;
+ List<InlineParent> ich = new ArrayList<InlineParent>();
+ 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;
+ }
+}
diff --git a/src/java/org/apache/fop/complexscripts/bidi/UnicodeBidiAlgorithm.java b/src/java/org/apache/fop/complexscripts/bidi/UnicodeBidiAlgorithm.java
new file mode 100644
index 000000000..0f24cba9d
--- /dev/null
+++ b/src/java/org/apache/fop/complexscripts/bidi/UnicodeBidiAlgorithm.java
@@ -0,0 +1,839 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.complexscripts.bidi;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import org.apache.fop.traits.Direction;
+import org.apache.fop.util.CharUtilities;
+
+// CSOFF: AvoidNestedBlocksCheck
+// CSOFF: EmptyForIteratorPadCheck
+// CSOFF: InnerAssignmentCheck
+// CSOFF: LineLengthCheck
+// CSOFF: NoWhitespaceAfterCheck
+// CSOFF: ParameterNumberCheck
+
+/**
+ * The <code>UnicodeBidiAlgorithm</code> class implements functionality prescribed by
+ * the Unicode Bidirectional Algorithm, Unicode Standard Annex #9.
+ *
+ * @author Glenn Adams
+ */
+public final class UnicodeBidiAlgorithm implements BidiConstants {
+
+ /**
+ * logging instance
+ */
+ private static final Log log = LogFactory.getLog(BidiResolver.class); // CSOK: ConstantNameCheck
+
+ private UnicodeBidiAlgorithm() {
+ }
+
+ /**
+ * Resolve the directionality levels of each character in a character seqeunce.
+ * If some character is encoded in the character sequence as a Unicode Surrogate Pair,
+ * then the directionality level of each of the two members of the pair will be identical.
+ * @return null if bidirectional processing is not required; otherwise, returns an array
+ * of integers, where each integer corresponds to exactly one UTF-16
+ * encoding element present in the input character sequence, and where each integer denotes
+ * the directionality level of the corresponding encoding element
+ * @param cs input character sequence representing a UTF-16 encoded string
+ * @param defaultLevel the default paragraph level, which must be zero (LR) or one (RL)
+ */
+ public static int[] resolveLevels ( CharSequence cs, Direction defaultLevel ) {
+ int[] chars = new int [ cs.length() ];
+ if ( convertToScalar ( cs, chars ) || ( defaultLevel == Direction.RL ) ) {
+ return resolveLevels ( chars, ( defaultLevel == Direction.RL ) ? 1 : 0, new int [ chars.length ] );
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Resolve the directionality levels of each character in a character seqeunce.
+ * @return null if bidirectional processing is not required; otherwise, returns an array
+ * of integers, where each integer corresponds to exactly one UTF-16
+ * encoding element present in the input character sequence, and where each integer denotes
+ * the directionality level of the corresponding encoding element
+ * @param chars array of input characters represented as unicode scalar values
+ * @param defaultLevel the default paragraph level, which must be zero (LR) or one (RL)
+ * @param levels array to receive levels, one for each character in chars array
+ */
+ public static int[] resolveLevels ( int[] chars, int defaultLevel, int[] levels ) {
+ return resolveLevels ( chars, getClasses ( chars ), defaultLevel, levels, false );
+ }
+
+ /**
+ * Resolve the directionality levels of each character in a character seqeunce.
+ * @return null if bidirectional processing is not required; otherwise, returns an array
+ * of integers, where each integer corresponds to exactly one UTF-16
+ * encoding element present in the input character sequence, and where each integer denotes
+ * the directionality level of the corresponding encoding element
+ * @param chars array of input characters represented as unicode scalar values
+ * @param classes array containing one bidi class per character in chars array
+ * @param defaultLevel the default paragraph level, which must be zero (LR) or one (RL)
+ * @param levels array to receive levels, one for each character in chars array
+ * @param useRuleL1 true if rule L1 should be used
+ */
+ public static int[] resolveLevels ( int[] chars, int[] classes, int defaultLevel, int[] levels, boolean useRuleL1 ) {
+ int[] ica = classes;
+ int[] wca = copySequence ( ica );
+ int[] ea = new int [ levels.length ];
+ resolveExplicit ( wca, defaultLevel, ea );
+ resolveRuns ( wca, defaultLevel, ea, levelsFromEmbeddings ( ea, levels ) );
+ if ( useRuleL1 ) {
+ resolveSeparators ( ica, wca, defaultLevel, levels );
+ }
+ dump ( "RL: CC(" + ( ( chars != null ) ? chars.length : -1 ) + ")", chars, classes, defaultLevel, levels );
+ return levels;
+ }
+
+ private static int[] copySequence ( int[] ta ) {
+ int[] na = new int [ ta.length ];
+ System.arraycopy ( ta, 0, na, 0, na.length );
+ return na;
+ }
+
+ private static void resolveExplicit ( int[] wca, int defaultLevel, int[] ea ) {
+ int[] es = new int [ MAX_LEVELS ]; /* embeddings stack */
+ int ei = 0; /* embeddings stack index */
+ int ec = defaultLevel; /* current embedding level */
+ for ( int i = 0, n = wca.length; i < n; i++ ) {
+ int bc = wca [ i ]; /* bidi class of current char */
+ int el; /* embedding level to assign to current char */
+ switch ( bc ) {
+ case LRE: // start left-to-right embedding
+ case RLE: // start right-to-left embedding
+ case LRO: // start left-to-right override
+ case RLO: // start right-to-left override
+ {
+ int en; /* new embedding level */
+ if ( ( bc == RLE ) || ( bc == RLO ) ) {
+ en = ( ( ec & ~OVERRIDE ) + 1 ) | 1;
+ } else {
+ en = ( ( ec & ~OVERRIDE ) + 2 ) & ~1;
+ }
+ if ( en < ( MAX_LEVELS + 1 ) ) {
+ es [ ei++ ] = ec;
+ if ( ( bc == LRO ) || ( bc == RLO ) ) {
+ ec = en | OVERRIDE;
+ } else {
+ ec = en & ~OVERRIDE;
+ }
+ } else {
+ // max levels exceeded, so don't change level or override
+ }
+ el = ec;
+ break;
+ }
+ case PDF: // pop directional formatting
+ {
+ el = ec;
+ if ( ei > 0 ) {
+ ec = es [ --ei ];
+ } else {
+ // ignore isolated PDF
+ }
+ break;
+ }
+ case B: // paragraph separator
+ {
+ el = ec = defaultLevel;
+ ei = 0;
+ break;
+ }
+ default:
+ {
+ el = ec;
+ break;
+ }
+ }
+ switch ( bc ) {
+ case BN:
+ break;
+ case LRE: case RLE: case LRO: case RLO: case PDF:
+ wca [ i ] = BN;
+ break;
+ default:
+ if ( ( el & OVERRIDE ) != 0 ) {
+ wca [ i ] = directionOfLevel ( el );
+ }
+ break;
+ }
+ ea [ i ] = el;
+ }
+ }
+
+ private static int directionOfLevel ( int level ) {
+ return ( ( level & 1 ) != 0 ) ? R : L;
+ }
+
+ private static int levelOfEmbedding ( int embedding ) {
+ return embedding & ~OVERRIDE;
+ }
+
+ private static int[] levelsFromEmbeddings ( int[] ea, int[] la ) {
+ assert ea != null;
+ assert la != null;
+ assert la.length == ea.length;
+ for ( int i = 0, n = la.length; i < n; i++ ) {
+ la [ i ] = levelOfEmbedding ( ea [ i ] );
+ }
+ return la;
+ }
+
+ private static void resolveRuns ( int[] wca, int defaultLevel, int[] ea, int[] la ) {
+ if ( la.length != wca.length ) {
+ throw new IllegalArgumentException ( "levels sequence length must match classes sequence length" );
+ } else if ( la.length != ea.length ) {
+ throw new IllegalArgumentException ( "levels sequence length must match embeddings sequence length" );
+ } else {
+ for ( int i = 0, n = ea.length, lPrev = defaultLevel; i < n; ) {
+ int s = i;
+ int e = s;
+ int l = findNextNonRetainedFormattingLevel ( wca, ea, s, lPrev );
+ while ( e < n ) {
+ if ( la [ e ] != l ) {
+ if ( startsWithRetainedFormattingRun ( wca, ea, e ) ) {
+ e += getLevelRunLength ( ea, e );
+ } else {
+ break;
+ }
+ } else {
+ e++;
+ }
+ }
+ lPrev = resolveRun ( wca, defaultLevel, ea, la, s, e, l, lPrev );
+ i = e;
+ }
+ }
+ }
+
+ private static int findNextNonRetainedFormattingLevel ( int[] wca, int[] ea, int start, int lPrev ) {
+ int s = start;
+ int e = wca.length;
+ while ( s < e ) {
+ if ( startsWithRetainedFormattingRun ( wca, ea, s ) ) {
+ s += getLevelRunLength ( ea, s );
+ } else {
+ break;
+ }
+ }
+ if ( s < e ) {
+ return levelOfEmbedding ( ea [ s ] );
+ } else {
+ return lPrev;
+ }
+ }
+
+ private static int getLevelRunLength ( int[] ea, int start ) {
+ assert start < ea.length;
+ int nl = 0;
+ for ( int s = start, e = ea.length, l0 = levelOfEmbedding ( ea [ start ] ); s < e; s++ ) {
+ if ( levelOfEmbedding ( ea [ s ] ) == l0 ) {
+ nl++;
+ } else {
+ break;
+ }
+ }
+ return nl;
+ }
+
+ private static boolean startsWithRetainedFormattingRun ( int[] wca, int[] ea, int start ) {
+ int nl = getLevelRunLength ( ea, start );
+ if ( nl > 0 ) {
+ int nc = getRetainedFormattingRunLength ( wca, start );
+ return ( nc >= nl );
+ } else {
+ return false;
+ }
+ }
+
+ private static int getRetainedFormattingRunLength ( int[] wca, int start ) {
+ assert start < wca.length;
+ int nc = 0;
+ for ( int s = start, e = wca.length; s < e; s++ ) {
+ if ( wca [ s ] == BidiConstants.BN ) {
+ nc++;
+ } else {
+ break;
+ }
+ }
+ return nc;
+ }
+
+ private static int resolveRun ( int[] wca, int defaultLevel, int[] ea, int[] la, int start, int end, int level, int levelPrev ) {
+
+ // determine start of run direction
+ int sor = directionOfLevel ( max ( levelPrev, level ) );
+
+ // determine end of run direction
+ int le = -1;
+ if ( end == wca.length ) {
+ le = max ( level, defaultLevel );
+ } else {
+ for ( int i = end; i < wca.length; i++ ) {
+ if ( wca [ i ] != BidiConstants.BN ) {
+ le = max ( level, la [ i ] );
+ break;
+ }
+ }
+ if ( le < 0 ) {
+ le = max ( level, defaultLevel );
+ }
+ }
+ int eor = directionOfLevel ( le );
+
+ if (log.isDebugEnabled()) {
+ log.debug ( "BR[" + padLeft ( start, 3 ) + "," + padLeft ( end, 3 ) + "] :" + padLeft ( level, 2 ) + ": SOR(" + getClassName(sor) + "), EOR(" + getClassName(eor) + ")" );
+ }
+
+ resolveWeak ( wca, defaultLevel, ea, la, start, end, level, sor, eor );
+ resolveNeutrals ( wca, defaultLevel, ea, la, start, end, level, sor, eor );
+ resolveImplicit ( wca, defaultLevel, ea, la, start, end, level, sor, eor );
+
+ // if this run is all retained formatting, then return prior level, otherwise this run's level
+ return isRetainedFormatting ( wca, start, end ) ? levelPrev : level;
+ }
+
+ private static void resolveWeak ( int[] wca, int defaultLevel, int[] ea, int[] la, int start, int end, int level, int sor, int eor ) {
+
+ // W1 - X BN* NSM -> X BN* X
+ for ( int i = start, n = end, bcPrev = sor; i < n; i++ ) {
+ int bc = wca [ i ];
+ if ( bc == NSM ) {
+ wca [ i ] = bcPrev;
+ } else if ( bc != BN ) {
+ bcPrev = bc;
+ }
+ }
+
+ // W2 - AL ... EN -> AL ... AN
+ for ( int i = start, n = end, bcPrev = sor; i < n; i++ ) {
+ int bc = wca [ i ];
+ if ( bc == EN ) {
+ if ( bcPrev == AL ) {
+ wca [ i ] = AN;
+ }
+ } else if ( isStrong ( bc ) ) {
+ bcPrev = bc;
+ }
+ }
+
+ // W3 - AL -> R
+ for ( int i = start, n = end; i < n; i++ ) {
+ int bc = wca [ i ];
+ if ( bc == AL ) {
+ wca [ i ] = R;
+ }
+ }
+
+ // W4 - EN BN* ES BN* EN -> EN BN* EN BN* EN; XN BN* CS BN* XN -> XN BN* XN BN* XN
+ for ( int i = start, n = end, bcPrev = sor; i < n; i++ ) {
+ int bc = wca [ i ];
+ if ( bc == ES ) {
+ int bcNext = eor;
+ for ( int j = i + 1; j < n; j++ ) {
+ if ( ( bc = wca [ j ] ) != BN ) {
+ bcNext = bc;
+ break;
+ }
+ }
+ if ( ( bcPrev == EN ) && ( bcNext == EN ) ) {
+ wca [ i ] = EN;
+ }
+ } else if ( bc == CS ) {
+ int bcNext = eor;
+ for ( int j = i + 1; j < n; j++ ) {
+ if ( ( bc = wca [ j ] ) != BN ) {
+ bcNext = bc;
+ break;
+ }
+ }
+ if ( ( bcPrev == EN ) && ( bcNext == EN ) ) {
+ wca [ i ] = EN;
+ } else if ( ( bcPrev == AN ) && ( bcNext == AN ) ) {
+ wca [ i ] = AN;
+ }
+ }
+ if ( bc != BN ) {
+ bcPrev = bc;
+ }
+ }
+
+ // W5 - EN (ET|BN)* -> EN (EN|BN)*; (ET|BN)* EN -> (EN|BN)* EN
+ for ( int i = start, n = end, bcPrev = sor; i < n; i++ ) {
+ int bc = wca [ i ];
+ if ( bc == ET ) {
+ int bcNext = eor;
+ for ( int j = i + 1; j < n; j++ ) {
+ bc = wca [ j ];
+ if ( ( bc != BN ) && ( bc != ET ) ) {
+ bcNext = bc;
+ break;
+ }
+ }
+ if ( ( bcPrev == EN ) || ( bcNext == EN ) ) {
+ wca [ i ] = EN;
+ }
+ } else if ( ( bc != BN ) && ( bc != ET ) ) {
+ bcPrev = bc;
+ }
+ }
+
+ // W6 - BN* (ET|ES|CS) BN* -> ON* ON ON*
+ for ( int i = start, n = end; i < n; i++ ) {
+ int bc = wca [ i ];
+ if ( ( bc == ET ) || ( bc == ES ) || ( bc == CS ) ) {
+ wca [ i ] = ON;
+ resolveAdjacentBoundaryNeutrals ( wca, start, end, i, ON );
+ }
+ }
+
+ // W7 - L ... EN -> L ... L
+ for ( int i = start, n = end, bcPrev = sor; i < n; i++ ) {
+ int bc = wca [ i ];
+ if ( bc == EN ) {
+ if ( bcPrev == L ) {
+ wca [ i ] = L;
+ }
+ } else if ( ( bc == L ) || ( bc == R ) ) {
+ bcPrev = bc;
+ }
+ }
+
+ }
+
+ private static void resolveNeutrals ( int[] wca, int defaultLevel, int[] ea, int[] la, int start, int end, int level, int sor, int eor ) {
+
+ // N1 - (L|R) N+ (L|R) -> L L+ L | R R+ R; (AN|EN) N+ R -> (AN|EN) R+ R; R N+ (AN|EN) -> R R+ (AN|EN)
+ for ( int i = start, n = end, bcPrev = sor; i < n; i++ ) {
+ int bc = wca [ i ];
+ if ( isNeutral ( bc ) ) {
+ int bcNext = eor;
+ for ( int j = i + 1; j < n; j++ ) {
+ bc = wca [ j ];
+ if ( ( bc == L ) || ( bc == R ) ) {
+ bcNext = bc;
+ break;
+ } else if ( ( bc == AN ) || ( bc == EN ) ) {
+ bcNext = R;
+ break;
+ } else if ( isNeutral ( bc ) ) {
+ continue;
+ } else if ( isRetainedFormatting ( bc ) ) {
+ continue;
+ } else {
+ break;
+ }
+ }
+ if ( bcPrev == bcNext ) {
+ wca [ i ] = bcPrev;
+ resolveAdjacentBoundaryNeutrals ( wca, start, end, i, bcPrev );
+ }
+ } else if ( ( bc == L ) || ( bc == R ) ) {
+ bcPrev = bc;
+ } else if ( ( bc == AN ) || ( bc == EN ) ) {
+ bcPrev = R;
+ }
+ }
+
+ // N2 - N -> embedding level
+ for ( int i = start, n = end; i < n; i++ ) {
+ int bc = wca [ i ];
+ if ( isNeutral ( bc ) ) {
+ int bcEmbedding = directionOfLevel ( levelOfEmbedding ( ea [ i ] ) );
+ wca [ i ] = bcEmbedding;
+ resolveAdjacentBoundaryNeutrals ( wca, start, end, i, bcEmbedding );
+ }
+ }
+
+ }
+
+ private static void resolveAdjacentBoundaryNeutrals ( int[] wca, int start, int end, int index, int bcNew ) {
+ if ( ( index < start ) || ( index >= end ) ) {
+ throw new IllegalArgumentException();
+ } else {
+ for ( int i = index - 1; i >= start; i-- ) {
+ int bc = wca [ i ];
+ if ( bc == BN ) {
+ wca [ i ] = bcNew;
+ } else {
+ break;
+ }
+ }
+ for ( int i = index + 1; i < end; i++ ) {
+ int bc = wca [ i ];
+ if ( bc == BN ) {
+ wca [ i ] = bcNew;
+ } else {
+ break;
+ }
+ }
+ }
+ }
+
+ private static void resolveImplicit ( int[] wca, int defaultLevel, int[] ea, int[] la, int start, int end, int level, int sor, int eor ) {
+ for ( int i = start, n = end; i < n; i++ ) {
+ int bc = wca [ i ]; // bidi class
+ int el = la [ i ]; // embedding level
+ int ed = 0; // embedding level delta
+ if ( ( el & 1 ) == 0 ) { // even
+ if ( bc == R ) {
+ ed = 1;
+ } else if ( bc == AN ) {
+ ed = 2;
+ } else if ( bc == EN ) {
+ ed = 2;
+ }
+ } else { // odd
+ if ( bc == L ) {
+ ed = 1;
+ } else if ( bc == EN ) {
+ ed = 1;
+ } else if ( bc == AN ) {
+ ed = 1;
+ }
+ }
+ la [ i ] = el + ed;
+ }
+ }
+
+ /**
+ * Resolve separators and boundary neutral levels to account for UAX#9 3.4 L1 while taking into
+ * account retention of formatting codes (5.2).
+ * @param ica original input class array (sequence)
+ * @param wca working copy of original intput class array (sequence), as modified by prior steps
+ * @param dl default paragraph level
+ * @param la array of output levels to be adjusted, as produced by bidi algorithm
+ */
+ private static void resolveSeparators ( int[] ica, int[] wca, int dl, int[] la ) {
+ // steps (1) through (3)
+ for ( int i = 0, n = ica.length; i < n; i++ ) {
+ int ic = ica[i];
+ if ( ( ic == BidiConstants.S ) || ( ic == BidiConstants.B ) ) {
+ la[i] = dl;
+ for ( int k = i - 1; k >= 0; k-- ) {
+ int pc = ica[k];
+ if ( isRetainedFormatting ( pc ) ) {
+ continue;
+ } if ( pc == BidiConstants.WS ) {
+ la[k] = dl;
+ } else {
+ break;
+ }
+ }
+ }
+ }
+ // step (4) - consider end of input sequence to be end of line, but skip any trailing boundary neutrals and retained formatting codes
+ for ( int i = ica.length; i > 0; i-- ) {
+ int k = i - 1;
+ int ic = ica[k];
+ if ( isRetainedFormatting ( ic ) ) {
+ continue;
+ } else if ( ic == BidiConstants.WS ) {
+ la[k] = dl;
+ } else {
+ break;
+ }
+ }
+ // step (5) - per section 5.2
+ for ( int i = 0, n = ica.length; i < n; i++ ) {
+ int ic = ica[i];
+ if ( isRetainedFormatting ( ic ) ) {
+ if ( i == 0 ) {
+ la[i] = dl;
+ } else {
+ la[i] = la [ i - 1 ];
+ }
+ }
+ }
+ }
+
+ private static boolean isStrong ( int bc ) {
+ switch ( bc ) {
+ case L:
+ case R:
+ case AL:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ private static boolean isNeutral ( int bc ) {
+ switch ( bc ) {
+ case WS:
+ case ON:
+ case S:
+ case B:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ private static boolean isRetainedFormatting ( int bc ) {
+ switch ( bc ) {
+ case LRE:
+ case LRO:
+ case RLE:
+ case RLO:
+ case PDF:
+ case BN:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ private static boolean isRetainedFormatting ( int[] ca, int s, int e ) {
+ for ( int i = s; i < e; i++ ) {
+ if ( ! isRetainedFormatting ( ca[i] ) ) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private static int max ( int x, int y ) {
+ if ( x > y ) {
+ return x;
+ } else {
+ return y;
+ }
+ }
+
+ private static int[] getClasses ( int[] chars ) {
+ int[] classes = new int [ chars.length ];
+ int bc;
+ for ( int i = 0, n = chars.length; i < n; i++ ) {
+ int ch = chars [ i ];
+ if ( ch >= 0 ) {
+ bc = BidiClass.getBidiClass ( chars [ i ] );
+ } else {
+ bc = SURROGATE;
+ }
+ classes [ i ] = bc;
+ }
+ return classes;
+ }
+
+ /**
+ * Convert character sequence (a UTF-16 encoded string) to an array of unicode scalar values
+ * expressed as integers. If a valid UTF-16 surrogate pair is encountered, it is converted to
+ * two integers, the first being the equivalent unicode scalar value, and the second being
+ * negative one (-1). This special mechanism is used to track the use of surrogate pairs while
+ * working with unicode scalar values, and permits maintaining indices that apply both to the
+ * input UTF-16 and out scalar value sequences.
+ * @return a boolean indicating that content is present that triggers bidirectional processing
+ * @param cs a UTF-16 encoded character sequence
+ * @param chars an integer array to accept the converted scalar values, where the length of the
+ * array must be the same as the length of the input character sequence
+ * @throws IllegalArgumentException if the input sequence is not a valid UTF-16 string, e.g.,
+ * if it contains an isolated UTF-16 surrogate
+ */
+ private static boolean convertToScalar ( CharSequence cs, int[] chars ) throws IllegalArgumentException {
+ boolean triggered = false;
+ if ( chars.length != cs.length() ) {
+ throw new IllegalArgumentException ( "characters array length must match input sequence length" );
+ }
+ for ( int i = 0, n = chars.length; i < n; ) {
+ int chIn = cs.charAt ( i );
+ int chOut;
+ if ( chIn < 0xD800 ) {
+ chOut = chIn;
+ } else if ( chIn < 0xDC00 ) {
+ int chHi = chIn;
+ int chLo;
+ if ( ( i + 1 ) < n ) {
+ chLo = cs.charAt ( i + 1 );
+ if ( ( chLo >= 0xDC00 ) && ( chLo <= 0xDFFF ) ) {
+ chOut = convertToScalar ( chHi, chLo );
+ } else {
+ throw new IllegalArgumentException ( "isolated high surrogate" );
+ }
+ } else {
+ throw new IllegalArgumentException ( "truncated surrogate pair" );
+ }
+ } else if ( chIn < 0xE000 ) {
+ throw new IllegalArgumentException ( "isolated low surrogate" );
+ } else {
+ chOut = chIn;
+ }
+ if ( ! triggered && triggersBidi ( chOut ) ) {
+ triggered = true;
+ }
+ if ( ( chOut & 0xFF0000 ) == 0 ) {
+ chars [ i++ ] = chOut;
+ } else {
+ chars [ i++ ] = chOut;
+ chars [ i++ ] = -1;
+ }
+ }
+ return triggered;
+ }
+
+ /**
+ * Convert UTF-16 surrogate pair to unicode scalar valuee.
+ * @return a unicode scalar value
+ * @param chHi high (most significant or first) surrogate
+ * @param chLo low (least significant or second) surrogate
+ * @throws IllegalArgumentException if one of the input surrogates is not valid
+ */
+ private static int convertToScalar ( int chHi, int chLo ) {
+ if ( ( chHi < 0xD800 ) || ( chHi > 0xDBFF ) ) {
+ throw new IllegalArgumentException ( "bad high surrogate" );
+ } else if ( ( chLo < 0xDC00 ) || ( chLo > 0xDFFF ) ) {
+ throw new IllegalArgumentException ( "bad low surrogate" );
+ } else {
+ return ( ( ( chHi & 0x03FF ) << 10 ) | ( chLo & 0x03FF ) ) + 0x10000;
+ }
+ }
+
+ /**
+ * Determine of character CH triggers bidirectional processing. Bidirectional
+ * processing is deemed triggerable if CH is a strong right-to-left character,
+ * an arabic letter or number, or is a right-to-left embedding or override
+ * character.
+ * @return true if character triggers bidirectional processing
+ * @param ch a unicode scalar value
+ */
+ private static boolean triggersBidi ( int ch ) {
+ switch ( BidiClass.getBidiClass ( ch ) ) {
+ case R:
+ case AL:
+ case AN:
+ case RLE:
+ case RLO:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ private static void dump ( String header, int[] chars, int[] classes, int defaultLevel, int[] levels ) {
+ log.debug ( header );
+ log.debug ( "BD: default level(" + defaultLevel + ")" );
+ StringBuffer sb = new StringBuffer();
+ if ( chars != null ) {
+ for ( int i = 0, n = chars.length; i < n; i++ ) {
+ int ch = chars [ i ];
+ sb.setLength(0);
+ if ( ( ch > 0x20 ) && ( ch < 0x7F ) ) {
+ sb.append ( (char) ch );
+ } else {
+ sb.append ( CharUtilities.charToNCRef ( ch ) );
+ }
+ for ( int k = sb.length(); k < 12; k++ ) {
+ sb.append ( ' ' );
+ }
+ sb.append ( ": " + padRight ( getClassName ( classes[i] ), 4 ) + " " + levels[i] );
+ log.debug ( sb );
+ }
+ } else {
+ for ( int i = 0, n = classes.length; i < n; i++ ) {
+ sb.setLength(0);
+ for ( int k = sb.length(); k < 12; k++ ) {
+ sb.append ( ' ' );
+ }
+ sb.append ( ": " + padRight ( getClassName ( classes[i] ), 4 ) + " " + levels[i] );
+ log.debug ( sb );
+ }
+ }
+ }
+
+ private static String getClassName ( int bc ) {
+ switch ( bc ) {
+ case L: // left-to-right
+ return "L";
+ case LRE: // left-to-right embedding
+ return "LRE";
+ case LRO: // left-to-right override
+ return "LRO";
+ case R: // right-to-left
+ return "R";
+ case AL: // right-to-left arabic
+ return "AL";
+ case RLE: // right-to-left embedding
+ return "RLE";
+ case RLO: // right-to-left override
+ return "RLO";
+ case PDF: // pop directional formatting
+ return "PDF";
+ case EN: // european number
+ return "EN";
+ case ES: // european number separator
+ return "ES";
+ case ET: // european number terminator
+ return "ET";
+ case AN: // arabic number
+ return "AN";
+ case CS: // common number separator
+ return "CS";
+ case NSM: // non-spacing mark
+ return "NSM";
+ case BN: // boundary neutral
+ return "BN";
+ case B: // paragraph separator
+ return "B";
+ case S: // segment separator
+ return "S";
+ case WS: // whitespace
+ return "WS";
+ case ON: // other neutrals
+ return "ON";
+ case SURROGATE: // placeholder for low surrogate
+ return "SUR";
+ default:
+ return "?";
+ }
+ }
+
+ private static String padLeft ( int n, int width ) {
+ return padLeft ( Integer.toString ( n ), width );
+ }
+
+ private static String padLeft ( String s, int width ) {
+ StringBuffer sb = new StringBuffer();
+ for ( int i = s.length(); i < width; i++ ) {
+ sb.append(' ');
+ }
+ sb.append ( s );
+ return sb.toString();
+ }
+
+ /* not used yet
+ private static String padRight ( int n, int width ) {
+ return padRight ( Integer.toString ( n ), width );
+ }
+ */
+
+ private static String padRight ( String s, int width ) {
+ StringBuffer sb = new StringBuffer ( s );
+ for ( int i = sb.length(); i < width; i++ ) {
+ sb.append(' ');
+ }
+ return sb.toString();
+ }
+
+}
diff --git a/src/java/org/apache/fop/complexscripts/fonts/AdvancedTypographicTableFormatException.java b/src/java/org/apache/fop/complexscripts/fonts/AdvancedTypographicTableFormatException.java
new file mode 100644
index 000000000..4f3e341d6
--- /dev/null
+++ b/src/java/org/apache/fop/complexscripts/fonts/AdvancedTypographicTableFormatException.java
@@ -0,0 +1,49 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.complexscripts.fonts;
+
+/**
+ * Exception thrown when attempting to decode a truetype font file and a format
+ * constraint is violated.
+ * @author Glenn Adams
+ */
+public class AdvancedTypographicTableFormatException extends RuntimeException {
+ /**
+ * Instantiate ATT format exception.
+ */
+ public AdvancedTypographicTableFormatException() {
+ super();
+ }
+ /**
+ * Instantiate ATT format exception.
+ * @param message a message string
+ */
+ public AdvancedTypographicTableFormatException(String message) {
+ super(message);
+ }
+ /**
+ * Instantiate ATT format exception.
+ * @param message a message string
+ * @param cause a <code>Throwable</code> that caused this exception
+ */
+ public AdvancedTypographicTableFormatException(String message, Throwable cause) {
+ super(message, cause);
+ }
+}
diff --git a/src/java/org/apache/fop/complexscripts/fonts/GlyphClassMapping.java b/src/java/org/apache/fop/complexscripts/fonts/GlyphClassMapping.java
new file mode 100644
index 000000000..4afc747a2
--- /dev/null
+++ b/src/java/org/apache/fop/complexscripts/fonts/GlyphClassMapping.java
@@ -0,0 +1,48 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.complexscripts.fonts;
+
+// CSOFF: LineLengthCheck
+
+/**
+ * The <code>GlyphClassMapping</code> interface provides glyph identifier to class
+ * index mapping support.
+ * @author Glenn Adams
+ */
+public interface GlyphClassMapping {
+
+ /**
+ * Obtain size of class table, i.e., ciMax + 1, where ciMax is the maximum
+ * class index.
+ * @param set for coverage set based class mappings, indicates set index, otherwise ignored
+ * @return size of class table
+ */
+ int getClassSize ( int set );
+
+ /**
+ * Map glyph identifier (code) to coverge index. Returns -1 if glyph identifier is not in the domain of
+ * the class table.
+ * @param gid glyph identifier (code)
+ * @param set for coverage set based class mappings, indicates set index, otherwise ignored
+ * @return non-negative glyph class index or -1 if glyph identifiers is not mapped by table
+ */
+ int getClassIndex ( int gid, int set );
+
+}
diff --git a/src/java/org/apache/fop/complexscripts/fonts/GlyphClassTable.java b/src/java/org/apache/fop/complexscripts/fonts/GlyphClassTable.java
new file mode 100644
index 000000000..88478531e
--- /dev/null
+++ b/src/java/org/apache/fop/complexscripts/fonts/GlyphClassTable.java
@@ -0,0 +1,277 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.complexscripts.fonts;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Iterator;
+
+// CSOFF: LineLengthCheck
+// CSOFF: NoWhitespaceAfterCheck
+
+/**
+ * Base class implementation of glyph class table.
+ * @author Glenn Adams
+ */
+public final class GlyphClassTable extends GlyphMappingTable implements GlyphClassMapping {
+
+ /** empty mapping table */
+ public static final int GLYPH_CLASS_TYPE_EMPTY = GLYPH_MAPPING_TYPE_EMPTY;
+
+ /** mapped mapping table */
+ public static final int GLYPH_CLASS_TYPE_MAPPED = GLYPH_MAPPING_TYPE_MAPPED;
+
+ /** range based mapping table */
+ public static final int GLYPH_CLASS_TYPE_RANGE = GLYPH_MAPPING_TYPE_RANGE;
+
+ /** empty mapping table */
+ public static final int GLYPH_CLASS_TYPE_COVERAGE_SET = 3;
+
+ private GlyphClassMapping cm;
+
+ private GlyphClassTable ( GlyphClassMapping cm ) {
+ assert cm != null;
+ assert cm instanceof GlyphMappingTable;
+ this.cm = cm;
+ }
+
+ /** {@inheritDoc} */
+ public int getType() {
+ return ( (GlyphMappingTable) cm ) .getType();
+ }
+
+ /** {@inheritDoc} */
+ public List getEntries() {
+ return ( (GlyphMappingTable) cm ) .getEntries();
+ }
+
+ /** {@inheritDoc} */
+ public int getClassSize ( int set ) {
+ return cm.getClassSize ( set );
+ }
+
+ /** {@inheritDoc} */
+ public int getClassIndex ( int gid, int set ) {
+ return cm.getClassIndex ( gid, set );
+ }
+
+ /**
+ * Create glyph class table.
+ * @param entries list of mapped or ranged class entries, or null or empty list
+ * @return a new covera table instance
+ */
+ public static GlyphClassTable createClassTable ( List entries ) {
+ GlyphClassMapping cm;
+ if ( ( entries == null ) || ( entries.size() == 0 ) ) {
+ cm = new EmptyClassTable ( entries );
+ } else if ( isMappedClass ( entries ) ) {
+ cm = new MappedClassTable ( entries );
+ } else if ( isRangeClass ( entries ) ) {
+ cm = new RangeClassTable ( entries );
+ } else if ( isCoverageSetClass ( entries ) ) {
+ cm = new CoverageSetClassTable ( entries );
+ } else {
+ cm = null;
+ }
+ assert cm != null : "unknown class type";
+ return new GlyphClassTable ( cm );
+ }
+
+ private static boolean isMappedClass ( List entries ) {
+ if ( ( entries == null ) || ( entries.size() == 0 ) ) {
+ return false;
+ } else {
+ for ( Iterator it = entries.iterator(); it.hasNext();) {
+ Object o = it.next();
+ if ( ! ( o instanceof Integer ) ) {
+ return false;
+ }
+ }
+ return true;
+ }
+ }
+
+ private static boolean isRangeClass ( List entries ) {
+ if ( ( entries == null ) || ( entries.size() == 0 ) ) {
+ return false;
+ } else {
+ for ( Iterator it = entries.iterator(); it.hasNext();) {
+ Object o = it.next();
+ if ( ! ( o instanceof MappingRange ) ) {
+ return false;
+ }
+ }
+ return true;
+ }
+ }
+
+ private static boolean isCoverageSetClass ( List entries ) {
+ if ( ( entries == null ) || ( entries.size() == 0 ) ) {
+ return false;
+ } else {
+ for ( Iterator it = entries.iterator(); it.hasNext();) {
+ Object o = it.next();
+ if ( ! ( o instanceof GlyphCoverageTable ) ) {
+ return false;
+ }
+ }
+ return true;
+ }
+ }
+
+ private static class EmptyClassTable extends GlyphMappingTable.EmptyMappingTable implements GlyphClassMapping {
+ public EmptyClassTable ( List entries ) {
+ super ( entries );
+ }
+ /** {@inheritDoc} */
+ public int getClassSize ( int set ) {
+ return 0;
+ }
+ /** {@inheritDoc} */
+ public int getClassIndex ( int gid, int set ) {
+ return -1;
+ }
+ }
+
+ private static class MappedClassTable extends GlyphMappingTable.MappedMappingTable implements GlyphClassMapping {
+ private int firstGlyph;
+ private int[] gca;
+ private int gcMax = -1;
+ public MappedClassTable ( List entries ) {
+ populate ( entries );
+ }
+ /** {@inheritDoc} */
+ public List getEntries() {
+ List entries = new java.util.ArrayList();
+ entries.add ( Integer.valueOf ( firstGlyph ) );
+ if ( gca != null ) {
+ for ( int i = 0, n = gca.length; i < n; i++ ) {
+ entries.add ( Integer.valueOf ( gca [ i ] ) );
+ }
+ }
+ return entries;
+ }
+ /** {@inheritDoc} */
+ public int getMappingSize() {
+ return gcMax + 1;
+ }
+ /** {@inheritDoc} */
+ public int getMappedIndex ( int gid ) {
+ int i = gid - firstGlyph;
+ if ( ( i >= 0 ) && ( i < gca.length ) ) {
+ return gca [ i ];
+ } else {
+ return -1;
+ }
+ }
+ /** {@inheritDoc} */
+ public int getClassSize ( int set ) {
+ return getMappingSize();
+ }
+ /** {@inheritDoc} */
+ public int getClassIndex ( int gid, int set ) {
+ return getMappedIndex ( gid );
+ }
+ private void populate ( List entries ) {
+ // obtain entries iterator
+ Iterator it = entries.iterator();
+ // extract first glyph
+ int firstGlyph = 0;
+ if ( it.hasNext() ) {
+ Object o = it.next();
+ if ( o instanceof Integer ) {
+ firstGlyph = ( (Integer) o ) . intValue();
+ } else {
+ throw new AdvancedTypographicTableFormatException ( "illegal entry, first entry must be Integer denoting first glyph value, but is: " + o );
+ }
+ }
+ // extract glyph class array
+ int i = 0, n = entries.size() - 1, gcMax = -1;
+ int[] gca = new int [ n ];
+ while ( it.hasNext() ) {
+ Object o = it.next();
+ if ( o instanceof Integer ) {
+ int gc = ( (Integer) o ) . intValue();
+ gca [ i++ ] = gc;
+ if ( gc > gcMax ) {
+ gcMax = gc;
+ }
+ } else {
+ throw new AdvancedTypographicTableFormatException ( "illegal mapping entry, must be Integer: " + o );
+ }
+ }
+ assert i == n;
+ assert this.gca == null;
+ this.firstGlyph = firstGlyph;
+ this.gca = gca;
+ this.gcMax = gcMax;
+ }
+ /** {@inheritDoc} */
+ public String toString() {
+ StringBuffer sb = new StringBuffer();
+ sb.append("{ firstGlyph = " + firstGlyph + ", classes = {");
+ for ( int i = 0, n = gca.length; i < n; i++ ) {
+ if ( i > 0 ) {
+ sb.append(',');
+ }
+ sb.append ( Integer.toString ( gca [ i ] ) );
+ }
+ sb.append("} }");
+ return sb.toString();
+ }
+ }
+
+ private static class RangeClassTable extends GlyphMappingTable.RangeMappingTable implements GlyphClassMapping {
+ public RangeClassTable ( List entries ) {
+ super ( entries );
+ }
+ /** {@inheritDoc} */
+ public int getMappedIndex ( int gid, int s, int m ) {
+ return m;
+ }
+ /** {@inheritDoc} */
+ public int getClassSize ( int set ) {
+ return getMappingSize();
+ }
+ /** {@inheritDoc} */
+ public int getClassIndex ( int gid, int set ) {
+ return getMappedIndex ( gid );
+ }
+ }
+
+ private static class CoverageSetClassTable extends GlyphMappingTable.EmptyMappingTable implements GlyphClassMapping {
+ public CoverageSetClassTable ( List entries ) {
+ throw new UnsupportedOperationException ( "coverage set class table not yet supported" );
+ }
+ /** {@inheritDoc} */
+ public int getType() {
+ return GLYPH_CLASS_TYPE_COVERAGE_SET;
+ }
+ /** {@inheritDoc} */
+ public int getClassSize ( int set ) {
+ return 0;
+ }
+ /** {@inheritDoc} */
+ public int getClassIndex ( int gid, int set ) {
+ return -1;
+ }
+ }
+
+}
diff --git a/src/java/org/apache/fop/complexscripts/fonts/GlyphCoverageMapping.java b/src/java/org/apache/fop/complexscripts/fonts/GlyphCoverageMapping.java
new file mode 100644
index 000000000..e8fde9474
--- /dev/null
+++ b/src/java/org/apache/fop/complexscripts/fonts/GlyphCoverageMapping.java
@@ -0,0 +1,46 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.complexscripts.fonts;
+
+// CSOFF: LineLengthCheck
+
+/**
+ * The <code>GlyphCoverageMapping</code> interface provides glyph identifier to coverage
+ * index mapping support.
+ * @author Glenn Adams
+ */
+public interface GlyphCoverageMapping {
+
+ /**
+ * Obtain size of coverage table, i.e., ciMax + 1, where ciMax is the maximum
+ * coverage index.
+ * @return size of coverage table
+ */
+ int getCoverageSize();
+
+ /**
+ * Map glyph identifier (code) to coverge index. Returns -1 if glyph identifier is not in the domain of
+ * the coverage table.
+ * @param gid glyph identifier (code)
+ * @return non-negative glyph coverage index or -1 if glyph identifiers is not mapped by table
+ */
+ int getCoverageIndex ( int gid );
+
+}
diff --git a/src/java/org/apache/fop/complexscripts/fonts/GlyphCoverageTable.java b/src/java/org/apache/fop/complexscripts/fonts/GlyphCoverageTable.java
new file mode 100644
index 000000000..a9b0fc2b5
--- /dev/null
+++ b/src/java/org/apache/fop/complexscripts/fonts/GlyphCoverageTable.java
@@ -0,0 +1,233 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.complexscripts.fonts;
+
+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
+
+/**
+ * Base class implementation of glyph coverage table.
+ * @author Glenn Adams
+ */
+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;
+
+ /** mapped mapping table */
+ public static final int GLYPH_COVERAGE_TYPE_MAPPED = GLYPH_MAPPING_TYPE_MAPPED;
+
+ /** range based mapping table */
+ public static final int GLYPH_COVERAGE_TYPE_RANGE = GLYPH_MAPPING_TYPE_RANGE;
+
+ private GlyphCoverageMapping cm;
+
+ private GlyphCoverageTable ( GlyphCoverageMapping cm ) {
+ assert cm != null;
+ assert cm instanceof GlyphMappingTable;
+ this.cm = cm;
+ }
+
+ /** {@inheritDoc} */
+ public int getType() {
+ return ( (GlyphMappingTable) cm ) .getType();
+ }
+
+ /** {@inheritDoc} */
+ public List getEntries() {
+ return ( (GlyphMappingTable) cm ) .getEntries();
+ }
+
+ /** {@inheritDoc} */
+ public int getCoverageSize() {
+ return cm.getCoverageSize();
+ }
+
+ /** {@inheritDoc} */
+ public int getCoverageIndex ( int gid ) {
+ return cm.getCoverageIndex ( gid );
+ }
+
+ /**
+ * Create glyph coverage table.
+ * @param entries list of mapped or ranged coverage entries, or null or empty list
+ * @return a new covera table instance
+ */
+ public static GlyphCoverageTable createCoverageTable ( List entries ) {
+ GlyphCoverageMapping cm;
+ if ( ( entries == null ) || ( entries.size() == 0 ) ) {
+ cm = new EmptyCoverageTable ( entries );
+ } else if ( isMappedCoverage ( entries ) ) {
+ cm = new MappedCoverageTable ( entries );
+ } else if ( isRangeCoverage ( entries ) ) {
+ cm = new RangeCoverageTable ( entries );
+ } else {
+ cm = null;
+ }
+ assert cm != null : "unknown coverage type";
+ return new GlyphCoverageTable ( cm );
+ }
+
+ private static boolean isMappedCoverage ( List entries ) {
+ if ( ( entries == null ) || ( entries.size() == 0 ) ) {
+ return false;
+ } else {
+ for ( Iterator it = entries.iterator(); it.hasNext();) {
+ Object o = it.next();
+ if ( ! ( o instanceof Integer ) ) {
+ return false;
+ }
+ }
+ return true;
+ }
+ }
+
+ private static boolean isRangeCoverage ( List entries ) {
+ if ( ( entries == null ) || ( entries.size() == 0 ) ) {
+ return false;
+ } else {
+ for ( Iterator it = entries.iterator(); it.hasNext();) {
+ Object o = it.next();
+ if ( ! ( o instanceof MappingRange ) ) {
+ return false;
+ }
+ }
+ return true;
+ }
+ }
+
+ private static class EmptyCoverageTable extends GlyphMappingTable.EmptyMappingTable implements GlyphCoverageMapping {
+ public EmptyCoverageTable ( List entries ) {
+ super ( entries );
+ }
+ /** {@inheritDoc} */
+ public int getCoverageSize() {
+ return 0;
+ }
+ /** {@inheritDoc} */
+ public int getCoverageIndex ( int gid ) {
+ return -1;
+ }
+ }
+
+ private static class MappedCoverageTable extends GlyphMappingTable.MappedMappingTable implements GlyphCoverageMapping {
+ private int[] map;
+ public MappedCoverageTable ( List entries ) {
+ populate ( entries );
+ }
+ /** {@inheritDoc} */
+ public List getEntries() {
+ List entries = new java.util.ArrayList();
+ if ( map != null ) {
+ for ( int i = 0, n = map.length; i < n; i++ ) {
+ entries.add ( Integer.valueOf ( map [ i ] ) );
+ }
+ }
+ return entries;
+ }
+ /** {@inheritDoc} */
+ public int getMappingSize() {
+ return ( map != null ) ? map.length : 0;
+ }
+ public int getMappedIndex ( int gid ) {
+ int i;
+ if ( ( i = Arrays.binarySearch ( map, gid ) ) >= 0 ) {
+ return i;
+ } else {
+ return -1;
+ }
+ }
+ /** {@inheritDoc} */
+ public int getCoverageSize() {
+ return getMappingSize();
+ }
+ /** {@inheritDoc} */
+ public int getCoverageIndex ( int gid ) {
+ return getMappedIndex ( gid );
+ }
+ private void populate ( List entries ) {
+ int i = 0, n = entries.size(), gidMax = -1;
+ int[] map = new int [ n ];
+ for ( Iterator it = entries.iterator(); it.hasNext();) {
+ Object o = it.next();
+ if ( o instanceof Integer ) {
+ int gid = ( (Integer) o ) . intValue();
+ if ( ( gid >= 0 ) && ( gid < 65536 ) ) {
+ if ( gid > gidMax ) {
+ map [ i++ ] = gidMax = gid;
+ } else {
+ log.info ( "ignoring out of order or duplicate glyph index: " + gid );
+ }
+ } else {
+ throw new AdvancedTypographicTableFormatException ( "illegal glyph index: " + gid );
+ }
+ } else {
+ throw new AdvancedTypographicTableFormatException ( "illegal coverage entry, must be Integer: " + o );
+ }
+ }
+ assert i == n;
+ assert this.map == null;
+ this.map = map;
+ }
+ /** {@inheritDoc} */
+ public String toString() {
+ StringBuffer sb = new StringBuffer();
+ sb.append('{');
+ for ( int i = 0, n = map.length; i < n; i++ ) {
+ if ( i > 0 ) {
+ sb.append(',');
+ }
+ sb.append ( Integer.toString ( map [ i ] ) );
+ }
+ sb.append('}');
+ return sb.toString();
+ }
+ }
+
+ private static class RangeCoverageTable extends GlyphMappingTable.RangeMappingTable implements GlyphCoverageMapping {
+ public RangeCoverageTable ( List entries ) {
+ super ( entries );
+ }
+ /** {@inheritDoc} */
+ public int getMappedIndex ( int gid, int s, int m ) {
+ return m + gid - s;
+ }
+ /** {@inheritDoc} */
+ public int getCoverageSize() {
+ return getMappingSize();
+ }
+ /** {@inheritDoc} */
+ public int getCoverageIndex ( int gid ) {
+ return getMappedIndex ( gid );
+ }
+ }
+
+}
diff --git a/src/java/org/apache/fop/complexscripts/fonts/GlyphDefinition.java b/src/java/org/apache/fop/complexscripts/fonts/GlyphDefinition.java
new file mode 100644
index 000000000..a3d511dce
--- /dev/null
+++ b/src/java/org/apache/fop/complexscripts/fonts/GlyphDefinition.java
@@ -0,0 +1,38 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.complexscripts.fonts;
+
+// CSOFF: LineLengthCheck
+
+/**
+ * The <code>GlyphDefinition</code> interface is a marker interface implemented by a glyph definition
+ * subtable.
+ * @author Glenn Adams
+ */
+public interface GlyphDefinition {
+
+ /**
+ * Determine if some definition is available for a specific glyph.
+ * @param gi a glyph index
+ * @return true if some (unspecified) definition is available for the specified glyph
+ */
+ boolean hasDefinition ( int gi );
+
+}
diff --git a/src/java/org/apache/fop/complexscripts/fonts/GlyphDefinitionSubtable.java b/src/java/org/apache/fop/complexscripts/fonts/GlyphDefinitionSubtable.java
new file mode 100644
index 000000000..ec7e1e3d2
--- /dev/null
+++ b/src/java/org/apache/fop/complexscripts/fonts/GlyphDefinitionSubtable.java
@@ -0,0 +1,76 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.complexscripts.fonts;
+
+// CSOFF: LineLengthCheck
+// CSOFF: InnerAssignmentCheck
+
+/**
+ * The <code>GlyphDefinitionSubtable</code> implements an abstract base of a glyph definition subtable,
+ * providing a default implementation of the <code>GlyphDefinition</code> interface.
+ * @author Glenn Adams
+ */
+public abstract class GlyphDefinitionSubtable extends GlyphSubtable implements GlyphDefinition {
+
+ /**
+ * Instantiate a <code>GlyphDefinitionSubtable</code>.
+ * @param id subtable identifier
+ * @param sequence subtable sequence
+ * @param flags subtable flags
+ * @param format subtable format
+ * @param mapping subtable coverage table
+ */
+ protected GlyphDefinitionSubtable ( String id, int sequence, int flags, int format, GlyphMappingTable mapping ) {
+ super ( id, sequence, flags, format, mapping );
+ }
+
+ /** {@inheritDoc} */
+ public int getTableType() {
+ return GlyphTable.GLYPH_TABLE_TYPE_DEFINITION;
+ }
+
+ /** {@inheritDoc} */
+ public String getTypeName() {
+ return GlyphDefinitionTable.getLookupTypeName ( getType() );
+ }
+
+ /** {@inheritDoc} */
+ public boolean usesReverseScan() {
+ return false;
+ }
+
+ /** {@inheritDoc} */
+ public boolean hasDefinition ( int gi ) {
+ GlyphCoverageMapping cvm;
+ if ( ( cvm = getCoverage() ) != null ) {
+ if ( cvm.getCoverageIndex ( gi ) >= 0 ) {
+ return true;
+ }
+ }
+ GlyphClassMapping clm;
+ if ( ( clm = getClasses() ) != null ) {
+ if ( clm.getClassIndex ( gi, 0 ) >= 0 ) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+}
diff --git a/src/java/org/apache/fop/complexscripts/fonts/GlyphDefinitionTable.java b/src/java/org/apache/fop/complexscripts/fonts/GlyphDefinitionTable.java
new file mode 100644
index 000000000..d5cd04615
--- /dev/null
+++ b/src/java/org/apache/fop/complexscripts/fonts/GlyphDefinitionTable.java
@@ -0,0 +1,451 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.complexscripts.fonts;
+
+import java.nio.CharBuffer;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import org.apache.fop.complexscripts.scripts.ScriptProcessor;
+import org.apache.fop.complexscripts.util.GlyphSequence;
+
+// CSOFF: InnerAssignmentCheck
+// CSOFF: LineLengthCheck
+
+/**
+ * The <code>GlyphDefinitionTable</code> class is a glyph table that implements
+ * glyph definition functionality according to the OpenType GDEF table.
+ * @author Glenn Adams
+ */
+public class GlyphDefinitionTable extends GlyphTable {
+
+ /** logging instance */
+ private static final Log log = LogFactory.getLog(GlyphDefinitionTable.class); // CSOK: ConstantNameCheck
+
+ /** glyph class subtable type */
+ public static final int GDEF_LOOKUP_TYPE_GLYPH_CLASS = 1;
+ /** attachment point subtable type */
+ public static final int GDEF_LOOKUP_TYPE_ATTACHMENT_POINT = 2;
+ /** ligature caret subtable type */
+ public static final int GDEF_LOOKUP_TYPE_LIGATURE_CARET = 3;
+ /** mark attachment subtable type */
+ public static final int GDEF_LOOKUP_TYPE_MARK_ATTACHMENT = 4;
+
+ /** pre-defined glyph class - base glyph */
+ public static final int GLYPH_CLASS_BASE = 1;
+ /** pre-defined glyph class - ligature glyph */
+ public static final int GLYPH_CLASS_LIGATURE = 2;
+ /** pre-defined glyph class - mark glyph */
+ public static final int GLYPH_CLASS_MARK = 3;
+ /** pre-defined glyph class - component glyph */
+ public static final int GLYPH_CLASS_COMPONENT = 4;
+
+ /** singleton glyph class table */
+ private GlyphClassSubtable gct;
+ /** singleton attachment point table */
+ // private AttachmentPointSubtable apt; // NOT YET USED
+ /** singleton ligature caret table */
+ // private LigatureCaretSubtable lct; // NOT YET USED
+ /** singleton mark attachment table */
+ private MarkAttachmentSubtable mat;
+
+ /**
+ * Instantiate a <code>GlyphDefinitionTable</code> object using the specified subtables.
+ * @param subtables a list of identified subtables
+ */
+ public GlyphDefinitionTable ( List subtables ) {
+ super ( null, new HashMap(0) );
+ if ( ( subtables == null ) || ( subtables.size() == 0 ) ) {
+ throw new AdvancedTypographicTableFormatException ( "subtables must be non-empty" );
+ } else {
+ for ( Iterator it = subtables.iterator(); it.hasNext();) {
+ Object o = it.next();
+ if ( o instanceof GlyphDefinitionSubtable ) {
+ addSubtable ( (GlyphSubtable) o );
+ } else {
+ throw new AdvancedTypographicTableFormatException ( "subtable must be a glyph definition subtable" );
+ }
+ }
+ freezeSubtables();
+ }
+ }
+
+ /**
+ * 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 ) {
+ this.gct = (GlyphClassSubtable) subtable;
+ } else if ( subtable instanceof AttachmentPointSubtable ) {
+ // TODO - not yet used
+ // this.apt = (AttachmentPointSubtable) subtable;
+ } else if ( subtable instanceof LigatureCaretSubtable ) {
+ // TODO - not yet used
+ // this.lct = (LigatureCaretSubtable) subtable;
+ } else if ( subtable instanceof MarkAttachmentSubtable ) {
+ this.mat = (MarkAttachmentSubtable) subtable;
+ } else {
+ throw new UnsupportedOperationException ( "unsupported glyph definition subtable type: " + subtable );
+ }
+ }
+
+ /**
+ * Determine if glyph belongs to pre-defined glyph class.
+ * @param gid a glyph identifier (index)
+ * @param gc a pre-defined glyph class (GLYPH_CLASS_BASE|GLYPH_CLASS_LIGATURE|GLYPH_CLASS_MARK|GLYPH_CLASS_COMPONENT).
+ * @return true if glyph belongs to specified glyph class
+ */
+ public boolean isGlyphClass ( int gid, int gc ) {
+ if ( gct != null ) {
+ return gct.isGlyphClass ( gid, gc );
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Determine glyph class.
+ * @param gid a glyph identifier (index)
+ * @return a pre-defined glyph class (GLYPH_CLASS_BASE|GLYPH_CLASS_LIGATURE|GLYPH_CLASS_MARK|GLYPH_CLASS_COMPONENT).
+ */
+ public int getGlyphClass ( int gid ) {
+ if ( gct != null ) {
+ return gct.getGlyphClass ( gid );
+ } else {
+ return -1;
+ }
+ }
+
+ /**
+ * Determine if glyph belongs to (font specific) mark attachment class.
+ * @param gid a glyph identifier (index)
+ * @param mac a (font specific) mark attachment class
+ * @return true if glyph belongs to specified mark attachment class
+ */
+ public boolean isMarkAttachClass ( int gid, int mac ) {
+ if ( mat != null ) {
+ return mat.isMarkAttachClass ( gid, mac );
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Determine mark attachment class.
+ * @param gid a glyph identifier (index)
+ * @return a non-negative mark attachment class, or -1 if no class defined
+ */
+ public int getMarkAttachClass ( int gid ) {
+ if ( mat != null ) {
+ return mat.getMarkAttachClass ( gid );
+ } else {
+ return -1;
+ }
+ }
+
+ /**
+ * Map a lookup type name to its constant (integer) value.
+ * @param name lookup type name
+ * @return lookup type
+ */
+ public static int getLookupTypeFromName ( String name ) {
+ int t;
+ String s = name.toLowerCase();
+ if ( "glyphclass".equals ( s ) ) {
+ t = GDEF_LOOKUP_TYPE_GLYPH_CLASS;
+ } else if ( "attachmentpoint".equals ( s ) ) {
+ t = GDEF_LOOKUP_TYPE_ATTACHMENT_POINT;
+ } else if ( "ligaturecaret".equals ( s ) ) {
+ t = GDEF_LOOKUP_TYPE_LIGATURE_CARET;
+ } else if ( "markattachment".equals ( s ) ) {
+ t = GDEF_LOOKUP_TYPE_MARK_ATTACHMENT;
+ } else {
+ t = -1;
+ }
+ return t;
+ }
+
+ /**
+ * Map a lookup type constant (integer) value to its name.
+ * @param type lookup type
+ * @return lookup type name
+ */
+ public static String getLookupTypeName ( int type ) {
+ String tn = null;
+ switch ( type ) {
+ case GDEF_LOOKUP_TYPE_GLYPH_CLASS:
+ tn = "glyphclass";
+ break;
+ case GDEF_LOOKUP_TYPE_ATTACHMENT_POINT:
+ tn = "attachmentpoint";
+ break;
+ case GDEF_LOOKUP_TYPE_LIGATURE_CARET:
+ tn = "ligaturecaret";
+ break;
+ case GDEF_LOOKUP_TYPE_MARK_ATTACHMENT:
+ tn = "markattachment";
+ break;
+ default:
+ tn = "unknown";
+ break;
+ }
+ return tn;
+ }
+
+ /**
+ * Create a definition subtable according to the specified arguments.
+ * @param type subtable type
+ * @param id subtable identifier
+ * @param sequence subtable sequence
+ * @param flags subtable flags (must be zero)
+ * @param format subtable format
+ * @param mapping subtable mapping table
+ * @param entries subtable entries
+ * @return a glyph subtable instance
+ */
+ public static GlyphSubtable createSubtable ( int type, String id, int sequence, int flags, int format, GlyphMappingTable mapping, List entries ) {
+ GlyphSubtable st = null;
+ switch ( type ) {
+ case GDEF_LOOKUP_TYPE_GLYPH_CLASS:
+ st = GlyphClassSubtable.create ( id, sequence, flags, format, mapping, entries );
+ break;
+ case GDEF_LOOKUP_TYPE_ATTACHMENT_POINT:
+ st = AttachmentPointSubtable.create ( id, sequence, flags, format, mapping, entries );
+ break;
+ case GDEF_LOOKUP_TYPE_LIGATURE_CARET:
+ st = LigatureCaretSubtable.create ( id, sequence, flags, format, mapping, entries );
+ break;
+ case GDEF_LOOKUP_TYPE_MARK_ATTACHMENT:
+ st = MarkAttachmentSubtable.create ( id, sequence, flags, format, mapping, entries );
+ break;
+ default:
+ break;
+ }
+ return st;
+ }
+
+ private abstract static class GlyphClassSubtable extends GlyphDefinitionSubtable {
+ GlyphClassSubtable ( String id, int sequence, int flags, int format, GlyphMappingTable mapping, List entries ) {
+ super ( id, sequence, flags, format, mapping );
+ }
+ /** {@inheritDoc} */
+ public int getType() {
+ return GDEF_LOOKUP_TYPE_GLYPH_CLASS;
+ }
+ /**
+ * Determine if glyph belongs to pre-defined glyph class.
+ * @param gid a glyph identifier (index)
+ * @param gc a pre-defined glyph class (GLYPH_CLASS_BASE|GLYPH_CLASS_LIGATURE|GLYPH_CLASS_MARK|GLYPH_CLASS_COMPONENT).
+ * @return true if glyph belongs to specified glyph class
+ */
+ public abstract boolean isGlyphClass ( int gid, int gc );
+ /**
+ * Determine glyph class.
+ * @param gid a glyph identifier (index)
+ * @return a pre-defined glyph class (GLYPH_CLASS_BASE|GLYPH_CLASS_LIGATURE|GLYPH_CLASS_MARK|GLYPH_CLASS_COMPONENT).
+ */
+ public abstract int getGlyphClass ( int gid );
+ static GlyphDefinitionSubtable create ( String id, int sequence, int flags, int format, GlyphMappingTable mapping, List entries ) {
+ if ( format == 1 ) {
+ return new GlyphClassSubtableFormat1 ( id, sequence, flags, format, mapping, entries );
+ } else {
+ throw new UnsupportedOperationException();
+ }
+ }
+ }
+
+ private static class GlyphClassSubtableFormat1 extends GlyphClassSubtable {
+ GlyphClassSubtableFormat1 ( String id, int sequence, int flags, int format, GlyphMappingTable mapping, List entries ) {
+ super ( id, sequence, flags, format, mapping, entries );
+ }
+ /** {@inheritDoc} */
+ public List getEntries() {
+ return null;
+ }
+ /** {@inheritDoc} */
+ public boolean isCompatible ( GlyphSubtable subtable ) {
+ return subtable instanceof GlyphClassSubtable;
+ }
+ /** {@inheritDoc} */
+ public boolean isGlyphClass ( int gid, int gc ) {
+ GlyphClassMapping cm = getClasses();
+ if ( cm != null ) {
+ return cm.getClassIndex ( gid, 0 ) == gc;
+ } else {
+ return false;
+ }
+ }
+ /** {@inheritDoc} */
+ public int getGlyphClass ( int gid ) {
+ GlyphClassMapping cm = getClasses();
+ if ( cm != null ) {
+ return cm.getClassIndex ( gid, 0 );
+ } else {
+ return -1;
+ }
+ }
+ }
+
+ private abstract static class AttachmentPointSubtable extends GlyphDefinitionSubtable {
+ AttachmentPointSubtable ( String id, int sequence, int flags, int format, GlyphMappingTable mapping, List entries ) {
+ super ( id, sequence, flags, format, mapping );
+ }
+ /** {@inheritDoc} */
+ public int getType() {
+ return GDEF_LOOKUP_TYPE_ATTACHMENT_POINT;
+ }
+ static GlyphDefinitionSubtable create ( String id, int sequence, int flags, int format, GlyphMappingTable mapping, List entries ) {
+ if ( format == 1 ) {
+ return new AttachmentPointSubtableFormat1 ( id, sequence, flags, format, mapping, entries );
+ } else {
+ throw new UnsupportedOperationException();
+ }
+ }
+ }
+
+ private static class AttachmentPointSubtableFormat1 extends AttachmentPointSubtable {
+ AttachmentPointSubtableFormat1 ( String id, int sequence, int flags, int format, GlyphMappingTable mapping, List entries ) {
+ super ( id, sequence, flags, format, mapping, entries );
+ }
+ /** {@inheritDoc} */
+ public List getEntries() {
+ return null;
+ }
+ /** {@inheritDoc} */
+ public boolean isCompatible ( GlyphSubtable subtable ) {
+ return subtable instanceof AttachmentPointSubtable;
+ }
+ }
+
+ private abstract static class LigatureCaretSubtable extends GlyphDefinitionSubtable {
+ LigatureCaretSubtable ( String id, int sequence, int flags, int format, GlyphMappingTable mapping, List entries ) {
+ super ( id, sequence, flags, format, mapping );
+ }
+ /** {@inheritDoc} */
+ public int getType() {
+ return GDEF_LOOKUP_TYPE_LIGATURE_CARET;
+ }
+ static GlyphDefinitionSubtable create ( String id, int sequence, int flags, int format, GlyphMappingTable mapping, List entries ) {
+ if ( format == 1 ) {
+ return new LigatureCaretSubtableFormat1 ( id, sequence, flags, format, mapping, entries );
+ } else {
+ throw new UnsupportedOperationException();
+ }
+ }
+ }
+
+ private static class LigatureCaretSubtableFormat1 extends LigatureCaretSubtable {
+ LigatureCaretSubtableFormat1 ( String id, int sequence, int flags, int format, GlyphMappingTable mapping, List entries ) {
+ super ( id, sequence, flags, format, mapping, entries );
+ }
+ /** {@inheritDoc} */
+ public List getEntries() {
+ return null;
+ }
+ /** {@inheritDoc} */
+ public boolean isCompatible ( GlyphSubtable subtable ) {
+ return subtable instanceof LigatureCaretSubtable;
+ }
+ }
+
+ private abstract static class MarkAttachmentSubtable extends GlyphDefinitionSubtable {
+ MarkAttachmentSubtable ( String id, int sequence, int flags, int format, GlyphMappingTable mapping, List entries ) {
+ super ( id, sequence, flags, format, mapping );
+ }
+ /** {@inheritDoc} */
+ public int getType() {
+ return GDEF_LOOKUP_TYPE_MARK_ATTACHMENT;
+ }
+ /**
+ * Determine if glyph belongs to (font specific) mark attachment class.
+ * @param gid a glyph identifier (index)
+ * @param mac a (font specific) mark attachment class
+ * @return true if glyph belongs to specified mark attachment class
+ */
+ public abstract boolean isMarkAttachClass ( int gid, int mac );
+ /**
+ * Determine mark attachment class.
+ * @param gid a glyph identifier (index)
+ * @return a non-negative mark attachment class, or -1 if no class defined
+ */
+ public abstract int getMarkAttachClass ( int gid );
+ static GlyphDefinitionSubtable create ( String id, int sequence, int flags, int format, GlyphMappingTable mapping, List entries ) {
+ if ( format == 1 ) {
+ return new MarkAttachmentSubtableFormat1 ( id, sequence, flags, format, mapping, entries );
+ } else {
+ throw new UnsupportedOperationException();
+ }
+ }
+ }
+
+ private static class MarkAttachmentSubtableFormat1 extends MarkAttachmentSubtable {
+ MarkAttachmentSubtableFormat1 ( String id, int sequence, int flags, int format, GlyphMappingTable mapping, List entries ) {
+ super ( id, sequence, flags, format, mapping, entries );
+ }
+ /** {@inheritDoc} */
+ public List getEntries() {
+ return null;
+ }
+ /** {@inheritDoc} */
+ public boolean isCompatible ( GlyphSubtable subtable ) {
+ return subtable instanceof MarkAttachmentSubtable;
+ }
+ /** {@inheritDoc} */
+ public boolean isMarkAttachClass ( int gid, int mac ) {
+ GlyphClassMapping cm = getClasses();
+ if ( cm != null ) {
+ return cm.getClassIndex ( gid, 0 ) == mac;
+ } else {
+ return false;
+ }
+ }
+ /** {@inheritDoc} */
+ public int getMarkAttachClass ( int gid ) {
+ GlyphClassMapping cm = getClasses();
+ if ( cm != null ) {
+ return cm.getClassIndex ( gid, 0 );
+ } else {
+ return -1;
+ }
+ }
+ }
+
+}
diff --git a/src/java/org/apache/fop/complexscripts/fonts/GlyphMappingTable.java b/src/java/org/apache/fop/complexscripts/fonts/GlyphMappingTable.java
new file mode 100644
index 000000000..a0791e8f6
--- /dev/null
+++ b/src/java/org/apache/fop/complexscripts/fonts/GlyphMappingTable.java
@@ -0,0 +1,322 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.complexscripts.fonts;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Iterator;
+
+// CSOFF: NoWhitespaceAfterCheck
+// CSOFF: InnerAssignmentCheck
+// CSOFF: LineLengthCheck
+
+/**
+ * Base class implementation of glyph mapping table. This base
+ * class maps glyph indices to arbitrary integers (mappping indices), and
+ * is used to implement both glyph coverage and glyph class maps.
+ * @author Glenn Adams
+ */
+public class GlyphMappingTable {
+
+ /** empty mapping table */
+ public static final int GLYPH_MAPPING_TYPE_EMPTY = 0;
+
+ /** mapped mapping table */
+ public static final int GLYPH_MAPPING_TYPE_MAPPED = 1;
+
+ /** range based mapping table */
+ public static final int GLYPH_MAPPING_TYPE_RANGE = 2;
+
+ /**
+ * Obtain mapping type.
+ * @return mapping format type
+ */
+ public int getType() {
+ return -1;
+ }
+
+ /**
+ * Obtain mapping entries.
+ * @return list of mapping entries
+ */
+ public List getEntries() {
+ return null;
+ }
+
+ /**
+ * Obtain size of mapping table, i.e., ciMax + 1, where ciMax is the maximum
+ * mapping index.
+ * @return size of mapping table
+ */
+ public int getMappingSize() {
+ return 0;
+ }
+
+ /**
+ * Map glyph identifier (code) to coverge index. Returns -1 if glyph identifier is not in the domain of
+ * the mapping table.
+ * @param gid glyph identifier (code)
+ * @return non-negative glyph mapping index or -1 if glyph identifiers is not mapped by table
+ */
+ public int getMappedIndex ( int gid ) {
+ return -1;
+ }
+
+ /** empty mapping table base class */
+ protected static class EmptyMappingTable extends GlyphMappingTable {
+ /**
+ * Construct empty mapping table.
+ */
+ public EmptyMappingTable() {
+ this ( (List) null );
+ }
+ /**
+ * Construct empty mapping table with entries (ignored).
+ * @param entries list of entries (ignored)
+ */
+ public EmptyMappingTable ( List entries ) {
+ }
+ /** {@inheritDoc} */
+ public int getType() {
+ return GLYPH_MAPPING_TYPE_EMPTY;
+ }
+ /** {@inheritDoc} */
+ public List getEntries() {
+ return new java.util.ArrayList();
+ }
+ /** {@inheritDoc} */
+ public int getMappingSize() {
+ return 0;
+ }
+ /** {@inheritDoc} */
+ public int getMappedIndex ( int gid ) {
+ return -1;
+ }
+ }
+
+ /** mapped mapping table base class */
+ protected static class MappedMappingTable extends GlyphMappingTable {
+ /**
+ * Construct mapped mapping table.
+ */
+ public MappedMappingTable() {
+ }
+ /** {@inheritDoc} */
+ public int getType() {
+ return GLYPH_MAPPING_TYPE_MAPPED;
+ }
+ }
+
+ /** range mapping table base class */
+ protected abstract static class RangeMappingTable extends GlyphMappingTable {
+ private int[] sa = null; // array of range (inclusive) starts
+ private int[] ea = null; // array of range (inclusive) ends
+ private int[] ma = null; // array of range mapped values
+ private int miMax = -1;
+ /**
+ * Construct range mapping table.
+ * @param entries of mapping ranges
+ */
+ public RangeMappingTable ( List entries ) {
+ populate ( entries );
+ }
+ /** {@inheritDoc} */
+ public int getType() {
+ return GLYPH_MAPPING_TYPE_RANGE;
+ }
+ /** {@inheritDoc} */
+ public List getEntries() {
+ List entries = new java.util.ArrayList();
+ if ( sa != null ) {
+ for ( int i = 0, n = sa.length; i < n; i++ ) {
+ entries.add ( new MappingRange ( sa [ i ], ea [ i ], ma [ i ] ) );
+ }
+ }
+ return entries;
+ }
+ /** {@inheritDoc} */
+ public int getMappingSize() {
+ return miMax + 1;
+ }
+ /** {@inheritDoc} */
+ public int getMappedIndex ( int gid ) {
+ int i, mi;
+ if ( ( i = Arrays.binarySearch ( sa, gid ) ) >= 0 ) {
+ mi = getMappedIndex ( gid, sa [ i ], ma [ i ] ); // matches start of (some) range
+ } else if ( ( i = - ( i + 1 ) ) == 0 ) {
+ mi = -1; // precedes first range
+ } else if ( gid > ea [ --i ] ) {
+ mi = -1; // follows preceding (or last) range
+ } else {
+ mi = getMappedIndex ( gid, sa [ i ], ma [ i ] ); // intersects (some) range
+ }
+ return mi;
+ }
+ /**
+ * Map glyph identifier (code) to coverge index. Returns -1 if glyph identifier is not in the domain of
+ * the mapping table.
+ * @param gid glyph identifier (code)
+ * @param s start of range
+ * @param m mapping value
+ * @return non-negative glyph mapping index or -1 if glyph identifiers is not mapped by table
+ */
+ public abstract int getMappedIndex ( int gid, int s, int m );
+ private void populate ( List entries ) {
+ int i = 0, n = entries.size(), gidMax = -1, miMax = -1;
+ int[] sa = new int [ n ];
+ int[] ea = new int [ n ];
+ int[] ma = new int [ n ];
+ for ( Iterator it = entries.iterator(); it.hasNext();) {
+ Object o = it.next();
+ if ( o instanceof MappingRange ) {
+ MappingRange r = (MappingRange) o;
+ int gs = r.getStart();
+ int ge = r.getEnd();
+ int mi = r.getIndex();
+ if ( ( gs < 0 ) || ( gs > 65535 ) ) {
+ throw new AdvancedTypographicTableFormatException ( "illegal glyph range: [" + gs + "," + ge + "]: bad start index" );
+ } else if ( ( ge < 0 ) || ( ge > 65535 ) ) {
+ throw new AdvancedTypographicTableFormatException ( "illegal glyph range: [" + gs + "," + ge + "]: bad end index" );
+ } else if ( gs > ge ) {
+ throw new AdvancedTypographicTableFormatException ( "illegal glyph range: [" + gs + "," + ge + "]: start index exceeds end index" );
+ } else if ( gs < gidMax ) {
+ throw new AdvancedTypographicTableFormatException ( "out of order glyph range: [" + gs + "," + ge + "]" );
+ } else if ( mi < 0 ) {
+ throw new AdvancedTypographicTableFormatException ( "illegal mapping index: " + mi );
+ } else {
+ int miLast;
+ sa [ i ] = gs;
+ ea [ i ] = gidMax = ge;
+ ma [ i ] = mi;
+ if ( ( miLast = mi + ( ge - gs ) ) > miMax ) {
+ miMax = miLast;
+ }
+ i++;
+ }
+ } else {
+ throw new AdvancedTypographicTableFormatException ( "illegal mapping entry, must be Integer: " + o );
+ }
+ }
+ assert i == n;
+ assert this.sa == null;
+ assert this.ea == null;
+ assert this.ma == null;
+ this.sa = sa;
+ this.ea = ea;
+ this.ma = ma;
+ this.miMax = miMax;
+ }
+ /** {@inheritDoc} */
+ public String toString() {
+ StringBuffer sb = new StringBuffer();
+ sb.append('{');
+ for ( int i = 0, n = sa.length; i < n; i++ ) {
+ if ( i > 0 ) {
+ sb.append(',');
+ }
+ sb.append ( '[' );
+ sb.append ( Integer.toString ( sa [ i ] ) );
+ sb.append ( Integer.toString ( ea [ i ] ) );
+ sb.append ( "]:" );
+ sb.append ( Integer.toString ( ma [ i ] ) );
+ }
+ sb.append('}');
+ return sb.toString();
+ }
+ }
+
+ /**
+ * The <code>MappingRange</code> class encapsulates a glyph [start,end] range and
+ * a mapping index.
+ */
+ public static class MappingRange {
+
+ private final int gidStart; // first glyph in range (inclusive)
+ private final int gidEnd; // last glyph in range (inclusive)
+ private final int index; // mapping index;
+
+ /**
+ * Instantiate a mapping range.
+ */
+ public MappingRange() {
+ this ( 0, 0, 0 );
+ }
+
+ /**
+ * Instantiate a specific mapping range.
+ * @param gidStart start of range
+ * @param gidEnd end of range
+ * @param index mapping index
+ */
+ public MappingRange ( int gidStart, int gidEnd, int index ) {
+ if ( ( gidStart < 0 ) || ( gidEnd < 0 ) || ( index < 0 ) ) {
+ throw new AdvancedTypographicTableFormatException();
+ } else if ( gidStart > gidEnd ) {
+ throw new AdvancedTypographicTableFormatException();
+ } else {
+ this.gidStart = gidStart;
+ this.gidEnd = gidEnd;
+ this.index = index;
+ }
+ }
+
+ /** @return start of range */
+ public int getStart() {
+ return gidStart;
+ }
+
+ /** @return end of range */
+ public int getEnd() {
+ return gidEnd;
+ }
+
+ /** @return mapping index */
+ public int getIndex() {
+ return index;
+ }
+
+ /** @return interval as a pair of integers */
+ public int[] getInterval() {
+ return new int[] { gidStart, gidEnd };
+ }
+
+ /**
+ * Obtain interval, filled into first two elements of specified array, or returning new array.
+ * @param interval an array of length two or greater or null
+ * @return interval as a pair of integers, filled into specified array
+ */
+ public int[] getInterval ( int[] interval ) {
+ if ( ( interval == null ) || ( interval.length != 2 ) ) {
+ throw new IllegalArgumentException();
+ } else {
+ interval[0] = gidStart;
+ interval[1] = gidEnd;
+ }
+ return interval;
+ }
+
+ /** @return length of interval */
+ public int getLength() {
+ return gidStart - gidEnd;
+ }
+
+ }
+
+}
diff --git a/src/java/org/apache/fop/complexscripts/fonts/GlyphPositioning.java b/src/java/org/apache/fop/complexscripts/fonts/GlyphPositioning.java
new file mode 100644
index 000000000..92fae7506
--- /dev/null
+++ b/src/java/org/apache/fop/complexscripts/fonts/GlyphPositioning.java
@@ -0,0 +1,43 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.complexscripts.fonts;
+
+// CSOFF: LineLengthCheck
+
+/**
+ * The <code>GlyphPositioning</code> interface is implemented by a glyph positioning subtable
+ * that supports the determination of glyph positioning information based on script and
+ * language of the corresponding character content.
+ * @author Glenn Adams
+ */
+public interface GlyphPositioning {
+
+ /**
+ * Perform glyph positioning at the current index, mutating the positioning state object as required.
+ * Only the context associated with the current index is processed.
+ * @param ps glyph positioning state object
+ * @return true if the glyph subtable applies, meaning that the current context matches the
+ * associated input context glyph coverage table; note that returning true does not mean any position
+ * adjustment occurred; it only means that no further glyph subtables for the current lookup table
+ * should be applied.
+ */
+ boolean position ( GlyphPositioningState ps );
+
+}
diff --git a/src/java/org/apache/fop/complexscripts/fonts/GlyphPositioningState.java b/src/java/org/apache/fop/complexscripts/fonts/GlyphPositioningState.java
new file mode 100644
index 000000000..ea62a7239
--- /dev/null
+++ b/src/java/org/apache/fop/complexscripts/fonts/GlyphPositioningState.java
@@ -0,0 +1,208 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.complexscripts.fonts;
+
+import org.apache.fop.complexscripts.util.GlyphSequence;
+import org.apache.fop.complexscripts.util.ScriptContextTester;
+
+// CSOFF: LineLengthCheck
+// CSOFF: ParameterNumberCheck
+
+/**
+ * The <code>GlyphPositioningState</code> implements an state object used during glyph positioning
+ * processing.
+ * @author Glenn Adams
+ */
+
+public class GlyphPositioningState extends GlyphProcessingState {
+
+ /** font size */
+ private int fontSize;
+ /** default advancements */
+ private int[] widths;
+ /** current adjustments */
+ private int[][] adjustments;
+ /** if true, then some adjustment was applied */
+ private boolean adjusted;
+
+ /**
+ * Construct glyph positioning state.
+ * @param gs input glyph sequence
+ * @param script script identifier
+ * @param language language identifier
+ * @param feature feature identifier
+ * @param fontSize font size (in micropoints)
+ * @param widths array of design advancements (in glyph index order)
+ * @param adjustments positioning adjustments to which positioning is applied
+ * @param sct script context tester (or null)
+ */
+ public GlyphPositioningState ( GlyphSequence gs, String script, String language, String feature, int fontSize, int[] widths, int[][] adjustments, ScriptContextTester sct ) {
+ super ( gs, script, language, feature, sct );
+ this.fontSize = fontSize;
+ this.widths = widths;
+ this.adjustments = adjustments;
+ }
+
+ /**
+ * Construct glyph positioning state using an existing state object using shallow copy
+ * except as follows: input glyph sequence is copied deep except for its characters array.
+ * @param ps existing positioning state to copy from
+ */
+ public GlyphPositioningState ( GlyphPositioningState ps ) {
+ super ( ps );
+ this.fontSize = ps.fontSize;
+ this.widths = ps.widths;
+ this.adjustments = ps.adjustments;
+ }
+
+ /**
+ * Obtain design advancement (width) of glyph at specified index.
+ * @param gi glyph index
+ * @return design advancement, or zero if glyph index is not present
+ */
+ public int getWidth ( int gi ) {
+ if ( ( widths != null ) && ( gi < widths.length ) ) {
+ return widths [ gi ];
+ } else {
+ return 0;
+ }
+ }
+
+ /**
+ * Perform adjustments at current position index.
+ * @param v value containing adjustments
+ * @return true if a non-zero adjustment was made
+ */
+ public boolean adjust ( GlyphPositioningTable.Value v ) {
+ return adjust ( v, 0 );
+ }
+
+ /**
+ * Perform adjustments at specified offset from current position index.
+ * @param v value containing adjustments
+ * @param offset from current position index
+ * @return true if a non-zero adjustment was made
+ */
+ public boolean adjust ( GlyphPositioningTable.Value v, int offset ) {
+ assert v != null;
+ if ( ( index + offset ) < indexLast ) {
+ return v.adjust ( adjustments [ index + offset ], fontSize );
+ } else {
+ throw new IndexOutOfBoundsException();
+ }
+ }
+
+ /**
+ * Obtain current adjustments at current position index.
+ * @return array of adjustments (int[4]) at current position
+ */
+ public int[] getAdjustment() {
+ return getAdjustment ( 0 );
+ }
+
+ /**
+ * Obtain current adjustments at specified offset from current position index.
+ * @param offset from current position index
+ * @return array of adjustments (int[4]) at specified offset
+ * @throws IndexOutOfBoundsException if offset is invalid
+ */
+ public int[] getAdjustment ( int offset ) throws IndexOutOfBoundsException {
+ if ( ( index + offset ) < indexLast ) {
+ return adjustments [ index + offset ];
+ } else {
+ throw new IndexOutOfBoundsException();
+ }
+ }
+
+ /**
+ * Apply positioning subtable to current state at current position (only),
+ * resulting in the consumption of zero or more input glyphs.
+ * @param st the glyph positioning subtable to apply
+ * @return true if subtable applied, or false if it did not (e.g., its
+ * input coverage table did not match current input context)
+ */
+ public boolean apply ( GlyphPositioningSubtable st ) {
+ assert st != null;
+ updateSubtableState ( st );
+ boolean applied = st.position ( this );
+ resetSubtableState();
+ return applied;
+ }
+
+ /**
+ * Apply a sequence of matched rule lookups to the <code>nig</code> input glyphs
+ * starting at the current position. If lookups are non-null and non-empty, then
+ * all input glyphs specified by <code>nig</code> are consumed irregardless of
+ * whether any specified lookup applied.
+ * @param lookups array of matched lookups (or null)
+ * @param nig number of glyphs in input sequence, starting at current position, to which
+ * the lookups are to apply, and to be consumed once the application has finished
+ * @return true if lookups are non-null and non-empty; otherwise, false
+ */
+ public boolean apply ( GlyphTable.RuleLookup[] lookups, int nig ) {
+ if ( ( lookups != null ) && ( lookups.length > 0 ) ) {
+ // apply each rule lookup to extracted input glyph array
+ for ( int i = 0, n = lookups.length; i < n; i++ ) {
+ GlyphTable.RuleLookup l = lookups [ i ];
+ if ( l != null ) {
+ GlyphTable.LookupTable lt = l.getLookup();
+ if ( lt != null ) {
+ // perform positioning on a copy of previous state
+ GlyphPositioningState ps = new GlyphPositioningState ( this );
+ // apply lookup table positioning
+ if ( lt.position ( ps, l.getSequenceIndex() ) ) {
+ setAdjusted ( true );
+ }
+ }
+ }
+ }
+ consume ( nig );
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Apply default application semantices; namely, consume one input glyph.
+ */
+ public void applyDefault() {
+ super.applyDefault();
+ }
+
+ /**
+ * Set adjusted state, used to record effect of non-zero adjustment.
+ * @param adjusted true if to set adjusted state, otherwise false to
+ * clear adjusted state
+ */
+ public void setAdjusted ( boolean adjusted ) {
+ this.adjusted = adjusted;
+ }
+
+ /**
+ * Get adjusted state.
+ * @return adjusted true if some non-zero adjustment occurred and
+ * was recorded by {@link #setAdjusted}; otherwise, false.
+ */
+ public boolean getAdjusted() {
+ return adjusted;
+ }
+
+}
diff --git a/src/java/org/apache/fop/complexscripts/fonts/GlyphPositioningSubtable.java b/src/java/org/apache/fop/complexscripts/fonts/GlyphPositioningSubtable.java
new file mode 100644
index 000000000..4325a3547
--- /dev/null
+++ b/src/java/org/apache/fop/complexscripts/fonts/GlyphPositioningSubtable.java
@@ -0,0 +1,129 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.complexscripts.fonts;
+
+import java.util.List;
+
+import org.apache.fop.complexscripts.util.GlyphSequence;
+import org.apache.fop.complexscripts.util.ScriptContextTester;
+
+// CSOFF: LineLengthCheck
+// CSOFF: NoWhitespaceAfterCheck
+// CSOFF: ParameterNumberCheck
+
+/**
+ * The <code>GlyphPositioningSubtable</code> implements an abstract base of a glyph subtable,
+ * providing a default implementation of the <code>GlyphPositioning</code> interface.
+ * @author Glenn Adams
+ */
+public abstract class GlyphPositioningSubtable extends GlyphSubtable implements GlyphPositioning {
+
+ /**
+ * Instantiate a <code>GlyphPositioningSubtable</code>.
+ * @param id subtable identifier
+ * @param sequence subtable sequence
+ * @param flags subtable flags
+ * @param format subtable format
+ * @param coverage subtable coverage table
+ */
+ protected GlyphPositioningSubtable ( String id, int sequence, int flags, int format, GlyphCoverageTable coverage ) {
+ super ( id, sequence, flags, format, coverage );
+ }
+
+ /** {@inheritDoc} */
+ public int getTableType() {
+ return GlyphTable.GLYPH_TABLE_TYPE_POSITIONING;
+ }
+
+ /** {@inheritDoc} */
+ public String getTypeName() {
+ return GlyphPositioningTable.getLookupTypeName ( getType() );
+ }
+
+ /** {@inheritDoc} */
+ public boolean isCompatible ( GlyphSubtable subtable ) {
+ return subtable instanceof GlyphPositioningSubtable;
+ }
+
+ /** {@inheritDoc} */
+ public boolean usesReverseScan() {
+ return false;
+ }
+
+ /** {@inheritDoc} */
+ public boolean position ( GlyphPositioningState ps ) {
+ return false;
+ }
+
+ /**
+ * Apply positioning using specified state and subtable array. For each position in input sequence,
+ * apply subtables in order until some subtable applies or none remain. If no subtable applied or no
+ * input was consumed for a given position, then apply default action (no adjustments and advance).
+ * If <code>sequenceIndex</code> is non-negative, then apply subtables only when current position
+ * matches <code>sequenceIndex</code> in relation to the starting position. Furthermore, upon
+ * successful application at <code>sequenceIndex</code>, then discontinue processing the remaining
+ * @param ps positioning state
+ * @param sta array of subtables to apply
+ * @param sequenceIndex if non negative, then apply subtables only at specified sequence index
+ * @return true if a non-zero adjustment occurred
+ */
+ public static final boolean position ( GlyphPositioningState ps, GlyphPositioningSubtable[] sta, int sequenceIndex ) {
+ int sequenceStart = ps.getPosition();
+ boolean appliedOneShot = false;
+ while ( ps.hasNext() ) {
+ boolean applied = false;
+ if ( ! appliedOneShot && ps.maybeApplicable() ) {
+ for ( int i = 0, n = sta.length; ! applied && ( i < n ); i++ ) {
+ if ( sequenceIndex < 0 ) {
+ applied = ps.apply ( sta [ i ] );
+ } else if ( ps.getPosition() == ( sequenceStart + sequenceIndex ) ) {
+ applied = ps.apply ( sta [ i ] );
+ if ( applied ) {
+ appliedOneShot = true;
+ }
+ }
+ }
+ }
+ if ( ! applied || ! ps.didConsume() ) {
+ ps.applyDefault();
+ }
+ ps.next();
+ }
+ return ps.getAdjusted();
+ }
+
+ /**
+ * Apply positioning.
+ * @param gs input glyph sequence
+ * @param script tag
+ * @param language tag
+ * @param feature tag
+ * @param fontSize the font size
+ * @param sta subtable array
+ * @param widths array
+ * @param adjustments array (receives output adjustments)
+ * @param sct script context tester
+ * @return true if a non-zero adjustment occurred
+ */
+ public static final boolean position ( GlyphSequence gs, String script, String language, String feature, int fontSize, GlyphPositioningSubtable[] sta, int[] widths, int[][] adjustments, ScriptContextTester sct ) {
+ return position ( new GlyphPositioningState ( gs, script, language, feature, fontSize, widths, adjustments, sct ), sta, -1 );
+ }
+
+}
diff --git a/src/java/org/apache/fop/complexscripts/fonts/GlyphPositioningTable.java b/src/java/org/apache/fop/complexscripts/fonts/GlyphPositioningTable.java
new file mode 100644
index 000000000..f8ac4e504
--- /dev/null
+++ b/src/java/org/apache/fop/complexscripts/fonts/GlyphPositioningTable.java
@@ -0,0 +1,2264 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.complexscripts.fonts;
+
+import java.util.Arrays;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import org.apache.fop.complexscripts.scripts.ScriptProcessor;
+import org.apache.fop.complexscripts.util.GlyphSequence;
+import org.apache.fop.complexscripts.util.GlyphTester;
+
+// CSOFF: LineLengthCheck
+// CSOFF: InnerAssignmentCheck
+// CSOFF: NoWhitespaceAfterCheck
+// CSOFF: ParameterNumberCheck
+
+/**
+ * The <code>GlyphPositioningTable</code> class is a glyph table that implements
+ * <code>GlyphPositioning</code> functionality.
+ * @author Glenn Adams
+ */
+public class GlyphPositioningTable extends GlyphTable {
+
+ /** logging instance */
+ private static final Log log = LogFactory.getLog(GlyphPositioningTable.class); // CSOK: ConstantNameCheck
+
+ /** single positioning subtable type */
+ public static final int GPOS_LOOKUP_TYPE_SINGLE = 1;
+ /** multiple positioning subtable type */
+ public static final int GPOS_LOOKUP_TYPE_PAIR = 2;
+ /** cursive positioning subtable type */
+ public static final int GPOS_LOOKUP_TYPE_CURSIVE = 3;
+ /** mark to base positioning subtable type */
+ public static final int GPOS_LOOKUP_TYPE_MARK_TO_BASE = 4;
+ /** mark to ligature positioning subtable type */
+ public static final int GPOS_LOOKUP_TYPE_MARK_TO_LIGATURE = 5;
+ /** mark to mark positioning subtable type */
+ public static final int GPOS_LOOKUP_TYPE_MARK_TO_MARK = 6;
+ /** contextual positioning subtable type */
+ public static final int GPOS_LOOKUP_TYPE_CONTEXTUAL = 7;
+ /** chained contextual positioning subtable type */
+ public static final int GPOS_LOOKUP_TYPE_CHAINED_CONTEXTUAL = 8;
+ /** extension positioning subtable type */
+ public static final int GPOS_LOOKUP_TYPE_EXTENSION_POSITIONING = 9;
+
+ /**
+ * Instantiate a <code>GlyphPositioningTable</code> object using the specified lookups
+ * and subtables.
+ * @param gdef glyph definition table that applies
+ * @param lookups a map of lookup specifications to subtable identifier strings
+ * @param subtables a list of identified subtables
+ */
+ public GlyphPositioningTable ( GlyphDefinitionTable gdef, Map lookups, List subtables ) {
+ super ( gdef, lookups );
+ if ( ( subtables == null ) || ( subtables.size() == 0 ) ) {
+ throw new AdvancedTypographicTableFormatException ( "subtables must be non-empty" );
+ } else {
+ for ( Iterator it = subtables.iterator(); it.hasNext();) {
+ Object o = it.next();
+ if ( o instanceof GlyphPositioningSubtable ) {
+ addSubtable ( (GlyphSubtable) o );
+ } else {
+ throw new AdvancedTypographicTableFormatException ( "subtable must be a glyph positioning subtable" );
+ }
+ }
+ freezeSubtables();
+ }
+ }
+
+ /**
+ * Map a lookup type name to its constant (integer) value.
+ * @param name lookup type name
+ * @return lookup type
+ */
+ public static int getLookupTypeFromName ( String name ) {
+ int t;
+ String s = name.toLowerCase();
+ if ( "single".equals ( s ) ) {
+ t = GPOS_LOOKUP_TYPE_SINGLE;
+ } else if ( "pair".equals ( s ) ) {
+ t = GPOS_LOOKUP_TYPE_PAIR;
+ } else if ( "cursive".equals ( s ) ) {
+ t = GPOS_LOOKUP_TYPE_CURSIVE;
+ } else if ( "marktobase".equals ( s ) ) {
+ t = GPOS_LOOKUP_TYPE_MARK_TO_BASE;
+ } else if ( "marktoligature".equals ( s ) ) {
+ t = GPOS_LOOKUP_TYPE_MARK_TO_LIGATURE;
+ } else if ( "marktomark".equals ( s ) ) {
+ t = GPOS_LOOKUP_TYPE_MARK_TO_MARK;
+ } else if ( "contextual".equals ( s ) ) {
+ t = GPOS_LOOKUP_TYPE_CONTEXTUAL;
+ } else if ( "chainedcontextual".equals ( s ) ) {
+ t = GPOS_LOOKUP_TYPE_CHAINED_CONTEXTUAL;
+ } else if ( "extensionpositioning".equals ( s ) ) {
+ t = GPOS_LOOKUP_TYPE_EXTENSION_POSITIONING;
+ } else {
+ t = -1;
+ }
+ return t;
+ }
+
+ /**
+ * Map a lookup type constant (integer) value to its name.
+ * @param type lookup type
+ * @return lookup type name
+ */
+ public static String getLookupTypeName ( int type ) {
+ String tn;
+ switch ( type ) {
+ case GPOS_LOOKUP_TYPE_SINGLE:
+ tn = "single";
+ break;
+ case GPOS_LOOKUP_TYPE_PAIR:
+ tn = "pair";
+ break;
+ case GPOS_LOOKUP_TYPE_CURSIVE:
+ tn = "cursive";
+ break;
+ case GPOS_LOOKUP_TYPE_MARK_TO_BASE:
+ tn = "marktobase";
+ break;
+ case GPOS_LOOKUP_TYPE_MARK_TO_LIGATURE:
+ tn = "marktoligature";
+ break;
+ case GPOS_LOOKUP_TYPE_MARK_TO_MARK:
+ tn = "marktomark";
+ break;
+ case GPOS_LOOKUP_TYPE_CONTEXTUAL:
+ tn = "contextual";
+ break;
+ case GPOS_LOOKUP_TYPE_CHAINED_CONTEXTUAL:
+ tn = "chainedcontextual";
+ break;
+ case GPOS_LOOKUP_TYPE_EXTENSION_POSITIONING:
+ tn = "extensionpositioning";
+ break;
+ default:
+ tn = "unknown";
+ break;
+ }
+ return tn;
+ }
+
+ /**
+ * Create a positioning subtable according to the specified arguments.
+ * @param type subtable type
+ * @param id subtable identifier
+ * @param sequence subtable sequence
+ * @param flags subtable flags
+ * @param format subtable format
+ * @param coverage subtable coverage table
+ * @param entries subtable entries
+ * @return a glyph subtable instance
+ */
+ public static GlyphSubtable createSubtable ( int type, String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries ) {
+ GlyphSubtable st = null;
+ switch ( type ) {
+ case GPOS_LOOKUP_TYPE_SINGLE:
+ st = SingleSubtable.create ( id, sequence, flags, format, coverage, entries );
+ break;
+ case GPOS_LOOKUP_TYPE_PAIR:
+ st = PairSubtable.create ( id, sequence, flags, format, coverage, entries );
+ break;
+ case GPOS_LOOKUP_TYPE_CURSIVE:
+ st = CursiveSubtable.create ( id, sequence, flags, format, coverage, entries );
+ break;
+ case GPOS_LOOKUP_TYPE_MARK_TO_BASE:
+ st = MarkToBaseSubtable.create ( id, sequence, flags, format, coverage, entries );
+ break;
+ case GPOS_LOOKUP_TYPE_MARK_TO_LIGATURE:
+ st = MarkToLigatureSubtable.create ( id, sequence, flags, format, coverage, entries );
+ break;
+ case GPOS_LOOKUP_TYPE_MARK_TO_MARK:
+ st = MarkToMarkSubtable.create ( id, sequence, flags, format, coverage, entries );
+ break;
+ case GPOS_LOOKUP_TYPE_CONTEXTUAL:
+ st = ContextualSubtable.create ( id, sequence, flags, format, coverage, entries );
+ break;
+ case GPOS_LOOKUP_TYPE_CHAINED_CONTEXTUAL:
+ st = ChainedContextualSubtable.create ( id, sequence, flags, format, coverage, entries );
+ break;
+ default:
+ break;
+ }
+ return st;
+ }
+
+ /**
+ * Create a positioning subtable according to the specified arguments.
+ * @param type subtable type
+ * @param id subtable identifier
+ * @param sequence subtable sequence
+ * @param flags subtable flags
+ * @param format subtable format
+ * @param coverage list of coverage table entries
+ * @param entries subtable entries
+ * @return a glyph subtable instance
+ */
+ public static GlyphSubtable createSubtable ( int type, String id, int sequence, int flags, int format, List coverage, List entries ) {
+ return createSubtable ( type, id, sequence, flags, format, GlyphCoverageTable.createCoverageTable ( coverage ), entries );
+ }
+
+ /**
+ * Perform positioning processing using all matching lookups.
+ * @param gs an input glyph sequence
+ * @param script a script identifier
+ * @param language a language identifier
+ * @param fontSize size in device units
+ * @param widths array of default advancements for each glyph
+ * @param adjustments accumulated adjustments array (sequence) of 4-tuples of placement [PX,PY] and advance [AX,AY] adjustments, in that order,
+ * with one 4-tuple for each element of glyph sequence
+ * @return true if some adjustment is not zero; otherwise, false
+ */
+ public boolean position ( GlyphSequence gs, String script, String language, int fontSize, int[] widths, int[][] adjustments ) {
+ Map/*<LookupSpec,List<LookupTable>>*/ lookups = matchLookups ( script, language, "*" );
+ if ( ( lookups != null ) && ( lookups.size() > 0 ) ) {
+ ScriptProcessor sp = ScriptProcessor.getInstance ( script );
+ return sp.position ( this, gs, script, language, fontSize, lookups, widths, adjustments );
+ } else {
+ return false;
+ }
+ }
+
+ private abstract static class SingleSubtable extends GlyphPositioningSubtable {
+ SingleSubtable ( String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries ) {
+ super ( id, sequence, flags, format, coverage );
+ }
+ /** {@inheritDoc} */
+ public int getType() {
+ return GPOS_LOOKUP_TYPE_SINGLE;
+ }
+ /** {@inheritDoc} */
+ public boolean isCompatible ( GlyphSubtable subtable ) {
+ return subtable instanceof SingleSubtable;
+ }
+ /** {@inheritDoc} */
+ public boolean position ( GlyphPositioningState ps ) {
+ int gi = ps.getGlyph(), ci;
+ if ( ( ci = getCoverageIndex ( gi ) ) < 0 ) {
+ return false;
+ } else {
+ Value v = getValue ( ci, gi );
+ if ( v != null ) {
+ if ( ps.adjust(v) ) {
+ ps.setAdjusted ( true );
+ }
+ ps.consume(1);
+ }
+ return true;
+ }
+ }
+ /**
+ * Obtain positioning value for coverage index.
+ * @param ci coverage index
+ * @param gi input glyph index
+ * @return positioning value or null if none applies
+ */
+ public abstract Value getValue ( int ci, int gi );
+ static GlyphPositioningSubtable create ( String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries ) {
+ if ( format == 1 ) {
+ return new SingleSubtableFormat1 ( id, sequence, flags, format, coverage, entries );
+ } else if ( format == 2 ) {
+ return new SingleSubtableFormat2 ( id, sequence, flags, format, coverage, entries );
+ } else {
+ throw new UnsupportedOperationException();
+ }
+ }
+ }
+
+ private static class SingleSubtableFormat1 extends SingleSubtable {
+ private Value value;
+ private int ciMax;
+ SingleSubtableFormat1 ( String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries ) {
+ super ( id, sequence, flags, format, coverage, entries );
+ populate ( entries );
+ }
+ /** {@inheritDoc} */
+ public List getEntries() {
+ if ( value != null ) {
+ List entries = new ArrayList ( 1 );
+ entries.add ( value );
+ return entries;
+ } else {
+ return null;
+ }
+ }
+ /** {@inheritDoc} */
+ public Value getValue ( int ci, int gi ) {
+ if ( ( value != null ) && ( ci <= ciMax ) ) {
+ return value;
+ } else {
+ return null;
+ }
+ }
+ private void populate ( List entries ) {
+ if ( ( entries == null ) || ( entries.size() != 1 ) ) {
+ throw new AdvancedTypographicTableFormatException ( "illegal entries, must be non-null and contain exactly one entry" );
+ } else {
+ Value v;
+ Object o = entries.get(0);
+ if ( o instanceof Value ) {
+ v = (Value) o;
+ } else {
+ throw new AdvancedTypographicTableFormatException ( "illegal entries entry, must be Value, but is: " + ( ( o != null ) ? o.getClass() : null ) );
+ }
+ assert this.value == null;
+ this.value = v;
+ this.ciMax = getCoverageSize() - 1;
+ }
+ }
+ }
+
+ private static class SingleSubtableFormat2 extends SingleSubtable {
+ private Value[] values;
+ SingleSubtableFormat2 ( String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries ) {
+ super ( id, sequence, flags, format, coverage, entries );
+ populate ( entries );
+ }
+ /** {@inheritDoc} */
+ public List getEntries() {
+ if ( values != null ) {
+ List entries = new ArrayList ( values.length );
+ for ( int i = 0, n = values.length; i < n; i++ ) {
+ entries.add ( values[i] );
+ }
+ return entries;
+ } else {
+ return null;
+ }
+ }
+ /** {@inheritDoc} */
+ public Value getValue ( int ci, int gi ) {
+ if ( ( values != null ) && ( ci < values.length ) ) {
+ return values [ ci ];
+ } else {
+ return null;
+ }
+ }
+ private void populate ( List entries ) {
+ if ( entries == null ) {
+ throw new AdvancedTypographicTableFormatException ( "illegal entries, must be non-null" );
+ } else if ( entries.size() != 1 ) {
+ throw new AdvancedTypographicTableFormatException ( "illegal entries, " + entries.size() + " entries present, but requires 1 entry" );
+ } else {
+ Object o;
+ if ( ( ( o = entries.get(0) ) == null ) || ! ( o instanceof Value[] ) ) {
+ throw new AdvancedTypographicTableFormatException ( "illegal entries, single entry must be a Value[], but is: " + ( ( o != null ) ? o.getClass() : null ) );
+ } else {
+ Value[] va = (Value[]) o;
+ if ( va.length != getCoverageSize() ) {
+ throw new AdvancedTypographicTableFormatException ( "illegal values array, " + entries.size() + " values present, but requires " + getCoverageSize() + " values" );
+ } else {
+ assert this.values == null;
+ this.values = va;
+ }
+ }
+ }
+ }
+ }
+
+ private abstract static class PairSubtable extends GlyphPositioningSubtable {
+ PairSubtable ( String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries ) {
+ super ( id, sequence, flags, format, coverage );
+ }
+ /** {@inheritDoc} */
+ public int getType() {
+ return GPOS_LOOKUP_TYPE_PAIR;
+ }
+ /** {@inheritDoc} */
+ public boolean isCompatible ( GlyphSubtable subtable ) {
+ return subtable instanceof PairSubtable;
+ }
+ /** {@inheritDoc} */
+ public boolean position ( GlyphPositioningState ps ) {
+ boolean applied = false;
+ int gi = ps.getGlyph(0), ci;
+ if ( ( ci = getCoverageIndex ( gi ) ) >= 0 ) {
+ int[] counts = ps.getGlyphsAvailable ( 0 );
+ int nga = counts[0];
+ if ( nga > 1 ) {
+ int[] iga = ps.getGlyphs ( 0, 2, null, counts );
+ if ( ( iga != null ) && ( iga.length == 2 ) ) {
+ PairValues pv = getPairValues ( ci, iga[0], iga[1] );
+ if ( pv != null ) {
+ Value v1 = pv.getValue1();
+ if ( v1 != null ) {
+ if ( ps.adjust(v1, 0) ) {
+ ps.setAdjusted ( true );
+ }
+ }
+ Value v2 = pv.getValue2();
+ if ( v2 != null ) {
+ if ( ps.adjust(v2, 1) ) {
+ ps.setAdjusted ( true );
+ }
+ }
+ ps.consume ( counts[0] + counts[1] );
+ applied = true;
+ }
+ }
+ }
+ }
+ return applied;
+ }
+ /**
+ * Obtain associated pair values.
+ * @param ci coverage index
+ * @param gi1 first input glyph index
+ * @param gi2 second input glyph index
+ * @return pair values or null if none applies
+ */
+ public abstract PairValues getPairValues ( int ci, int gi1, int gi2 );
+ static GlyphPositioningSubtable create ( String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries ) {
+ if ( format == 1 ) {
+ return new PairSubtableFormat1 ( id, sequence, flags, format, coverage, entries );
+ } else if ( format == 2 ) {
+ return new PairSubtableFormat2 ( id, sequence, flags, format, coverage, entries );
+ } else {
+ throw new UnsupportedOperationException();
+ }
+ }
+ }
+
+ private static class PairSubtableFormat1 extends PairSubtable {
+ private PairValues[][] pvm; // pair values matrix
+ PairSubtableFormat1 ( String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries ) {
+ super ( id, sequence, flags, format, coverage, entries );
+ populate ( entries );
+ }
+ /** {@inheritDoc} */
+ public List getEntries() {
+ if ( pvm != null ) {
+ List entries = new ArrayList ( 1 );
+ entries.add ( pvm );
+ return entries;
+ } else {
+ return null;
+ }
+ }
+ /** {@inheritDoc} */
+ public PairValues getPairValues ( int ci, int gi1, int gi2 ) {
+ if ( ( pvm != null ) && ( ci < pvm.length ) ) {
+ PairValues[] pvt = pvm [ ci ];
+ for ( int i = 0, n = pvt.length; i < n; i++ ) {
+ PairValues pv = pvt [ i ];
+ if ( pv != null ) {
+ int g = pv.getGlyph();
+ if ( g < gi2 ) {
+ continue;
+ } else if ( g == gi2 ) {
+ return pv;
+ } else {
+ break;
+ }
+ }
+ }
+ }
+ return null;
+ }
+ private void populate ( List entries ) {
+ if ( entries == null ) {
+ throw new AdvancedTypographicTableFormatException ( "illegal entries, must be non-null" );
+ } else if ( entries.size() != 1 ) {
+ throw new AdvancedTypographicTableFormatException ( "illegal entries, " + entries.size() + " entries present, but requires 1 entry" );
+ } else {
+ Object o;
+ if ( ( ( o = entries.get(0) ) == null ) || ! ( o instanceof PairValues[][] ) ) {
+ throw new AdvancedTypographicTableFormatException ( "illegal entries, first (and only) entry must be a PairValues[][], but is: " + ( ( o != null ) ? o.getClass() : null ) );
+ } else {
+ pvm = (PairValues[][]) o;
+ }
+ }
+ }
+ }
+
+ private static class PairSubtableFormat2 extends PairSubtable {
+ private GlyphClassTable cdt1; // class def table 1
+ private GlyphClassTable cdt2; // class def table 2
+ private int nc1; // class 1 count
+ private int nc2; // class 2 count
+ private PairValues[][] pvm; // pair values matrix
+ PairSubtableFormat2 ( String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries ) {
+ super ( id, sequence, flags, format, coverage, entries );
+ populate ( entries );
+ }
+ /** {@inheritDoc} */
+ public List getEntries() {
+ if ( pvm != null ) {
+ List entries = new ArrayList ( 5 );
+ entries.add ( cdt1 );
+ entries.add ( cdt2 );
+ entries.add ( Integer.valueOf ( nc1 ) );
+ entries.add ( Integer.valueOf ( nc2 ) );
+ entries.add ( pvm );
+ return entries;
+ } else {
+ return null;
+ }
+ }
+ /** {@inheritDoc} */
+ public PairValues getPairValues ( int ci, int gi1, int gi2 ) {
+ if ( pvm != null ) {
+ int c1 = cdt1.getClassIndex ( gi1, 0 );
+ if ( ( c1 >= 0 ) && ( c1 < nc1 ) && ( c1 < pvm.length ) ) {
+ PairValues[] pvt = pvm [ c1 ];
+ if ( pvt != null ) {
+ int c2 = cdt2.getClassIndex ( gi2, 0 );
+ if ( ( c2 >= 0 ) && ( c2 < nc2 ) && ( c2 < pvt.length ) ) {
+ return pvt [ c2 ];
+ }
+ }
+ }
+ }
+ return null;
+ }
+ private void populate ( List entries ) {
+ if ( entries == null ) {
+ throw new AdvancedTypographicTableFormatException ( "illegal entries, must be non-null" );
+ } else if ( entries.size() != 5 ) {
+ throw new AdvancedTypographicTableFormatException ( "illegal entries, " + entries.size() + " entries present, but requires 5 entries" );
+ } else {
+ Object o;
+ if ( ( ( o = entries.get(0) ) == null ) || ! ( o instanceof GlyphClassTable ) ) {
+ throw new AdvancedTypographicTableFormatException ( "illegal entries, first entry must be an GlyphClassTable, but is: " + ( ( o != null ) ? o.getClass() : null ) );
+ } else {
+ cdt1 = (GlyphClassTable) o;
+ }
+ if ( ( ( o = entries.get(1) ) == null ) || ! ( o instanceof GlyphClassTable ) ) {
+ throw new AdvancedTypographicTableFormatException ( "illegal entries, second entry must be an GlyphClassTable, but is: " + ( ( o != null ) ? o.getClass() : null ) );
+ } else {
+ cdt2 = (GlyphClassTable) o;
+ }
+ if ( ( ( o = entries.get(2) ) == null ) || ! ( o instanceof Integer ) ) {
+ throw new AdvancedTypographicTableFormatException ( "illegal entries, third entry must be an Integer, but is: " + ( ( o != null ) ? o.getClass() : null ) );
+ } else {
+ nc1 = ((Integer)(o)).intValue();
+ }
+ if ( ( ( o = entries.get(3) ) == null ) || ! ( o instanceof Integer ) ) {
+ throw new AdvancedTypographicTableFormatException ( "illegal entries, fourth entry must be an Integer, but is: " + ( ( o != null ) ? o.getClass() : null ) );
+ } else {
+ nc2 = ((Integer)(o)).intValue();
+ }
+ if ( ( ( o = entries.get(4) ) == null ) || ! ( o instanceof PairValues[][] ) ) {
+ throw new AdvancedTypographicTableFormatException ( "illegal entries, fifth entry must be a PairValues[][], but is: " + ( ( o != null ) ? o.getClass() : null ) );
+ } else {
+ pvm = (PairValues[][]) o;
+ }
+ }
+ }
+ }
+
+ private abstract static class CursiveSubtable extends GlyphPositioningSubtable {
+ CursiveSubtable ( String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries ) {
+ super ( id, sequence, flags, format, coverage );
+ }
+ /** {@inheritDoc} */
+ public int getType() {
+ return GPOS_LOOKUP_TYPE_CURSIVE;
+ }
+ /** {@inheritDoc} */
+ public boolean isCompatible ( GlyphSubtable subtable ) {
+ return subtable instanceof CursiveSubtable;
+ }
+ /** {@inheritDoc} */
+ public boolean position ( GlyphPositioningState ps ) {
+ boolean applied = false;
+ int gi = ps.getGlyph(0), ci;
+ if ( ( ci = getCoverageIndex ( gi ) ) >= 0 ) {
+ int[] counts = ps.getGlyphsAvailable ( 0 );
+ int nga = counts[0];
+ if ( nga > 1 ) {
+ int[] iga = ps.getGlyphs ( 0, 2, null, counts );
+ if ( ( iga != null ) && ( iga.length == 2 ) ) {
+ // int gi1 = gi;
+ int ci1 = ci;
+ int gi2 = iga [ 1 ];
+ int ci2 = getCoverageIndex ( gi2 );
+ Anchor[] aa = getExitEntryAnchors ( ci1, ci2 );
+ if ( aa != null ) {
+ Anchor exa = aa [ 0 ];
+ Anchor ena = aa [ 1 ];
+ // int exw = ps.getWidth ( gi1 );
+ int enw = ps.getWidth ( gi2 );
+ if ( ( exa != null ) && ( ena != null ) ) {
+ Value v = ena.getAlignmentAdjustment ( exa );
+ v.adjust ( - enw, 0, 0, 0 );
+ if ( ps.adjust ( v ) ) {
+ ps.setAdjusted ( true );
+ }
+ }
+ // consume only first glyph of exit/entry glyph pair
+ ps.consume ( 1 );
+ applied = true;
+ }
+ }
+ }
+ }
+ return applied;
+ }
+ /**
+ * Obtain exit anchor for first glyph with coverage index <code>ci1</code> and entry anchor for second
+ * glyph with coverage index <code>ci2</code>.
+ * @param ci1 coverage index of first glyph (may be negative)
+ * @param ci2 coverage index of second glyph (may be negative)
+ * @return array of two anchors or null if either coverage index is negative or corresponding anchor is
+ * missing, where the first entry is the exit anchor of the first glyph and the second entry is the
+ * entry anchor of the second glyph
+ */
+ public abstract Anchor[] getExitEntryAnchors ( int ci1, int ci2 );
+ static GlyphPositioningSubtable create ( String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries ) {
+ if ( format == 1 ) {
+ return new CursiveSubtableFormat1 ( id, sequence, flags, format, coverage, entries );
+ } else {
+ throw new UnsupportedOperationException();
+ }
+ }
+ }
+
+ private static class CursiveSubtableFormat1 extends CursiveSubtable {
+ private Anchor[] aa; // anchor array, where even entries are entry anchors, and odd entries are exit anchors
+ CursiveSubtableFormat1 ( String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries ) {
+ super ( id, sequence, flags, format, coverage, entries );
+ populate ( entries );
+ }
+ /** {@inheritDoc} */
+ public List getEntries() {
+ if ( aa != null ) {
+ List entries = new ArrayList ( 1 );
+ entries.add ( aa );
+ return entries;
+ } else {
+ return null;
+ }
+ }
+ /** {@inheritDoc} */
+ public Anchor[] getExitEntryAnchors ( int ci1, int ci2 ) {
+ if ( ( ci1 >= 0 ) && ( ci2 >= 0 ) ) {
+ int ai1 = ( ci1 * 2 ) + 1; // ci1 denotes glyph with exit anchor
+ int ai2 = ( ci2 * 2 ) + 0; // ci2 denotes glyph with entry anchor
+ if ( ( aa != null ) && ( ai1 < aa.length ) && ( ai2 < aa.length ) ) {
+ Anchor exa = aa [ ai1 ];
+ Anchor ena = aa [ ai2 ];
+ if ( ( exa != null ) && ( ena != null ) ) {
+ return new Anchor[] { exa, ena };
+ }
+ }
+ }
+ return null;
+ }
+ private void populate ( List entries ) {
+ if ( entries == null ) {
+ throw new AdvancedTypographicTableFormatException ( "illegal entries, must be non-null" );
+ } else if ( entries.size() != 1 ) {
+ throw new AdvancedTypographicTableFormatException ( "illegal entries, " + entries.size() + " entries present, but requires 1 entry" );
+ } else {
+ Object o;
+ if ( ( ( o = entries.get(0) ) == null ) || ! ( o instanceof Anchor[] ) ) {
+ throw new AdvancedTypographicTableFormatException ( "illegal entries, first (and only) entry must be a Anchor[], but is: " + ( ( o != null ) ? o.getClass() : null ) );
+ } else if ( ( ( (Anchor[]) o ) . length % 2 ) != 0 ) {
+ throw new AdvancedTypographicTableFormatException ( "illegal entries, Anchor[] array must have an even number of entries, but has: " + ( (Anchor[]) o ) . length );
+ } else {
+ aa = (Anchor[]) o;
+ }
+ }
+ }
+ }
+
+ private abstract static class MarkToBaseSubtable extends GlyphPositioningSubtable {
+ MarkToBaseSubtable ( String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries ) {
+ super ( id, sequence, flags, format, coverage );
+ }
+ /** {@inheritDoc} */
+ public int getType() {
+ return GPOS_LOOKUP_TYPE_MARK_TO_BASE;
+ }
+ /** {@inheritDoc} */
+ public boolean isCompatible ( GlyphSubtable subtable ) {
+ return subtable instanceof MarkToBaseSubtable;
+ }
+ /** {@inheritDoc} */
+ public boolean position ( GlyphPositioningState ps ) {
+ boolean applied = false;
+ int giMark = ps.getGlyph(), ciMark;
+ if ( ( ciMark = getCoverageIndex ( giMark ) ) >= 0 ) {
+ MarkAnchor ma = getMarkAnchor ( ciMark, giMark );
+ if ( ma != null ) {
+ for ( int i = 0, n = ps.getPosition(); i < n; i++ ) {
+ int gi = ps.getGlyph ( - ( i + 1 ) );
+ if ( ps.isMark ( gi ) ) {
+ continue;
+ } else {
+ Anchor a = getBaseAnchor ( gi, ma.getMarkClass() );
+ if ( a != null ) {
+ Value v = a.getAlignmentAdjustment ( ma );
+ // start experimental fix for END OF AYAH in Lateef/Scheherazade
+ int[] aa = ps.getAdjustment();
+ if ( aa[2] == 0 ) {
+ v.adjust ( 0, 0, - ps.getWidth ( giMark ), 0 );
+ }
+ // end experimental fix for END OF AYAH in Lateef/Scheherazade
+ if ( ps.adjust ( v ) ) {
+ ps.setAdjusted ( true );
+ }
+ }
+ ps.consume(1);
+ applied = true;
+ break;
+ }
+ }
+ }
+ }
+ return applied;
+ }
+ /**
+ * Obtain mark anchor associated with mark coverage index.
+ * @param ciMark coverage index
+ * @param giMark input glyph index of mark glyph
+ * @return mark anchor or null if none applies
+ */
+ public abstract MarkAnchor getMarkAnchor ( int ciMark, int giMark );
+ /**
+ * Obtain anchor associated with base glyph index and mark class.
+ * @param giBase input glyph index of base glyph
+ * @param markClass class number of mark glyph
+ * @return anchor or null if none applies
+ */
+ public abstract Anchor getBaseAnchor ( int giBase, int markClass );
+ static GlyphPositioningSubtable create ( String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries ) {
+ if ( format == 1 ) {
+ return new MarkToBaseSubtableFormat1 ( id, sequence, flags, format, coverage, entries );
+ } else {
+ throw new UnsupportedOperationException();
+ }
+ }
+ }
+
+ private static class MarkToBaseSubtableFormat1 extends MarkToBaseSubtable {
+ private GlyphCoverageTable bct; // base coverage table
+ private int nmc; // mark class count
+ private MarkAnchor[] maa; // mark anchor array, ordered by mark coverage index
+ private Anchor[][] bam; // base anchor matrix, ordered by base coverage index, then by mark class
+ MarkToBaseSubtableFormat1 ( String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries ) {
+ super ( id, sequence, flags, format, coverage, entries );
+ populate ( entries );
+ }
+ /** {@inheritDoc} */
+ public List getEntries() {
+ if ( ( bct != null ) && ( maa != null ) && ( nmc > 0 ) && ( bam != null ) ) {
+ List entries = new ArrayList ( 4 );
+ entries.add ( bct );
+ entries.add ( Integer.valueOf ( nmc ) );
+ entries.add ( maa );
+ entries.add ( bam );
+ return entries;
+ } else {
+ return null;
+ }
+ }
+ /** {@inheritDoc} */
+ public MarkAnchor getMarkAnchor ( int ciMark, int giMark ) {
+ if ( ( maa != null ) && ( ciMark < maa.length ) ) {
+ return maa [ ciMark ];
+ } else {
+ return null;
+ }
+ }
+ /** {@inheritDoc} */
+ public Anchor getBaseAnchor ( int giBase, int markClass ) {
+ int ciBase;
+ if ( ( bct != null ) && ( ( ciBase = bct.getCoverageIndex ( giBase ) ) >= 0 ) ) {
+ if ( ( bam != null ) && ( ciBase < bam.length ) ) {
+ Anchor[] ba = bam [ ciBase ];
+ if ( ( ba != null ) && ( markClass < ba.length ) ) {
+ return ba [ markClass ];
+ }
+ }
+ }
+ return null;
+ }
+ private void populate ( List entries ) {
+ if ( entries == null ) {
+ throw new AdvancedTypographicTableFormatException ( "illegal entries, must be non-null" );
+ } else if ( entries.size() != 4 ) {
+ throw new AdvancedTypographicTableFormatException ( "illegal entries, " + entries.size() + " entries present, but requires 4 entries" );
+ } else {
+ Object o;
+ if ( ( ( o = entries.get(0) ) == null ) || ! ( o instanceof GlyphCoverageTable ) ) {
+ throw new AdvancedTypographicTableFormatException ( "illegal entries, first entry must be an GlyphCoverageTable, but is: " + ( ( o != null ) ? o.getClass() : null ) );
+ } else {
+ bct = (GlyphCoverageTable) o;
+ }
+ if ( ( ( o = entries.get(1) ) == null ) || ! ( o instanceof Integer ) ) {
+ throw new AdvancedTypographicTableFormatException ( "illegal entries, second entry must be an Integer, but is: " + ( ( o != null ) ? o.getClass() : null ) );
+ } else {
+ nmc = ((Integer)(o)).intValue();
+ }
+ if ( ( ( o = entries.get(2) ) == null ) || ! ( o instanceof MarkAnchor[] ) ) {
+ throw new AdvancedTypographicTableFormatException ( "illegal entries, third entry must be a MarkAnchor[], but is: " + ( ( o != null ) ? o.getClass() : null ) );
+ } else {
+ maa = (MarkAnchor[]) o;
+ }
+ if ( ( ( o = entries.get(3) ) == null ) || ! ( o instanceof Anchor[][] ) ) {
+ throw new AdvancedTypographicTableFormatException ( "illegal entries, fourth entry must be a Anchor[][], but is: " + ( ( o != null ) ? o.getClass() : null ) );
+ } else {
+ bam = (Anchor[][]) o;
+ }
+ }
+ }
+ }
+
+ private abstract static class MarkToLigatureSubtable extends GlyphPositioningSubtable {
+ MarkToLigatureSubtable ( String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries ) {
+ super ( id, sequence, flags, format, coverage );
+ }
+ /** {@inheritDoc} */
+ public int getType() {
+ return GPOS_LOOKUP_TYPE_MARK_TO_LIGATURE;
+ }
+ /** {@inheritDoc} */
+ public boolean isCompatible ( GlyphSubtable subtable ) {
+ return subtable instanceof MarkToLigatureSubtable;
+ }
+ /** {@inheritDoc} */
+ public boolean position ( GlyphPositioningState ps ) {
+ boolean applied = false;
+ int giMark = ps.getGlyph(), ciMark;
+ if ( ( ciMark = getCoverageIndex ( giMark ) ) >= 0 ) {
+ MarkAnchor ma = getMarkAnchor ( ciMark, giMark );
+ int mxc = getMaxComponentCount();
+ if ( ma != null ) {
+ for ( int i = 0, n = ps.getPosition(); i < n; i++ ) {
+ int gi = ps.getGlyph ( - ( i + 1 ) );
+ if ( ps.isMark ( gi ) ) {
+ continue;
+ } else {
+ Anchor a = getLigatureAnchor ( gi, mxc, i, ma.getMarkClass() );
+ if ( a != null ) {
+ if ( ps.adjust ( a.getAlignmentAdjustment ( ma ) ) ) {
+ ps.setAdjusted ( true );
+ }
+ }
+ ps.consume(1);
+ applied = true;
+ break;
+ }
+ }
+ }
+ }
+ return applied;
+ }
+ /**
+ * Obtain mark anchor associated with mark coverage index.
+ * @param ciMark coverage index
+ * @param giMark input glyph index of mark glyph
+ * @return mark anchor or null if none applies
+ */
+ public abstract MarkAnchor getMarkAnchor ( int ciMark, int giMark );
+ /**
+ * Obtain maximum component count.
+ * @return maximum component count (>=0)
+ */
+ public abstract int getMaxComponentCount();
+ /**
+ * Obtain anchor associated with ligature glyph index and mark class.
+ * @param giLig input glyph index of ligature glyph
+ * @param maxComponents maximum component count
+ * @param component component number (0...maxComponents-1)
+ * @param markClass class number of mark glyph
+ * @return anchor or null if none applies
+ */
+ public abstract Anchor getLigatureAnchor ( int giLig, int maxComponents, int component, int markClass );
+ static GlyphPositioningSubtable create ( String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries ) {
+ if ( format == 1 ) {
+ return new MarkToLigatureSubtableFormat1 ( id, sequence, flags, format, coverage, entries );
+ } else {
+ throw new UnsupportedOperationException();
+ }
+ }
+ }
+
+ private static class MarkToLigatureSubtableFormat1 extends MarkToLigatureSubtable {
+ private GlyphCoverageTable lct; // ligature coverage table
+ private int nmc; // mark class count
+ private int mxc; // maximum ligature component count
+ private MarkAnchor[] maa; // mark anchor array, ordered by mark coverage index
+ private Anchor[][][] lam; // ligature anchor matrix, ordered by ligature coverage index, then ligature component, then mark class
+ MarkToLigatureSubtableFormat1 ( String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries ) {
+ super ( id, sequence, flags, format, coverage, entries );
+ populate ( entries );
+ }
+ /** {@inheritDoc} */
+ public List getEntries() {
+ if ( lam != null ) {
+ List entries = new ArrayList ( 5 );
+ entries.add ( lct );
+ entries.add ( Integer.valueOf ( nmc ) );
+ entries.add ( Integer.valueOf ( mxc ) );
+ entries.add ( maa );
+ entries.add ( lam );
+ return entries;
+ } else {
+ return null;
+ }
+ }
+ /** {@inheritDoc} */
+ public MarkAnchor getMarkAnchor ( int ciMark, int giMark ) {
+ if ( ( maa != null ) && ( ciMark < maa.length ) ) {
+ return maa [ ciMark ];
+ } else {
+ return null;
+ }
+ }
+ /** {@inheritDoc} */
+ public int getMaxComponentCount() {
+ return mxc;
+ }
+ /** {@inheritDoc} */
+ public Anchor getLigatureAnchor ( int giLig, int maxComponents, int component, int markClass ) {
+ int ciLig;
+ if ( ( lct != null ) && ( ( ciLig = lct.getCoverageIndex ( giLig ) ) >= 0 ) ) {
+ if ( ( lam != null ) && ( ciLig < lam.length ) ) {
+ Anchor[][] lcm = lam [ ciLig ];
+ if ( component < maxComponents ) {
+ Anchor[] la = lcm [ component ];
+ if ( ( la != null ) && ( markClass < la.length ) ) {
+ return la [ markClass ];
+ }
+ }
+ }
+ }
+ return null;
+ }
+ private void populate ( List entries ) {
+ if ( entries == null ) {
+ throw new AdvancedTypographicTableFormatException ( "illegal entries, must be non-null" );
+ } else if ( entries.size() != 5 ) {
+ throw new AdvancedTypographicTableFormatException ( "illegal entries, " + entries.size() + " entries present, but requires 5 entries" );
+ } else {
+ Object o;
+ if ( ( ( o = entries.get(0) ) == null ) || ! ( o instanceof GlyphCoverageTable ) ) {
+ throw new AdvancedTypographicTableFormatException ( "illegal entries, first entry must be an GlyphCoverageTable, but is: " + ( ( o != null ) ? o.getClass() : null ) );
+ } else {
+ lct = (GlyphCoverageTable) o;
+ }
+ if ( ( ( o = entries.get(1) ) == null ) || ! ( o instanceof Integer ) ) {
+ throw new AdvancedTypographicTableFormatException ( "illegal entries, second entry must be an Integer, but is: " + ( ( o != null ) ? o.getClass() : null ) );
+ } else {
+ nmc = ((Integer)(o)).intValue();
+ }
+ if ( ( ( o = entries.get(2) ) == null ) || ! ( o instanceof Integer ) ) {
+ throw new AdvancedTypographicTableFormatException ( "illegal entries, third entry must be an Integer, but is: " + ( ( o != null ) ? o.getClass() : null ) );
+ } else {
+ mxc = ((Integer)(o)).intValue();
+ }
+ if ( ( ( o = entries.get(3) ) == null ) || ! ( o instanceof MarkAnchor[] ) ) {
+ throw new AdvancedTypographicTableFormatException ( "illegal entries, fourth entry must be a MarkAnchor[], but is: " + ( ( o != null ) ? o.getClass() : null ) );
+ } else {
+ maa = (MarkAnchor[]) o;
+ }
+ if ( ( ( o = entries.get(4) ) == null ) || ! ( o instanceof Anchor[][][] ) ) {
+ throw new AdvancedTypographicTableFormatException ( "illegal entries, fifth entry must be a Anchor[][][], but is: " + ( ( o != null ) ? o.getClass() : null ) );
+ } else {
+ lam = (Anchor[][][]) o;
+ }
+ }
+ }
+ }
+
+ private abstract static class MarkToMarkSubtable extends GlyphPositioningSubtable {
+ MarkToMarkSubtable ( String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries ) {
+ super ( id, sequence, flags, format, coverage );
+ }
+ /** {@inheritDoc} */
+ public int getType() {
+ return GPOS_LOOKUP_TYPE_MARK_TO_MARK;
+ }
+ /** {@inheritDoc} */
+ public boolean isCompatible ( GlyphSubtable subtable ) {
+ return subtable instanceof MarkToMarkSubtable;
+ }
+ /** {@inheritDoc} */
+ public boolean position ( GlyphPositioningState ps ) {
+ boolean applied = false;
+ int giMark1 = ps.getGlyph(), ciMark1;
+ if ( ( ciMark1 = getCoverageIndex ( giMark1 ) ) >= 0 ) {
+ MarkAnchor ma = getMark1Anchor ( ciMark1, giMark1 );
+ if ( ma != null ) {
+ if ( ps.hasPrev() ) {
+ Anchor a = getMark2Anchor ( ps.getGlyph(-1), ma.getMarkClass() );
+ if ( a != null ) {
+ if ( ps.adjust ( a.getAlignmentAdjustment ( ma ) ) ) {
+ ps.setAdjusted ( true );
+ }
+ }
+ ps.consume(1);
+ applied = true;
+ }
+ }
+ }
+ return applied;
+ }
+ /**
+ * Obtain mark 1 anchor associated with mark 1 coverage index.
+ * @param ciMark1 mark 1 coverage index
+ * @param giMark1 input glyph index of mark 1 glyph
+ * @return mark 1 anchor or null if none applies
+ */
+ public abstract MarkAnchor getMark1Anchor ( int ciMark1, int giMark1 );
+ /**
+ * Obtain anchor associated with mark 2 glyph index and mark 1 class.
+ * @param giMark2 input glyph index of mark 2 glyph
+ * @param markClass class number of mark 1 glyph
+ * @return anchor or null if none applies
+ */
+ public abstract Anchor getMark2Anchor ( int giBase, int markClass );
+ static GlyphPositioningSubtable create ( String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries ) {
+ if ( format == 1 ) {
+ return new MarkToMarkSubtableFormat1 ( id, sequence, flags, format, coverage, entries );
+ } else {
+ throw new UnsupportedOperationException();
+ }
+ }
+ }
+
+ private static class MarkToMarkSubtableFormat1 extends MarkToMarkSubtable {
+ private GlyphCoverageTable mct2; // mark 2 coverage table
+ private int nmc; // mark class count
+ private MarkAnchor[] maa; // mark1 anchor array, ordered by mark1 coverage index
+ private Anchor[][] mam; // mark2 anchor matrix, ordered by mark2 coverage index, then by mark1 class
+ MarkToMarkSubtableFormat1 ( String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries ) {
+ super ( id, sequence, flags, format, coverage, entries );
+ populate ( entries );
+ }
+ /** {@inheritDoc} */
+ public List getEntries() {
+ if ( ( mct2 != null ) && ( maa != null ) && ( nmc > 0 ) && ( mam != null ) ) {
+ List entries = new ArrayList ( 4 );
+ entries.add ( mct2 );
+ entries.add ( Integer.valueOf ( nmc ) );
+ entries.add ( maa );
+ entries.add ( mam );
+ return entries;
+ } else {
+ return null;
+ }
+ }
+ /** {@inheritDoc} */
+ public MarkAnchor getMark1Anchor ( int ciMark1, int giMark1 ) {
+ if ( ( maa != null ) && ( ciMark1 < maa.length ) ) {
+ return maa [ ciMark1 ];
+ } else {
+ return null;
+ }
+ }
+ /** {@inheritDoc} */
+ public Anchor getMark2Anchor ( int giMark2, int markClass ) {
+ int ciMark2;
+ if ( ( mct2 != null ) && ( ( ciMark2 = mct2.getCoverageIndex ( giMark2 ) ) >= 0 ) ) {
+ if ( ( mam != null ) && ( ciMark2 < mam.length ) ) {
+ Anchor[] ma = mam [ ciMark2 ];
+ if ( ( ma != null ) && ( markClass < ma.length ) ) {
+ return ma [ markClass ];
+ }
+ }
+ }
+ return null;
+ }
+ private void populate ( List entries ) {
+ if ( entries == null ) {
+ throw new AdvancedTypographicTableFormatException ( "illegal entries, must be non-null" );
+ } else if ( entries.size() != 4 ) {
+ throw new AdvancedTypographicTableFormatException ( "illegal entries, " + entries.size() + " entries present, but requires 4 entries" );
+ } else {
+ Object o;
+ if ( ( ( o = entries.get(0) ) == null ) || ! ( o instanceof GlyphCoverageTable ) ) {
+ throw new AdvancedTypographicTableFormatException ( "illegal entries, first entry must be an GlyphCoverageTable, but is: " + ( ( o != null ) ? o.getClass() : null ) );
+ } else {
+ mct2 = (GlyphCoverageTable) o;
+ }
+ if ( ( ( o = entries.get(1) ) == null ) || ! ( o instanceof Integer ) ) {
+ throw new AdvancedTypographicTableFormatException ( "illegal entries, second entry must be an Integer, but is: " + ( ( o != null ) ? o.getClass() : null ) );
+ } else {
+ nmc = ((Integer)(o)).intValue();
+ }
+ if ( ( ( o = entries.get(2) ) == null ) || ! ( o instanceof MarkAnchor[] ) ) {
+ throw new AdvancedTypographicTableFormatException ( "illegal entries, third entry must be a MarkAnchor[], but is: " + ( ( o != null ) ? o.getClass() : null ) );
+ } else {
+ maa = (MarkAnchor[]) o;
+ }
+ if ( ( ( o = entries.get(3) ) == null ) || ! ( o instanceof Anchor[][] ) ) {
+ throw new AdvancedTypographicTableFormatException ( "illegal entries, fourth entry must be a Anchor[][], but is: " + ( ( o != null ) ? o.getClass() : null ) );
+ } else {
+ mam = (Anchor[][]) o;
+ }
+ }
+ }
+ }
+
+ private abstract static class ContextualSubtable extends GlyphPositioningSubtable {
+ ContextualSubtable ( String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries ) {
+ super ( id, sequence, flags, format, coverage );
+ }
+ /** {@inheritDoc} */
+ public int getType() {
+ return GPOS_LOOKUP_TYPE_CONTEXTUAL;
+ }
+ /** {@inheritDoc} */
+ public boolean isCompatible ( GlyphSubtable subtable ) {
+ return subtable instanceof ContextualSubtable;
+ }
+ /** {@inheritDoc} */
+ public boolean position ( GlyphPositioningState ps ) {
+ boolean applied = false;
+ int gi = ps.getGlyph(), ci;
+ 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 applied;
+ }
+ /**
+ * Obtain rule lookups set associated current input glyph context.
+ * @param ci coverage index of glyph at current position
+ * @param gi glyph index of glyph at current position
+ * @param ps glyph positioning state
+ * @param rv array of ints used to receive multiple return values, must be of length 1 or greater,
+ * where the first entry is used to return the input sequence length of the matched rule
+ * @return array of rule lookups or null if none applies
+ */
+ public abstract RuleLookup[] getLookups ( int ci, int gi, GlyphPositioningState ps, int[] rv );
+ static GlyphPositioningSubtable create ( String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries ) {
+ if ( format == 1 ) {
+ return new ContextualSubtableFormat1 ( id, sequence, flags, format, coverage, entries );
+ } else if ( format == 2 ) {
+ return new ContextualSubtableFormat2 ( id, sequence, flags, format, coverage, entries );
+ } else if ( format == 3 ) {
+ return new ContextualSubtableFormat3 ( id, sequence, flags, format, coverage, entries );
+ } else {
+ throw new UnsupportedOperationException();
+ }
+ }
+ }
+
+ private static class ContextualSubtableFormat1 extends ContextualSubtable {
+ private RuleSet[] rsa; // rule set array, ordered by glyph coverage index
+ ContextualSubtableFormat1 ( String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries ) {
+ super ( id, sequence, flags, format, coverage, entries );
+ populate ( entries );
+ }
+ /** {@inheritDoc} */
+ public List getEntries() {
+ if ( rsa != null ) {
+ List entries = new ArrayList ( 1 );
+ entries.add ( rsa );
+ return entries;
+ } else {
+ return null;
+ }
+ }
+ /** {@inheritDoc} */
+ public void resolveLookupReferences ( Map/*<String,LookupTable>*/ lookupTables ) {
+ GlyphTable.resolveLookupReferences ( rsa, lookupTables );
+ }
+ /** {@inheritDoc} */
+ public RuleLookup[] getLookups ( int ci, int gi, GlyphPositioningState ps, int[] rv ) {
+ assert ps != null;
+ assert ( rv != null ) && ( rv.length > 0 );
+ assert rsa != null;
+ if ( rsa.length > 0 ) {
+ RuleSet rs = rsa [ 0 ];
+ if ( rs != null ) {
+ Rule[] ra = rs.getRules();
+ for ( int i = 0, n = ra.length; i < n; i++ ) {
+ Rule r = ra [ i ];
+ if ( ( r != null ) && ( r instanceof ChainedGlyphSequenceRule ) ) {
+ ChainedGlyphSequenceRule cr = (ChainedGlyphSequenceRule) r;
+ int[] iga = cr.getGlyphs ( gi );
+ if ( matches ( ps, iga, 0, rv ) ) {
+ return r.getLookups();
+ }
+ }
+ }
+ }
+ }
+ return null;
+ }
+ static boolean matches ( GlyphPositioningState ps, int[] glyphs, int offset, int[] rv ) {
+ if ( ( glyphs == null ) || ( glyphs.length == 0 ) ) {
+ return true; // match null or empty glyph sequence
+ } else {
+ boolean reverse = offset < 0;
+ GlyphTester ignores = ps.getIgnoreDefault();
+ int[] counts = ps.getGlyphsAvailable ( offset, reverse, ignores );
+ int nga = counts[0];
+ int ngm = glyphs.length;
+ if ( nga < ngm ) {
+ return false; // insufficient glyphs available to match
+ } else {
+ int[] ga = ps.getGlyphs ( offset, ngm, reverse, ignores, null, counts );
+ for ( int k = 0; k < ngm; k++ ) {
+ if ( ga [ k ] != glyphs [ k ] ) {
+ return false; // match fails at ga [ k ]
+ }
+ }
+ if ( rv != null ) {
+ rv[0] = counts[0] + counts[1];
+ }
+ return true; // all glyphs match
+ }
+ }
+ }
+ private void populate ( List entries ) {
+ if ( entries == null ) {
+ throw new AdvancedTypographicTableFormatException ( "illegal entries, must be non-null" );
+ } else if ( entries.size() != 1 ) {
+ throw new AdvancedTypographicTableFormatException ( "illegal entries, " + entries.size() + " entries present, but requires 1 entry" );
+ } else {
+ Object o;
+ if ( ( ( o = entries.get(0) ) == null ) || ! ( o instanceof RuleSet[] ) ) {
+ throw new AdvancedTypographicTableFormatException ( "illegal entries, first entry must be an RuleSet[], but is: " + ( ( o != null ) ? o.getClass() : null ) );
+ } else {
+ rsa = (RuleSet[]) o;
+ }
+ }
+ }
+ }
+
+ private static class ContextualSubtableFormat2 extends ContextualSubtable {
+ private GlyphClassTable cdt; // class def table
+ private int ngc; // class set count
+ private RuleSet[] rsa; // rule set array, ordered by class number [0...ngc - 1]
+ ContextualSubtableFormat2 ( String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries ) {
+ super ( id, sequence, flags, format, coverage, entries );
+ populate ( entries );
+ }
+ /** {@inheritDoc} */
+ public List getEntries() {
+ if ( rsa != null ) {
+ List entries = new ArrayList ( 3 );
+ entries.add ( cdt );
+ entries.add ( Integer.valueOf ( ngc ) );
+ entries.add ( rsa );
+ return entries;
+ } else {
+ return null;
+ }
+ }
+ /** {@inheritDoc} */
+ public void resolveLookupReferences ( Map/*<String,LookupTable>*/ lookupTables ) {
+ GlyphTable.resolveLookupReferences ( rsa, lookupTables );
+ }
+ /** {@inheritDoc} */
+ public RuleLookup[] getLookups ( int ci, int gi, GlyphPositioningState ps, int[] rv ) {
+ assert ps != null;
+ assert ( rv != null ) && ( rv.length > 0 );
+ assert rsa != null;
+ if ( rsa.length > 0 ) {
+ RuleSet rs = rsa [ 0 ];
+ if ( rs != null ) {
+ Rule[] ra = rs.getRules();
+ for ( int i = 0, n = ra.length; i < n; i++ ) {
+ Rule r = ra [ i ];
+ if ( ( r != null ) && ( r instanceof ChainedClassSequenceRule ) ) {
+ ChainedClassSequenceRule cr = (ChainedClassSequenceRule) r;
+ int[] ca = cr.getClasses ( cdt.getClassIndex ( gi, ps.getClassMatchSet ( gi ) ) );
+ if ( matches ( ps, cdt, ca, 0, rv ) ) {
+ return r.getLookups();
+ }
+ }
+ }
+ }
+ }
+ return null;
+ }
+ static boolean matches ( GlyphPositioningState ps, GlyphClassTable cdt, int[] classes, int offset, int[] rv ) {
+ if ( ( cdt == null ) || ( classes == null ) || ( classes.length == 0 ) ) {
+ return true; // match null class definitions, null or empty class sequence
+ } else {
+ boolean reverse = offset < 0;
+ GlyphTester ignores = ps.getIgnoreDefault();
+ int[] counts = ps.getGlyphsAvailable ( offset, reverse, ignores );
+ int nga = counts[0];
+ int ngm = classes.length;
+ if ( nga < ngm ) {
+ return false; // insufficient glyphs available to match
+ } else {
+ int[] ga = ps.getGlyphs ( offset, ngm, reverse, ignores, null, counts );
+ for ( int k = 0; k < ngm; k++ ) {
+ int gi = ga [ k ];
+ int ms = ps.getClassMatchSet ( gi );
+ int gc = cdt.getClassIndex ( gi, ms );
+ if ( ( gc < 0 ) || ( gc >= cdt.getClassSize ( ms ) ) ) {
+ return false; // none or invalid class fails mat ch
+ } else if ( gc != classes [ k ] ) {
+ return false; // match fails at ga [ k ]
+ }
+ }
+ if ( rv != null ) {
+ rv[0] = counts[0] + counts[1];
+ }
+ return true; // all glyphs match
+ }
+ }
+ }
+ private void populate ( List entries ) {
+ if ( entries == null ) {
+ throw new AdvancedTypographicTableFormatException ( "illegal entries, must be non-null" );
+ } else if ( entries.size() != 3 ) {
+ throw new AdvancedTypographicTableFormatException ( "illegal entries, " + entries.size() + " entries present, but requires 3 entries" );
+ } else {
+ Object o;
+ if ( ( ( o = entries.get(0) ) == null ) || ! ( o instanceof GlyphClassTable ) ) {
+ throw new AdvancedTypographicTableFormatException ( "illegal entries, first entry must be an GlyphClassTable, but is: " + ( ( o != null ) ? o.getClass() : null ) );
+ } else {
+ cdt = (GlyphClassTable) o;
+ }
+ if ( ( ( o = entries.get(1) ) == null ) || ! ( o instanceof Integer ) ) {
+ throw new AdvancedTypographicTableFormatException ( "illegal entries, second entry must be an Integer, but is: " + ( ( o != null ) ? o.getClass() : null ) );
+ } else {
+ ngc = ((Integer)(o)).intValue();
+ }
+ if ( ( ( o = entries.get(2) ) == null ) || ! ( o instanceof RuleSet[] ) ) {
+ throw new AdvancedTypographicTableFormatException ( "illegal entries, third entry must be an RuleSet[], but is: " + ( ( o != null ) ? o.getClass() : null ) );
+ } else {
+ rsa = (RuleSet[]) o;
+ if ( rsa.length != ngc ) {
+ throw new AdvancedTypographicTableFormatException ( "illegal entries, RuleSet[] length is " + rsa.length + ", but expected " + ngc + " glyph classes" );
+ }
+ }
+ }
+ }
+ }
+
+ private static class ContextualSubtableFormat3 extends ContextualSubtable {
+ private RuleSet[] rsa; // rule set array, containing a single rule set
+ ContextualSubtableFormat3 ( String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries ) {
+ super ( id, sequence, flags, format, coverage, entries );
+ populate ( entries );
+ }
+ /** {@inheritDoc} */
+ public List getEntries() {
+ if ( rsa != null ) {
+ List entries = new ArrayList ( 1 );
+ entries.add ( rsa );
+ return entries;
+ } else {
+ return null;
+ }
+ }
+ /** {@inheritDoc} */
+ public void resolveLookupReferences ( Map/*<String,LookupTable>*/ lookupTables ) {
+ GlyphTable.resolveLookupReferences ( rsa, lookupTables );
+ }
+ /** {@inheritDoc} */
+ public RuleLookup[] getLookups ( int ci, int gi, GlyphPositioningState ps, int[] rv ) {
+ assert ps != null;
+ assert ( rv != null ) && ( rv.length > 0 );
+ assert rsa != null;
+ if ( rsa.length > 0 ) {
+ RuleSet rs = rsa [ 0 ];
+ if ( rs != null ) {
+ Rule[] ra = rs.getRules();
+ for ( int i = 0, n = ra.length; i < n; i++ ) {
+ Rule r = ra [ i ];
+ if ( ( r != null ) && ( r instanceof ChainedCoverageSequenceRule ) ) {
+ ChainedCoverageSequenceRule cr = (ChainedCoverageSequenceRule) r;
+ GlyphCoverageTable[] gca = cr.getCoverages();
+ if ( matches ( ps, gca, 0, rv ) ) {
+ return r.getLookups();
+ }
+ }
+ }
+ }
+ }
+ return null;
+ }
+ static boolean matches ( GlyphPositioningState ps, GlyphCoverageTable[] gca, int offset, int[] rv ) {
+ if ( ( gca == null ) || ( gca.length == 0 ) ) {
+ return true; // match null or empty coverage array
+ } else {
+ boolean reverse = offset < 0;
+ GlyphTester ignores = ps.getIgnoreDefault();
+ int[] counts = ps.getGlyphsAvailable ( offset, reverse, ignores );
+ int nga = counts[0];
+ int ngm = gca.length;
+ if ( nga < ngm ) {
+ return false; // insufficient glyphs available to match
+ } else {
+ int[] ga = ps.getGlyphs ( offset, ngm, reverse, ignores, null, counts );
+ for ( int k = 0; k < ngm; k++ ) {
+ GlyphCoverageTable ct = gca [ k ];
+ if ( ct != null ) {
+ if ( ct.getCoverageIndex ( ga [ k ] ) < 0 ) {
+ return false; // match fails at ga [ k ]
+ }
+ }
+ }
+ if ( rv != null ) {
+ rv[0] = counts[0] + counts[1];
+ }
+ return true; // all glyphs match
+ }
+ }
+ }
+ private void populate ( List entries ) {
+ if ( entries == null ) {
+ throw new AdvancedTypographicTableFormatException ( "illegal entries, must be non-null" );
+ } else if ( entries.size() != 1 ) {
+ throw new AdvancedTypographicTableFormatException ( "illegal entries, " + entries.size() + " entries present, but requires 1 entry" );
+ } else {
+ Object o;
+ if ( ( ( o = entries.get(0) ) == null ) || ! ( o instanceof RuleSet[] ) ) {
+ throw new AdvancedTypographicTableFormatException ( "illegal entries, first entry must be an RuleSet[], but is: " + ( ( o != null ) ? o.getClass() : null ) );
+ } else {
+ rsa = (RuleSet[]) o;
+ }
+ }
+ }
+ }
+
+ private abstract static class ChainedContextualSubtable extends GlyphPositioningSubtable {
+ ChainedContextualSubtable ( String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries ) {
+ super ( id, sequence, flags, format, coverage );
+ }
+ /** {@inheritDoc} */
+ public int getType() {
+ return GPOS_LOOKUP_TYPE_CHAINED_CONTEXTUAL;
+ }
+ /** {@inheritDoc} */
+ public boolean isCompatible ( GlyphSubtable subtable ) {
+ return subtable instanceof ChainedContextualSubtable;
+ }
+ /** {@inheritDoc} */
+ public boolean position ( GlyphPositioningState ps ) {
+ boolean applied = false;
+ int gi = ps.getGlyph(), ci;
+ 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 applied;
+ }
+ /**
+ * Obtain rule lookups set associated current input glyph context.
+ * @param ci coverage index of glyph at current position
+ * @param gi glyph index of glyph at current position
+ * @param ps glyph positioning state
+ * @param rv array of ints used to receive multiple return values, must be of length 1 or greater,
+ * where the first entry is used to return the input sequence length of the matched rule
+ * @return array of rule lookups or null if none applies
+ */
+ public abstract RuleLookup[] getLookups ( int ci, int gi, GlyphPositioningState ps, int[] rv );
+ static GlyphPositioningSubtable create ( String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries ) {
+ if ( format == 1 ) {
+ return new ChainedContextualSubtableFormat1 ( id, sequence, flags, format, coverage, entries );
+ } else if ( format == 2 ) {
+ return new ChainedContextualSubtableFormat2 ( id, sequence, flags, format, coverage, entries );
+ } else if ( format == 3 ) {
+ return new ChainedContextualSubtableFormat3 ( id, sequence, flags, format, coverage, entries );
+ } else {
+ throw new UnsupportedOperationException();
+ }
+ }
+ }
+
+ private static class ChainedContextualSubtableFormat1 extends ChainedContextualSubtable {
+ private RuleSet[] rsa; // rule set array, ordered by glyph coverage index
+ ChainedContextualSubtableFormat1 ( String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries ) {
+ super ( id, sequence, flags, format, coverage, entries );
+ populate ( entries );
+ }
+ /** {@inheritDoc} */
+ public List getEntries() {
+ if ( rsa != null ) {
+ List entries = new ArrayList ( 1 );
+ entries.add ( rsa );
+ return entries;
+ } else {
+ return null;
+ }
+ }
+ /** {@inheritDoc} */
+ public void resolveLookupReferences ( Map/*<String,LookupTable>*/ lookupTables ) {
+ GlyphTable.resolveLookupReferences ( rsa, lookupTables );
+ }
+ /** {@inheritDoc} */
+ public RuleLookup[] getLookups ( int ci, int gi, GlyphPositioningState ps, int[] rv ) {
+ assert ps != null;
+ assert ( rv != null ) && ( rv.length > 0 );
+ assert rsa != null;
+ if ( rsa.length > 0 ) {
+ RuleSet rs = rsa [ 0 ];
+ if ( rs != null ) {
+ Rule[] ra = rs.getRules();
+ for ( int i = 0, n = ra.length; i < n; i++ ) {
+ Rule r = ra [ i ];
+ if ( ( r != null ) && ( r instanceof ChainedGlyphSequenceRule ) ) {
+ ChainedGlyphSequenceRule cr = (ChainedGlyphSequenceRule) r;
+ int[] iga = cr.getGlyphs ( gi );
+ if ( matches ( ps, iga, 0, rv ) ) {
+ int[] bga = cr.getBacktrackGlyphs();
+ if ( matches ( ps, bga, -1, null ) ) {
+ int[] lga = cr.getLookaheadGlyphs();
+ if ( matches ( ps, lga, rv[0], null ) ) {
+ return r.getLookups();
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ return null;
+ }
+ private boolean matches ( GlyphPositioningState ps, int[] glyphs, int offset, int[] rv ) {
+ return ContextualSubtableFormat1.matches ( ps, glyphs, offset, rv );
+ }
+ private void populate ( List entries ) {
+ if ( entries == null ) {
+ throw new AdvancedTypographicTableFormatException ( "illegal entries, must be non-null" );
+ } else if ( entries.size() != 1 ) {
+ throw new AdvancedTypographicTableFormatException ( "illegal entries, " + entries.size() + " entries present, but requires 1 entry" );
+ } else {
+ Object o;
+ if ( ( ( o = entries.get(0) ) == null ) || ! ( o instanceof RuleSet[] ) ) {
+ throw new AdvancedTypographicTableFormatException ( "illegal entries, first entry must be an RuleSet[], but is: " + ( ( o != null ) ? o.getClass() : null ) );
+ } else {
+ rsa = (RuleSet[]) o;
+ }
+ }
+ }
+ }
+
+ private static class ChainedContextualSubtableFormat2 extends ChainedContextualSubtable {
+ private GlyphClassTable icdt; // input class def table
+ private GlyphClassTable bcdt; // backtrack class def table
+ private GlyphClassTable lcdt; // lookahead class def table
+ private int ngc; // class set count
+ private RuleSet[] rsa; // rule set array, ordered by class number [0...ngc - 1]
+ ChainedContextualSubtableFormat2 ( String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries ) {
+ super ( id, sequence, flags, format, coverage, entries );
+ populate ( entries );
+ }
+ /** {@inheritDoc} */
+ public List getEntries() {
+ if ( rsa != null ) {
+ List entries = new ArrayList ( 5 );
+ entries.add ( icdt );
+ entries.add ( bcdt );
+ entries.add ( lcdt );
+ entries.add ( Integer.valueOf ( ngc ) );
+ entries.add ( rsa );
+ return entries;
+ } else {
+ return null;
+ }
+ }
+ /** {@inheritDoc} */
+ public void resolveLookupReferences ( Map/*<String,LookupTable>*/ lookupTables ) {
+ GlyphTable.resolveLookupReferences ( rsa, lookupTables );
+ }
+ /** {@inheritDoc} */
+ public RuleLookup[] getLookups ( int ci, int gi, GlyphPositioningState ps, int[] rv ) {
+ assert ps != null;
+ assert ( rv != null ) && ( rv.length > 0 );
+ assert rsa != null;
+ if ( rsa.length > 0 ) {
+ RuleSet rs = rsa [ 0 ];
+ if ( rs != null ) {
+ Rule[] ra = rs.getRules();
+ for ( int i = 0, n = ra.length; i < n; i++ ) {
+ Rule r = ra [ i ];
+ if ( ( r != null ) && ( r instanceof ChainedClassSequenceRule ) ) {
+ ChainedClassSequenceRule cr = (ChainedClassSequenceRule) r;
+ int[] ica = cr.getClasses ( icdt.getClassIndex ( gi, ps.getClassMatchSet ( gi ) ) );
+ if ( matches ( ps, icdt, ica, 0, rv ) ) {
+ int[] bca = cr.getBacktrackClasses();
+ if ( matches ( ps, bcdt, bca, -1, null ) ) {
+ int[] lca = cr.getLookaheadClasses();
+ if ( matches ( ps, lcdt, lca, rv[0], null ) ) {
+ return r.getLookups();
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ return null;
+ }
+ private boolean matches ( GlyphPositioningState ps, GlyphClassTable cdt, int[] classes, int offset, int[] rv ) {
+ return ContextualSubtableFormat2.matches ( ps, cdt, classes, offset, rv );
+ }
+ private void populate ( List entries ) {
+ if ( entries == null ) {
+ throw new AdvancedTypographicTableFormatException ( "illegal entries, must be non-null" );
+ } else if ( entries.size() != 5 ) {
+ throw new AdvancedTypographicTableFormatException ( "illegal entries, " + entries.size() + " entries present, but requires 5 entries" );
+ } else {
+ Object o;
+ if ( ( ( o = entries.get(0) ) == null ) || ! ( o instanceof GlyphClassTable ) ) {
+ throw new AdvancedTypographicTableFormatException ( "illegal entries, first entry must be an GlyphClassTable, but is: " + ( ( o != null ) ? o.getClass() : null ) );
+ } else {
+ icdt = (GlyphClassTable) o;
+ }
+ if ( ( ( o = entries.get(1) ) != null ) && ! ( o instanceof GlyphClassTable ) ) {
+ throw new AdvancedTypographicTableFormatException ( "illegal entries, second entry must be an GlyphClassTable, but is: " + o.getClass() );
+ } else {
+ bcdt = (GlyphClassTable) o;
+ }
+ if ( ( ( o = entries.get(2) ) != null ) && ! ( o instanceof GlyphClassTable ) ) {
+ throw new AdvancedTypographicTableFormatException ( "illegal entries, third entry must be an GlyphClassTable, but is: " + o.getClass() );
+ } else {
+ lcdt = (GlyphClassTable) o;
+ }
+ if ( ( ( o = entries.get(3) ) == null ) || ! ( o instanceof Integer ) ) {
+ throw new AdvancedTypographicTableFormatException ( "illegal entries, fourth entry must be an Integer, but is: " + ( ( o != null ) ? o.getClass() : null ) );
+ } else {
+ ngc = ((Integer)(o)).intValue();
+ }
+ if ( ( ( o = entries.get(4) ) == null ) || ! ( o instanceof RuleSet[] ) ) {
+ throw new AdvancedTypographicTableFormatException ( "illegal entries, fifth entry must be an RuleSet[], but is: " + ( ( o != null ) ? o.getClass() : null ) );
+ } else {
+ rsa = (RuleSet[]) o;
+ if ( rsa.length != ngc ) {
+ throw new AdvancedTypographicTableFormatException ( "illegal entries, RuleSet[] length is " + rsa.length + ", but expected " + ngc + " glyph classes" );
+ }
+ }
+ }
+ }
+ }
+
+ private static class ChainedContextualSubtableFormat3 extends ChainedContextualSubtable {
+ private RuleSet[] rsa; // rule set array, containing a single rule set
+ ChainedContextualSubtableFormat3 ( String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries ) {
+ super ( id, sequence, flags, format, coverage, entries );
+ populate ( entries );
+ }
+ /** {@inheritDoc} */
+ public List getEntries() {
+ if ( rsa != null ) {
+ List entries = new ArrayList ( 1 );
+ entries.add ( rsa );
+ return entries;
+ } else {
+ return null;
+ }
+ }
+ /** {@inheritDoc} */
+ public void resolveLookupReferences ( Map/*<String,LookupTable>*/ lookupTables ) {
+ GlyphTable.resolveLookupReferences ( rsa, lookupTables );
+ }
+ /** {@inheritDoc} */
+ public RuleLookup[] getLookups ( int ci, int gi, GlyphPositioningState ps, int[] rv ) {
+ assert ps != null;
+ assert ( rv != null ) && ( rv.length > 0 );
+ assert rsa != null;
+ if ( rsa.length > 0 ) {
+ RuleSet rs = rsa [ 0 ];
+ if ( rs != null ) {
+ Rule[] ra = rs.getRules();
+ for ( int i = 0, n = ra.length; i < n; i++ ) {
+ Rule r = ra [ i ];
+ if ( ( r != null ) && ( r instanceof ChainedCoverageSequenceRule ) ) {
+ ChainedCoverageSequenceRule cr = (ChainedCoverageSequenceRule) r;
+ GlyphCoverageTable[] igca = cr.getCoverages();
+ if ( matches ( ps, igca, 0, rv ) ) {
+ GlyphCoverageTable[] bgca = cr.getBacktrackCoverages();
+ if ( matches ( ps, bgca, -1, null ) ) {
+ GlyphCoverageTable[] lgca = cr.getLookaheadCoverages();
+ if ( matches ( ps, lgca, rv[0], null ) ) {
+ return r.getLookups();
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ return null;
+ }
+ private boolean matches ( GlyphPositioningState ps, GlyphCoverageTable[] gca, int offset, int[] rv ) {
+ return ContextualSubtableFormat3.matches ( ps, gca, offset, rv );
+ }
+ private void populate ( List entries ) {
+ if ( entries == null ) {
+ throw new AdvancedTypographicTableFormatException ( "illegal entries, must be non-null" );
+ } else if ( entries.size() != 1 ) {
+ throw new AdvancedTypographicTableFormatException ( "illegal entries, " + entries.size() + " entries present, but requires 1 entry" );
+ } else {
+ Object o;
+ if ( ( ( o = entries.get(0) ) == null ) || ! ( o instanceof RuleSet[] ) ) {
+ throw new AdvancedTypographicTableFormatException ( "illegal entries, first entry must be an RuleSet[], but is: " + ( ( o != null ) ? o.getClass() : null ) );
+ } else {
+ rsa = (RuleSet[]) o;
+ }
+ }
+ }
+ }
+
+ /**
+ * The <code>DeviceTable</code> class implements a positioning device table record, comprising
+ * adjustments to be made to scaled design units according to the scaled size.
+ */
+ public static class DeviceTable {
+
+ private final int startSize;
+ private final int endSize;
+ private final int[] deltas;
+
+ /**
+ * Instantiate a DeviceTable.
+ * @param startSize the
+ * @param endSize the ending (scaled) size
+ * @param deltas adjustments for each scaled size
+ */
+ public DeviceTable ( int startSize, int endSize, int[] deltas ) {
+ assert startSize >= 0;
+ assert startSize <= endSize;
+ assert deltas != null;
+ assert deltas.length == ( endSize - startSize ) + 1;
+ this.startSize = startSize;
+ this.endSize = endSize;
+ this.deltas = deltas;
+ }
+
+ /** @return the start size */
+ public int getStartSize() {
+ return startSize;
+ }
+
+ /** @return the end size */
+ public int getEndSize() {
+ return endSize;
+ }
+
+ /** @return the deltas */
+ public int[] getDeltas() {
+ return deltas;
+ }
+
+ /**
+ * Find device adjustment.
+ * @param fontSize the font size to search for
+ * @return an adjustment if font size matches an entry
+ * @asf.todo at present, assumes that 1 device unit equals one point
+ */
+ public int findAdjustment ( int fontSize ) {
+ int fs = fontSize / 1000;
+ if ( fs < startSize ) {
+ return 0;
+ } else if ( fs <= endSize ) {
+ return deltas [ fs - startSize ] * 1000;
+ } else {
+ return 0;
+ }
+ }
+
+ /** {@inheritDoc} */
+ public String toString() {
+ return "{ start = " + startSize + ", end = " + endSize + ", deltas = " + Arrays.toString ( deltas ) + "}";
+ }
+
+ }
+
+ /**
+ * The <code>Value</code> class implements a positioning value record, comprising placement
+ * and advancement information in X and Y axes, and optionally including device data used to
+ * perform device (grid-fitted) specific fine grain adjustments.
+ */
+ public static class Value {
+
+ /** X_PLACEMENT value format flag */
+ public static final int X_PLACEMENT = 0x0001;
+ /** Y_PLACEMENT value format flag */
+ public static final int Y_PLACEMENT = 0x0002;
+ /** X_ADVANCE value format flag */
+ public static final int X_ADVANCE = 0x0004;
+ /** Y_ADVANCE value format flag */
+ public static final int Y_ADVANCE = 0x0008;
+ /** X_PLACEMENT_DEVICE value format flag */
+ public static final int X_PLACEMENT_DEVICE = 0x0010;
+ /** Y_PLACEMENT_DEVICE value format flag */
+ public static final int Y_PLACEMENT_DEVICE = 0x0020;
+ /** X_ADVANCE_DEVICE value format flag */
+ public static final int X_ADVANCE_DEVICE = 0x0040;
+ /** Y_ADVANCE_DEVICE value format flag */
+ public static final int Y_ADVANCE_DEVICE = 0x0080;
+
+ /** X_PLACEMENT value index (within adjustments arrays) */
+ public static final int IDX_X_PLACEMENT = 0;
+ /** Y_PLACEMENT value index (within adjustments arrays) */
+ public static final int IDX_Y_PLACEMENT = 1;
+ /** X_ADVANCE value index (within adjustments arrays) */
+ public static final int IDX_X_ADVANCE = 2;
+ /** Y_ADVANCE value index (within adjustments arrays) */
+ public static final int IDX_Y_ADVANCE = 3;
+
+ private int xPlacement; // x placement
+ private int yPlacement; // y placement
+ private int xAdvance; // x advance
+ private int yAdvance; // y advance
+ private final DeviceTable xPlaDevice; // x placement device table
+ private final DeviceTable yPlaDevice; // y placement device table
+ private final DeviceTable xAdvDevice; // x advance device table
+ private final DeviceTable yAdvDevice; // x advance device table
+
+ /**
+ * Instantiate a Value.
+ * @param xPlacement the x placement or zero
+ * @param yPlacement the y placement or zero
+ * @param xAdvance the x advance or zero
+ * @param yAdvance the y advance or zero
+ * @param xPlaDevice the x placement device table or null
+ * @param yPlaDevice the y placement device table or null
+ * @param xAdvDevice the x advance device table or null
+ * @param yAdvDevice the y advance device table or null
+ */
+ public Value ( int xPlacement, int yPlacement, int xAdvance, int yAdvance, DeviceTable xPlaDevice, DeviceTable yPlaDevice, DeviceTable xAdvDevice, DeviceTable yAdvDevice ) {
+ this.xPlacement = xPlacement;
+ this.yPlacement = yPlacement;
+ this.xAdvance = xAdvance;
+ this.yAdvance = yAdvance;
+ this.xPlaDevice = xPlaDevice;
+ this.yPlaDevice = yPlaDevice;
+ this.xAdvDevice = xAdvDevice;
+ this.yAdvDevice = yAdvDevice;
+ }
+
+ /** @return the x placement */
+ public int getXPlacement() {
+ return xPlacement;
+ }
+
+ /** @return the y placement */
+ public int getYPlacement() {
+ return yPlacement;
+ }
+
+ /** @return the x advance */
+ public int getXAdvance() {
+ return xAdvance;
+ }
+
+ /** @return the y advance */
+ public int getYAdvance() {
+ return yAdvance;
+ }
+
+ /** @return the x placement device table */
+ public DeviceTable getXPlaDevice() {
+ return xPlaDevice;
+ }
+
+ /** @return the y placement device table */
+ public DeviceTable getYPlaDevice() {
+ return yPlaDevice;
+ }
+
+ /** @return the x advance device table */
+ public DeviceTable getXAdvDevice() {
+ return xAdvDevice;
+ }
+
+ /** @return the y advance device table */
+ public DeviceTable getYAdvDevice() {
+ return yAdvDevice;
+ }
+
+ /**
+ * Apply value to specific adjustments to without use of device table adjustments.
+ * @param xPlacement the x placement or zero
+ * @param yPlacement the y placement or zero
+ * @param xAdvance the x advance or zero
+ * @param yAdvance the y advance or zero
+ */
+ public void adjust ( int xPlacement, int yPlacement, int xAdvance, int yAdvance ) {
+ this.xPlacement += xPlacement;
+ this.yPlacement += yPlacement;
+ this.xAdvance += xAdvance;
+ this.yAdvance += yAdvance;
+ }
+
+ /**
+ * Apply value to adjustments using font size for device table adjustments.
+ * @param adjustments array of four integers containing X,Y placement and X,Y advance adjustments
+ * @param fontSize font size for device table adjustments
+ * @return true if some adjustment was made
+ */
+ public boolean adjust ( int[] adjustments, int fontSize ) {
+ boolean adjust = false;
+ int dv;
+ if ( ( dv = xPlacement ) != 0 ) {
+ adjustments [ IDX_X_PLACEMENT ] += dv;
+ adjust = true;
+ }
+ if ( ( dv = yPlacement ) != 0 ) {
+ adjustments [ IDX_Y_PLACEMENT ] += dv;
+ adjust = true;
+ }
+ if ( ( dv = xAdvance ) != 0 ) {
+ adjustments [ IDX_X_ADVANCE ] += dv;
+ adjust = true;
+ }
+ if ( ( dv = yAdvance ) != 0 ) {
+ adjustments [ IDX_Y_ADVANCE ] += dv;
+ adjust = true;
+ }
+ if ( fontSize != 0 ) {
+ DeviceTable dt;
+ if ( ( dt = xPlaDevice ) != null ) {
+ if ( ( dv = dt.findAdjustment ( fontSize ) ) != 0 ) {
+ adjustments [ IDX_X_PLACEMENT ] += dv;
+ adjust = true;
+ }
+ }
+ if ( ( dt = yPlaDevice ) != null ) {
+ if ( ( dv = dt.findAdjustment ( fontSize ) ) != 0 ) {
+ adjustments [ IDX_Y_PLACEMENT ] += dv;
+ adjust = true;
+ }
+ }
+ if ( ( dt = xAdvDevice ) != null ) {
+ if ( ( dv = dt.findAdjustment ( fontSize ) ) != 0 ) {
+ adjustments [ IDX_X_ADVANCE ] += dv;
+ adjust = true;
+ }
+ }
+ if ( ( dt = yAdvDevice ) != null ) {
+ if ( ( dv = dt.findAdjustment ( fontSize ) ) != 0 ) {
+ adjustments [ IDX_Y_ADVANCE ] += dv;
+ adjust = true;
+ }
+ }
+ }
+ return adjust;
+ }
+
+ /** {@inheritDoc} */
+ public String toString() {
+ StringBuffer sb = new StringBuffer();
+ boolean first = true;
+ sb.append ( "{ " );
+ if ( xPlacement != 0 ) {
+ if ( ! first ) {
+ sb.append ( ", " );
+ } else {
+ first = false;
+ }
+ sb.append ( "xPlacement = " + xPlacement );
+ }
+ if ( yPlacement != 0 ) {
+ if ( ! first ) {
+ sb.append ( ", " );
+ } else {
+ first = false;
+ }
+ sb.append ( "yPlacement = " + yPlacement );
+ }
+ if ( xAdvance != 0 ) {
+ if ( ! first ) {
+ sb.append ( ", " );
+ } else {
+ first = false;
+ }
+ sb.append ( "xAdvance = " + xAdvance );
+ }
+ if ( yAdvance != 0 ) {
+ if ( ! first ) {
+ sb.append ( ", " );
+ } else {
+ first = false;
+ }
+ sb.append ( "yAdvance = " + yAdvance );
+ }
+ if ( xPlaDevice != null ) {
+ if ( ! first ) {
+ sb.append ( ", " );
+ } else {
+ first = false;
+ }
+ sb.append ( "xPlaDevice = " + xPlaDevice );
+ }
+ if ( yPlaDevice != null ) {
+ if ( ! first ) {
+ sb.append ( ", " );
+ } else {
+ first = false;
+ }
+ sb.append ( "xPlaDevice = " + yPlaDevice );
+ }
+ if ( xAdvDevice != null ) {
+ if ( ! first ) {
+ sb.append ( ", " );
+ } else {
+ first = false;
+ }
+ sb.append ( "xAdvDevice = " + xAdvDevice );
+ }
+ if ( yAdvDevice != null ) {
+ if ( ! first ) {
+ sb.append ( ", " );
+ } else {
+ first = false;
+ }
+ sb.append ( "xAdvDevice = " + yAdvDevice );
+ }
+ sb.append(" }");
+ return sb.toString();
+ }
+
+ }
+
+ /**
+ * The <code>PairValues</code> class implements a pair value record, comprising a glyph id (or zero)
+ * and two optional positioning values.
+ */
+ public static class PairValues {
+
+ private final int glyph; // glyph id (or 0)
+ private final Value value1; // value for first glyph in pair (or null)
+ private final Value value2; // value for second glyph in pair (or null)
+
+ /**
+ * Instantiate a PairValues.
+ * @param glyph the glyph id (or zero)
+ * @param value1 the value of the first glyph in pair (or null)
+ * @param value2 the value of the second glyph in pair (or null)
+ */
+ public PairValues ( int glyph, Value value1, Value value2 ) {
+ assert glyph >= 0;
+ this.glyph = glyph;
+ this.value1 = value1;
+ this.value2 = value2;
+ }
+
+ /** @return the glyph id */
+ public int getGlyph() {
+ return glyph;
+ }
+
+ /** @return the first value */
+ public Value getValue1() {
+ return value1;
+ }
+
+ /** @return the second value */
+ public Value getValue2() {
+ return value2;
+ }
+
+ /** {@inheritDoc} */
+ public String toString() {
+ StringBuffer sb = new StringBuffer();
+ boolean first = true;
+ sb.append ( "{ " );
+ if ( glyph != 0 ) {
+ if ( ! first ) {
+ sb.append ( ", " );
+ } else {
+ first = false;
+ }
+ sb.append ( "glyph = " + glyph );
+ }
+ if ( value1 != null ) {
+ if ( ! first ) {
+ sb.append ( ", " );
+ } else {
+ first = false;
+ }
+ sb.append ( "value1 = " + value1 );
+ }
+ if ( value2 != null ) {
+ if ( ! first ) {
+ sb.append ( ", " );
+ } else {
+ first = false;
+ }
+ sb.append ( "value2 = " + value2 );
+ }
+ sb.append(" }");
+ return sb.toString();
+ }
+
+ }
+
+ /**
+ * The <code>Anchor</code> class implements a anchor record, comprising an X,Y coordinate pair,
+ * an optional anchor point index (or -1), and optional X or Y device tables (or null if absent).
+ */
+ public static class Anchor {
+
+ private final int x; // xCoordinate (in design units)
+ private final int y; // yCoordinate (in design units)
+ private final int anchorPoint; // anchor point index (or -1)
+ private final DeviceTable xDevice; // x device table
+ private final DeviceTable yDevice; // y device table
+
+ /**
+ * Instantiate an Anchor (format 1).
+ * @param x the x coordinate
+ * @param y the y coordinate
+ */
+ public Anchor ( int x, int y ) {
+ this ( x, y, -1, null, null );
+ }
+
+ /**
+ * Instantiate an Anchor (format 2).
+ * @param x the x coordinate
+ * @param y the y coordinate
+ * @param anchorPoint anchor index (or -1)
+ */
+ public Anchor ( int x, int y, int anchorPoint ) {
+ this ( x, y, anchorPoint, null, null );
+ }
+
+ /**
+ * Instantiate an Anchor (format 3).
+ * @param x the x coordinate
+ * @param y the y coordinate
+ * @param xDevice the x device table (or null if not present)
+ * @param yDevice the y device table (or null if not present)
+ */
+ public Anchor ( int x, int y, DeviceTable xDevice, DeviceTable yDevice ) {
+ this ( x, y, -1, xDevice, yDevice );
+ }
+
+ /**
+ * Instantiate an Anchor based on an existing anchor.
+ * @param a the existing anchor
+ */
+ protected Anchor ( Anchor a ) {
+ this ( a.x, a.y, a.anchorPoint, a.xDevice, a.yDevice );
+ }
+
+ private Anchor ( int x, int y, int anchorPoint, DeviceTable xDevice, DeviceTable yDevice ) {
+ assert ( anchorPoint >= 0 ) || ( anchorPoint == -1 );
+ this.x = x;
+ this.y = y;
+ this.anchorPoint = anchorPoint;
+ this.xDevice = xDevice;
+ this.yDevice = yDevice;
+ }
+
+ /** @return the x coordinate */
+ public int getX() {
+ return x;
+ }
+
+ /** @return the y coordinate */
+ public int getY() {
+ return y;
+ }
+
+ /** @return the anchor point index (or -1 if not specified) */
+ public int getAnchorPoint() {
+ return anchorPoint;
+ }
+
+ /** @return the x device table (or null if not specified) */
+ public DeviceTable getXDevice() {
+ return xDevice;
+ }
+
+ /** @return the y device table (or null if not specified) */
+ public DeviceTable getYDevice() {
+ return yDevice;
+ }
+
+ /**
+ * Obtain adjustment value required to align the specified anchor
+ * with this anchor.
+ * @param a the anchor to align
+ * @return the adjustment value needed to effect alignment
+ */
+ public Value getAlignmentAdjustment ( Anchor a ) {
+ assert a != null;
+ // TODO - handle anchor point
+ // TODO - handle device tables
+ return new Value ( x - a.x, y - a.y, 0, 0, null, null, null, null );
+ }
+
+ /** {@inheritDoc} */
+ public String toString() {
+ StringBuffer sb = new StringBuffer();
+ sb.append ( "{ [" + x + "," + y + "]" );
+ if ( anchorPoint != -1 ) {
+ sb.append ( ", anchorPoint = " + anchorPoint );
+ }
+ if ( xDevice != null ) {
+ sb.append ( ", xDevice = " + xDevice );
+ }
+ if ( yDevice != null ) {
+ sb.append ( ", yDevice = " + yDevice );
+ }
+ sb.append(" }");
+ return sb.toString();
+ }
+
+ }
+
+ /**
+ * The <code>MarkAnchor</code> class is a subclass of the <code>Anchor</code> class, adding a mark
+ * class designation.
+ */
+ public static class MarkAnchor extends Anchor {
+
+ private final int markClass; // mark class
+
+ /**
+ * Instantiate a MarkAnchor
+ * @param markClass the mark class
+ * @param a the underlying anchor (whose fields are copied)
+ */
+ public MarkAnchor ( int markClass, Anchor a ) {
+ super ( a );
+ this.markClass = markClass;
+ }
+
+ /** @return the mark class */
+ public int getMarkClass() {
+ return markClass;
+ }
+
+ /** {@inheritDoc} */
+ public String toString() {
+ return "{ markClass = " + markClass + ", anchor = " + super.toString() + " }";
+ }
+
+ }
+
+}
diff --git a/src/java/org/apache/fop/complexscripts/fonts/GlyphProcessingState.java b/src/java/org/apache/fop/complexscripts/fonts/GlyphProcessingState.java
new file mode 100644
index 000000000..acccdc86c
--- /dev/null
+++ b/src/java/org/apache/fop/complexscripts/fonts/GlyphProcessingState.java
@@ -0,0 +1,1135 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.complexscripts.fonts;
+
+import java.nio.IntBuffer;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.fop.complexscripts.util.GlyphSequence;
+import org.apache.fop.complexscripts.util.GlyphContextTester;
+import org.apache.fop.complexscripts.util.GlyphTester;
+import org.apache.fop.complexscripts.util.ScriptContextTester;
+
+// CSOFF: LineLengthCheck
+// CSOFF: NoWhitespaceAfterCheck
+
+/**
+ * The <code>GlyphProcessingState</code> implements a common, base state object used during glyph substitution
+ * and positioning processing.
+ * @author Glenn Adams
+ */
+
+public class GlyphProcessingState {
+
+ /** governing glyph definition table */
+ protected GlyphDefinitionTable gdef;
+ /** governing script */
+ protected String script;
+ /** governing language */
+ protected String language;
+ /** governing feature */
+ protected String feature;
+ /** current input glyph sequence */
+ protected GlyphSequence igs;
+ /** current index in input sequence */
+ protected int index;
+ /** last (maximum) index of input sequence (exclusive) */
+ protected int indexLast;
+ /** consumed, updated after each successful subtable application */
+ protected int consumed;
+ /** lookup flags */
+ protected int lookupFlags;
+ /** class match set */
+ protected int classMatchSet;
+ /** script specific context tester or null */
+ protected ScriptContextTester sct;
+ /** glyph context tester or null */
+ protected GlyphContextTester gct;
+ /** ignore base glyph tester */
+ protected GlyphTester ignoreBase;
+ /** ignore ligature glyph tester */
+ protected GlyphTester ignoreLigature;
+ /** ignore mark glyph tester */
+ protected GlyphTester ignoreMark;
+ /** default ignore glyph tester */
+ protected GlyphTester ignoreDefault;
+
+ /**
+ * Construct glyph processing state.
+ * @param gs input glyph sequence
+ * @param script script identifier
+ * @param language language identifier
+ * @param feature feature identifier
+ * @param sct script context tester (or null)
+ */
+ protected GlyphProcessingState ( GlyphSequence gs, String script, String language, String feature, ScriptContextTester sct ) {
+ this.script = script;
+ this.language = language;
+ this.feature = feature;
+ this.igs = gs;
+ this.indexLast = gs.getGlyphCount();
+ this.sct = sct;
+ this.gct = ( sct != null ) ? sct.getTester ( feature ) : null;
+ this.ignoreBase = new GlyphTester() { public boolean test(int gi, int flags) { return isIgnoredBase(gi, flags); } };
+ this.ignoreLigature = new GlyphTester() { public boolean test(int gi, int flags) { return isIgnoredLigature(gi, flags); } };
+ this.ignoreMark = new GlyphTester() { public boolean test(int gi, int flags) { return isIgnoredMark(gi, flags); } };
+ }
+
+ /**
+ * Construct glyph processing state using an existing state object using shallow copy
+ * except as follows: input glyph sequence is copied deep except for its characters array.
+ * @param s existing processing state to copy from
+ */
+ protected GlyphProcessingState ( GlyphProcessingState s ) {
+ this ( new GlyphSequence ( s.igs ), s.script, s.language, s.feature, s.sct );
+ setPosition ( s.index );
+ }
+
+ /**
+ * Set governing glyph definition table.
+ * @param gdef glyph definition table (or null, to unset)
+ */
+ public void setGDEF ( GlyphDefinitionTable gdef ) {
+ if ( this.gdef == null ) {
+ this.gdef = gdef;
+ } else if ( gdef == null ) {
+ this.gdef = null;
+ }
+ }
+
+ /**
+ * Obtain governing glyph definition table.
+ * @return glyph definition table (or null, to not set)
+ */
+ public GlyphDefinitionTable getGDEF() {
+ return gdef;
+ }
+
+ /**
+ * Set governing lookup flags
+ * @param flags lookup flags (or zero, to unset)
+ */
+ public void setLookupFlags ( int flags ) {
+ if ( this.lookupFlags == 0 ) {
+ this.lookupFlags = flags;
+ } else if ( flags == 0 ) {
+ this.lookupFlags = 0;
+ }
+ }
+
+ /**
+ * Obtain governing lookup flags.
+ * @return lookup flags (zero may indicate unset or no flags)
+ */
+ public int getLookupFlags() {
+ return lookupFlags;
+ }
+
+ /**
+ * Obtain governing class match set.
+ * @param gi glyph index that may be used to determine which match set applies
+ * @return class match set (zero may indicate unset or no set)
+ */
+ public int getClassMatchSet ( int gi ) {
+ return 0;
+ }
+
+ /**
+ * Set default ignore tester.
+ * @param ignoreDefault glyph tester (or null, to unset)
+ */
+ public void setIgnoreDefault ( GlyphTester ignoreDefault ) {
+ if ( this.ignoreDefault == null ) {
+ this.ignoreDefault = ignoreDefault;
+ } else if ( ignoreDefault == null ) {
+ this.ignoreDefault = null;
+ }
+ }
+
+ /**
+ * Obtain governing default ignores tester.
+ * @return default ignores tester
+ */
+ public GlyphTester getIgnoreDefault() {
+ return ignoreDefault;
+ }
+
+ /**
+ * Update glyph subtable specific state. Each time a
+ * different glyph subtable is to be applied, it is used
+ * to update this state prior to application, after which
+ * this state is to be reset.
+ * @param st glyph subtable to use for update
+ */
+ public void updateSubtableState ( GlyphSubtable st ) {
+ setGDEF ( st.getGDEF() );
+ setLookupFlags ( st.getFlags() );
+ setIgnoreDefault ( getIgnoreTester ( getLookupFlags() ) );
+ }
+
+ /**
+ * Reset glyph subtable specific state.
+ */
+ public void resetSubtableState() {
+ setGDEF ( null );
+ setLookupFlags ( 0 );
+ setIgnoreDefault ( null );
+ }
+
+ /**
+ * Obtain current position index in input glyph sequence.
+ * @return current index
+ */
+ public int getPosition() {
+ return index;
+ }
+
+ /**
+ * Set (seek to) position index in input glyph sequence.
+ * @param index to seek to
+ * @throws IndexOutOfBoundsException if index is less than zero
+ * or exceeds last valid position
+ */
+ public void setPosition ( int index ) throws IndexOutOfBoundsException {
+ if ( ( index >= 0 ) && ( index <= indexLast ) ) {
+ this.index = index;
+ } else {
+ throw new IndexOutOfBoundsException();
+ }
+ }
+
+ /**
+ * Obtain last valid position index in input glyph sequence.
+ * @return current last index
+ */
+ public int getLastPosition() {
+ return indexLast;
+ }
+
+ /**
+ * Determine if at least one glyph remains in
+ * input sequence.
+ * @return true if one or more glyph remains
+ */
+ public boolean hasNext() {
+ return hasNext ( 1 );
+ }
+
+ /**
+ * Determine if at least <code>count</code> glyphs remain in
+ * input sequence.
+ * @param count of glyphs to test
+ * @return true if at least <code>count</code> glyphs are available
+ */
+ public boolean hasNext ( int count ) {
+ return ( index + count ) <= indexLast;
+ }
+
+ /**
+ * Update the current position index based upon previously consumed
+ * glyphs, i.e., add the consuemd count to the current position index.
+ * If no glyphs were previously consumed, then forces exactly one
+ * glyph to be consumed.
+ * @return the new (updated) position index
+ */
+ public int next() {
+ if ( index < indexLast ) {
+ // force consumption of at least one input glyph
+ if ( consumed == 0 ) {
+ consumed = 1;
+ }
+ index += consumed; consumed = 0;
+ if ( index > indexLast ) {
+ index = indexLast;
+ }
+ }
+ return index;
+ }
+
+ /**
+ * Determine if at least one backtrack (previous) glyph is present
+ * in input sequence.
+ * @return true if one or more glyph remains
+ */
+ public boolean hasPrev() {
+ return hasPrev ( 1 );
+ }
+
+ /**
+ * Determine if at least <code>count</code> backtrack (previous) glyphs
+ * are present in input sequence.
+ * @param count of glyphs to test
+ * @return true if at least <code>count</code> glyphs are available
+ */
+ public boolean hasPrev ( int count ) {
+ return ( index - count ) >= 0;
+ }
+
+ /**
+ * Update the current position index based upon previously consumed
+ * glyphs, i.e., subtract the consuemd count from the current position index.
+ * If no glyphs were previously consumed, then forces exactly one
+ * glyph to be consumed. This method is used to traverse an input
+ * glyph sequence in reverse order.
+ * @return the new (updated) position index
+ */
+ public int prev() {
+ if ( index > 0 ) {
+ // force consumption of at least one input glyph
+ if ( consumed == 0 ) {
+ consumed = 1;
+ }
+ index -= consumed; consumed = 0;
+ if ( index < 0 ) {
+ index = 0;
+ }
+ }
+ return index;
+ }
+
+ /**
+ * Record the consumption of <code>count</code> glyphs such that
+ * this consumption never exceeds the number of glyphs in the input glyph
+ * sequence.
+ * @param count of glyphs to consume
+ * @return newly adjusted consumption count
+ * @throws IndexOutOfBoundsException if count would cause consumption
+ * to exceed count of glyphs in input glyph sequence
+ */
+ public int consume ( int count ) throws IndexOutOfBoundsException {
+ if ( ( consumed + count ) <= indexLast ) {
+ consumed += count;
+ return consumed;
+ } else {
+ throw new IndexOutOfBoundsException();
+ }
+ }
+
+ /**
+ * Determine if any consumption has occurred.
+ * @return true if consumption count is greater than zero
+ */
+ public boolean didConsume() {
+ return consumed > 0;
+ }
+
+ /**
+ * Obtain reference to input glyph sequence, which must not be modified.
+ * @return input glyph sequence
+ */
+ public GlyphSequence getInput() {
+ return igs;
+ }
+
+ /**
+ * Obtain glyph at specified offset from current position.
+ * @param offset from current position
+ * @return glyph at specified offset from current position
+ * @throws IndexOutOfBoundsException if no glyph available at offset
+ */
+ public int getGlyph ( int offset ) throws IndexOutOfBoundsException {
+ int i = index + offset;
+ if ( ( i >= 0 ) && ( i < indexLast ) ) {
+ return igs.getGlyph ( i );
+ } else {
+ throw new IndexOutOfBoundsException ( "attempting index at " + i );
+ }
+ }
+
+ /**
+ * Obtain glyph at current position.
+ * @return glyph at current position
+ * @throws IndexOutOfBoundsException if no glyph available
+ */
+ public int getGlyph() throws IndexOutOfBoundsException {
+ return getGlyph ( 0 );
+ }
+
+ /**
+ * Set (replace) glyph at specified offset from current position.
+ * @param offset from current position
+ * @param glyph to set at specified offset from current position
+ * @throws IndexOutOfBoundsException if specified offset is not valid position
+ */
+ public void setGlyph ( int offset, int glyph ) throws IndexOutOfBoundsException {
+ int i = index + offset;
+ if ( ( i >= 0 ) && ( i < indexLast ) ) {
+ igs.setGlyph ( i, glyph );
+ } else {
+ throw new IndexOutOfBoundsException ( "attempting index at " + i );
+ }
+ }
+
+ /**
+ * Obtain character association of glyph at specified offset from current position.
+ * @param offset from current position
+ * @return character association of glyph at current position
+ * @throws IndexOutOfBoundsException if offset results in an invalid index into input glyph sequence
+ */
+ public GlyphSequence.CharAssociation getAssociation ( int offset ) throws IndexOutOfBoundsException {
+ int i = index + offset;
+ if ( ( i >= 0 ) && ( i < indexLast ) ) {
+ return igs.getAssociation ( i );
+ } else {
+ throw new IndexOutOfBoundsException ( "attempting index at " + i );
+ }
+ }
+
+ /**
+ * Obtain character association of glyph at current position.
+ * @return character association of glyph at current position
+ * @throws IndexOutOfBoundsException if no glyph available
+ */
+ public GlyphSequence.CharAssociation getAssociation() throws IndexOutOfBoundsException {
+ return getAssociation ( 0 );
+ }
+
+ /**
+ * Obtain <code>count</code> glyphs starting at specified offset from current position. If
+ * <code>reverseOrder</code> is true, then glyphs are returned in reverse order starting at specified offset
+ * and going in reverse towards beginning of input glyph sequence.
+ * @param offset from current position
+ * @param count number of glyphs to obtain
+ * @param reverseOrder true if to obtain in reverse order
+ * @param ignoreTester glyph tester to use to determine which glyphs are ignored (or null, in which case none are ignored)
+ * @param glyphs array to use to fetch glyphs
+ * @param counts int[2] array to receive fetched glyph counts, where counts[0] will
+ * receive the number of glyphs obtained, and counts[1] will receive the number of glyphs
+ * ignored
+ * @return array of glyphs
+ * @throws IndexOutOfBoundsException if offset or count results in an
+ * invalid index into input glyph sequence
+ */
+ public int[] getGlyphs ( int offset, int count, boolean reverseOrder, GlyphTester ignoreTester, int[] glyphs, int[] counts ) throws IndexOutOfBoundsException {
+ if ( count < 0 ) {
+ count = getGlyphsAvailable ( offset, reverseOrder, ignoreTester ) [ 0 ];
+ }
+ int start = index + offset;
+ if ( start < 0 ) {
+ throw new IndexOutOfBoundsException ( "will attempt index at " + start );
+ } else if ( ! reverseOrder && ( ( start + count ) > indexLast ) ) {
+ throw new IndexOutOfBoundsException ( "will attempt index at " + ( start + count ) );
+ } else if ( reverseOrder && ( ( start + 1 ) < count ) ) {
+ throw new IndexOutOfBoundsException ( "will attempt index at " + ( start - count ) );
+ }
+ if ( glyphs == null ) {
+ glyphs = new int [ count ];
+ } else if ( glyphs.length != count ) {
+ throw new IllegalArgumentException ( "glyphs array is non-null, but its length (" + glyphs.length + "), is not equal to count (" + count + ")" );
+ }
+ if ( ! reverseOrder ) {
+ return getGlyphsForward ( start, count, ignoreTester, glyphs, counts );
+ } else {
+ return getGlyphsReverse ( start, count, ignoreTester, glyphs, counts );
+ }
+ }
+
+ private int[] getGlyphsForward ( int start, int count, GlyphTester ignoreTester, int[] glyphs, int[] counts ) throws IndexOutOfBoundsException {
+ int counted = 0;
+ int ignored = 0;
+ for ( int i = start, n = indexLast, k = 0; i < n; i++ ) {
+ int gi = getGlyph ( i - index );
+ if ( gi == 65535 ) {
+ ignored++;
+ } else {
+ if ( ( ignoreTester == null ) || ! ignoreTester.test ( gi, getLookupFlags() ) ) {
+ if ( k < count ) {
+ glyphs [ k++ ] = gi; counted++;
+ } else {
+ break;
+ }
+ } else {
+ ignored++;
+ }
+ }
+ }
+ if ( ( counts != null ) && ( counts.length > 1 ) ) {
+ counts[0] = counted;
+ counts[1] = ignored;
+ }
+ return glyphs;
+ }
+
+ private int[] getGlyphsReverse ( int start, int count, GlyphTester ignoreTester, int[] glyphs, int[] counts ) throws IndexOutOfBoundsException {
+ int counted = 0;
+ int ignored = 0;
+ for ( int i = start, k = 0; i >= 0; i-- ) {
+ int gi = getGlyph ( i - index );
+ if ( gi == 65535 ) {
+ ignored++;
+ } else {
+ if ( ( ignoreTester == null ) || ! ignoreTester.test ( gi, getLookupFlags() ) ) {
+ if ( k < count ) {
+ glyphs [ k++ ] = gi; counted++;
+ } else {
+ break;
+ }
+ } else {
+ ignored++;
+ }
+ }
+ }
+ if ( ( counts != null ) && ( counts.length > 1 ) ) {
+ counts[0] = counted;
+ counts[1] = ignored;
+ }
+ return glyphs;
+ }
+
+ /**
+ * Obtain <code>count</code> glyphs starting at specified offset from current position. If
+ * offset is negative, then glyphs are returned in reverse order starting at specified offset
+ * and going in reverse towards beginning of input glyph sequence.
+ * @param offset from current position
+ * @param count number of glyphs to obtain
+ * @param glyphs array to use to fetch glyphs
+ * @param counts int[2] array to receive fetched glyph counts, where counts[0] will
+ * receive the number of glyphs obtained, and counts[1] will receive the number of glyphs
+ * ignored
+ * @return array of glyphs
+ * @throws IndexOutOfBoundsException if offset or count results in an
+ * invalid index into input glyph sequence
+ */
+ public int[] getGlyphs ( int offset, int count, int[] glyphs, int[] counts ) throws IndexOutOfBoundsException {
+ return getGlyphs ( offset, count, offset < 0, ignoreDefault, glyphs, counts );
+ }
+
+ /**
+ * Obtain all glyphs starting from current position to end of input glyph sequence.
+ * @return array of available glyphs
+ * @throws IndexOutOfBoundsException if no glyph available
+ */
+ public int[] getGlyphs() throws IndexOutOfBoundsException {
+ return getGlyphs ( 0, indexLast - index, false, null, null, null );
+ }
+
+ /**
+ * Obtain <code>count</code> ignored glyphs starting at specified offset from current position. If
+ * <code>reverseOrder</code> is true, then glyphs are returned in reverse order starting at specified offset
+ * and going in reverse towards beginning of input glyph sequence.
+ * @param offset from current position
+ * @param count number of glyphs to obtain
+ * @param reverseOrder true if to obtain in reverse order
+ * @param ignoreTester glyph tester to use to determine which glyphs are ignored (or null, in which case none are ignored)
+ * @param glyphs array to use to fetch glyphs
+ * @param counts int[2] array to receive fetched glyph counts, where counts[0] will
+ * receive the number of glyphs obtained, and counts[1] will receive the number of glyphs
+ * ignored
+ * @return array of glyphs
+ * @throws IndexOutOfBoundsException if offset or count results in an
+ * invalid index into input glyph sequence
+ */
+ public int[] getIgnoredGlyphs ( int offset, int count, boolean reverseOrder, GlyphTester ignoreTester, int[] glyphs, int[] counts ) throws IndexOutOfBoundsException {
+ return getGlyphs ( offset, count, reverseOrder, new NotGlyphTester ( ignoreTester ), glyphs, counts );
+ }
+
+ /**
+ * Obtain <code>count</code> ignored glyphs starting at specified offset from current position. If <code>offset</code> is
+ * negative, then fetch in reverse order.
+ * @param offset from current position
+ * @param count number of glyphs to obtain
+ * @return array of glyphs
+ * @throws IndexOutOfBoundsException if offset or count results in an
+ * invalid index into input glyph sequence
+ */
+ public int[] getIgnoredGlyphs ( int offset, int count ) throws IndexOutOfBoundsException {
+ return getIgnoredGlyphs ( offset, count, offset < 0, ignoreDefault, null, null );
+ }
+
+ /**
+ * Determine number of glyphs available starting at specified offset from current position. If
+ * <code>reverseOrder</code> is true, then search backwards in input glyph sequence.
+ * @param offset from current position
+ * @param reverseOrder true if to obtain in reverse order
+ * @param ignoreTester glyph tester to use to determine which glyphs to count (or null, in which case none are ignored)
+ * @return an int[2] array where counts[0] is the number of glyphs available, and counts[1] is the number of glyphs ignored
+ * @throws IndexOutOfBoundsException if offset or count results in an
+ * invalid index into input glyph sequence
+ */
+ public int[] getGlyphsAvailable ( int offset, boolean reverseOrder, GlyphTester ignoreTester ) throws IndexOutOfBoundsException {
+ int start = index + offset;
+ if ( ( start < 0 ) || ( start > indexLast ) ) {
+ return new int[] { 0, 0 };
+ } else if ( ! reverseOrder ) {
+ return getGlyphsAvailableForward ( start, ignoreTester );
+ } else {
+ return getGlyphsAvailableReverse ( start, ignoreTester );
+ }
+ }
+
+ private int[] getGlyphsAvailableForward ( int start, GlyphTester ignoreTester ) throws IndexOutOfBoundsException {
+ int counted = 0;
+ int ignored = 0;
+ if ( ignoreTester == null ) {
+ counted = indexLast - start;
+ } else {
+ for ( int i = start, n = indexLast; i < n; i++ ) {
+ int gi = getGlyph ( i - index );
+ if ( gi == 65535 ) {
+ ignored++;
+ } else {
+ if ( ignoreTester.test ( gi, getLookupFlags() ) ) {
+ ignored++;
+ } else {
+ counted++;
+ }
+ }
+ }
+ }
+ return new int[] { counted, ignored };
+ }
+
+ private int[] getGlyphsAvailableReverse ( int start, GlyphTester ignoreTester ) throws IndexOutOfBoundsException {
+ int counted = 0;
+ int ignored = 0;
+ if ( ignoreTester == null ) {
+ counted = start + 1;
+ } else {
+ for ( int i = start; i >= 0; i-- ) {
+ int gi = getGlyph ( i - index );
+ if ( gi == 65535 ) {
+ ignored++;
+ } else {
+ if ( ignoreTester.test ( gi, getLookupFlags() ) ) {
+ ignored++;
+ } else {
+ counted++;
+ }
+ }
+ }
+ }
+ return new int[] { counted, ignored };
+ }
+
+ /**
+ * Determine number of glyphs available starting at specified offset from current position. If
+ * <code>reverseOrder</code> is true, then search backwards in input glyph sequence. Uses the
+ * default ignores tester.
+ * @param offset from current position
+ * @param reverseOrder true if to obtain in reverse order
+ * @return an int[2] array where counts[0] is the number of glyphs available, and counts[1] is the number of glyphs ignored
+ * @throws IndexOutOfBoundsException if offset or count results in an
+ * invalid index into input glyph sequence
+ */
+ public int[] getGlyphsAvailable ( int offset, boolean reverseOrder ) throws IndexOutOfBoundsException {
+ return getGlyphsAvailable ( offset, reverseOrder, ignoreDefault );
+ }
+
+ /**
+ * Determine number of glyphs available starting at specified offset from current position. If
+ * offset is negative, then search backwards in input glyph sequence. Uses the
+ * default ignores tester.
+ * @param offset from current position
+ * @return an int[2] array where counts[0] is the number of glyphs available, and counts[1] is the number of glyphs ignored
+ * @throws IndexOutOfBoundsException if offset or count results in an
+ * invalid index into input glyph sequence
+ */
+ public int[] getGlyphsAvailable ( int offset ) throws IndexOutOfBoundsException {
+ return getGlyphsAvailable ( offset, offset < 0 );
+ }
+
+ /**
+ * Obtain <code>count</code> character associations of glyphs starting at specified offset from current position. If
+ * <code>reverseOrder</code> is true, then associations are returned in reverse order starting at specified offset
+ * and going in reverse towards beginning of input glyph sequence.
+ * @param offset from current position
+ * @param count number of associations to obtain
+ * @param reverseOrder true if to obtain in reverse order
+ * @param ignoreTester glyph tester to use to determine which glyphs are ignored (or null, in which case none are ignored)
+ * @param associations array to use to fetch associations
+ * @param counts int[2] array to receive fetched association counts, where counts[0] will
+ * receive the number of associations obtained, and counts[1] will receive the number of glyphs whose
+ * associations were ignored
+ * @return array of associations
+ * @throws IndexOutOfBoundsException if offset or count results in an
+ * invalid index into input glyph sequence
+ */
+ public GlyphSequence.CharAssociation[] getAssociations ( int offset, int count, boolean reverseOrder, GlyphTester ignoreTester, GlyphSequence.CharAssociation[] associations, int[] counts )
+ throws IndexOutOfBoundsException {
+ if ( count < 0 ) {
+ count = getGlyphsAvailable ( offset, reverseOrder, ignoreTester ) [ 0 ];
+ }
+ int start = index + offset;
+ if ( start < 0 ) {
+ throw new IndexOutOfBoundsException ( "will attempt index at " + start );
+ } else if ( ! reverseOrder && ( ( start + count ) > indexLast ) ) {
+ throw new IndexOutOfBoundsException ( "will attempt index at " + ( start + count ) );
+ } else if ( reverseOrder && ( ( start + 1 ) < count ) ) {
+ throw new IndexOutOfBoundsException ( "will attempt index at " + ( start - count ) );
+ }
+ if ( associations == null ) {
+ associations = new GlyphSequence.CharAssociation [ count ];
+ } else if ( associations.length != count ) {
+ throw new IllegalArgumentException ( "associations array is non-null, but its length (" + associations.length + "), is not equal to count (" + count + ")" );
+ }
+ if ( ! reverseOrder ) {
+ return getAssociationsForward ( start, count, ignoreTester, associations, counts );
+ } else {
+ return getAssociationsReverse ( start, count, ignoreTester, associations, counts );
+ }
+ }
+
+ private GlyphSequence.CharAssociation[] getAssociationsForward ( int start, int count, GlyphTester ignoreTester, GlyphSequence.CharAssociation[] associations, int[] counts )
+ throws IndexOutOfBoundsException {
+ int counted = 0;
+ int ignored = 0;
+ for ( int i = start, n = indexLast, k = 0; i < n; i++ ) {
+ int gi = getGlyph ( i - index );
+ if ( gi == 65535 ) {
+ ignored++;
+ } else {
+ if ( ( ignoreTester == null ) || ! ignoreTester.test ( gi, getLookupFlags() ) ) {
+ if ( k < count ) {
+ associations [ k++ ] = getAssociation ( i - index ); counted++;
+ } else {
+ break;
+ }
+ } else {
+ ignored++;
+ }
+ }
+ }
+ if ( ( counts != null ) && ( counts.length > 1 ) ) {
+ counts[0] = counted;
+ counts[1] = ignored;
+ }
+ return associations;
+ }
+
+ private GlyphSequence.CharAssociation[] getAssociationsReverse ( int start, int count, GlyphTester ignoreTester, GlyphSequence.CharAssociation[] associations, int[] counts )
+ throws IndexOutOfBoundsException {
+ int counted = 0;
+ int ignored = 0;
+ for ( int i = start, k = 0; i >= 0; i-- ) {
+ int gi = getGlyph ( i - index );
+ if ( gi == 65535 ) {
+ ignored++;
+ } else {
+ if ( ( ignoreTester == null ) || ! ignoreTester.test ( gi, getLookupFlags() ) ) {
+ if ( k < count ) {
+ associations [ k++ ] = getAssociation ( i - index ); counted++;
+ } else {
+ break;
+ }
+ } else {
+ ignored++;
+ }
+ }
+ }
+ if ( ( counts != null ) && ( counts.length > 1 ) ) {
+ counts[0] = counted;
+ counts[1] = ignored;
+ }
+ return associations;
+ }
+
+ /**
+ * Obtain <code>count</code> character associations of glyphs starting at specified offset from current position. If
+ * offset is negative, then search backwards in input glyph sequence. Uses the
+ * default ignores tester.
+ * @param offset from current position
+ * @param count number of associations to obtain
+ * @return array of associations
+ * @throws IndexOutOfBoundsException if offset or count results in an
+ * invalid index into input glyph sequence
+ */
+ public GlyphSequence.CharAssociation[] getAssociations ( int offset, int count ) throws IndexOutOfBoundsException {
+ return getAssociations ( offset, count, offset < 0, ignoreDefault, null, null );
+ }
+
+ /**
+ * Obtain <code>count</code> character associations of ignored glyphs starting at specified offset from current position. If
+ * <code>reverseOrder</code> is true, then glyphs are returned in reverse order starting at specified offset
+ * and going in reverse towards beginning of input glyph sequence.
+ * @param offset from current position
+ * @param count number of character associations to obtain
+ * @param reverseOrder true if to obtain in reverse order
+ * @param ignoreTester glyph tester to use to determine which glyphs are ignored (or null, in which case none are ignored)
+ * @param associations array to use to fetch associations
+ * @param counts int[2] array to receive fetched association counts, where counts[0] will
+ * receive the number of associations obtained, and counts[1] will receive the number of glyphs whose
+ * associations were ignored
+ * @return array of associations
+ * @throws IndexOutOfBoundsException if offset or count results in an
+ * invalid index into input glyph sequence
+ */
+ public GlyphSequence.CharAssociation[] getIgnoredAssociations ( int offset, int count, boolean reverseOrder, GlyphTester ignoreTester, GlyphSequence.CharAssociation[] associations, int[] counts )
+ throws IndexOutOfBoundsException {
+ return getAssociations ( offset, count, reverseOrder, new NotGlyphTester ( ignoreTester ), associations, counts );
+ }
+
+ /**
+ * Obtain <code>count</code> character associations of ignored glyphs starting at specified offset from current position. If
+ * offset is negative, then search backwards in input glyph sequence. Uses the
+ * default ignores tester.
+ * @param offset from current position
+ * @param count number of character associations to obtain
+ * @return array of associations
+ * @throws IndexOutOfBoundsException if offset or count results in an
+ * invalid index into input glyph sequence
+ */
+ public GlyphSequence.CharAssociation[] getIgnoredAssociations ( int offset, int count ) throws IndexOutOfBoundsException {
+ return getIgnoredAssociations ( offset, count, offset < 0, ignoreDefault, null, null );
+ }
+
+ /**
+ * Replace subsequence of input glyph sequence starting at specified offset from current position and of
+ * length <code>count</code> glyphs with a subsequence of the sequence <code>gs</code> starting from the specified
+ * offset <code>gsOffset</code> of length <code>gsCount</code> glyphs.
+ * @param offset from current position
+ * @param count number of glyphs to replace, which, if negative means all glyphs from offset to end of input sequence
+ * @param gs glyph sequence from which to obtain replacement glyphs
+ * @param gsOffset offset of first glyph in replacement sequence
+ * @param gsCount count of glyphs in replacement sequence starting at <code>gsOffset</code>
+ * @return true if replacement occurred, or false if replacement would result in no change to input glyph sequence
+ * @throws IndexOutOfBoundsException if offset or count results in an
+ * invalid index into input glyph sequence
+ */
+ public boolean replaceInput ( int offset, int count, GlyphSequence gs, int gsOffset, int gsCount ) throws IndexOutOfBoundsException {
+ int nig = ( igs != null ) ? igs.getGlyphCount() : 0;
+ int position = getPosition() + offset;
+ if ( position < 0 ) {
+ position = 0;
+ } else if ( position > nig ) {
+ position = nig;
+ }
+ if ( ( count < 0 ) || ( ( position + count ) > nig ) ) {
+ count = nig - position;
+ }
+ int nrg = ( gs != null ) ? gs.getGlyphCount() : 0;
+ if ( gsOffset < 0 ) {
+ gsOffset = 0;
+ } else if ( gsOffset > nrg ) {
+ gsOffset = nrg;
+ }
+ if ( ( gsCount < 0 ) || ( ( gsOffset + gsCount ) > nrg ) ) {
+ gsCount = nrg - gsOffset;
+ }
+ int ng = nig + gsCount - count;
+ IntBuffer gb = IntBuffer.allocate ( ng );
+ List al = new ArrayList ( ng );
+ for ( int i = 0, n = position; i < n; i++ ) {
+ gb.put ( igs.getGlyph ( i ) );
+ al.add ( igs.getAssociation ( i ) );
+ }
+ for ( int i = gsOffset, n = gsOffset + gsCount; i < n; i++ ) {
+ gb.put ( gs.getGlyph ( i ) );
+ al.add ( gs.getAssociation ( i ) );
+ }
+ for ( int i = position + count, n = nig; i < n; i++ ) {
+ gb.put ( igs.getGlyph ( i ) );
+ al.add ( igs.getAssociation ( i ) );
+ }
+ gb.flip();
+ if ( igs.compareGlyphs ( gb ) != 0 ) {
+ this.igs = new GlyphSequence ( igs.getCharacters(), gb, al );
+ this.indexLast = gb.limit();
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Replace subsequence of input glyph sequence starting at specified offset from current position and of
+ * length <code>count</code> glyphs with all glyphs in the replacement sequence <code>gs</code>.
+ * @param offset from current position
+ * @param count number of glyphs to replace, which, if negative means all glyphs from offset to end of input sequence
+ * @param gs glyph sequence from which to obtain replacement glyphs
+ * @return true if replacement occurred, or false if replacement would result in no change to input glyph sequence
+ * @throws IndexOutOfBoundsException if offset or count results in an
+ * invalid index into input glyph sequence
+ */
+ public boolean replaceInput ( int offset, int count, GlyphSequence gs ) throws IndexOutOfBoundsException {
+ return replaceInput ( offset, count, gs, 0, gs.getGlyphCount() );
+ }
+
+ /**
+ * Erase glyphs in input glyph sequence starting at specified offset from current position, where each glyph
+ * in the specified <code>glyphs</code> array is matched, one at a time, and when a (forward searching) match is found
+ * in the input glyph sequence, the matching glyph is replaced with the glyph index 65535.
+ * @param offset from current position
+ * @param glyphs array of glyphs to erase
+ * @return the number of glyphs erased, which may be less than the number of specified glyphs
+ * @throws IndexOutOfBoundsException if offset or count results in an
+ * invalid index into input glyph sequence
+ */
+ public int erase ( int offset, int[] glyphs ) throws IndexOutOfBoundsException {
+ int start = index + offset;
+ if ( ( start < 0 ) || ( start > indexLast ) ) {
+ throw new IndexOutOfBoundsException ( "will attempt index at " + start );
+ } else {
+ int erased = 0;
+ for ( int i = start - index, n = indexLast - start; i < n; i++ ) {
+ int gi = getGlyph ( i );
+ if ( gi == glyphs [ erased ] ) {
+ setGlyph ( i, 65535 );
+ erased++;
+ }
+ }
+ return erased;
+ }
+ }
+
+ /**
+ * Determine if is possible that the current input sequence satisfies a script specific
+ * context testing predicate. If no predicate applies, then application is always possible.
+ * @return true if no script specific context tester applies or if a specified tester returns
+ * true for the current input sequence context
+ */
+ public boolean maybeApplicable() {
+ if ( gct == null ) {
+ return true;
+ } else {
+ return gct.test ( script, language, feature, igs, index, getLookupFlags() );
+ }
+ }
+
+ /**
+ * Apply default application semantices; namely, consume one glyph.
+ */
+ public void applyDefault() {
+ consumed += 1;
+ }
+
+ /**
+ * Determine if specified glyph is a base glyph according to the governing
+ * glyph definition table.
+ * @param gi glyph index to test
+ * @return true if glyph definition table records glyph as a base glyph; otherwise, false
+ */
+ public boolean isBase ( int gi ) {
+ if ( gdef != null ) {
+ return gdef.isGlyphClass ( gi, GlyphDefinitionTable.GLYPH_CLASS_BASE );
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Determine if specified glyph is an ignored base glyph according to the governing
+ * glyph definition table.
+ * @param gi glyph index to test
+ * @param flags that apply to lookup in scope
+ * @return true if glyph definition table records glyph as a base glyph; otherwise, false
+ */
+ public boolean isIgnoredBase ( int gi, int flags ) {
+ return ( ( flags & GlyphSubtable.LF_IGNORE_BASE ) != 0 ) && isBase ( gi );
+ }
+
+ /**
+ * Determine if specified glyph is an ligature glyph according to the governing
+ * glyph definition table.
+ * @param gi glyph index to test
+ * @return true if glyph definition table records glyph as a ligature glyph; otherwise, false
+ */
+ public boolean isLigature ( int gi ) {
+ if ( gdef != null ) {
+ return gdef.isGlyphClass ( gi, GlyphDefinitionTable.GLYPH_CLASS_LIGATURE );
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Determine if specified glyph is an ignored ligature glyph according to the governing
+ * glyph definition table.
+ * @param gi glyph index to test
+ * @param flags that apply to lookup in scope
+ * @return true if glyph definition table records glyph as a ligature glyph; otherwise, false
+ */
+ public boolean isIgnoredLigature ( int gi, int flags ) {
+ return ( ( flags & GlyphSubtable.LF_IGNORE_LIGATURE ) != 0 ) && isLigature ( gi );
+ }
+
+ /**
+ * Determine if specified glyph is a mark glyph according to the governing
+ * glyph definition table.
+ * @param gi glyph index to test
+ * @return true if glyph definition table records glyph as a mark glyph; otherwise, false
+ */
+ public boolean isMark ( int gi ) {
+ if ( gdef != null ) {
+ return gdef.isGlyphClass ( gi, GlyphDefinitionTable.GLYPH_CLASS_MARK );
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Determine if specified glyph is an ignored ligature glyph according to the governing
+ * glyph definition table.
+ * @param gi glyph index to test
+ * @param flags that apply to lookup in scope
+ * @return true if glyph definition table records glyph as a ligature glyph; otherwise, false
+ */
+ public boolean isIgnoredMark ( int gi, int flags ) {
+ if ( ( flags & GlyphSubtable.LF_IGNORE_MARK ) != 0 ) {
+ return isMark ( gi );
+ } else if ( ( flags & GlyphSubtable.LF_MARK_ATTACHMENT_TYPE ) != 0 ) {
+ int lac = ( flags & GlyphSubtable.LF_MARK_ATTACHMENT_TYPE ) >> 8;
+ int gac = gdef.getMarkAttachClass ( gi );
+ return ( gac != lac );
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Obtain an ignored glyph tester that corresponds to the specified lookup flags.
+ * @param flags lookup flags
+ * @return a glyph tester
+ */
+ public GlyphTester getIgnoreTester ( int flags ) {
+ if ( ( flags & GlyphSubtable.LF_IGNORE_BASE ) != 0 ) {
+ if ( ( flags & (GlyphSubtable.LF_IGNORE_LIGATURE | GlyphSubtable.LF_IGNORE_MARK) ) == 0 ) {
+ return ignoreBase;
+ } else {
+ return getCombinedIgnoreTester ( flags );
+ }
+ }
+ if ( ( flags & GlyphSubtable.LF_IGNORE_LIGATURE ) != 0 ) {
+ if ( ( flags & (GlyphSubtable.LF_IGNORE_BASE | GlyphSubtable.LF_IGNORE_MARK) ) == 0 ) {
+ return ignoreLigature;
+ } else {
+ return getCombinedIgnoreTester ( flags );
+ }
+ }
+ if ( ( flags & GlyphSubtable.LF_IGNORE_MARK ) != 0 ) {
+ if ( ( flags & (GlyphSubtable.LF_IGNORE_BASE | GlyphSubtable.LF_IGNORE_LIGATURE) ) == 0 ) {
+ return ignoreMark;
+ } else {
+ return getCombinedIgnoreTester ( flags );
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Obtain an ignored glyph tester that corresponds to the specified multiple (combined) lookup flags.
+ * @param flags lookup flags
+ * @return a glyph tester
+ */
+ public GlyphTester getCombinedIgnoreTester ( int flags ) {
+ GlyphTester[] gta = new GlyphTester [ 3 ];
+ int ngt = 0;
+ if ( ( flags & GlyphSubtable.LF_IGNORE_BASE ) != 0 ) {
+ gta [ ngt++ ] = ignoreBase;
+ }
+ if ( ( flags & GlyphSubtable.LF_IGNORE_LIGATURE ) != 0 ) {
+ gta [ ngt++ ] = ignoreLigature;
+ }
+ if ( ( flags & GlyphSubtable.LF_IGNORE_MARK ) != 0 ) {
+ gta [ ngt++ ] = ignoreMark;
+ }
+ return getCombinedOrTester ( gta, ngt );
+ }
+
+ /**
+ * Obtain an combined OR glyph tester.
+ * @param gta an array of glyph testers
+ * @param ngt number of glyph testers present in specified array
+ * @return a combined OR glyph tester
+ */
+ public GlyphTester getCombinedOrTester ( GlyphTester[] gta, int ngt ) {
+ if ( ngt > 0 ) {
+ return new CombinedOrGlyphTester ( gta, ngt );
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Obtain an combined AND glyph tester.
+ * @param gta an array of glyph testers
+ * @param ngt number of glyph testers present in specified array
+ * @return a combined AND glyph tester
+ */
+ public GlyphTester getCombinedAndTester ( GlyphTester[] gta, int ngt ) {
+ if ( ngt > 0 ) {
+ return new CombinedAndGlyphTester ( gta, ngt );
+ } else {
+ return null;
+ }
+ }
+
+ /** combined OR glyph tester */
+ private static class CombinedOrGlyphTester implements GlyphTester {
+ private GlyphTester[] gta;
+ private int ngt;
+ CombinedOrGlyphTester ( GlyphTester[] gta, int ngt ) {
+ this.gta = gta;
+ this.ngt = ngt;
+ }
+ /** {@inheritDoc} */
+ public boolean test ( int gi, int flags ) {
+ for ( int i = 0, n = ngt; i < n; i++ ) {
+ GlyphTester gt = gta [ i ];
+ if ( gt != null ) {
+ if ( gt.test ( gi, flags ) ) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+ }
+
+ /** combined AND glyph tester */
+ private static class CombinedAndGlyphTester implements GlyphTester {
+ private GlyphTester[] gta;
+ private int ngt;
+ CombinedAndGlyphTester ( GlyphTester[] gta, int ngt ) {
+ this.gta = gta;
+ this.ngt = ngt;
+ }
+ /** {@inheritDoc} */
+ public boolean test ( int gi, int flags ) {
+ for ( int i = 0, n = ngt; i < n; i++ ) {
+ GlyphTester gt = gta [ i ];
+ if ( gt != null ) {
+ if ( ! gt.test ( gi, flags ) ) {
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+ }
+
+ /** NOT glyph tester */
+ private static class NotGlyphTester implements GlyphTester {
+ private GlyphTester gt;
+ NotGlyphTester ( GlyphTester gt ) {
+ this.gt = gt;
+ }
+ /** {@inheritDoc} */
+ public boolean test ( int gi, int flags ) {
+ if ( gt != null ) {
+ if ( gt.test ( gi, flags ) ) {
+ return false;
+ }
+ }
+ return true;
+ }
+ }
+
+}
diff --git a/src/java/org/apache/fop/complexscripts/fonts/GlyphSubstitution.java b/src/java/org/apache/fop/complexscripts/fonts/GlyphSubstitution.java
new file mode 100644
index 000000000..b8f9d02bf
--- /dev/null
+++ b/src/java/org/apache/fop/complexscripts/fonts/GlyphSubstitution.java
@@ -0,0 +1,41 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.complexscripts.fonts;
+
+// CSOFF: LineLengthCheck
+
+/**
+ * The <code>GlyphSubstitution</code> interface is implemented by a glyph substitution subtable
+ * that supports the determination of glyph substitution information based on script and
+ * language of the corresponding character content.
+ * @author Glenn Adams
+ */
+public interface GlyphSubstitution {
+
+ /**
+ * Perform glyph substitution at the current index, mutating the substitution state object as required.
+ * Only the context associated with the current index is processed.
+ * @param ss glyph substitution state object
+ * @return true if the glyph subtable was applied, meaning that the current context matches the
+ * associated input context glyph coverage table
+ */
+ boolean substitute ( GlyphSubstitutionState ss );
+
+}
diff --git a/src/java/org/apache/fop/complexscripts/fonts/GlyphSubstitutionState.java b/src/java/org/apache/fop/complexscripts/fonts/GlyphSubstitutionState.java
new file mode 100644
index 000000000..4af8c371e
--- /dev/null
+++ b/src/java/org/apache/fop/complexscripts/fonts/GlyphSubstitutionState.java
@@ -0,0 +1,230 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.complexscripts.fonts;
+
+import java.nio.IntBuffer;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.fop.complexscripts.util.GlyphSequence;
+import org.apache.fop.complexscripts.util.ScriptContextTester;
+
+// CSOFF: LineLengthCheck
+// CSOFF: NoWhitespaceAfterCheck
+
+/**
+ * The <code>GlyphSubstitutionState</code> implements an state object used during glyph substitution
+ * processing.
+ * @author Glenn Adams
+ */
+
+public class GlyphSubstitutionState extends GlyphProcessingState {
+
+ /** alternates index */
+ private int[] alternatesIndex;
+ /** current output glyph sequence */
+ private IntBuffer ogb;
+ /** current output glyph to character associations */
+ private List oal;
+ /** character association predications */
+ private boolean predications;
+
+ /**
+ * Construct glyph substitution state.
+ * @param gs input glyph sequence
+ * @param script script identifier
+ * @param language language identifier
+ * @param feature feature identifier
+ * @param sct script context tester (or null)
+ */
+ public GlyphSubstitutionState ( GlyphSequence gs, String script, String language, String feature, ScriptContextTester sct ) {
+ super ( gs, script, language, feature, sct );
+ this.ogb = IntBuffer.allocate ( gs.getGlyphCount() );
+ this.oal = new ArrayList ( gs.getGlyphCount() );
+ this.predications = gs.getPredications();
+ }
+
+ /**
+ * Construct glyph substitution state using an existing state object using shallow copy
+ * except as follows: input glyph sequence is copied deep except for its characters array.
+ * @param ss existing positioning state to copy from
+ */
+ public GlyphSubstitutionState ( GlyphSubstitutionState ss ) {
+ super ( ss );
+ this.ogb = IntBuffer.allocate ( indexLast );
+ this.oal = new ArrayList ( indexLast );
+ }
+
+ /**
+ * Set alternates indices.
+ * @param alternates array of alternates indices ordered by coverage index
+ */
+ public void setAlternates ( int[] alternates ) {
+ this.alternatesIndex = alternates;
+ }
+
+ /**
+ * Obtain alternates index associated with specified coverage index. An alternates
+ * index is used to select among stylistic alternates of a glyph at a particular
+ * coverage index. This information must be provided by the document itself (in the
+ * form of an extension attribute value), since a font has no way to determine which
+ * alternate the user desires.
+ * @param ci coverage index
+ * @return an alternates index
+ */
+ public int getAlternatesIndex ( int ci ) {
+ if ( alternatesIndex == null ) {
+ return 0;
+ } else if ( ( ci < 0 ) || ( ci > alternatesIndex.length ) ) {
+ return 0;
+ } else {
+ return alternatesIndex [ ci ];
+ }
+ }
+
+ /**
+ * 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, Object predication ) {
+ if ( ! ogb.hasRemaining() ) {
+ ogb = growBuffer ( ogb );
+ }
+ ogb.put ( glyph );
+ if ( predications && ( predication != null ) ) {
+ a.setPredication ( feature, predication );
+ }
+ oal.add ( a );
+ }
+
+ /**
+ * 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, 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 ], predication );
+ }
+ }
+
+ /**
+ * Obtain output glyph sequence.
+ * @return newly constructed glyph sequence comprised of original
+ * characters, output glyphs, and output associations
+ */
+ public GlyphSequence getOutput() {
+ int position = ogb.position();
+ if ( position > 0 ) {
+ ogb.limit ( position );
+ ogb.rewind();
+ return new GlyphSequence ( igs.getCharacters(), ogb, oal );
+ } else {
+ return igs;
+ }
+ }
+
+ /**
+ * Apply substitution subtable to current state at current position (only),
+ * resulting in the consumption of zero or more input glyphs, and possibly
+ * replacing the current input glyphs starting at the current position, in
+ * which case it is possible that indexLast is altered to be either less than
+ * or greater than its value prior to this application.
+ * @param st the glyph substitution subtable to apply
+ * @return true if subtable applied, or false if it did not (e.g., its
+ * input coverage table did not match current input context)
+ */
+ public boolean apply ( GlyphSubstitutionSubtable st ) {
+ assert st != null;
+ updateSubtableState ( st );
+ boolean applied = st.substitute ( this );
+ resetSubtableState();
+ return applied;
+ }
+
+ /**
+ * Apply a sequence of matched rule lookups to the <code>nig</code> input glyphs
+ * starting at the current position. If lookups are non-null and non-empty, then
+ * all input glyphs specified by <code>nig</code> are consumed irregardless of
+ * whether any specified lookup applied.
+ * @param lookups array of matched lookups (or null)
+ * @param nig number of glyphs in input sequence, starting at current position, to which
+ * the lookups are to apply, and to be consumed once the application has finished
+ * @return true if lookups are non-null and non-empty; otherwise, false
+ */
+ public boolean apply ( GlyphTable.RuleLookup[] lookups, int nig ) {
+ // int nbg = index;
+ int nlg = indexLast - ( index + nig );
+ int nog = 0;
+ if ( ( lookups != null ) && ( lookups.length > 0 ) ) {
+ // apply each rule lookup to extracted input glyph array
+ for ( int i = 0, n = lookups.length; i < n; i++ ) {
+ GlyphTable.RuleLookup l = lookups [ i ];
+ if ( l != null ) {
+ GlyphTable.LookupTable lt = l.getLookup();
+ if ( lt != null ) {
+ // perform substitution on a copy of previous state
+ GlyphSubstitutionState ss = new GlyphSubstitutionState ( this );
+ // apply lookup table substitutions
+ GlyphSequence gs = lt.substitute ( ss, l.getSequenceIndex() );
+ // replace current input sequence starting at current position with result
+ if ( replaceInput ( 0, -1, gs ) ) {
+ nog = gs.getGlyphCount() - nlg;
+ }
+ }
+ }
+ }
+ // output glyphs and associations
+ putGlyphs ( getGlyphs ( 0, nog, false, null, null, null ), getAssociations ( 0, nog, false, null, null, null ), null );
+ // consume replaced input glyphs
+ consume ( nog );
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Apply default application semantices; namely, consume one input glyph,
+ * writing that glyph (and its association) to the output glyphs (and associations).
+ */
+ public void applyDefault() {
+ super.applyDefault();
+ int gi = getGlyph();
+ if ( gi != 65535 ) {
+ putGlyph ( gi, getAssociation(), null );
+ }
+ }
+
+ private static IntBuffer growBuffer ( IntBuffer ib ) {
+ int capacity = ib.capacity();
+ int capacityNew = capacity * 2;
+ IntBuffer ibNew = IntBuffer.allocate ( capacityNew );
+ ib.rewind();
+ return ibNew.put ( ib );
+ }
+
+}
diff --git a/src/java/org/apache/fop/complexscripts/fonts/GlyphSubstitutionSubtable.java b/src/java/org/apache/fop/complexscripts/fonts/GlyphSubstitutionSubtable.java
new file mode 100644
index 000000000..d111b465f
--- /dev/null
+++ b/src/java/org/apache/fop/complexscripts/fonts/GlyphSubstitutionSubtable.java
@@ -0,0 +1,124 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.complexscripts.fonts;
+
+import org.apache.fop.complexscripts.util.GlyphSequence;
+import org.apache.fop.complexscripts.util.ScriptContextTester;
+
+// CSOFF: LineLengthCheck
+// CSOFF: NoWhitespaceAfterCheck
+
+/**
+ * The <code>GlyphSubstitutionSubtable</code> implements an abstract base of a glyph substitution subtable,
+ * providing a default implementation of the <code>GlyphSubstitution</code> interface.
+ * @author Glenn Adams
+ */
+public abstract class GlyphSubstitutionSubtable extends GlyphSubtable implements GlyphSubstitution {
+
+ /**
+ * Instantiate a <code>GlyphSubstitutionSubtable</code>.
+ * @param id subtable identifier
+ * @param sequence subtable sequence
+ * @param flags subtable flags
+ * @param format subtable format
+ * @param coverage subtable coverage table
+ */
+ protected GlyphSubstitutionSubtable ( String id, int sequence, int flags, int format, GlyphCoverageTable coverage ) {
+ super ( id, sequence, flags, format, coverage );
+ }
+
+ /** {@inheritDoc} */
+ public int getTableType() {
+ return GlyphTable.GLYPH_TABLE_TYPE_SUBSTITUTION;
+ }
+
+ /** {@inheritDoc} */
+ public String getTypeName() {
+ return GlyphSubstitutionTable.getLookupTypeName ( getType() );
+ }
+
+ /** {@inheritDoc} */
+ public boolean isCompatible ( GlyphSubtable subtable ) {
+ return subtable instanceof GlyphSubstitutionSubtable;
+ }
+
+ /** {@inheritDoc} */
+ public boolean usesReverseScan() {
+ return false;
+ }
+
+ /** {@inheritDoc} */
+ public boolean substitute ( GlyphSubstitutionState ss ) {
+ return false;
+ }
+
+ /**
+ * Apply substitutions using specified state and subtable array. For each position in input sequence,
+ * apply subtables in order until some subtable applies or none remain. If no subtable applied or no
+ * input was consumed for a given position, then apply default action (copy input glyph and advance).
+ * If <code>sequenceIndex</code> is non-negative, then apply subtables only when current position
+ * matches <code>sequenceIndex</code> in relation to the starting position. Furthermore, upon
+ * successful application at <code>sequenceIndex</code>, then apply default action for all remaining
+ * glyphs in input sequence.
+ * @param ss substitution state
+ * @param sta array of subtables to apply
+ * @param sequenceIndex if non negative, then apply subtables only at specified sequence index
+ * @return output glyph sequence
+ */
+ public static final GlyphSequence substitute ( GlyphSubstitutionState ss, GlyphSubstitutionSubtable[] sta, int sequenceIndex ) {
+ int sequenceStart = ss.getPosition();
+ boolean appliedOneShot = false;
+ while ( ss.hasNext() ) {
+ boolean applied = false;
+ if ( ! appliedOneShot && ss.maybeApplicable() ) {
+ for ( int i = 0, n = sta.length; ! applied && ( i < n ); i++ ) {
+ if ( sequenceIndex < 0 ) {
+ applied = ss.apply ( sta [ i ] );
+ } else if ( ss.getPosition() == ( sequenceStart + sequenceIndex ) ) {
+ applied = ss.apply ( sta [ i ] );
+ if ( applied ) {
+ appliedOneShot = true;
+ }
+ }
+ }
+ }
+ if ( ! applied || ! ss.didConsume() ) {
+ ss.applyDefault();
+ }
+ ss.next();
+ }
+ return ss.getOutput();
+ }
+
+ /**
+ * Apply substitutions.
+ * @param gs input glyph sequence
+ * @param script tag
+ * @param language tag
+ * @param feature tag
+ * @param sta subtable array
+ * @param sct script context tester
+ * @return output glyph sequence
+ */
+ public static final GlyphSequence substitute ( GlyphSequence gs, String script, String language, String feature, GlyphSubstitutionSubtable[] sta, ScriptContextTester sct ) {
+ return substitute ( new GlyphSubstitutionState ( gs, script, language, feature, sct ), sta, -1 );
+ }
+
+}
diff --git a/src/java/org/apache/fop/complexscripts/fonts/GlyphSubstitutionTable.java b/src/java/org/apache/fop/complexscripts/fonts/GlyphSubstitutionTable.java
new file mode 100644
index 000000000..1b724a63b
--- /dev/null
+++ b/src/java/org/apache/fop/complexscripts/fonts/GlyphSubstitutionTable.java
@@ -0,0 +1,1474 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.complexscripts.fonts;
+
+import java.nio.CharBuffer;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import org.apache.fop.complexscripts.scripts.ScriptProcessor;
+import org.apache.fop.complexscripts.util.GlyphSequence;
+import org.apache.fop.complexscripts.util.GlyphTester;
+
+// CSOFF: InnerAssignmentCheck
+// CSOFF: LineLengthCheck
+// CSOFF: NoWhitespaceAfterCheck
+
+/**
+ * The <code>GlyphSubstitutionTable</code> class is a glyph table that implements
+ * <code>GlyphSubstitution</code> functionality.
+ * @author Glenn Adams
+ */
+public class GlyphSubstitutionTable extends GlyphTable {
+
+ /** logging instance */
+ private static final Log log = LogFactory.getLog(GlyphSubstitutionTable.class); // CSOK: ConstantNameCheck
+
+ /** single substitution subtable type */
+ public static final int GSUB_LOOKUP_TYPE_SINGLE = 1;
+ /** multiple substitution subtable type */
+ public static final int GSUB_LOOKUP_TYPE_MULTIPLE = 2;
+ /** alternate substitution subtable type */
+ public static final int GSUB_LOOKUP_TYPE_ALTERNATE = 3;
+ /** ligature substitution subtable type */
+ public static final int GSUB_LOOKUP_TYPE_LIGATURE = 4;
+ /** contextual substitution subtable type */
+ public static final int GSUB_LOOKUP_TYPE_CONTEXTUAL = 5;
+ /** chained contextual substitution subtable type */
+ public static final int GSUB_LOOKUP_TYPE_CHAINED_CONTEXTUAL = 6;
+ /** extension substitution substitution subtable type */
+ public static final int GSUB_LOOKUP_TYPE_EXTENSION_SUBSTITUTION = 7;
+ /** reverse chained contextual single substitution subtable type */
+ public static final int GSUB_LOOKUP_TYPE_REVERSE_CHAINED_SINGLE = 8;
+
+ /**
+ * Instantiate a <code>GlyphSubstitutionTable</code> object using the specified lookups
+ * and subtables.
+ * @param gdef glyph definition table that applies
+ * @param lookups a map of lookup specifications to subtable identifier strings
+ * @param subtables a list of identified subtables
+ */
+ public GlyphSubstitutionTable ( GlyphDefinitionTable gdef, Map lookups, List subtables ) {
+ super ( gdef, lookups );
+ if ( ( subtables == null ) || ( subtables.size() == 0 ) ) {
+ throw new AdvancedTypographicTableFormatException ( "subtables must be non-empty" );
+ } else {
+ for ( Iterator it = subtables.iterator(); it.hasNext();) {
+ Object o = it.next();
+ if ( o instanceof GlyphSubstitutionSubtable ) {
+ addSubtable ( (GlyphSubtable) o );
+ } else {
+ throw new AdvancedTypographicTableFormatException ( "subtable must be a glyph substitution subtable" );
+ }
+ }
+ freezeSubtables();
+ }
+ }
+
+ /**
+ * Perform substitution processing using all matching lookups.
+ * @param gs an input glyph sequence
+ * @param script a script identifier
+ * @param language a language identifier
+ * @return the substituted (output) glyph sequence
+ */
+ public GlyphSequence substitute ( GlyphSequence gs, String script, String language ) {
+ GlyphSequence ogs;
+ Map/*<LookupSpec,List<LookupTable>>*/ lookups = matchLookups ( script, language, "*" );
+ if ( ( lookups != null ) && ( lookups.size() > 0 ) ) {
+ ScriptProcessor sp = ScriptProcessor.getInstance ( script );
+ ogs = sp.substitute ( this, gs, script, language, lookups );
+ } else {
+ ogs = gs;
+ }
+ return ogs;
+ }
+
+ /**
+ * Map a lookup type name to its constant (integer) value.
+ * @param name lookup type name
+ * @return lookup type
+ */
+ public static int getLookupTypeFromName ( String name ) {
+ int t;
+ String s = name.toLowerCase();
+ if ( "single".equals ( s ) ) {
+ t = GSUB_LOOKUP_TYPE_SINGLE;
+ } else if ( "multiple".equals ( s ) ) {
+ t = GSUB_LOOKUP_TYPE_MULTIPLE;
+ } else if ( "alternate".equals ( s ) ) {
+ t = GSUB_LOOKUP_TYPE_ALTERNATE;
+ } else if ( "ligature".equals ( s ) ) {
+ t = GSUB_LOOKUP_TYPE_LIGATURE;
+ } else if ( "contextual".equals ( s ) ) {
+ t = GSUB_LOOKUP_TYPE_CONTEXTUAL;
+ } else if ( "chainedcontextual".equals ( s ) ) {
+ t = GSUB_LOOKUP_TYPE_CHAINED_CONTEXTUAL;
+ } else if ( "extensionsubstitution".equals ( s ) ) {
+ t = GSUB_LOOKUP_TYPE_EXTENSION_SUBSTITUTION;
+ } else if ( "reversechainiingcontextualsingle".equals ( s ) ) {
+ t = GSUB_LOOKUP_TYPE_REVERSE_CHAINED_SINGLE;
+ } else {
+ t = -1;
+ }
+ return t;
+ }
+
+ /**
+ * Map a lookup type constant (integer) value to its name.
+ * @param type lookup type
+ * @return lookup type name
+ */
+ public static String getLookupTypeName ( int type ) {
+ String tn = null;
+ switch ( type ) {
+ case GSUB_LOOKUP_TYPE_SINGLE:
+ tn = "single";
+ break;
+ case GSUB_LOOKUP_TYPE_MULTIPLE:
+ tn = "multiple";
+ break;
+ case GSUB_LOOKUP_TYPE_ALTERNATE:
+ tn = "alternate";
+ break;
+ case GSUB_LOOKUP_TYPE_LIGATURE:
+ tn = "ligature";
+ break;
+ case GSUB_LOOKUP_TYPE_CONTEXTUAL:
+ tn = "contextual";
+ break;
+ case GSUB_LOOKUP_TYPE_CHAINED_CONTEXTUAL:
+ tn = "chainedcontextual";
+ break;
+ case GSUB_LOOKUP_TYPE_EXTENSION_SUBSTITUTION:
+ tn = "extensionsubstitution";
+ break;
+ case GSUB_LOOKUP_TYPE_REVERSE_CHAINED_SINGLE:
+ tn = "reversechainiingcontextualsingle";
+ break;
+ default:
+ tn = "unknown";
+ break;
+ }
+ return tn;
+ }
+
+ /**
+ * Create a substitution subtable according to the specified arguments.
+ * @param type subtable type
+ * @param id subtable identifier
+ * @param sequence subtable sequence
+ * @param flags subtable flags
+ * @param format subtable format
+ * @param coverage subtable coverage table
+ * @param entries subtable entries
+ * @return a glyph subtable instance
+ */
+ public static GlyphSubtable createSubtable ( int type, String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries ) {
+ GlyphSubtable st = null;
+ switch ( type ) {
+ case GSUB_LOOKUP_TYPE_SINGLE:
+ st = SingleSubtable.create ( id, sequence, flags, format, coverage, entries );
+ break;
+ case GSUB_LOOKUP_TYPE_MULTIPLE:
+ st = MultipleSubtable.create ( id, sequence, flags, format, coverage, entries );
+ break;
+ case GSUB_LOOKUP_TYPE_ALTERNATE:
+ st = AlternateSubtable.create ( id, sequence, flags, format, coverage, entries );
+ break;
+ case GSUB_LOOKUP_TYPE_LIGATURE:
+ st = LigatureSubtable.create ( id, sequence, flags, format, coverage, entries );
+ break;
+ case GSUB_LOOKUP_TYPE_CONTEXTUAL:
+ st = ContextualSubtable.create ( id, sequence, flags, format, coverage, entries );
+ break;
+ case GSUB_LOOKUP_TYPE_CHAINED_CONTEXTUAL:
+ st = ChainedContextualSubtable.create ( id, sequence, flags, format, coverage, entries );
+ break;
+ case GSUB_LOOKUP_TYPE_REVERSE_CHAINED_SINGLE:
+ st = ReverseChainedSingleSubtable.create ( id, sequence, flags, format, coverage, entries );
+ break;
+ default:
+ break;
+ }
+ return st;
+ }
+
+ /**
+ * Create a substitution subtable according to the specified arguments.
+ * @param type subtable type
+ * @param id subtable identifier
+ * @param sequence subtable sequence
+ * @param flags subtable flags
+ * @param format subtable format
+ * @param coverage list of coverage table entries
+ * @param entries subtable entries
+ * @return a glyph subtable instance
+ */
+ public static GlyphSubtable createSubtable ( int type, String id, int sequence, int flags, int format, List coverage, List entries ) {
+ return createSubtable ( type, id, sequence, flags, format, GlyphCoverageTable.createCoverageTable ( coverage ), entries );
+ }
+
+ private abstract static class SingleSubtable extends GlyphSubstitutionSubtable {
+ SingleSubtable ( String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries ) {
+ super ( id, sequence, flags, format, coverage );
+ }
+ /** {@inheritDoc} */
+ public int getType() {
+ return GSUB_LOOKUP_TYPE_SINGLE;
+ }
+ /** {@inheritDoc} */
+ public boolean isCompatible ( GlyphSubtable subtable ) {
+ return subtable instanceof SingleSubtable;
+ }
+ /** {@inheritDoc} */
+ public boolean substitute ( GlyphSubstitutionState ss ) {
+ int gi = ss.getGlyph(), ci;
+ if ( ( ci = getCoverageIndex ( gi ) ) < 0 ) {
+ return false;
+ } else {
+ int go = getGlyphForCoverageIndex ( ci, gi );
+ if ( ( go < 0 ) || ( go > 65535 ) ) {
+ go = 65535;
+ }
+ ss.putGlyph ( go, ss.getAssociation(), Boolean.TRUE );
+ ss.consume(1);
+ return true;
+ }
+ }
+ /**
+ * Obtain glyph for coverage index.
+ * @param ci coverage index
+ * @param gi original glyph index
+ * @return substituted glyph value
+ * @throws IllegalArgumentException if coverage index is not valid
+ */
+ public abstract int getGlyphForCoverageIndex ( int ci, int gi ) throws IllegalArgumentException;
+ static GlyphSubstitutionSubtable create ( String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries ) {
+ if ( format == 1 ) {
+ return new SingleSubtableFormat1 ( id, sequence, flags, format, coverage, entries );
+ } else if ( format == 2 ) {
+ return new SingleSubtableFormat2 ( id, sequence, flags, format, coverage, entries );
+ } else {
+ throw new UnsupportedOperationException();
+ }
+ }
+ }
+
+ private static class SingleSubtableFormat1 extends SingleSubtable {
+ private int delta;
+ private int ciMax;
+ SingleSubtableFormat1 ( String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries ) {
+ super ( id, sequence, flags, format, coverage, entries );
+ populate ( entries );
+ }
+ /** {@inheritDoc} */
+ public List getEntries() {
+ List entries = new ArrayList ( 1 );
+ entries.add ( Integer.valueOf ( delta ) );
+ return entries;
+ }
+ /** {@inheritDoc} */
+ public int getGlyphForCoverageIndex ( int ci, int gi ) throws IllegalArgumentException {
+ if ( ci <= ciMax ) {
+ return gi + delta;
+ } else {
+ throw new IllegalArgumentException ( "coverage index " + ci + " out of range, maximum coverage index is " + ciMax );
+ }
+ }
+ private void populate ( List entries ) {
+ if ( ( entries == null ) || ( entries.size() != 1 ) ) {
+ throw new AdvancedTypographicTableFormatException ( "illegal entries, must be non-null and contain exactly one entry" );
+ } else {
+ Object o = entries.get(0);
+ int delta = 0;
+ if ( o instanceof Integer ) {
+ delta = ( (Integer) o ) . intValue();
+ } else {
+ throw new AdvancedTypographicTableFormatException ( "illegal entries entry, must be Integer, but is: " + o );
+ }
+ this.delta = delta;
+ this.ciMax = getCoverageSize() - 1;
+ }
+ }
+ }
+
+ private static class SingleSubtableFormat2 extends SingleSubtable {
+ private int[] glyphs;
+ SingleSubtableFormat2 ( String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries ) {
+ super ( id, sequence, flags, format, coverage, entries );
+ populate ( entries );
+ }
+ /** {@inheritDoc} */
+ public List getEntries() {
+ List entries = new ArrayList ( glyphs.length );
+ for ( int i = 0, n = glyphs.length; i < n; i++ ) {
+ entries.add ( Integer.valueOf ( glyphs[i] ) );
+ }
+ return entries;
+ }
+ /** {@inheritDoc} */
+ public int getGlyphForCoverageIndex ( int ci, int gi ) throws IllegalArgumentException {
+ if ( glyphs == null ) {
+ return -1;
+ } else if ( ci >= glyphs.length ) {
+ throw new IllegalArgumentException ( "coverage index " + ci + " out of range, maximum coverage index is " + glyphs.length );
+ } else {
+ return glyphs [ ci ];
+ }
+ }
+ private void populate ( List entries ) {
+ int i = 0, n = entries.size();
+ int[] glyphs = new int [ n ];
+ for ( Iterator it = entries.iterator(); it.hasNext();) {
+ Object o = it.next();
+ if ( o instanceof Integer ) {
+ int gid = ( (Integer) o ) .intValue();
+ if ( ( gid >= 0 ) && ( gid < 65536 ) ) {
+ glyphs [ i++ ] = gid;
+ } else {
+ throw new AdvancedTypographicTableFormatException ( "illegal glyph index: " + gid );
+ }
+ } else {
+ throw new AdvancedTypographicTableFormatException ( "illegal entries entry, must be Integer: " + o );
+ }
+ }
+ assert i == n;
+ assert this.glyphs == null;
+ this.glyphs = glyphs;
+ }
+ }
+
+ private abstract static class MultipleSubtable extends GlyphSubstitutionSubtable {
+ public MultipleSubtable ( String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries ) {
+ super ( id, sequence, flags, format, coverage );
+ }
+ /** {@inheritDoc} */
+ public int getType() {
+ return GSUB_LOOKUP_TYPE_MULTIPLE;
+ }
+ /** {@inheritDoc} */
+ public boolean isCompatible ( GlyphSubtable subtable ) {
+ return subtable instanceof MultipleSubtable;
+ }
+ /** {@inheritDoc} */
+ public boolean substitute ( GlyphSubstitutionState ss ) {
+ int gi = ss.getGlyph(), ci;
+ if ( ( ci = getCoverageIndex ( gi ) ) < 0 ) {
+ return false;
+ } else {
+ int[] ga = getGlyphsForCoverageIndex ( ci, gi );
+ if ( ga != null ) {
+ ss.putGlyphs ( ga, GlyphSequence.CharAssociation.replicate ( ss.getAssociation(), ga.length ), Boolean.TRUE );
+ ss.consume(1);
+ }
+ return true;
+ }
+ }
+ /**
+ * Obtain glyph sequence for coverage index.
+ * @param ci coverage index
+ * @param gi original glyph index
+ * @return sequence of glyphs to substitute for input glyph
+ * @throws IllegalArgumentException if coverage index is not valid
+ */
+ public abstract int[] getGlyphsForCoverageIndex ( int ci, int gi ) throws IllegalArgumentException;
+ static GlyphSubstitutionSubtable create ( String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries ) {
+ if ( format == 1 ) {
+ return new MultipleSubtableFormat1 ( id, sequence, flags, format, coverage, entries );
+ } else {
+ throw new UnsupportedOperationException();
+ }
+ }
+ }
+
+ private static class MultipleSubtableFormat1 extends MultipleSubtable {
+ private int[][] gsa; // glyph sequence array, ordered by coverage index
+ MultipleSubtableFormat1 ( String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries ) {
+ super ( id, sequence, flags, format, coverage, entries );
+ populate ( entries );
+ }
+ /** {@inheritDoc} */
+ public List getEntries() {
+ if ( gsa != null ) {
+ List entries = new ArrayList ( 1 );
+ entries.add ( gsa );
+ return entries;
+ } else {
+ return null;
+ }
+ }
+ /** {@inheritDoc} */
+ public int[] getGlyphsForCoverageIndex ( int ci, int gi ) throws IllegalArgumentException {
+ if ( gsa == null ) {
+ return null;
+ } else if ( ci >= gsa.length ) {
+ throw new IllegalArgumentException ( "coverage index " + ci + " out of range, maximum coverage index is " + gsa.length );
+ } else {
+ return gsa [ ci ];
+ }
+ }
+ private void populate ( List entries ) {
+ if ( entries == null ) {
+ throw new AdvancedTypographicTableFormatException ( "illegal entries, must be non-null" );
+ } else if ( entries.size() != 1 ) {
+ throw new AdvancedTypographicTableFormatException ( "illegal entries, " + entries.size() + " entries present, but requires 1 entry" );
+ } else {
+ Object o;
+ if ( ( ( o = entries.get(0) ) == null ) || ! ( o instanceof int[][] ) ) {
+ throw new AdvancedTypographicTableFormatException ( "illegal entries, first entry must be an int[][], but is: " + ( ( o != null ) ? o.getClass() : null ) );
+ } else {
+ gsa = (int[][]) o;
+ }
+ }
+ }
+ }
+
+ private abstract static class AlternateSubtable extends GlyphSubstitutionSubtable {
+ public AlternateSubtable ( String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries ) {
+ super ( id, sequence, flags, format, coverage );
+ }
+ /** {@inheritDoc} */
+ public int getType() {
+ return GSUB_LOOKUP_TYPE_ALTERNATE;
+ }
+ /** {@inheritDoc} */
+ public boolean isCompatible ( GlyphSubtable subtable ) {
+ return subtable instanceof AlternateSubtable;
+ }
+ /** {@inheritDoc} */
+ public boolean substitute ( GlyphSubstitutionState ss ) {
+ int gi = ss.getGlyph(), ci;
+ if ( ( ci = getCoverageIndex ( gi ) ) < 0 ) {
+ return false;
+ } else {
+ int[] ga = getAlternatesForCoverageIndex ( ci, gi );
+ int ai = ss.getAlternatesIndex ( ci );
+ int go;
+ if ( ( ai < 0 ) || ( ai >= ga.length ) ) {
+ go = gi;
+ } else {
+ go = ga [ ai ];
+ }
+ if ( ( go < 0 ) || ( go > 65535 ) ) {
+ go = 65535;
+ }
+ ss.putGlyph ( go, ss.getAssociation(), Boolean.TRUE );
+ ss.consume(1);
+ return true;
+ }
+ }
+ /**
+ * Obtain glyph alternates for coverage index.
+ * @param ci coverage index
+ * @param gi original glyph index
+ * @return sequence of glyphs to substitute for input glyph
+ * @throws IllegalArgumentException if coverage index is not valid
+ */
+ public abstract int[] getAlternatesForCoverageIndex ( int ci, int gi ) throws IllegalArgumentException;
+ static GlyphSubstitutionSubtable create ( String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries ) {
+ if ( format == 1 ) {
+ return new AlternateSubtableFormat1 ( id, sequence, flags, format, coverage, entries );
+ } else {
+ throw new UnsupportedOperationException();
+ }
+ }
+ }
+
+ private static class AlternateSubtableFormat1 extends AlternateSubtable {
+ private int[][] gaa; // glyph alternates array, ordered by coverage index
+ AlternateSubtableFormat1 ( String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries ) {
+ super ( id, sequence, flags, format, coverage, entries );
+ populate ( entries );
+ }
+ /** {@inheritDoc} */
+ public List getEntries() {
+ List entries = new ArrayList ( gaa.length );
+ for ( int i = 0, n = gaa.length; i < n; i++ ) {
+ entries.add ( gaa[i] );
+ }
+ return entries;
+ }
+ /** {@inheritDoc} */
+ public int[] getAlternatesForCoverageIndex ( int ci, int gi ) throws IllegalArgumentException {
+ if ( gaa == null ) {
+ return null;
+ } else if ( ci >= gaa.length ) {
+ throw new IllegalArgumentException ( "coverage index " + ci + " out of range, maximum coverage index is " + gaa.length );
+ } else {
+ return gaa [ ci ];
+ }
+ }
+ private void populate ( List entries ) {
+ int i = 0, n = entries.size();
+ int[][] gaa = new int [ n ][];
+ for ( Iterator it = entries.iterator(); it.hasNext();) {
+ Object o = it.next();
+ if ( o instanceof int[] ) {
+ gaa [ i++ ] = (int[]) o;
+ } else {
+ throw new AdvancedTypographicTableFormatException ( "illegal entries entry, must be int[]: " + o );
+ }
+ }
+ assert i == n;
+ assert this.gaa == null;
+ this.gaa = gaa;
+ }
+ }
+
+ private abstract static class LigatureSubtable extends GlyphSubstitutionSubtable {
+ public LigatureSubtable ( String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries ) {
+ super ( id, sequence, flags, format, coverage );
+ }
+ /** {@inheritDoc} */
+ public int getType() {
+ return GSUB_LOOKUP_TYPE_LIGATURE;
+ }
+ /** {@inheritDoc} */
+ public boolean isCompatible ( GlyphSubtable subtable ) {
+ return subtable instanceof LigatureSubtable;
+ }
+ /** {@inheritDoc} */
+ public boolean substitute ( GlyphSubstitutionState ss ) {
+ int gi = ss.getGlyph(), ci;
+ if ( ( ci = getCoverageIndex ( gi ) ) < 0 ) {
+ return false;
+ } else {
+ LigatureSet ls = getLigatureSetForCoverageIndex ( ci, gi );
+ if ( ls != null ) {
+ boolean reverse = false;
+ GlyphTester ignores = ss.getIgnoreDefault();
+ int[] counts = ss.getGlyphsAvailable ( 0, reverse, ignores );
+ int nga = counts[0], ngi;
+ if ( nga > 1 ) {
+ int[] iga = ss.getGlyphs ( 0, nga, reverse, ignores, null, counts );
+ Ligature l = findLigature ( ls, iga );
+ if ( l != null ) {
+ int go = l.getLigature();
+ if ( ( go < 0 ) || ( go > 65535 ) ) {
+ go = 65535;
+ }
+ int nmg = 1 + l.getNumComponents();
+ // fetch matched number of component glyphs to determine matched and ignored count
+ ss.getGlyphs ( 0, nmg, reverse, ignores, null, counts );
+ nga = counts[0];
+ ngi = counts[1];
+ // 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 ), Boolean.TRUE );
+ // fetch and output ignored glyphs (if necessary)
+ if ( ngi > 0 ) {
+ ss.putGlyphs ( ss.getIgnoredGlyphs ( 0, ngi ), ss.getIgnoredAssociations ( 0, ngi ), null );
+ }
+ ss.consume ( nga + ngi );
+ }
+ }
+ }
+ return true;
+ }
+ }
+ private Ligature findLigature ( LigatureSet ls, int[] glyphs ) {
+ Ligature[] la = ls.getLigatures();
+ int k = -1;
+ int maxComponents = -1;
+ for ( int i = 0, n = la.length; i < n; i++ ) {
+ Ligature l = la [ i ];
+ if ( l.matchesComponents ( glyphs ) ) {
+ int nc = l.getNumComponents();
+ if ( nc > maxComponents ) {
+ maxComponents = nc;
+ k = i;
+ }
+ }
+ }
+ if ( k >= 0 ) {
+ return la [ k ];
+ } else {
+ return null;
+ }
+ }
+ /**
+ * Obtain ligature set for coverage index.
+ * @param ci coverage index
+ * @param gi original glyph index
+ * @return ligature set (or null if none defined)
+ * @throws IllegalArgumentException if coverage index is not valid
+ */
+ public abstract LigatureSet getLigatureSetForCoverageIndex ( int ci, int gi ) throws IllegalArgumentException;
+ static GlyphSubstitutionSubtable create ( String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries ) {
+ if ( format == 1 ) {
+ return new LigatureSubtableFormat1 ( id, sequence, flags, format, coverage, entries );
+ } else {
+ throw new UnsupportedOperationException();
+ }
+ }
+ }
+
+ private static class LigatureSubtableFormat1 extends LigatureSubtable {
+ private LigatureSet[] ligatureSets;
+ public LigatureSubtableFormat1 ( String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries ) {
+ super ( id, sequence, flags, format, coverage, entries );
+ populate ( entries );
+ }
+ /** {@inheritDoc} */
+ public List getEntries() {
+ List entries = new ArrayList ( ligatureSets.length );
+ for ( int i = 0, n = ligatureSets.length; i < n; i++ ) {
+ entries.add ( ligatureSets[i] );
+ }
+ return entries;
+ }
+ /** {@inheritDoc} */
+ public LigatureSet getLigatureSetForCoverageIndex ( int ci, int gi ) throws IllegalArgumentException {
+ if ( ligatureSets == null ) {
+ return null;
+ } else if ( ci >= ligatureSets.length ) {
+ throw new IllegalArgumentException ( "coverage index " + ci + " out of range, maximum coverage index is " + ligatureSets.length );
+ } else {
+ return ligatureSets [ ci ];
+ }
+ }
+ private void populate ( List entries ) {
+ int i = 0, n = entries.size();
+ LigatureSet[] ligatureSets = new LigatureSet [ n ];
+ for ( Iterator it = entries.iterator(); it.hasNext();) {
+ Object o = it.next();
+ if ( o instanceof LigatureSet ) {
+ ligatureSets [ i++ ] = (LigatureSet) o;
+ } else {
+ throw new AdvancedTypographicTableFormatException ( "illegal ligatures entry, must be LigatureSet: " + o );
+ }
+ }
+ assert i == n;
+ assert this.ligatureSets == null;
+ this.ligatureSets = ligatureSets;
+ }
+ }
+
+ private abstract static class ContextualSubtable extends GlyphSubstitutionSubtable {
+ public ContextualSubtable ( String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries ) {
+ super ( id, sequence, flags, format, coverage );
+ }
+ /** {@inheritDoc} */
+ public int getType() {
+ return GSUB_LOOKUP_TYPE_CONTEXTUAL;
+ }
+ /** {@inheritDoc} */
+ public boolean isCompatible ( GlyphSubtable subtable ) {
+ return subtable instanceof ContextualSubtable;
+ }
+ /** {@inheritDoc} */
+ public boolean substitute ( GlyphSubstitutionState ss ) {
+ int gi = ss.getGlyph(), ci;
+ if ( ( ci = getCoverageIndex ( gi ) ) < 0 ) {
+ return false;
+ } else {
+ int[] rv = new int[1];
+ RuleLookup[] la = getLookups ( ci, gi, ss, rv );
+ if ( la != null ) {
+ ss.apply ( la, rv[0] );
+ }
+ return true;
+ }
+ }
+ /**
+ * Obtain rule lookups set associated current input glyph context.
+ * @param ci coverage index of glyph at current position
+ * @param gi glyph index of glyph at current position
+ * @param ss glyph substitution state
+ * @param rv array of ints used to receive multiple return values, must be of length 1 or greater,
+ * where the first entry is used to return the input sequence length of the matched rule
+ * @return array of rule lookups or null if none applies
+ */
+ public abstract RuleLookup[] getLookups ( int ci, int gi, GlyphSubstitutionState ss, int[] rv );
+ static GlyphSubstitutionSubtable create ( String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries ) {
+ if ( format == 1 ) {
+ return new ContextualSubtableFormat1 ( id, sequence, flags, format, coverage, entries );
+ } else if ( format == 2 ) {
+ return new ContextualSubtableFormat2 ( id, sequence, flags, format, coverage, entries );
+ } else if ( format == 3 ) {
+ return new ContextualSubtableFormat3 ( id, sequence, flags, format, coverage, entries );
+ } else {
+ throw new UnsupportedOperationException();
+ }
+ }
+ }
+
+ private static class ContextualSubtableFormat1 extends ContextualSubtable {
+ private RuleSet[] rsa; // rule set array, ordered by glyph coverage index
+ ContextualSubtableFormat1 ( String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries ) {
+ super ( id, sequence, flags, format, coverage, entries );
+ populate ( entries );
+ }
+ /** {@inheritDoc} */
+ public List getEntries() {
+ if ( rsa != null ) {
+ List entries = new ArrayList ( 1 );
+ entries.add ( rsa );
+ return entries;
+ } else {
+ return null;
+ }
+ }
+ /** {@inheritDoc} */
+ public void resolveLookupReferences ( Map/*<String,LookupTable>*/ lookupTables ) {
+ GlyphTable.resolveLookupReferences ( rsa, lookupTables );
+ }
+ /** {@inheritDoc} */
+ public RuleLookup[] getLookups ( int ci, int gi, GlyphSubstitutionState ss, int[] rv ) {
+ assert ss != null;
+ assert ( rv != null ) && ( rv.length > 0 );
+ assert rsa != null;
+ if ( rsa.length > 0 ) {
+ RuleSet rs = rsa [ 0 ];
+ if ( rs != null ) {
+ Rule[] ra = rs.getRules();
+ for ( int i = 0, n = ra.length; i < n; i++ ) {
+ Rule r = ra [ i ];
+ if ( ( r != null ) && ( r instanceof ChainedGlyphSequenceRule ) ) {
+ ChainedGlyphSequenceRule cr = (ChainedGlyphSequenceRule) r;
+ int[] iga = cr.getGlyphs ( gi );
+ if ( matches ( ss, iga, 0, rv ) ) {
+ return r.getLookups();
+ }
+ }
+ }
+ }
+ }
+ return null;
+ }
+ static boolean matches ( GlyphSubstitutionState ss, int[] glyphs, int offset, int[] rv ) {
+ if ( ( glyphs == null ) || ( glyphs.length == 0 ) ) {
+ return true; // match null or empty glyph sequence
+ } else {
+ boolean reverse = offset < 0;
+ GlyphTester ignores = ss.getIgnoreDefault();
+ int[] counts = ss.getGlyphsAvailable ( offset, reverse, ignores );
+ int nga = counts[0];
+ int ngm = glyphs.length;
+ if ( nga < ngm ) {
+ return false; // insufficient glyphs available to match
+ } else {
+ int[] ga = ss.getGlyphs ( offset, ngm, reverse, ignores, null, counts );
+ for ( int k = 0; k < ngm; k++ ) {
+ if ( ga [ k ] != glyphs [ k ] ) {
+ return false; // match fails at ga [ k ]
+ }
+ }
+ if ( rv != null ) {
+ rv[0] = counts[0] + counts[1];
+ }
+ return true; // all glyphs match
+ }
+ }
+ }
+ private void populate ( List entries ) {
+ if ( entries == null ) {
+ throw new AdvancedTypographicTableFormatException ( "illegal entries, must be non-null" );
+ } else if ( entries.size() != 1 ) {
+ throw new AdvancedTypographicTableFormatException ( "illegal entries, " + entries.size() + " entries present, but requires 1 entry" );
+ } else {
+ Object o;
+ if ( ( ( o = entries.get(0) ) == null ) || ! ( o instanceof RuleSet[] ) ) {
+ throw new AdvancedTypographicTableFormatException ( "illegal entries, first entry must be an RuleSet[], but is: " + ( ( o != null ) ? o.getClass() : null ) );
+ } else {
+ rsa = (RuleSet[]) o;
+ }
+ }
+ }
+ }
+
+ private static class ContextualSubtableFormat2 extends ContextualSubtable {
+ private GlyphClassTable cdt; // class def table
+ private int ngc; // class set count
+ private RuleSet[] rsa; // rule set array, ordered by class number [0...ngc - 1]
+ ContextualSubtableFormat2 ( String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries ) {
+ super ( id, sequence, flags, format, coverage, entries );
+ populate ( entries );
+ }
+ /** {@inheritDoc} */
+ public List getEntries() {
+ if ( rsa != null ) {
+ List entries = new ArrayList ( 3 );
+ entries.add ( cdt );
+ entries.add ( Integer.valueOf ( ngc ) );
+ entries.add ( rsa );
+ return entries;
+ } else {
+ return null;
+ }
+ }
+ /** {@inheritDoc} */
+ public void resolveLookupReferences ( Map/*<String,LookupTable>*/ lookupTables ) {
+ GlyphTable.resolveLookupReferences ( rsa, lookupTables );
+ }
+ /** {@inheritDoc} */
+ public RuleLookup[] getLookups ( int ci, int gi, GlyphSubstitutionState ss, int[] rv ) {
+ assert ss != null;
+ assert ( rv != null ) && ( rv.length > 0 );
+ assert rsa != null;
+ if ( rsa.length > 0 ) {
+ RuleSet rs = rsa [ 0 ];
+ if ( rs != null ) {
+ Rule[] ra = rs.getRules();
+ for ( int i = 0, n = ra.length; i < n; i++ ) {
+ Rule r = ra [ i ];
+ if ( ( r != null ) && ( r instanceof ChainedClassSequenceRule ) ) {
+ ChainedClassSequenceRule cr = (ChainedClassSequenceRule) r;
+ int[] ca = cr.getClasses ( cdt.getClassIndex ( gi, ss.getClassMatchSet ( gi ) ) );
+ if ( matches ( ss, cdt, ca, 0, rv ) ) {
+ return r.getLookups();
+ }
+ }
+ }
+ }
+ }
+ return null;
+ }
+ static boolean matches ( GlyphSubstitutionState ss, GlyphClassTable cdt, int[] classes, int offset, int[] rv ) {
+ if ( ( cdt == null ) || ( classes == null ) || ( classes.length == 0 ) ) {
+ return true; // match null class definitions, null or empty class sequence
+ } else {
+ boolean reverse = offset < 0;
+ GlyphTester ignores = ss.getIgnoreDefault();
+ int[] counts = ss.getGlyphsAvailable ( offset, reverse, ignores );
+ int nga = counts[0];
+ int ngm = classes.length;
+ if ( nga < ngm ) {
+ return false; // insufficient glyphs available to match
+ } else {
+ int[] ga = ss.getGlyphs ( offset, ngm, reverse, ignores, null, counts );
+ for ( int k = 0; k < ngm; k++ ) {
+ int gi = ga [ k ];
+ int ms = ss.getClassMatchSet ( gi );
+ int gc = cdt.getClassIndex ( gi, ms );
+ if ( ( gc < 0 ) || ( gc >= cdt.getClassSize ( ms ) ) ) {
+ return false; // none or invalid class fails mat ch
+ } else if ( gc != classes [ k ] ) {
+ return false; // match fails at ga [ k ]
+ }
+ }
+ if ( rv != null ) {
+ rv[0] = counts[0] + counts[1];
+ }
+ return true; // all glyphs match
+ }
+ }
+ }
+ private void populate ( List entries ) {
+ if ( entries == null ) {
+ throw new AdvancedTypographicTableFormatException ( "illegal entries, must be non-null" );
+ } else if ( entries.size() != 3 ) {
+ throw new AdvancedTypographicTableFormatException ( "illegal entries, " + entries.size() + " entries present, but requires 3 entries" );
+ } else {
+ Object o;
+ if ( ( ( o = entries.get(0) ) == null ) || ! ( o instanceof GlyphClassTable ) ) {
+ throw new AdvancedTypographicTableFormatException ( "illegal entries, first entry must be an GlyphClassTable, but is: " + ( ( o != null ) ? o.getClass() : null ) );
+ } else {
+ cdt = (GlyphClassTable) o;
+ }
+ if ( ( ( o = entries.get(1) ) == null ) || ! ( o instanceof Integer ) ) {
+ throw new AdvancedTypographicTableFormatException ( "illegal entries, second entry must be an Integer, but is: " + ( ( o != null ) ? o.getClass() : null ) );
+ } else {
+ ngc = ((Integer)(o)).intValue();
+ }
+ if ( ( ( o = entries.get(2) ) == null ) || ! ( o instanceof RuleSet[] ) ) {
+ throw new AdvancedTypographicTableFormatException ( "illegal entries, third entry must be an RuleSet[], but is: " + ( ( o != null ) ? o.getClass() : null ) );
+ } else {
+ rsa = (RuleSet[]) o;
+ if ( rsa.length != ngc ) {
+ throw new AdvancedTypographicTableFormatException ( "illegal entries, RuleSet[] length is " + rsa.length + ", but expected " + ngc + " glyph classes" );
+ }
+ }
+ }
+ }
+ }
+
+ private static class ContextualSubtableFormat3 extends ContextualSubtable {
+ private RuleSet[] rsa; // rule set array, containing a single rule set
+ ContextualSubtableFormat3 ( String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries ) {
+ super ( id, sequence, flags, format, coverage, entries );
+ populate ( entries );
+ }
+ /** {@inheritDoc} */
+ public List getEntries() {
+ if ( rsa != null ) {
+ List entries = new ArrayList ( 1 );
+ entries.add ( rsa );
+ return entries;
+ } else {
+ return null;
+ }
+ }
+ /** {@inheritDoc} */
+ public void resolveLookupReferences ( Map/*<String,LookupTable>*/ lookupTables ) {
+ GlyphTable.resolveLookupReferences ( rsa, lookupTables );
+ }
+ /** {@inheritDoc} */
+ public RuleLookup[] getLookups ( int ci, int gi, GlyphSubstitutionState ss, int[] rv ) {
+ assert ss != null;
+ assert ( rv != null ) && ( rv.length > 0 );
+ assert rsa != null;
+ if ( rsa.length > 0 ) {
+ RuleSet rs = rsa [ 0 ];
+ if ( rs != null ) {
+ Rule[] ra = rs.getRules();
+ for ( int i = 0, n = ra.length; i < n; i++ ) {
+ Rule r = ra [ i ];
+ if ( ( r != null ) && ( r instanceof ChainedCoverageSequenceRule ) ) {
+ ChainedCoverageSequenceRule cr = (ChainedCoverageSequenceRule) r;
+ GlyphCoverageTable[] gca = cr.getCoverages();
+ if ( matches ( ss, gca, 0, rv ) ) {
+ return r.getLookups();
+ }
+ }
+ }
+ }
+ }
+ return null;
+ }
+ static boolean matches ( GlyphSubstitutionState ss, GlyphCoverageTable[] gca, int offset, int[] rv ) {
+ if ( ( gca == null ) || ( gca.length == 0 ) ) {
+ return true; // match null or empty coverage array
+ } else {
+ boolean reverse = offset < 0;
+ GlyphTester ignores = ss.getIgnoreDefault();
+ int[] counts = ss.getGlyphsAvailable ( offset, reverse, ignores );
+ int nga = counts[0];
+ int ngm = gca.length;
+ if ( nga < ngm ) {
+ return false; // insufficient glyphs available to match
+ } else {
+ int[] ga = ss.getGlyphs ( offset, ngm, reverse, ignores, null, counts );
+ for ( int k = 0; k < ngm; k++ ) {
+ GlyphCoverageTable ct = gca [ k ];
+ if ( ct != null ) {
+ if ( ct.getCoverageIndex ( ga [ k ] ) < 0 ) {
+ return false; // match fails at ga [ k ]
+ }
+ }
+ }
+ if ( rv != null ) {
+ rv[0] = counts[0] + counts[1];
+ }
+ return true; // all glyphs match
+ }
+ }
+ }
+ private void populate ( List entries ) {
+ if ( entries == null ) {
+ throw new AdvancedTypographicTableFormatException ( "illegal entries, must be non-null" );
+ } else if ( entries.size() != 1 ) {
+ throw new AdvancedTypographicTableFormatException ( "illegal entries, " + entries.size() + " entries present, but requires 1 entry" );
+ } else {
+ Object o;
+ if ( ( ( o = entries.get(0) ) == null ) || ! ( o instanceof RuleSet[] ) ) {
+ throw new AdvancedTypographicTableFormatException ( "illegal entries, first entry must be an RuleSet[], but is: " + ( ( o != null ) ? o.getClass() : null ) );
+ } else {
+ rsa = (RuleSet[]) o;
+ }
+ }
+ }
+ }
+
+ private abstract static class ChainedContextualSubtable extends GlyphSubstitutionSubtable {
+ public ChainedContextualSubtable ( String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries ) {
+ super ( id, sequence, flags, format, coverage );
+ }
+ /** {@inheritDoc} */
+ public int getType() {
+ return GSUB_LOOKUP_TYPE_CHAINED_CONTEXTUAL;
+ }
+ /** {@inheritDoc} */
+ public boolean isCompatible ( GlyphSubtable subtable ) {
+ return subtable instanceof ChainedContextualSubtable;
+ }
+ /** {@inheritDoc} */
+ public boolean substitute ( GlyphSubstitutionState ss ) {
+ int gi = ss.getGlyph(), ci;
+ if ( ( ci = getCoverageIndex ( gi ) ) < 0 ) {
+ return false;
+ } else {
+ int[] rv = new int[1];
+ RuleLookup[] la = getLookups ( ci, gi, ss, rv );
+ if ( la != null ) {
+ ss.apply ( la, rv[0] );
+ return true;
+ } else {
+ return false;
+ }
+ }
+ }
+ /**
+ * Obtain rule lookups set associated current input glyph context.
+ * @param ci coverage index of glyph at current position
+ * @param gi glyph index of glyph at current position
+ * @param ss glyph substitution state
+ * @param rv array of ints used to receive multiple return values, must be of length 1 or greater
+ * @return array of rule lookups or null if none applies
+ */
+ public abstract RuleLookup[] getLookups ( int ci, int gi, GlyphSubstitutionState ss, int[] rv );
+ static GlyphSubstitutionSubtable create ( String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries ) {
+ if ( format == 1 ) {
+ return new ChainedContextualSubtableFormat1 ( id, sequence, flags, format, coverage, entries );
+ } else if ( format == 2 ) {
+ return new ChainedContextualSubtableFormat2 ( id, sequence, flags, format, coverage, entries );
+ } else if ( format == 3 ) {
+ return new ChainedContextualSubtableFormat3 ( id, sequence, flags, format, coverage, entries );
+ } else {
+ throw new UnsupportedOperationException();
+ }
+ }
+ }
+
+ private static class ChainedContextualSubtableFormat1 extends ChainedContextualSubtable {
+ private RuleSet[] rsa; // rule set array, ordered by glyph coverage index
+ ChainedContextualSubtableFormat1 ( String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries ) {
+ super ( id, sequence, flags, format, coverage, entries );
+ populate ( entries );
+ }
+ /** {@inheritDoc} */
+ public List getEntries() {
+ if ( rsa != null ) {
+ List entries = new ArrayList ( 1 );
+ entries.add ( rsa );
+ return entries;
+ } else {
+ return null;
+ }
+ }
+ /** {@inheritDoc} */
+ public void resolveLookupReferences ( Map/*<String,LookupTable>*/ lookupTables ) {
+ GlyphTable.resolveLookupReferences ( rsa, lookupTables );
+ }
+ /** {@inheritDoc} */
+ public RuleLookup[] getLookups ( int ci, int gi, GlyphSubstitutionState ss, int[] rv ) {
+ assert ss != null;
+ assert ( rv != null ) && ( rv.length > 0 );
+ assert rsa != null;
+ if ( rsa.length > 0 ) {
+ RuleSet rs = rsa [ 0 ];
+ if ( rs != null ) {
+ Rule[] ra = rs.getRules();
+ for ( int i = 0, n = ra.length; i < n; i++ ) {
+ Rule r = ra [ i ];
+ if ( ( r != null ) && ( r instanceof ChainedGlyphSequenceRule ) ) {
+ ChainedGlyphSequenceRule cr = (ChainedGlyphSequenceRule) r;
+ int[] iga = cr.getGlyphs ( gi );
+ if ( matches ( ss, iga, 0, rv ) ) {
+ int[] bga = cr.getBacktrackGlyphs();
+ if ( matches ( ss, bga, -1, null ) ) {
+ int[] lga = cr.getLookaheadGlyphs();
+ if ( matches ( ss, lga, rv[0], null ) ) {
+ return r.getLookups();
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ return null;
+ }
+ private boolean matches ( GlyphSubstitutionState ss, int[] glyphs, int offset, int[] rv ) {
+ return ContextualSubtableFormat1.matches ( ss, glyphs, offset, rv );
+ }
+ private void populate ( List entries ) {
+ if ( entries == null ) {
+ throw new AdvancedTypographicTableFormatException ( "illegal entries, must be non-null" );
+ } else if ( entries.size() != 1 ) {
+ throw new AdvancedTypographicTableFormatException ( "illegal entries, " + entries.size() + " entries present, but requires 1 entry" );
+ } else {
+ Object o;
+ if ( ( ( o = entries.get(0) ) == null ) || ! ( o instanceof RuleSet[] ) ) {
+ throw new AdvancedTypographicTableFormatException ( "illegal entries, first entry must be an RuleSet[], but is: " + ( ( o != null ) ? o.getClass() : null ) );
+ } else {
+ rsa = (RuleSet[]) o;
+ }
+ }
+ }
+ }
+
+ private static class ChainedContextualSubtableFormat2 extends ChainedContextualSubtable {
+ private GlyphClassTable icdt; // input class def table
+ private GlyphClassTable bcdt; // backtrack class def table
+ private GlyphClassTable lcdt; // lookahead class def table
+ private int ngc; // class set count
+ private RuleSet[] rsa; // rule set array, ordered by class number [0...ngc - 1]
+ ChainedContextualSubtableFormat2 ( String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries ) {
+ super ( id, sequence, flags, format, coverage, entries );
+ populate ( entries );
+ }
+ /** {@inheritDoc} */
+ public List getEntries() {
+ if ( rsa != null ) {
+ List entries = new ArrayList ( 5 );
+ entries.add ( icdt );
+ entries.add ( bcdt );
+ entries.add ( lcdt );
+ entries.add ( Integer.valueOf ( ngc ) );
+ entries.add ( rsa );
+ return entries;
+ } else {
+ return null;
+ }
+ }
+ /** {@inheritDoc} */
+ public RuleLookup[] getLookups ( int ci, int gi, GlyphSubstitutionState ss, int[] rv ) {
+ assert ss != null;
+ assert ( rv != null ) && ( rv.length > 0 );
+ assert rsa != null;
+ if ( rsa.length > 0 ) {
+ RuleSet rs = rsa [ 0 ];
+ if ( rs != null ) {
+ Rule[] ra = rs.getRules();
+ for ( int i = 0, n = ra.length; i < n; i++ ) {
+ Rule r = ra [ i ];
+ if ( ( r != null ) && ( r instanceof ChainedClassSequenceRule ) ) {
+ ChainedClassSequenceRule cr = (ChainedClassSequenceRule) r;
+ int[] ica = cr.getClasses ( icdt.getClassIndex ( gi, ss.getClassMatchSet ( gi ) ) );
+ if ( matches ( ss, icdt, ica, 0, rv ) ) {
+ int[] bca = cr.getBacktrackClasses();
+ if ( matches ( ss, bcdt, bca, -1, null ) ) {
+ int[] lca = cr.getLookaheadClasses();
+ if ( matches ( ss, lcdt, lca, rv[0], null ) ) {
+ return r.getLookups();
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ return null;
+ }
+ private boolean matches ( GlyphSubstitutionState ss, GlyphClassTable cdt, int[] classes, int offset, int[] rv ) {
+ return ContextualSubtableFormat2.matches ( ss, cdt, classes, offset, rv );
+ }
+ /** {@inheritDoc} */
+ public void resolveLookupReferences ( Map/*<String,LookupTable>*/ lookupTables ) {
+ GlyphTable.resolveLookupReferences ( rsa, lookupTables );
+ }
+ private void populate ( List entries ) {
+ if ( entries == null ) {
+ throw new AdvancedTypographicTableFormatException ( "illegal entries, must be non-null" );
+ } else if ( entries.size() != 5 ) {
+ throw new AdvancedTypographicTableFormatException ( "illegal entries, " + entries.size() + " entries present, but requires 5 entries" );
+ } else {
+ Object o;
+ if ( ( ( o = entries.get(0) ) == null ) || ! ( o instanceof GlyphClassTable ) ) {
+ throw new AdvancedTypographicTableFormatException ( "illegal entries, first entry must be an GlyphClassTable, but is: " + ( ( o != null ) ? o.getClass() : null ) );
+ } else {
+ icdt = (GlyphClassTable) o;
+ }
+ if ( ( ( o = entries.get(1) ) != null ) && ! ( o instanceof GlyphClassTable ) ) {
+ throw new AdvancedTypographicTableFormatException ( "illegal entries, second entry must be an GlyphClassTable, but is: " + o.getClass() );
+ } else {
+ bcdt = (GlyphClassTable) o;
+ }
+ if ( ( ( o = entries.get(2) ) != null ) && ! ( o instanceof GlyphClassTable ) ) {
+ throw new AdvancedTypographicTableFormatException ( "illegal entries, third entry must be an GlyphClassTable, but is: " + o.getClass() );
+ } else {
+ lcdt = (GlyphClassTable) o;
+ }
+ if ( ( ( o = entries.get(3) ) == null ) || ! ( o instanceof Integer ) ) {
+ throw new AdvancedTypographicTableFormatException ( "illegal entries, fourth entry must be an Integer, but is: " + ( ( o != null ) ? o.getClass() : null ) );
+ } else {
+ ngc = ((Integer)(o)).intValue();
+ }
+ if ( ( ( o = entries.get(4) ) == null ) || ! ( o instanceof RuleSet[] ) ) {
+ throw new AdvancedTypographicTableFormatException ( "illegal entries, fifth entry must be an RuleSet[], but is: " + ( ( o != null ) ? o.getClass() : null ) );
+ } else {
+ rsa = (RuleSet[]) o;
+ if ( rsa.length != ngc ) {
+ throw new AdvancedTypographicTableFormatException ( "illegal entries, RuleSet[] length is " + rsa.length + ", but expected " + ngc + " glyph classes" );
+ }
+ }
+ }
+ }
+ }
+
+ private static class ChainedContextualSubtableFormat3 extends ChainedContextualSubtable {
+ private RuleSet[] rsa; // rule set array, containing a single rule set
+ ChainedContextualSubtableFormat3 ( String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries ) {
+ super ( id, sequence, flags, format, coverage, entries );
+ populate ( entries );
+ }
+ /** {@inheritDoc} */
+ public List getEntries() {
+ if ( rsa != null ) {
+ List entries = new ArrayList ( 1 );
+ entries.add ( rsa );
+ return entries;
+ } else {
+ return null;
+ }
+ }
+ /** {@inheritDoc} */
+ public void resolveLookupReferences ( Map/*<String,LookupTable>*/ lookupTables ) {
+ GlyphTable.resolveLookupReferences ( rsa, lookupTables );
+ }
+ /** {@inheritDoc} */
+ public RuleLookup[] getLookups ( int ci, int gi, GlyphSubstitutionState ss, int[] rv ) {
+ assert ss != null;
+ assert ( rv != null ) && ( rv.length > 0 );
+ assert rsa != null;
+ if ( rsa.length > 0 ) {
+ RuleSet rs = rsa [ 0 ];
+ if ( rs != null ) {
+ Rule[] ra = rs.getRules();
+ for ( int i = 0, n = ra.length; i < n; i++ ) {
+ Rule r = ra [ i ];
+ if ( ( r != null ) && ( r instanceof ChainedCoverageSequenceRule ) ) {
+ ChainedCoverageSequenceRule cr = (ChainedCoverageSequenceRule) r;
+ GlyphCoverageTable[] igca = cr.getCoverages();
+ if ( matches ( ss, igca, 0, rv ) ) {
+ GlyphCoverageTable[] bgca = cr.getBacktrackCoverages();
+ if ( matches ( ss, bgca, -1, null ) ) {
+ GlyphCoverageTable[] lgca = cr.getLookaheadCoverages();
+ if ( matches ( ss, lgca, rv[0], null ) ) {
+ return r.getLookups();
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ return null;
+ }
+ private boolean matches ( GlyphSubstitutionState ss, GlyphCoverageTable[] gca, int offset, int[] rv ) {
+ return ContextualSubtableFormat3.matches ( ss, gca, offset, rv );
+ }
+ private void populate ( List entries ) {
+ if ( entries == null ) {
+ throw new AdvancedTypographicTableFormatException ( "illegal entries, must be non-null" );
+ } else if ( entries.size() != 1 ) {
+ throw new AdvancedTypographicTableFormatException ( "illegal entries, " + entries.size() + " entries present, but requires 1 entry" );
+ } else {
+ Object o;
+ if ( ( ( o = entries.get(0) ) == null ) || ! ( o instanceof RuleSet[] ) ) {
+ throw new AdvancedTypographicTableFormatException ( "illegal entries, first entry must be an RuleSet[], but is: " + ( ( o != null ) ? o.getClass() : null ) );
+ } else {
+ rsa = (RuleSet[]) o;
+ }
+ }
+ }
+ }
+
+ private abstract static class ReverseChainedSingleSubtable extends GlyphSubstitutionSubtable {
+ public ReverseChainedSingleSubtable ( String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries ) {
+ super ( id, sequence, flags, format, coverage );
+ }
+ /** {@inheritDoc} */
+ public int getType() {
+ return GSUB_LOOKUP_TYPE_REVERSE_CHAINED_SINGLE;
+ }
+ /** {@inheritDoc} */
+ public boolean isCompatible ( GlyphSubtable subtable ) {
+ return subtable instanceof ReverseChainedSingleSubtable;
+ }
+ /** {@inheritDoc} */
+ public boolean usesReverseScan() {
+ return true;
+ }
+ static GlyphSubstitutionSubtable create ( String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries ) {
+ if ( format == 1 ) {
+ return new ReverseChainedSingleSubtableFormat1 ( id, sequence, flags, format, coverage, entries );
+ } else {
+ throw new UnsupportedOperationException();
+ }
+ }
+ }
+
+ private static class ReverseChainedSingleSubtableFormat1 extends ReverseChainedSingleSubtable {
+ ReverseChainedSingleSubtableFormat1 ( String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries ) {
+ super ( id, sequence, flags, format, coverage, entries );
+ populate ( entries );
+ }
+ /** {@inheritDoc} */
+ public List getEntries() {
+ return null;
+ }
+ private void populate ( List entries ) {
+ }
+ }
+
+ /**
+ * The <code>Ligature</code> class implements a ligature lookup result in terms of
+ * a ligature glyph (code) and the <emph>N+1...</emph> components that comprise the ligature,
+ * where the <emph>Nth</emph> component was consumed in the coverage table lookup mapping to
+ * this ligature instance.
+ */
+ public static class Ligature {
+
+ private final int ligature; // (resulting) ligature glyph
+ private final int[] components; // component glyph codes (note that first component is implied)
+
+ /**
+ * Instantiate a ligature.
+ * @param ligature glyph id
+ * @param components sequence of <emph>N+1...</emph> component glyph (or character) identifiers
+ */
+ public Ligature ( int ligature, int[] components ) {
+ if ( ( ligature < 0 ) || ( ligature > 65535 ) ) {
+ throw new AdvancedTypographicTableFormatException ( "invalid ligature glyph index: " + ligature );
+ } 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 ];
+ if ( ( gc < 0 ) || ( gc > 65535 ) ) {
+ throw new AdvancedTypographicTableFormatException ( "invalid component glyph index: " + gc );
+ }
+ }
+ this.ligature = ligature;
+ this.components = components;
+ }
+ }
+
+ /** @return ligature glyph id */
+ public int getLigature() {
+ return ligature;
+ }
+
+ /** @return array of <emph>N+1...</emph> components */
+ public int[] getComponents() {
+ return components;
+ }
+
+ /** @return components count */
+ public int getNumComponents() {
+ return components.length;
+ }
+
+ /**
+ * Determine if input sequence at offset matches ligature's components.
+ * @param glyphs array of glyph components to match (including first, implied glyph)
+ * @return true if matches
+ */
+ public boolean matchesComponents ( int[] glyphs ) {
+ if ( glyphs.length < ( components.length + 1 ) ) {
+ return false;
+ } else {
+ for ( int i = 0, n = components.length; i < n; i++ ) {
+ if ( glyphs [ i + 1 ] != components [ i ] ) {
+ return false;
+ }
+ }
+ return true;
+ }
+ }
+
+ /** {@inheritDoc} */
+ public String toString() {
+ StringBuffer sb = new StringBuffer();
+ sb.append("{components={");
+ for ( int i = 0, n = components.length; i < n; i++ ) {
+ if ( i > 0 ) {
+ sb.append(',');
+ }
+ sb.append(Integer.toString(components[i]));
+ }
+ sb.append("},ligature=");
+ sb.append(Integer.toString(ligature));
+ sb.append("}");
+ return sb.toString();
+ }
+
+ }
+
+ /**
+ * The <code>LigatureSet</code> class implements a set of ligatures.
+ */
+ public static class LigatureSet {
+
+ private final Ligature[] ligatures; // set of ligatures all of which share the first (implied) component
+ private final int maxComponents; // maximum number of components (including first)
+
+ /**
+ * Instantiate a set of ligatures.
+ * @param ligatures collection of ligatures
+ */
+ public LigatureSet ( List ligatures ) {
+ this ( (Ligature[]) ligatures.toArray ( new Ligature [ ligatures.size() ] ) );
+ }
+
+ /**
+ * Instantiate a set of ligatures.
+ * @param ligatures array of ligatures
+ */
+ public LigatureSet ( Ligature[] ligatures ) {
+ if ( ligatures == null ) {
+ throw new AdvancedTypographicTableFormatException ( "invalid ligatures, must be non-null array" );
+ } else {
+ this.ligatures = ligatures;
+ int ncMax = -1;
+ for ( int i = 0, n = ligatures.length; i < n; i++ ) {
+ Ligature l = ligatures [ i ];
+ int nc = l.getNumComponents() + 1;
+ if ( nc > ncMax ) {
+ ncMax = nc;
+ }
+ }
+ maxComponents = ncMax;
+ }
+ }
+
+ /** @return array of ligatures in this ligature set */
+ public Ligature[] getLigatures() {
+ return ligatures;
+ }
+
+ /** @return count of ligatures in this ligature set */
+ public int getNumLigatures() {
+ return ligatures.length;
+ }
+
+ /** @return maximum number of components in one ligature (including first component) */
+ public int getMaxComponents() {
+ return maxComponents;
+ }
+
+ /** {@inheritDoc} */
+ public String toString() {
+ StringBuffer sb = new StringBuffer();
+ sb.append("{ligs={");
+ for ( int i = 0, n = ligatures.length; i < n; i++ ) {
+ if ( i > 0 ) {
+ sb.append(',');
+ }
+ sb.append(ligatures[i]);
+ }
+ sb.append("}}");
+ return sb.toString();
+ }
+
+ }
+
+}
+
diff --git a/src/java/org/apache/fop/complexscripts/fonts/GlyphSubtable.java b/src/java/org/apache/fop/complexscripts/fonts/GlyphSubtable.java
new file mode 100644
index 000000000..ba0141e44
--- /dev/null
+++ b/src/java/org/apache/fop/complexscripts/fonts/GlyphSubtable.java
@@ -0,0 +1,314 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.complexscripts.fonts;
+
+import java.lang.ref.WeakReference;
+
+import java.util.List;
+import java.util.Map;
+
+// CSOFF: InnerAssignmentCheck
+// CSOFF: LineLengthCheck
+
+/**
+ * The <code>GlyphSubtable</code> implements an abstract glyph subtable that
+ * encapsulates identification, type, format, and coverage information.
+ * @author Glenn Adams
+ */
+public abstract class GlyphSubtable implements Comparable {
+
+ /** lookup flag - right to left */
+ public static final int LF_RIGHT_TO_LEFT = 0x0001;
+ /** lookup flag - ignore base glyphs */
+ public static final int LF_IGNORE_BASE = 0x0002;
+ /** lookup flag - ignore ligatures */
+ public static final int LF_IGNORE_LIGATURE = 0x0004;
+ /** lookup flag - ignore marks */
+ public static final int LF_IGNORE_MARK = 0x0008;
+ /** lookup flag - use mark filtering set */
+ public static final int LF_USE_MARK_FILTERING_SET = 0x0010;
+ /** lookup flag - reserved */
+ public static final int LF_RESERVED = 0x0E00;
+ /** lookup flag - mark attachment type */
+ public static final int LF_MARK_ATTACHMENT_TYPE = 0xFF00;
+ /** internal flag - use reverse scan */
+ public static final int LF_INTERNAL_USE_REVERSE_SCAN = 0x10000;
+
+ /** lookup identifier, having form of "lu%d" where %d is index of lookup in lookup list; shared by multiple subtables in a single lookup */
+ private String lookupId;
+ /** subtable sequence (index) number in lookup, zero based */
+ private int sequence;
+ /** subtable flags */
+ private int flags;
+ /** subtable format */
+ private int format;
+ /** subtable mapping table */
+ private GlyphMappingTable mapping;
+ /** weak reference to parent (gsub or gpos) table */
+ private WeakReference table;
+
+ /**
+ * Instantiate this glyph subtable.
+ * @param lookupId lookup identifier, having form of "lu%d" where %d is index of lookup in lookup list
+ * @param sequence subtable sequence (within lookup), starting with zero
+ * @param flags subtable flags
+ * @param format subtable format
+ * @param mapping subtable mapping table
+ */
+ protected GlyphSubtable ( String lookupId, int sequence, int flags, int format, GlyphMappingTable mapping )
+ {
+ if ( ( lookupId == null ) || ( lookupId.length() == 0 ) ) {
+ throw new AdvancedTypographicTableFormatException ( "invalid lookup identifier, must be non-empty string" );
+ } else if ( mapping == null ) {
+ throw new AdvancedTypographicTableFormatException ( "invalid mapping table, must not be null" );
+ } else {
+ this.lookupId = lookupId;
+ this.sequence = sequence;
+ this.flags = flags;
+ this.format = format;
+ this.mapping = mapping;
+ }
+ }
+
+ /** @return this subtable's lookup identifer */
+ public String getLookupId() {
+ return lookupId;
+ }
+
+ /** @return this subtable's table type */
+ public abstract int getTableType();
+
+ /** @return this subtable's type */
+ public abstract int getType();
+
+ /** @return this subtable's type name */
+ public abstract String getTypeName();
+
+ /**
+ * Determine if a glyph subtable is compatible with this glyph subtable. Two glyph subtables are
+ * compatible if the both may appear in a single lookup table.
+ * @param subtable a glyph subtable to determine compatibility
+ * @return true if specified subtable is compatible with this glyph subtable, where by compatible
+ * is meant that they share the same lookup type
+ */
+ public abstract boolean isCompatible ( GlyphSubtable subtable );
+
+ /** @return true if subtable uses reverse scanning of glyph sequence, meaning from the last glyph
+ * in a glyph sequence to the first glyph
+ */
+ public abstract boolean usesReverseScan();
+
+ /** @return this subtable's sequence (index) within lookup */
+ public int getSequence() {
+ return sequence;
+ }
+
+ /** @return this subtable's flags */
+ public int getFlags() {
+ return flags;
+ }
+
+ /** @return this subtable's format */
+ public int getFormat() {
+ return format;
+ }
+
+ /** @return this subtable's governing glyph definition table or null if none available */
+ public GlyphDefinitionTable getGDEF() {
+ GlyphTable gt = getTable();
+ if ( gt != null ) {
+ return gt.getGlyphDefinitions();
+ } else {
+ return null;
+ }
+ }
+
+ /** @return this subtable's coverage mapping or null if mapping is not a coverage mapping */
+ public GlyphCoverageMapping getCoverage() {
+ if ( mapping instanceof GlyphCoverageMapping ) {
+ return (GlyphCoverageMapping) mapping;
+ } else {
+ return null;
+ }
+ }
+
+ /** @return this subtable's class mapping or null if mapping is not a class mapping */
+ public GlyphClassMapping getClasses() {
+ if ( mapping instanceof GlyphClassMapping ) {
+ return (GlyphClassMapping) mapping;
+ } else {
+ return null;
+ }
+ }
+
+ /** @return this subtable's lookup entries */
+ public abstract List getEntries();
+
+ /** @return this subtable's parent table (or null if undefined) */
+ public synchronized GlyphTable getTable() {
+ WeakReference r = this.table;
+ return ( r != null ) ? (GlyphTable) r.get() : null;
+ }
+
+ /**
+ * Establish a weak reference from this subtable to its parent
+ * table. If table parameter is specified as <code>null</code>, then
+ * clear and remove weak reference.
+ * @param table the table or null
+ * @throws IllegalStateException if table is already set to non-null
+ */
+ public synchronized void setTable ( GlyphTable table ) throws IllegalStateException {
+ WeakReference r = this.table;
+ if ( table == null ) {
+ this.table = null;
+ if ( r != null ) {
+ r.clear();
+ }
+ } else if ( r == null ) {
+ this.table = new WeakReference ( table );
+ } else {
+ throw new IllegalStateException ( "table already set" );
+ }
+ }
+
+ /**
+ * Resolve references to lookup tables, e.g., in RuleLookup, to the lookup tables themselves.
+ * @param lookupTables map from lookup table identifers, e.g. "lu4", to lookup tables
+ */
+ public void resolveLookupReferences ( Map/*<String,GlyphTable.LookupTable>*/ lookupTables ) {
+ }
+
+ /**
+ * Map glyph id to coverage index.
+ * @param gid glyph id
+ * @return the corresponding coverage index of the specified glyph id
+ */
+ public int getCoverageIndex ( int gid ) {
+ if ( mapping instanceof GlyphCoverageMapping ) {
+ return ( (GlyphCoverageMapping) mapping ) .getCoverageIndex ( gid );
+ } else {
+ return -1;
+ }
+ }
+
+ /**
+ * Map glyph id to coverage index.
+ * @return the corresponding coverage index of the specified glyph id
+ */
+ public int getCoverageSize() {
+ if ( mapping instanceof GlyphCoverageMapping ) {
+ return ( (GlyphCoverageMapping) mapping ) .getCoverageSize();
+ } else {
+ return 0;
+ }
+ }
+
+ /** {@inheritDoc} */
+ public int hashCode() {
+ int hc = sequence;
+ hc = ( hc * 3 ) + ( lookupId.hashCode() ^ hc );
+ return hc;
+ }
+
+ /**
+ * {@inheritDoc}
+ * @return true if the lookup identifier and the sequence number of the specified subtable is the same
+ * as the lookup identifier and sequence number of this subtable
+ */
+ public boolean equals ( Object o ) {
+ if ( o instanceof GlyphSubtable ) {
+ GlyphSubtable st = (GlyphSubtable) o;
+ return lookupId.equals ( st.lookupId ) && ( sequence == st.sequence );
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ * @return the result of comparing the lookup identifier and the sequence number of the specified subtable with
+ * the lookup identifier and sequence number of this subtable
+ */
+ public int compareTo ( Object o ) {
+ int d;
+ if ( o instanceof GlyphSubtable ) {
+ GlyphSubtable st = (GlyphSubtable) o;
+ if ( ( d = lookupId.compareTo ( st.lookupId ) ) == 0 ) {
+ if ( sequence < st.sequence ) {
+ d = -1;
+ } else if ( sequence > st.sequence ) {
+ d = 1;
+ }
+ }
+ } else {
+ d = -1;
+ }
+ return d;
+ }
+
+ /**
+ * Determine if any of the specified subtables uses reverse scanning.
+ * @param subtables array of glyph subtables
+ * @return true if any of the specified subtables uses reverse scanning.
+ */
+ public static boolean usesReverseScan ( GlyphSubtable[] subtables ) {
+ if ( ( subtables == null ) || ( subtables.length == 0 ) ) {
+ return false;
+ } else {
+ for ( int i = 0, n = subtables.length; i < n; i++ ) {
+ if ( subtables[i].usesReverseScan() ) {
+ return true;
+ }
+ }
+ return false;
+ }
+ }
+
+ /**
+ * Determine consistent flags for a set of subtables.
+ * @param subtables array of glyph subtables
+ * @return consistent flags
+ * @throws IllegalStateException if inconsistent flags
+ */
+ public static int getFlags ( GlyphSubtable[] subtables ) throws IllegalStateException {
+ if ( ( subtables == null ) || ( subtables.length == 0 ) ) {
+ return 0;
+ } else {
+ int flags = 0;
+ // obtain first non-zero value of flags in array of subtables
+ for ( int i = 0, n = subtables.length; i < n; i++ ) {
+ int f = subtables[i].getFlags();
+ if ( flags == 0 ) {
+ flags = f;
+ break;
+ }
+ }
+ // enforce flag consistency
+ for ( int i = 0, n = subtables.length; i < n; i++ ) {
+ int f = subtables[i].getFlags();
+ if ( f != flags ) {
+ throw new IllegalStateException ( "inconsistent lookup flags " + f + ", expected " + flags );
+ }
+ }
+ return flags | ( usesReverseScan ( subtables ) ? LF_INTERNAL_USE_REVERSE_SCAN : 0 );
+ }
+ }
+
+}
diff --git a/src/java/org/apache/fop/complexscripts/fonts/GlyphTable.java b/src/java/org/apache/fop/complexscripts/fonts/GlyphTable.java
new file mode 100644
index 000000000..91f8d4924
--- /dev/null
+++ b/src/java/org/apache/fop/complexscripts/fonts/GlyphTable.java
@@ -0,0 +1,1300 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.complexscripts.fonts;
+
+import java.util.Arrays;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeSet;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import org.apache.fop.complexscripts.util.GlyphSequence;
+import org.apache.fop.complexscripts.util.ScriptContextTester;
+
+// CSOFF: EmptyForIteratorPadCheck
+// CSOFF: InnerAssignmentCheck
+// CSOFF: LineLengthCheck
+// CSOFF: NoWhitespaceAfterCheck
+// CSOFF: ParameterNumberCheck
+// CSOFF: SimplifyBooleanReturnCheck
+
+/**
+ * Base class for all advanced typographic glyph tables.
+ * @author Glenn Adams
+ */
+public class GlyphTable {
+
+ /** logging instance */
+ private static final Log log = LogFactory.getLog(GlyphTable.class); // CSOK: ConstantNameCheck
+
+ /** substitution glyph table type */
+ public static final int GLYPH_TABLE_TYPE_SUBSTITUTION = 1;
+ /** positioning glyph table type */
+ public static final int GLYPH_TABLE_TYPE_POSITIONING = 2;
+ /** justification glyph table type */
+ public static final int GLYPH_TABLE_TYPE_JUSTIFICATION = 3;
+ /** baseline glyph table type */
+ public static final int GLYPH_TABLE_TYPE_BASELINE = 4;
+ /** definition glyph table type */
+ public static final int GLYPH_TABLE_TYPE_DEFINITION = 5;
+
+ // (optional) glyph definition table in table types other than glyph definition table
+ private GlyphTable gdef;
+
+ // map from lookup specs to lists of strings, each of which identifies a lookup table (consisting of one or more subtables)
+ private Map/*<LookupSpec,List<String>>*/ lookups;
+
+ // map from lookup identifiers to lookup tables
+ private Map/*<String,LookupTable>*/ lookupTables;
+
+ // if true, then prevent further subtable addition
+ private boolean frozen;
+
+ /**
+ * Instantiate glyph table with specified lookups.
+ * @param gdef glyph definition table that applies
+ * @param lookups map from lookup specs to lookup tables
+ */
+ public GlyphTable ( GlyphTable gdef, Map/*<LookupSpec,List<String>>*/ lookups ) {
+ if ( ( gdef != null ) && ! ( gdef instanceof GlyphDefinitionTable ) ) {
+ throw new AdvancedTypographicTableFormatException ( "bad glyph definition table" );
+ } else if ( lookups == null ) {
+ throw new AdvancedTypographicTableFormatException ( "lookups must be non-null map" );
+ } else {
+ this.gdef = gdef;
+ this.lookups = lookups;
+ this.lookupTables = new LinkedHashMap/*<String,List<LookupTable>>*/();
+ }
+ }
+
+ /**
+ * Obtain glyph definition table.
+ * @return (possibly null) glyph definition table
+ */
+ public GlyphDefinitionTable getGlyphDefinitions() {
+ return (GlyphDefinitionTable) gdef;
+ }
+
+ /**
+ * Obtain list of all lookup specifications.
+ * @return (possibly empty) list of all lookup specifications
+ */
+ public List/*<LookupSpec>*/ getLookups() {
+ return matchLookupSpecs ( "*", "*", "*" );
+ }
+
+ /**
+ * Obtain ordered list of all lookup tables, where order is by lookup identifier, which
+ * lexicographic ordering follows the lookup list order.
+ * @return (possibly empty) ordered list of all lookup tables
+ */
+ public List/*<LookupTable>*/ getLookupTables() {
+ TreeSet/*<String>*/ lids = new TreeSet/*<String>*/ ( lookupTables.keySet() );
+ List/*<LookupTable>*/ ltl = new ArrayList/*<LookupTable>*/ ( lids.size() );
+ for ( Iterator it = lids.iterator(); it.hasNext(); ) {
+ String lid = (String) it.next();
+ ltl.add ( lookupTables.get ( lid ) );
+ }
+ return ltl;
+ }
+
+ /**
+ * Obtain lookup table by lookup id. This method is used by test code, and provides
+ * access to embedded lookups not normally accessed by {script, language, feature} lookup spec.
+ * @param lid lookup id
+ * @return table associated with lookup id or null if none
+ */
+ public LookupTable getLookupTable ( String lid ) {
+ return (LookupTable) lookupTables.get ( lid );
+ }
+
+ /**
+ * Add a subtable.
+ * @param subtable a (non-null) glyph subtable
+ */
+ protected void addSubtable ( GlyphSubtable subtable ) {
+ // ensure table is not frozen
+ if ( frozen ) {
+ throw new IllegalStateException ( "glyph table is frozen, subtable addition prohibited" );
+ }
+ // set subtable's table reference to this table
+ subtable.setTable ( this );
+ // add subtable to this table's subtable collection
+ String lid = subtable.getLookupId();
+ if ( lookupTables.containsKey ( lid ) ) {
+ LookupTable lt = (LookupTable) lookupTables.get ( lid );
+ lt.addSubtable ( subtable );
+ } else {
+ LookupTable lt = new LookupTable ( lid, subtable );
+ lookupTables.put ( lid, lt );
+ }
+ }
+
+ /**
+ * Freeze subtables, i.e., do not allow further subtable addition, and
+ * create resulting cached state.
+ */
+ protected void freezeSubtables() {
+ if ( ! frozen ) {
+ for ( Iterator it = lookupTables.values().iterator(); it.hasNext(); ) {
+ LookupTable lt = (LookupTable) it.next();
+ lt.freezeSubtables ( lookupTables );
+ }
+ frozen = true;
+ }
+ }
+
+ /**
+ * Match lookup specifications according to <script,language,feature> tuple, where
+ * '*' is a wildcard for a tuple component.
+ * @param script a script identifier
+ * @param language a language identifier
+ * @param feature a feature identifier
+ * @return a (possibly empty) array of matching lookup specifications
+ */
+ public List/*<LookupSpec>*/ matchLookupSpecs ( String script, String language, String feature ) {
+ Set/*<LookupSpec>*/ keys = lookups.keySet();
+ List/*<LookupSpec>*/ matches = new ArrayList/*<LookupSpec>*/();
+ for ( Iterator it = keys.iterator(); it.hasNext();) {
+ LookupSpec ls = (LookupSpec) it.next();
+ if ( ! "*".equals(script) ) {
+ if ( ! ls.getScript().equals ( script ) ) {
+ continue;
+ }
+ }
+ if ( ! "*".equals(language) ) {
+ if ( ! ls.getLanguage().equals ( language ) ) {
+ continue;
+ }
+ }
+ if ( ! "*".equals(feature) ) {
+ if ( ! ls.getFeature().equals ( feature ) ) {
+ continue;
+ }
+ }
+ matches.add ( ls );
+ }
+ return matches;
+ }
+
+ /**
+ * Match lookup specifications according to <script,language,feature> tuple, where
+ * '*' is a wildcard for a tuple component.
+ * @param script a script identifier
+ * @param language a language identifier
+ * @param feature a feature identifier
+ * @return a (possibly empty) map from matching lookup specifications to lists of corresponding lookup tables
+ */
+ public Map/*<LookupSpec,List<LookupTable>>*/ matchLookups ( String script, String language, String feature ) {
+ List/*<LookupSpec>*/ lsl = matchLookupSpecs ( script, language, feature );
+ Map lm = new LinkedHashMap();
+ for ( Iterator it = lsl.iterator(); it.hasNext(); ) {
+ LookupSpec ls = (LookupSpec) it.next();
+ lm.put ( ls, findLookupTables ( ls ) );
+ }
+ return lm;
+ }
+
+ /**
+ * Obtain ordered list of glyph lookup tables that match a specific lookup specification.
+ * @param ls a (non-null) lookup specification
+ * @return a (possibly empty) ordered list of lookup tables whose corresponding lookup specifications match the specified lookup spec
+ */
+ public List/*<LookupTable>*/ findLookupTables ( LookupSpec ls ) {
+ TreeSet/*<LookupTable>*/ lts = new TreeSet/*<LookupTable>*/();
+ List/*<String>*/ ids;
+ if ( ( ids = (List/*<String>*/) lookups.get ( ls ) ) != null ) {
+ for ( Iterator it = ids.iterator(); it.hasNext();) {
+ String lid = (String) it.next();
+ LookupTable lt;
+ if ( ( lt = (LookupTable) lookupTables.get ( lid ) ) != null ) {
+ lts.add ( lt );
+ }
+ }
+ }
+ return new ArrayList/*<LookupTable>*/ ( lts );
+ }
+
+ /**
+ * Assemble ordered array of lookup table use specifications according to the specified features and candidate lookups,
+ * where the order of the array is in accordance to the order of the applicable lookup list.
+ * @param features array of feature identifiers to apply
+ * @param lookups a mapping from lookup specifications to lists of look tables from which to select lookup tables according to the specified features
+ * @return ordered array of assembled lookup table use specifications
+ */
+ public UseSpec[] assembleLookups ( String[] features, Map/*<LookupSpec,List<LookupTable>>*/ lookups ) {
+ TreeSet/*<UseSpec>*/ uss = new TreeSet/*<UseSpec>*/();
+ for ( int i = 0, n = features.length; i < n; i++ ) {
+ String feature = features[i];
+ for ( Iterator it = lookups.entrySet().iterator(); it.hasNext(); ) {
+ Map.Entry/*<LookupSpec,List<LookupTable>>*/ e = (Map.Entry/*<LookupSpec,List<LookupTable>>*/) it.next();
+ LookupSpec ls = (LookupSpec) e.getKey();
+ if ( ls.getFeature().equals ( feature ) ) {
+ List/*<LookupTable>*/ ltl = (List/*<LookupTable>*/) e.getValue();
+ if ( ltl != null ) {
+ for ( Iterator ltit = ltl.iterator(); ltit.hasNext(); ) {
+ LookupTable lt = (LookupTable) ltit.next();
+ uss.add ( new UseSpec ( lt, feature ) );
+ }
+ }
+ }
+ }
+ }
+ return (UseSpec[]) uss.toArray ( new UseSpec [ uss.size() ] );
+ }
+
+ /** {@inheritDoc} */
+ public String toString() {
+ StringBuffer sb = new StringBuffer(super.toString());
+ sb.append("{");
+ sb.append("lookups={");
+ sb.append(lookups.toString());
+ sb.append("},lookupTables={");
+ sb.append(lookupTables.toString());
+ sb.append("}}");
+ return sb.toString();
+ }
+
+ /**
+ * Obtain glyph table type from name.
+ * @param name of table type to map to type value
+ * @return glyph table type (as an integer constant)
+ */
+ public static int getTableTypeFromName ( String name ) {
+ int t;
+ String s = name.toLowerCase();
+ if ( "gsub".equals ( s ) ) {
+ t = GLYPH_TABLE_TYPE_SUBSTITUTION;
+ } else if ( "gpos".equals ( s ) ) {
+ t = GLYPH_TABLE_TYPE_POSITIONING;
+ } else if ( "jstf".equals ( s ) ) {
+ t = GLYPH_TABLE_TYPE_JUSTIFICATION;
+ } else if ( "base".equals ( s ) ) {
+ t = GLYPH_TABLE_TYPE_BASELINE;
+ } else if ( "gdef".equals ( s ) ) {
+ t = GLYPH_TABLE_TYPE_DEFINITION;
+ } else {
+ t = -1;
+ }
+ return t;
+ }
+
+ /**
+ * Resolve references to lookup tables in a collection of rules sets.
+ * @param rsa array of rule sets
+ * @param lookupTables map from lookup table identifers, e.g. "lu4", to lookup tables
+ */
+ public static void resolveLookupReferences ( RuleSet[] rsa, Map/*<String,LookupTable>*/ lookupTables ) {
+ if ( ( rsa != null ) && ( lookupTables != null ) ) {
+ for ( int i = 0, n = rsa.length; i < n; i++ ) {
+ RuleSet rs = rsa [ i ];
+ if ( rs != null ) {
+ rs.resolveLookupReferences ( lookupTables );
+ }
+ }
+ }
+ }
+
+ /**
+ * A structure class encapsulating a lookup specification as a <script,language,feature> tuple.
+ */
+ public static class LookupSpec implements Comparable {
+
+ private final String script;
+ private final String language;
+ private final String feature;
+
+ /**
+ * Instantiate lookup spec.
+ * @param script a script identifier
+ * @param language a language identifier
+ * @param feature a feature identifier
+ */
+ public LookupSpec ( String script, String language, String feature ) {
+ if ( ( script == null ) || ( script.length() == 0 ) ) {
+ throw new AdvancedTypographicTableFormatException ( "script must be non-empty string" );
+ } else if ( ( language == null ) || ( language.length() == 0 ) ) {
+ throw new AdvancedTypographicTableFormatException ( "language must be non-empty string" );
+ } else if ( ( feature == null ) || ( feature.length() == 0 ) ) {
+ throw new AdvancedTypographicTableFormatException ( "feature must be non-empty string" );
+ } else if ( script.equals("*") ) {
+ throw new AdvancedTypographicTableFormatException ( "script must not be wildcard" );
+ } else if ( language.equals("*") ) {
+ throw new AdvancedTypographicTableFormatException ( "language must not be wildcard" );
+ } else if ( feature.equals("*") ) {
+ throw new AdvancedTypographicTableFormatException ( "feature must not be wildcard" );
+ } else {
+ this.script = script.trim();
+ this.language = language.trim();
+ this.feature = feature.trim();
+ }
+ }
+
+ /** @return script identifier */
+ public String getScript() {
+ return script;
+ }
+
+ /** @return language identifier */
+ public String getLanguage() {
+ return language;
+ }
+
+ /** @return feature identifier */
+ public String getFeature() {
+ return feature;
+ }
+
+ /** {@inheritDoc} */
+ public int hashCode() {
+ int hc = 0;
+ hc = 7 * hc + ( hc ^ script.hashCode() );
+ hc = 11 * hc + ( hc ^ language.hashCode() );
+ hc = 17 * hc + ( hc ^ feature.hashCode() );
+ return hc;
+ }
+
+ /** {@inheritDoc} */
+ public boolean equals ( Object o ) {
+ if ( o instanceof LookupSpec ) {
+ LookupSpec l = (LookupSpec) o;
+ if ( ! l.script.equals ( script ) ) {
+ return false;
+ } else if ( ! l.language.equals ( language ) ) {
+ return false;
+ } else if ( ! l.feature.equals ( feature ) ) {
+ return false;
+ } else {
+ return true;
+ }
+ } else {
+ return false;
+ }
+ }
+
+ /** {@inheritDoc} */
+ public int compareTo ( Object o ) {
+ int d;
+ if ( o instanceof LookupSpec ) {
+ LookupSpec ls = (LookupSpec) o;
+ if ( ( d = script.compareTo ( ls.script ) ) == 0 ) {
+ if ( ( d = language.compareTo ( ls.language ) ) == 0 ) {
+ if ( ( d = feature.compareTo ( ls.feature ) ) == 0 ) {
+ d = 0;
+ }
+ }
+ }
+ } else {
+ d = -1;
+ }
+ return d;
+ }
+
+ /** {@inheritDoc} */
+ public String toString() {
+ StringBuffer sb = new StringBuffer(super.toString());
+ sb.append("{");
+ sb.append("<'" + script + "'");
+ sb.append(",'" + language + "'");
+ sb.append(",'" + feature + "'");
+ sb.append(">}");
+ return sb.toString();
+ }
+
+ }
+
+ /**
+ * The <code>LookupTable</code> class comprising an identifier and an ordered list
+ * of glyph subtables, each of which employ the same lookup identifier.
+ */
+ public static class LookupTable implements Comparable {
+
+ private final String id; // lookup identifiers
+ private final List/*<GlyphSubtable>*/ subtables; // list of subtables
+ private boolean doesSub; // performs substitutions
+ private boolean doesPos; // performs positioning
+ private boolean frozen; // if true, then don't permit further subtable additions
+ // frozen state
+ private GlyphSubtable[] subtablesArray;
+ private static GlyphSubtable[] subtablesArrayEmpty = new GlyphSubtable[0];
+
+ /**
+ * Instantiate a LookupTable.
+ * @param id the lookup table's identifier
+ * @param subtable an initial subtable (or null)
+ */
+ public LookupTable ( String id, GlyphSubtable subtable ) {
+ this ( id, makeSingleton ( subtable ) );
+ }
+
+ /**
+ * Instantiate a LookupTable.
+ * @param id the lookup table's identifier
+ * @param subtables a pre-poplated list of subtables or null
+ */
+ public LookupTable ( String id, List/*<GlyphSubtable>*/ subtables ) {
+ assert id != null;
+ assert id.length() != 0;
+ this.id = id;
+ this.subtables = new LinkedList/*<GlyphSubtable>*/();
+ if ( subtables != null ) {
+ for ( Iterator it = subtables.iterator(); it.hasNext(); ) {
+ GlyphSubtable st = (GlyphSubtable) it.next();
+ addSubtable ( st );
+ }
+ }
+ }
+
+ /** @return the identifier */
+ public String getId() {
+ return id;
+ }
+
+ /** @return the subtables as an array */
+ public GlyphSubtable[] getSubtables() {
+ if ( frozen ) {
+ return ( subtablesArray != null ) ? subtablesArray : subtablesArrayEmpty;
+ } else {
+ if ( doesSub ) {
+ return (GlyphSubtable[]) subtables.toArray ( new GlyphSubstitutionSubtable [ subtables.size() ] );
+ } else if ( doesPos ) {
+ return (GlyphSubtable[]) subtables.toArray ( new GlyphPositioningSubtable [ subtables.size() ] );
+ } else {
+ return null;
+ }
+ }
+ }
+
+ /**
+ * Add a subtable into this lookup table's collecion of subtables according to its
+ * natural order.
+ * @param subtable to add
+ * @return true if subtable was not already present, otherwise false
+ */
+ public boolean addSubtable ( GlyphSubtable subtable ) {
+ boolean added = false;
+ // ensure table is not frozen
+ if ( frozen ) {
+ throw new IllegalStateException ( "glyph table is frozen, subtable addition prohibited" );
+ }
+ // validate subtable to ensure consistency with current subtables
+ validateSubtable ( subtable );
+ // insert subtable into ordered list
+ for ( ListIterator/*<GlyphSubtable>*/ lit = subtables.listIterator(0); lit.hasNext(); ) {
+ GlyphSubtable st = (GlyphSubtable) lit.next();
+ int d;
+ if ( ( d = subtable.compareTo ( st ) ) < 0 ) {
+ // insert within list
+ lit.set ( subtable );
+ lit.add ( st );
+ added = true;
+ } else if ( d == 0 ) {
+ // duplicate entry is ignored
+ added = false; subtable = null;
+ }
+ }
+ // append at end of list
+ if ( ! added && ( subtable != null ) ) {
+ subtables.add ( subtable );
+ added = true;
+ }
+ return added;
+ }
+
+ private void validateSubtable ( GlyphSubtable subtable ) {
+ if ( subtable == null ) {
+ throw new AdvancedTypographicTableFormatException ( "subtable must be non-null" );
+ }
+ if ( subtable instanceof GlyphSubstitutionSubtable ) {
+ if ( doesPos ) {
+ throw new AdvancedTypographicTableFormatException ( "subtable must be positioning subtable, but is: " + subtable );
+ } else {
+ doesSub = true;
+ }
+ }
+ if ( subtable instanceof GlyphPositioningSubtable ) {
+ if ( doesSub ) {
+ throw new AdvancedTypographicTableFormatException ( "subtable must be substitution subtable, but is: " + subtable );
+ } else {
+ doesPos = true;
+ }
+ }
+ if ( subtables.size() > 0 ) {
+ GlyphSubtable st = (GlyphSubtable) subtables.get(0);
+ if ( ! st.isCompatible ( subtable ) ) {
+ throw new AdvancedTypographicTableFormatException ( "subtable " + subtable + " is not compatible with subtable " + st );
+ }
+ }
+ }
+
+ /**
+ * Freeze subtables, i.e., do not allow further subtable addition, and
+ * create resulting cached state. In addition, resolve any references to
+ * lookup tables that appear in this lookup table's subtables.
+ * @param lookupTables map from lookup table identifers, e.g. "lu4", to lookup tables
+ */
+ public void freezeSubtables ( Map/*<String,LookupTable>*/ lookupTables ) {
+ if ( ! frozen ) {
+ GlyphSubtable[] sta = getSubtables();
+ resolveLookupReferences ( sta, lookupTables );
+ this.subtablesArray = sta;
+ this.frozen = true;
+ }
+ }
+
+ private void resolveLookupReferences ( GlyphSubtable[] subtables, Map/*<String,LookupTable>*/ lookupTables ) {
+ if ( subtables != null ) {
+ for ( int i = 0, n = subtables.length; i < n; i++ ) {
+ GlyphSubtable st = subtables [ i ];
+ if ( st != null ) {
+ st.resolveLookupReferences ( lookupTables );
+ }
+ }
+ }
+ }
+
+ /**
+ * Determine if this glyph table performs substitution.
+ * @return true if it performs substitution
+ */
+ public boolean performsSubstitution() {
+ return doesSub;
+ }
+
+ /**
+ * Perform substitution processing using this lookup table's subtables.
+ * @param gs an input glyph sequence
+ * @param script a script identifier
+ * @param language a language identifier
+ * @param feature a feature identifier
+ * @param sct a script specific context tester (or null)
+ * @return the substituted (output) glyph sequence
+ */
+ public GlyphSequence substitute ( GlyphSequence gs, String script, String language, String feature, ScriptContextTester sct ) {
+ if ( performsSubstitution() ) {
+ return GlyphSubstitutionSubtable.substitute ( gs, script, language, feature, (GlyphSubstitutionSubtable[]) subtablesArray, sct );
+ } else {
+ return gs;
+ }
+ }
+
+ /**
+ * Perform substitution processing on an existing glyph substitution state object using this lookup table's subtables.
+ * @param ss a glyph substitution state object
+ * @param sequenceIndex if non negative, then apply subtables only at specified sequence index
+ * @return the substituted (output) glyph sequence
+ */
+ public GlyphSequence substitute ( GlyphSubstitutionState ss, int sequenceIndex ) {
+ if ( performsSubstitution() ) {
+ return GlyphSubstitutionSubtable.substitute ( ss, (GlyphSubstitutionSubtable[]) subtablesArray, sequenceIndex );
+ } else {
+ return ss.getInput();
+ }
+ }
+
+ /**
+ * Determine if this glyph table performs positioning.
+ * @return true if it performs positioning
+ */
+ public boolean performsPositioning() {
+ return doesPos;
+ }
+
+ /**
+ * Perform positioning processing using this lookup table's subtables.
+ * @param gs an input glyph sequence
+ * @param script a script identifier
+ * @param language a language identifier
+ * @param feature a feature identifier
+ * @param fontSize size in device units
+ * @param widths array of default advancements for each glyph in font
+ * @param adjustments accumulated adjustments array (sequence) of 4-tuples of placement [PX,PY] and advance [AX,AY] adjustments, in that order,
+ * with one 4-tuple for each element of glyph sequence
+ * @param sct a script specific context tester (or null)
+ * @return true if some adjustment is not zero; otherwise, false
+ */
+ public boolean position ( GlyphSequence gs, String script, String language, String feature, int fontSize, int[] widths, int[][] adjustments, ScriptContextTester sct ) {
+ if ( performsPositioning() ) {
+ return GlyphPositioningSubtable.position ( gs, script, language, feature, fontSize, (GlyphPositioningSubtable[]) subtablesArray, widths, adjustments, sct );
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Perform positioning processing on an existing glyph positioning state object using this lookup table's subtables.
+ * @param ps a glyph positioning state object
+ * @param sequenceIndex if non negative, then apply subtables only at specified sequence index
+ * @return true if some adjustment is not zero; otherwise, false
+ */
+ public boolean position ( GlyphPositioningState ps, int sequenceIndex ) {
+ if ( performsPositioning() ) {
+ return GlyphPositioningSubtable.position ( ps, (GlyphPositioningSubtable[]) subtablesArray, sequenceIndex );
+ } else {
+ return false;
+ }
+ }
+
+ /** {@inheritDoc} */
+ public int hashCode() {
+ return id.hashCode();
+ }
+
+ /**
+ * {@inheritDoc}
+ * @return true if identifier of the specified lookup table is the same
+ * as the identifier of this lookup table
+ */
+ public boolean equals ( Object o ) {
+ if ( o instanceof LookupTable ) {
+ LookupTable lt = (LookupTable) o;
+ return id.equals ( lt.id );
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ * @return the result of comparing the identifier of the specified lookup table with
+ * 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;
+ 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;
+ }
+ }
+
+ /** {@inheritDoc} */
+ public String toString() {
+ StringBuffer sb = new StringBuffer();
+ sb.append ( "{ " );
+ sb.append ( "id = " + id );
+ sb.append ( ", subtables = " + subtables );
+ sb.append ( " }" );
+ return sb.toString();
+ }
+
+ private static List/*<GlyphSubtable>*/ makeSingleton ( GlyphSubtable subtable ) {
+ if ( subtable == null ) {
+ return null;
+ } else {
+ List/*<GlyphSubtable>*/ stl = new ArrayList/*<GlyphSubtable>*/ ( 1 );
+ stl.add ( subtable );
+ return stl;
+ }
+ }
+
+ }
+
+ /**
+ * The <code>UseSpec</code> class comprises a lookup table reference
+ * and the feature that selected the lookup table.
+ */
+ public static class UseSpec implements Comparable {
+
+ /** lookup table to apply */
+ private final LookupTable lookupTable;
+ /** feature that caused selection of the lookup table */
+ private final String feature;
+
+ /**
+ * Construct a glyph lookup table use specification.
+ * @param lookupTable a glyph lookup table
+ * @param feature a feature that caused lookup table selection
+ */
+ public UseSpec ( LookupTable lookupTable, String feature ) {
+ this.lookupTable = lookupTable;
+ this.feature = feature;
+ }
+
+ /** @return the lookup table */
+ public LookupTable getLookupTable() {
+ return lookupTable;
+ }
+
+ /** @return the feature that selected this lookup table */
+ public String getFeature() {
+ return feature;
+ }
+
+ /**
+ * Perform substitution processing using this use specification's lookup table.
+ * @param gs an input glyph sequence
+ * @param script a script identifier
+ * @param language a language identifier
+ * @param sct a script specific context tester (or null)
+ * @return the substituted (output) glyph sequence
+ */
+ public GlyphSequence substitute ( GlyphSequence gs, String script, String language, ScriptContextTester sct ) {
+ return lookupTable.substitute ( gs, script, language, feature, sct );
+ }
+
+ /**
+ * Perform positioning processing using this use specification's lookup table.
+ * @param gs an input glyph sequence
+ * @param script a script identifier
+ * @param language a language identifier
+ * @param fontSize size in device units
+ * @param widths array of default advancements for each glyph in font
+ * @param adjustments accumulated adjustments array (sequence) of 4-tuples of placement [PX,PY] and advance [AX,AY] adjustments, in that order,
+ * with one 4-tuple for each element of glyph sequence
+ * @param sct a script specific context tester (or null)
+ * @return true if some adjustment is not zero; otherwise, false
+ */
+ public boolean position ( GlyphSequence gs, String script, String language, int fontSize, int[] widths, int[][] adjustments, ScriptContextTester sct ) {
+ return lookupTable.position ( gs, script, language, feature, fontSize, widths, adjustments, sct );
+ }
+
+ /** {@inheritDoc} */
+ public int hashCode() {
+ return lookupTable.hashCode();
+ }
+
+ /** {@inheritDoc} */
+ public boolean equals ( Object o ) {
+ if ( o instanceof UseSpec ) {
+ UseSpec u = (UseSpec) o;
+ return lookupTable.equals ( u.lookupTable );
+ } else {
+ return false;
+ }
+ }
+
+ /** {@inheritDoc} */
+ public int compareTo ( Object o ) {
+ if ( o instanceof UseSpec ) {
+ UseSpec u = (UseSpec) o;
+ return lookupTable.compareTo ( u.lookupTable );
+ } else {
+ return -1;
+ }
+ }
+
+ }
+
+ /**
+ * The <code>RuleLookup</code> class implements a rule lookup record, comprising
+ * a glyph sequence index and a lookup table index (in an applicable lookup list).
+ */
+ public static class RuleLookup {
+
+ private final int sequenceIndex; // index into input glyph sequence
+ private final int lookupIndex; // lookup list index
+ private LookupTable lookup; // resolved lookup table
+
+ /**
+ * Instantiate a RuleLookup.
+ * @param sequenceIndex the index into the input sequence
+ * @param lookupIndex the lookup table index
+ */
+ public RuleLookup ( int sequenceIndex, int lookupIndex ) {
+ this.sequenceIndex = sequenceIndex;
+ this.lookupIndex = lookupIndex;
+ this.lookup = null;
+ }
+
+ /** @return the sequence index */
+ public int getSequenceIndex() {
+ return sequenceIndex;
+ }
+
+ /** @return the lookup index */
+ public int getLookupIndex() {
+ return lookupIndex;
+ }
+
+ /** @return the lookup table */
+ public LookupTable getLookup() {
+ return lookup;
+ }
+
+ /**
+ * Resolve references to lookup tables.
+ * @param lookupTables map from lookup table identifers, e.g. "lu4", to lookup tables
+ */
+ public void resolveLookupReferences ( Map/*<String,LookupTable>*/ lookupTables ) {
+ if ( lookupTables != null ) {
+ String lid = "lu" + Integer.toString ( lookupIndex );
+ LookupTable lt = (LookupTable) lookupTables.get ( lid );
+ if ( lt != null ) {
+ this.lookup = lt;
+ } else {
+ log.warn ( "unable to resolve glyph lookup table reference '" + lid + "' amongst lookup tables: " + lookupTables.values() );
+ }
+ }
+ }
+
+ /** {@inheritDoc} */
+ public String toString() {
+ return "{ sequenceIndex = " + sequenceIndex + ", lookupIndex = " + lookupIndex + " }";
+ }
+
+ }
+
+ /**
+ * The <code>Rule</code> class implements an array of rule lookup records.
+ */
+ public abstract static class Rule {
+
+ private final RuleLookup[] lookups; // rule lookups
+ private final int inputSequenceLength; // input sequence length
+
+ /**
+ * Instantiate a Rule.
+ * @param lookups the rule's lookups
+ * @param inputSequenceLength the number of glyphs in the input sequence for this rule
+ */
+ protected Rule ( RuleLookup[] lookups, int inputSequenceLength ) {
+ assert lookups != null;
+ this.lookups = lookups;
+ this.inputSequenceLength = inputSequenceLength;
+ }
+
+ /** @return the lookups */
+ public RuleLookup[] getLookups() {
+ return lookups;
+ }
+
+ /** @return the input sequence length */
+ public int getInputSequenceLength() {
+ return inputSequenceLength;
+ }
+
+ /**
+ * Resolve references to lookup tables, e.g., in RuleLookup, to the lookup tables themselves.
+ * @param lookupTables map from lookup table identifers, e.g. "lu4", to lookup tables
+ */
+ public void resolveLookupReferences ( Map/*<String,LookupTable>*/ lookupTables ) {
+ if ( lookups != null ) {
+ for ( int i = 0, n = lookups.length; i < n; i++ ) {
+ RuleLookup l = lookups [ i ];
+ if ( l != null ) {
+ l.resolveLookupReferences ( lookupTables );
+ }
+ }
+ }
+ }
+
+ /** {@inheritDoc} */
+ public String toString() {
+ return "{ lookups = " + Arrays.toString ( lookups ) + ", inputSequenceLength = " + inputSequenceLength + " }";
+ }
+
+ }
+
+ /**
+ * The <code>GlyphSequenceRule</code> class implements a subclass of <code>Rule</code>
+ * that supports matching on a specific glyph sequence.
+ */
+ public static class GlyphSequenceRule extends Rule {
+
+ private final int[] glyphs; // glyphs
+
+ /**
+ * Instantiate a GlyphSequenceRule.
+ * @param lookups the rule's lookups
+ * @param inputSequenceLength number of glyphs constituting input sequence (to be consumed)
+ * @param glyphs the rule's glyph sequence to match, starting with second glyph in sequence
+ */
+ public GlyphSequenceRule ( RuleLookup[] lookups, int inputSequenceLength, int[] glyphs ) {
+ super ( lookups, inputSequenceLength );
+ assert glyphs != null;
+ this.glyphs = glyphs;
+ }
+
+ /**
+ * Obtain glyphs. N.B. that this array starts with the second
+ * glyph of the input sequence.
+ * @return the glyphs
+ */
+ public int[] getGlyphs() {
+ return glyphs;
+ }
+
+ /**
+ * Obtain glyphs augmented by specified first glyph entry.
+ * @param firstGlyph to fill in first glyph entry
+ * @return the glyphs augmented by first glyph
+ */
+ public int[] getGlyphs ( int firstGlyph ) {
+ int[] ga = new int [ glyphs.length + 1 ];
+ ga [ 0 ] = firstGlyph;
+ System.arraycopy ( glyphs, 0, ga, 1, glyphs.length );
+ return ga;
+ }
+
+ /** {@inheritDoc} */
+ public String toString() {
+ StringBuffer sb = new StringBuffer();
+ sb.append ( "{ " );
+ sb.append ( "lookups = " + Arrays.toString ( getLookups() ) );
+ sb.append ( ", glyphs = " + Arrays.toString ( glyphs ) );
+ sb.append ( " }" );
+ return sb.toString();
+ }
+
+ }
+
+ /**
+ * The <code>ClassSequenceRule</code> class implements a subclass of <code>Rule</code>
+ * that supports matching on a specific glyph class sequence.
+ */
+ public static class ClassSequenceRule extends Rule {
+
+ private final int[] classes; // glyph classes
+
+ /**
+ * Instantiate a ClassSequenceRule.
+ * @param lookups the rule's lookups
+ * @param inputSequenceLength number of glyphs constituting input sequence (to be consumed)
+ * @param classes the rule's glyph class sequence to match, starting with second glyph in sequence
+ */
+ public ClassSequenceRule ( RuleLookup[] lookups, int inputSequenceLength, int[] classes ) {
+ super ( lookups, inputSequenceLength );
+ assert classes != null;
+ this.classes = classes;
+ }
+
+ /**
+ * Obtain glyph classes. N.B. that this array starts with the class of the second
+ * glyph of the input sequence.
+ * @return the classes
+ */
+ public int[] getClasses() {
+ return classes;
+ }
+
+ /**
+ * Obtain glyph classes augmented by specified first class entry.
+ * @param firstClass to fill in first class entry
+ * @return the classes augmented by first class
+ */
+ public int[] getClasses ( int firstClass ) {
+ int[] ca = new int [ classes.length + 1 ];
+ ca [ 0 ] = firstClass;
+ System.arraycopy ( classes, 0, ca, 1, classes.length );
+ return ca;
+ }
+
+ /** {@inheritDoc} */
+ public String toString() {
+ StringBuffer sb = new StringBuffer();
+ sb.append ( "{ " );
+ sb.append ( "lookups = " + Arrays.toString ( getLookups() ) );
+ sb.append ( ", classes = " + Arrays.toString( classes ) );
+ sb.append ( " }" );
+ return sb.toString();
+ }
+
+ }
+
+ /**
+ * The <code>CoverageSequenceRule</code> class implements a subclass of <code>Rule</code>
+ * that supports matching on a specific glyph coverage sequence.
+ */
+ public static class CoverageSequenceRule extends Rule {
+
+ private final GlyphCoverageTable[] coverages; // glyph coverages
+
+ /**
+ * Instantiate a ClassSequenceRule.
+ * @param lookups the rule's lookups
+ * @param inputSequenceLength number of glyphs constituting input sequence (to be consumed)
+ * @param coverages the rule's glyph coverage sequence to match, starting with first glyph in sequence
+ */
+ public CoverageSequenceRule ( RuleLookup[] lookups, int inputSequenceLength, GlyphCoverageTable[] coverages ) {
+ super ( lookups, inputSequenceLength );
+ assert coverages != null;
+ this.coverages = coverages;
+ }
+
+ /** @return the coverages */
+ public GlyphCoverageTable[] getCoverages() {
+ return coverages;
+ }
+
+ /** {@inheritDoc} */
+ public String toString() {
+ StringBuffer sb = new StringBuffer();
+ sb.append ( "{ " );
+ sb.append ( "lookups = " + Arrays.toString ( getLookups() ) );
+ sb.append ( ", coverages = " + Arrays.toString( coverages ) );
+ sb.append ( " }" );
+ return sb.toString();
+ }
+
+ }
+
+ /**
+ * The <code>ChainedGlyphSequenceRule</code> class implements a subclass of <code>GlyphSequenceRule</code>
+ * that supports matching on a specific glyph sequence in a specific chained contextual.
+ */
+ public static class ChainedGlyphSequenceRule extends GlyphSequenceRule {
+
+ private final int[] backtrackGlyphs; // backtrack glyphs
+ private final int[] lookaheadGlyphs; // lookahead glyphs
+
+ /**
+ * Instantiate a ChainedGlyphSequenceRule.
+ * @param lookups the rule's lookups
+ * @param inputSequenceLength number of glyphs constituting input sequence (to be consumed)
+ * @param glyphs the rule's input glyph sequence to match, starting with second glyph in sequence
+ * @param backtrackGlyphs the rule's backtrack glyph sequence to match, starting with first glyph in sequence
+ * @param lookaheadGlyphs the rule's lookahead glyph sequence to match, starting with first glyph in sequence
+ */
+ public ChainedGlyphSequenceRule ( RuleLookup[] lookups, int inputSequenceLength, int[] glyphs, int[] backtrackGlyphs, int[] lookaheadGlyphs ) {
+ super ( lookups, inputSequenceLength, glyphs );
+ assert backtrackGlyphs != null;
+ assert lookaheadGlyphs != null;
+ this.backtrackGlyphs = backtrackGlyphs;
+ this.lookaheadGlyphs = lookaheadGlyphs;
+ }
+
+ /** @return the backtrack glyphs */
+ public int[] getBacktrackGlyphs() {
+ return backtrackGlyphs;
+ }
+
+ /** @return the lookahead glyphs */
+ public int[] getLookaheadGlyphs() {
+ return lookaheadGlyphs;
+ }
+
+ /** {@inheritDoc} */
+ public String toString() {
+ StringBuffer sb = new StringBuffer();
+ sb.append ( "{ " );
+ sb.append ( "lookups = " + Arrays.toString ( getLookups() ) );
+ sb.append ( ", glyphs = " + Arrays.toString ( getGlyphs() ) );
+ sb.append ( ", backtrackGlyphs = " + Arrays.toString ( backtrackGlyphs ) );
+ sb.append ( ", lookaheadGlyphs = " + Arrays.toString ( lookaheadGlyphs ) );
+ sb.append ( " }" );
+ return sb.toString();
+ }
+
+ }
+
+ /**
+ * The <code>ChainedClassSequenceRule</code> class implements a subclass of <code>ClassSequenceRule</code>
+ * that supports matching on a specific glyph class sequence in a specific chained contextual.
+ */
+ public static class ChainedClassSequenceRule extends ClassSequenceRule {
+
+ private final int[] backtrackClasses; // backtrack classes
+ private final int[] lookaheadClasses; // lookahead classes
+
+ /**
+ * Instantiate a ChainedClassSequenceRule.
+ * @param lookups the rule's lookups
+ * @param inputSequenceLength number of glyphs constituting input sequence (to be consumed)
+ * @param classes the rule's input glyph class sequence to match, starting with second glyph in sequence
+ * @param backtrackClasses the rule's backtrack glyph class sequence to match, starting with first glyph in sequence
+ * @param lookaheadClasses the rule's lookahead glyph class sequence to match, starting with first glyph in sequence
+ */
+ public ChainedClassSequenceRule ( RuleLookup[] lookups, int inputSequenceLength, int[] classes, int[] backtrackClasses, int[] lookaheadClasses ) {
+ super ( lookups, inputSequenceLength, classes );
+ assert backtrackClasses != null;
+ assert lookaheadClasses != null;
+ this.backtrackClasses = backtrackClasses;
+ this.lookaheadClasses = lookaheadClasses;
+ }
+
+ /** @return the backtrack classes */
+ public int[] getBacktrackClasses() {
+ return backtrackClasses;
+ }
+
+ /** @return the lookahead classes */
+ public int[] getLookaheadClasses() {
+ return lookaheadClasses;
+ }
+
+ /** {@inheritDoc} */
+ public String toString() {
+ StringBuffer sb = new StringBuffer();
+ sb.append ( "{ " );
+ sb.append ( "lookups = " + Arrays.toString ( getLookups() ) );
+ sb.append ( ", classes = " + Arrays.toString ( getClasses() ) );
+ sb.append ( ", backtrackClasses = " + Arrays.toString ( backtrackClasses ) );
+ sb.append ( ", lookaheadClasses = " + Arrays.toString ( lookaheadClasses ) );
+ sb.append ( " }" );
+ return sb.toString();
+ }
+
+ }
+
+ /**
+ * The <code>ChainedCoverageSequenceRule</code> class implements a subclass of <code>CoverageSequenceRule</code>
+ * that supports matching on a specific glyph class sequence in a specific chained contextual.
+ */
+ public static class ChainedCoverageSequenceRule extends CoverageSequenceRule {
+
+ private final GlyphCoverageTable[] backtrackCoverages; // backtrack coverages
+ private final GlyphCoverageTable[] lookaheadCoverages; // lookahead coverages
+
+ /**
+ * Instantiate a ChainedCoverageSequenceRule.
+ * @param lookups the rule's lookups
+ * @param inputSequenceLength number of glyphs constituting input sequence (to be consumed)
+ * @param coverages the rule's input glyph class sequence to match, starting with first glyph in sequence
+ * @param backtrackCoverages the rule's backtrack glyph class sequence to match, starting with first glyph in sequence
+ * @param lookaheadCoverages the rule's lookahead glyph class sequence to match, starting with first glyph in sequence
+ */
+ public ChainedCoverageSequenceRule ( RuleLookup[] lookups, int inputSequenceLength, GlyphCoverageTable[] coverages, GlyphCoverageTable[] backtrackCoverages, GlyphCoverageTable[] lookaheadCoverages ) {
+ super ( lookups, inputSequenceLength, coverages );
+ assert backtrackCoverages != null;
+ assert lookaheadCoverages != null;
+ this.backtrackCoverages = backtrackCoverages;
+ this.lookaheadCoverages = lookaheadCoverages;
+ }
+
+ /** @return the backtrack coverages */
+ public GlyphCoverageTable[] getBacktrackCoverages() {
+ return backtrackCoverages;
+ }
+
+ /** @return the lookahead coverages */
+ public GlyphCoverageTable[] getLookaheadCoverages() {
+ return lookaheadCoverages;
+ }
+
+ /** {@inheritDoc} */
+ public String toString() {
+ StringBuffer sb = new StringBuffer();
+ sb.append ( "{ " );
+ sb.append ( "lookups = " + Arrays.toString ( getLookups() ) );
+ sb.append ( ", coverages = " + Arrays.toString ( getCoverages() ) );
+ sb.append ( ", backtrackCoverages = " + Arrays.toString ( backtrackCoverages ) );
+ sb.append ( ", lookaheadCoverages = " + Arrays.toString ( lookaheadCoverages ) );
+ sb.append ( " }" );
+ return sb.toString();
+ }
+
+ }
+
+ /**
+ * The <code>RuleSet</code> class implements a collection of rules, which
+ * may or may not be the same rule type.
+ */
+ public static class RuleSet {
+
+ private final Rule[] rules; // set of rules
+
+ /**
+ * Instantiate a Rule Set.
+ * @param rules the rules
+ * @throws AdvancedTypographicTableFormatException if rules or some element of rules is null
+ */
+ public RuleSet ( Rule[] rules ) throws AdvancedTypographicTableFormatException {
+ // enforce rules array instance
+ if ( rules == null ) {
+ throw new AdvancedTypographicTableFormatException ( "rules[] is null" );
+ }
+ this.rules = rules;
+ }
+
+ /** @return the rules */
+ public Rule[] getRules() {
+ return rules;
+ }
+
+ /**
+ * Resolve references to lookup tables, e.g., in RuleLookup, to the lookup tables themselves.
+ * @param lookupTables map from lookup table identifers, e.g. "lu4", to lookup tables
+ */
+ public void resolveLookupReferences ( Map/*<String,LookupTable>*/ lookupTables ) {
+ if ( rules != null ) {
+ for ( int i = 0, n = rules.length; i < n; i++ ) {
+ Rule r = rules [ i ];
+ if ( r != null ) {
+ r.resolveLookupReferences ( lookupTables );
+ }
+ }
+ }
+ }
+
+ /** {@inheritDoc} */
+ public String toString() {
+ return "{ rules = " + Arrays.toString ( rules ) + " }";
+ }
+
+ }
+
+ /**
+ * The <code>HomogenousRuleSet</code> class implements a collection of rules, which
+ * must be the same rule type (i.e., same concrete rule class) or null.
+ */
+ public static class HomogeneousRuleSet extends RuleSet {
+
+ /**
+ * Instantiate a Homogeneous Rule Set.
+ * @param rules the rules
+ * @throws AdvancedTypographicTableFormatException if some rule[i] is not an instance of rule[0]
+ */
+ public HomogeneousRuleSet ( Rule[] rules ) throws AdvancedTypographicTableFormatException {
+ super ( rules );
+ // find first non-null rule
+ Rule r0 = null;
+ for ( int i = 1, n = rules.length; ( r0 == null ) && ( i < n ); i++ ) {
+ if ( rules[i] != null ) {
+ r0 = rules[i];
+ }
+ }
+ // enforce rule instance homogeneity
+ if ( r0 != null ) {
+ Class c = r0.getClass();
+ for ( int i = 1, n = rules.length; i < n; i++ ) {
+ Rule r = rules[i];
+ if ( ( r != null ) && ! c.isInstance ( r ) ) {
+ throw new AdvancedTypographicTableFormatException ( "rules[" + i + "] is not an instance of " + c.getName() );
+ }
+ }
+ }
+
+ }
+
+ }
+
+}
diff --git a/src/java/org/apache/fop/complexscripts/fonts/IncompatibleSubtableException.java b/src/java/org/apache/fop/complexscripts/fonts/IncompatibleSubtableException.java
new file mode 100644
index 000000000..0ad8fc105
--- /dev/null
+++ b/src/java/org/apache/fop/complexscripts/fonts/IncompatibleSubtableException.java
@@ -0,0 +1,41 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.complexscripts.fonts;
+
+/**
+ * Exception thrown during when attempting to map glyphs to associated characters
+ * in the case that the associated characters do not represent a compact interval.
+ * @author Glenn Adams
+ */
+public class IncompatibleSubtableException extends RuntimeException {
+ /**
+ * Instantiate incompatible subtable exception
+ */
+ public IncompatibleSubtableException() {
+ super();
+ }
+ /**
+ * Instantiate incompatible subtable exception
+ * @param message a message string
+ */
+ public IncompatibleSubtableException(String message) {
+ super(message);
+ }
+}
diff --git a/src/java/org/apache/fop/complexscripts/fonts/OTFAdvancedTypographicTableReader.java b/src/java/org/apache/fop/complexscripts/fonts/OTFAdvancedTypographicTableReader.java
new file mode 100644
index 000000000..539f9af30
--- /dev/null
+++ b/src/java/org/apache/fop/complexscripts/fonts/OTFAdvancedTypographicTableReader.java
@@ -0,0 +1,3797 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.complexscripts.fonts;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import org.apache.fop.complexscripts.fonts.AdvancedTypographicTableFormatException;
+import org.apache.fop.complexscripts.fonts.GlyphClassTable;
+import org.apache.fop.complexscripts.fonts.GlyphCoverageTable;
+import org.apache.fop.complexscripts.fonts.GlyphDefinitionSubtable;
+import org.apache.fop.complexscripts.fonts.GlyphDefinitionTable;
+import org.apache.fop.complexscripts.fonts.GlyphMappingTable;
+import org.apache.fop.complexscripts.fonts.GlyphPositioningSubtable;
+import org.apache.fop.complexscripts.fonts.GlyphPositioningTable;
+import org.apache.fop.complexscripts.fonts.GlyphSubstitutionSubtable;
+import org.apache.fop.complexscripts.fonts.GlyphSubstitutionTable;
+import org.apache.fop.complexscripts.fonts.GlyphSubtable;
+import org.apache.fop.complexscripts.fonts.GlyphTable;
+import org.apache.fop.fonts.truetype.FontFileReader;
+import org.apache.fop.fonts.truetype.TTFDirTabEntry;
+import org.apache.fop.fonts.truetype.TTFFile;
+
+// CSOFF: AvoidNestedBlocksCheck
+// CSOFF: NoWhitespaceAfterCheck
+// CSOFF: InnerAssignmentCheck
+// CSOFF: SimplifyBooleanReturnCheck
+// CSOFF: LineLengthCheck
+
+/**
+ * OpenType Font (OTF) advanced typographic table reader. Used by @{Link org.apache.fop.fonts.truetype.TTFFile}
+ * to read advanced typographic tables (GDEF, GSUB, GPOS).
+ *
+ * @author Glenn Adams
+ */
+public final class OTFAdvancedTypographicTableReader {
+
+ // logging state
+ private static Log log = LogFactory.getLog(OTFAdvancedTypographicTableReader.class);
+ // instance state
+ private TTFFile ttf; // parent font file reader
+ private FontFileReader in; // input reader
+ private GlyphDefinitionTable gdef; // glyph definition table
+ private GlyphSubstitutionTable gsub; // glyph substitution table
+ private GlyphPositioningTable gpos; // glyph positioning table
+ // transient parsing state
+ private transient Map/*<String,Object[3]>*/ seScripts; // script-tag => Object[3] : { default-language-tag, List(language-tag), seLanguages }
+ private transient Map/*<String,Object[2]>*/ seLanguages; // language-tag => Object[2] : { "f<required-feature-index>", List("f<feature-index>")
+ private transient Map/*<String,List<String>>*/ seFeatures; // "f<feature-index>" => Object[2] : { feature-tag, List("lu<lookup-index>") }
+ private transient GlyphMappingTable seMapping; // subtable entry mappings
+ private transient List seEntries; // subtable entry entries
+ private transient List seSubtables; // subtable entry subtables
+
+ /**
+ * Construct an <code>OTFAdvancedTypographicTableReader</code> instance.
+ * @param ttf parent font file reader (must be non-null)
+ * @param in font file reader (must be non-null)
+ */
+ public OTFAdvancedTypographicTableReader ( TTFFile ttf, FontFileReader in ) {
+ assert ttf != null;
+ assert in != null;
+ this.ttf = ttf;
+ this.in = in;
+ }
+
+ /**
+ * Read all advanced typographic tables.
+ * @throws AdvancedTypographicTableFormatException if ATT table has invalid format
+ */
+ public void readAll() throws AdvancedTypographicTableFormatException {
+ try {
+ readGDEF();
+ readGSUB();
+ readGPOS();
+ } catch ( AdvancedTypographicTableFormatException e ) {
+ resetATStateAll();
+ throw e;
+ } catch ( IOException e ) {
+ resetATStateAll();
+ throw new AdvancedTypographicTableFormatException ( e.getMessage(), e );
+ } finally {
+ resetATState();
+ }
+ }
+
+ /**
+ * Determine if advanced (typographic) table is present.
+ * @return true if advanced (typographic) table is present
+ */
+ public boolean hasAdvancedTable() {
+ return ( gdef != null ) || ( gsub != null ) || ( gpos != null );
+ }
+
+ /**
+ * Returns the GDEF table or null if none present.
+ * @return the GDEF table
+ */
+ public GlyphDefinitionTable getGDEF() {
+ return gdef;
+ }
+
+ /**
+ * Returns the GSUB table or null if none present.
+ * @return the GSUB table
+ */
+ public GlyphSubstitutionTable getGSUB() {
+ return gsub;
+ }
+
+ /**
+ * Returns the GPOS table or null if none present.
+ * @return the GPOS table
+ */
+ public GlyphPositioningTable getGPOS() {
+ return gpos;
+ }
+
+ private void readLangSysTable(String tableTag, long langSysTable, String langSysTag) throws IOException {
+ in.seekSet(langSysTable);
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " lang sys table: " + langSysTag );
+ }
+ // read lookup order (reorder) table offset
+ int lo = in.readTTFUShort();
+ // read required feature index
+ int rf = in.readTTFUShort();
+ String rfi;
+ if ( rf != 65535 ) {
+ rfi = "f" + rf;
+ } else {
+ rfi = null;
+ }
+ // read (non-required) feature count
+ int nf = in.readTTFUShort();
+ // dump info if debugging
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " lang sys table reorder table: " + lo );
+ log.debug(tableTag + " lang sys table required feature index: " + rf );
+ log.debug(tableTag + " lang sys table non-required feature count: " + nf );
+ }
+ // read (non-required) feature indices
+ int[] fia = new int[nf];
+ List fl = new java.util.ArrayList();
+ for ( int i = 0; i < nf; i++ ) {
+ int fi = in.readTTFUShort();
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " lang sys table non-required feature index: " + fi );
+ }
+ fia[i] = fi;
+ fl.add ( "f" + fi );
+ }
+ if ( seLanguages == null ) {
+ seLanguages = new java.util.LinkedHashMap();
+ }
+ seLanguages.put ( langSysTag, new Object[] { rfi, fl } );
+ }
+
+ private static String defaultTag = "dflt";
+
+ private void readScriptTable(String tableTag, long scriptTable, String scriptTag) throws IOException {
+ in.seekSet(scriptTable);
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " script table: " + scriptTag );
+ }
+ // read default language system table offset
+ int dl = in.readTTFUShort();
+ String dt = defaultTag;
+ if ( dl > 0 ) {
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " default lang sys tag: " + dt );
+ log.debug(tableTag + " default lang sys table offset: " + dl );
+ }
+ }
+ // read language system record count
+ int nl = in.readTTFUShort();
+ List ll = new java.util.ArrayList();
+ if ( nl > 0 ) {
+ String[] lta = new String[nl];
+ int[] loa = new int[nl];
+ // read language system records
+ for ( int i = 0, n = nl; i < n; i++ ) {
+ String lt = in.readTTFString(4);
+ int lo = in.readTTFUShort();
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " lang sys tag: " + lt );
+ log.debug(tableTag + " lang sys table offset: " + lo );
+ }
+ lta[i] = lt;
+ loa[i] = lo;
+ if ( dl == lo ) {
+ dl = 0;
+ dt = lt;
+ }
+ ll.add ( lt );
+ }
+ // read non-default language system tables
+ for ( int i = 0, n = nl; i < n; i++ ) {
+ readLangSysTable ( tableTag, scriptTable + loa [ i ], lta [ i ] );
+ }
+ }
+ // read default language system table (if specified)
+ if ( dl > 0 ) {
+ readLangSysTable ( tableTag, scriptTable + dl, dt );
+ } else if ( dt != null ) {
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " lang sys default: " + dt );
+ }
+ }
+ seScripts.put ( scriptTag, new Object[] { dt, ll, seLanguages } );
+ seLanguages = null;
+ }
+
+ private void readScriptList(String tableTag, long scriptList) throws IOException {
+ in.seekSet(scriptList);
+ // read script record count
+ int ns = in.readTTFUShort();
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " script list record count: " + ns );
+ }
+ if ( ns > 0 ) {
+ String[] sta = new String[ns];
+ int[] soa = new int[ns];
+ // read script records
+ for ( int i = 0, n = ns; i < n; i++ ) {
+ String st = in.readTTFString(4);
+ int so = in.readTTFUShort();
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " script tag: " + st );
+ log.debug(tableTag + " script table offset: " + so );
+ }
+ sta[i] = st;
+ soa[i] = so;
+ }
+ // read script tables
+ for ( int i = 0, n = ns; i < n; i++ ) {
+ seLanguages = null;
+ readScriptTable ( tableTag, scriptList + soa [ i ], sta [ i ] );
+ }
+ }
+ }
+
+ private void readFeatureTable(String tableTag, long featureTable, String featureTag, int featureIndex) throws IOException {
+ in.seekSet(featureTable);
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " feature table: " + featureTag );
+ }
+ // read feature params offset
+ int po = in.readTTFUShort();
+ // read lookup list indices count
+ int nl = in.readTTFUShort();
+ // dump info if debugging
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " feature table parameters offset: " + po );
+ log.debug(tableTag + " feature table lookup list index count: " + nl );
+ }
+ // read lookup table indices
+ int[] lia = new int[nl];
+ List lul = new java.util.ArrayList();
+ for ( int i = 0; i < nl; i++ ) {
+ int li = in.readTTFUShort();
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " feature table lookup index: " + li );
+ }
+ lia[i] = li;
+ lul.add ( "lu" + li );
+ }
+ seFeatures.put ( "f" + featureIndex, new Object[] { featureTag, lul } );
+ }
+
+ private void readFeatureList(String tableTag, long featureList) throws IOException {
+ in.seekSet(featureList);
+ // read feature record count
+ int nf = in.readTTFUShort();
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " feature list record count: " + nf );
+ }
+ if ( nf > 0 ) {
+ String[] fta = new String[nf];
+ int[] foa = new int[nf];
+ // read feature records
+ for ( int i = 0, n = nf; i < n; i++ ) {
+ String ft = in.readTTFString(4);
+ int fo = in.readTTFUShort();
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " feature tag: " + ft );
+ log.debug(tableTag + " feature table offset: " + fo );
+ }
+ fta[i] = ft;
+ foa[i] = fo;
+ }
+ // read feature tables
+ for ( int i = 0, n = nf; i < n; i++ ) {
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " feature index: " + i );
+ }
+ readFeatureTable ( tableTag, featureList + foa [ i ], fta [ i ], i );
+ }
+ }
+ }
+
+ static final class GDEFLookupType {
+ static final int GLYPH_CLASS = 1;
+ static final int ATTACHMENT_POINT = 2;
+ static final int LIGATURE_CARET = 3;
+ static final int MARK_ATTACHMENT = 4;
+ private GDEFLookupType() {
+ }
+ public static int getSubtableType ( int lt ) {
+ int st;
+ switch ( lt ) {
+ case GDEFLookupType.GLYPH_CLASS:
+ st = GlyphDefinitionTable.GDEF_LOOKUP_TYPE_GLYPH_CLASS;
+ break;
+ case GDEFLookupType.ATTACHMENT_POINT:
+ st = GlyphDefinitionTable.GDEF_LOOKUP_TYPE_ATTACHMENT_POINT;
+ break;
+ case GDEFLookupType.LIGATURE_CARET:
+ st = GlyphDefinitionTable.GDEF_LOOKUP_TYPE_LIGATURE_CARET;
+ break;
+ case GDEFLookupType.MARK_ATTACHMENT:
+ st = GlyphDefinitionTable.GDEF_LOOKUP_TYPE_MARK_ATTACHMENT;
+ break;
+ default:
+ st = -1;
+ break;
+ }
+ return st;
+ }
+ public static String toString(int type) {
+ String s;
+ switch ( type ) {
+ case GLYPH_CLASS:
+ s = "GlyphClass";
+ break;
+ case ATTACHMENT_POINT:
+ s = "AttachmentPoint";
+ break;
+ case LIGATURE_CARET:
+ s = "LigatureCaret";
+ break;
+ case MARK_ATTACHMENT:
+ s = "MarkAttachment";
+ break;
+ default:
+ s = "?";
+ break;
+ }
+ return s;
+ }
+ }
+
+ static final class GSUBLookupType {
+ static final int SINGLE = 1;
+ static final int MULTIPLE = 2;
+ static final int ALTERNATE = 3;
+ static final int LIGATURE = 4;
+ static final int CONTEXTUAL = 5;
+ static final int CHAINED_CONTEXTUAL = 6;
+ static final int EXTENSION = 7;
+ static final int REVERSE_CHAINED_SINGLE = 8;
+ private GSUBLookupType() {
+ }
+ public static int getSubtableType ( int lt ) {
+ int st;
+ switch ( lt ) {
+ case GSUBLookupType.SINGLE:
+ st = GlyphSubstitutionTable.GSUB_LOOKUP_TYPE_SINGLE;
+ break;
+ case GSUBLookupType.MULTIPLE:
+ st = GlyphSubstitutionTable.GSUB_LOOKUP_TYPE_MULTIPLE;
+ break;
+ case GSUBLookupType.ALTERNATE:
+ st = GlyphSubstitutionTable.GSUB_LOOKUP_TYPE_ALTERNATE;
+ break;
+ case GSUBLookupType.LIGATURE:
+ st = GlyphSubstitutionTable.GSUB_LOOKUP_TYPE_LIGATURE;
+ break;
+ case GSUBLookupType.CONTEXTUAL:
+ st = GlyphSubstitutionTable.GSUB_LOOKUP_TYPE_CONTEXTUAL;
+ break;
+ case GSUBLookupType.CHAINED_CONTEXTUAL:
+ st = GlyphSubstitutionTable.GSUB_LOOKUP_TYPE_CHAINED_CONTEXTUAL;
+ break;
+ case GSUBLookupType.EXTENSION:
+ st = GlyphSubstitutionTable.GSUB_LOOKUP_TYPE_EXTENSION_SUBSTITUTION;
+ break;
+ case GSUBLookupType.REVERSE_CHAINED_SINGLE:
+ st = GlyphSubstitutionTable.GSUB_LOOKUP_TYPE_REVERSE_CHAINED_SINGLE;
+ break;
+ default:
+ st = -1;
+ break;
+ }
+ return st;
+ }
+ public static String toString(int type) {
+ String s;
+ switch ( type ) {
+ case SINGLE:
+ s = "Single";
+ break;
+ case MULTIPLE:
+ s = "Multiple";
+ break;
+ case ALTERNATE:
+ s = "Alternate";
+ break;
+ case LIGATURE:
+ s = "Ligature";
+ break;
+ case CONTEXTUAL:
+ s = "Contextual";
+ break;
+ case CHAINED_CONTEXTUAL:
+ s = "ChainedContextual";
+ break;
+ case EXTENSION:
+ s = "Extension";
+ break;
+ case REVERSE_CHAINED_SINGLE:
+ s = "ReverseChainedSingle";
+ break;
+ default:
+ s = "?";
+ break;
+ }
+ return s;
+ }
+ }
+
+ static final class GPOSLookupType {
+ static final int SINGLE = 1;
+ static final int PAIR = 2;
+ static final int CURSIVE = 3;
+ static final int MARK_TO_BASE = 4;
+ static final int MARK_TO_LIGATURE = 5;
+ static final int MARK_TO_MARK = 6;
+ static final int CONTEXTUAL = 7;
+ static final int CHAINED_CONTEXTUAL = 8;
+ static final int EXTENSION = 9;
+ private GPOSLookupType() {
+ }
+ public static String toString(int type) {
+ String s;
+ switch ( type ) {
+ case SINGLE:
+ s = "Single";
+ break;
+ case PAIR:
+ s = "Pair";
+ break;
+ case CURSIVE:
+ s = "Cursive";
+ break;
+ case MARK_TO_BASE:
+ s = "MarkToBase";
+ break;
+ case MARK_TO_LIGATURE:
+ s = "MarkToLigature";
+ break;
+ case MARK_TO_MARK:
+ s = "MarkToMark";
+ break;
+ case CONTEXTUAL:
+ s = "Contextual";
+ break;
+ case CHAINED_CONTEXTUAL:
+ s = "ChainedContextual";
+ break;
+ case EXTENSION:
+ s = "Extension";
+ break;
+ default:
+ s = "?";
+ break;
+ }
+ return s;
+ }
+ }
+
+ static final class LookupFlag {
+ static final int RIGHT_TO_LEFT = 0x0001;
+ static final int IGNORE_BASE_GLYPHS = 0x0002;
+ static final int IGNORE_LIGATURE = 0x0004;
+ static final int IGNORE_MARKS = 0x0008;
+ static final int USE_MARK_FILTERING_SET = 0x0010;
+ static final int MARK_ATTACHMENT_TYPE = 0xFF00;
+ private LookupFlag() {
+ }
+ public static String toString(int flags) {
+ StringBuffer sb = new StringBuffer();
+ boolean first = true;
+ if ( ( flags & RIGHT_TO_LEFT ) != 0 ) {
+ if ( first ) {
+ first = false;
+ } else {
+ sb.append ( '|' );
+ }
+ sb.append ( "RightToLeft" );
+ }
+ if ( ( flags & IGNORE_BASE_GLYPHS ) != 0 ) {
+ if ( first ) {
+ first = false;
+ } else {
+ sb.append ( '|' );
+ }
+ sb.append ( "IgnoreBaseGlyphs" );
+ }
+ if ( ( flags & IGNORE_LIGATURE ) != 0 ) {
+ if ( first ) {
+ first = false;
+ } else {
+ sb.append ( '|' );
+ }
+ sb.append ( "IgnoreLigature" );
+ }
+ if ( ( flags & IGNORE_MARKS ) != 0 ) {
+ if ( first ) {
+ first = false;
+ } else {
+ sb.append ( '|' );
+ }
+ sb.append ( "IgnoreMarks" );
+ }
+ if ( ( flags & USE_MARK_FILTERING_SET ) != 0 ) {
+ if ( first ) {
+ first = false;
+ } else {
+ sb.append ( '|' );
+ }
+ sb.append ( "UseMarkFilteringSet" );
+ }
+ if ( sb.length() == 0 ) {
+ sb.append ( '-' );
+ }
+ return sb.toString();
+ }
+ }
+
+ private GlyphCoverageTable readCoverageTableFormat1(String label, long tableOffset, int coverageFormat) throws IOException {
+ List entries = new java.util.ArrayList();
+ in.seekSet(tableOffset);
+ // skip over format (already known)
+ in.skip ( 2 );
+ // read glyph count
+ int ng = in.readTTFUShort();
+ int[] ga = new int[ng];
+ for ( int i = 0, n = ng; i < n; i++ ) {
+ int g = in.readTTFUShort();
+ ga[i] = g;
+ entries.add ( Integer.valueOf(g) );
+ }
+ // dump info if debugging
+ if (log.isDebugEnabled()) {
+ log.debug(label + " glyphs: " + toString(ga) );
+ }
+ return GlyphCoverageTable.createCoverageTable ( entries );
+ }
+
+ private GlyphCoverageTable readCoverageTableFormat2(String label, long tableOffset, int coverageFormat) throws IOException {
+ List entries = new java.util.ArrayList();
+ in.seekSet(tableOffset);
+ // skip over format (already known)
+ in.skip ( 2 );
+ // read range record count
+ int nr = in.readTTFUShort();
+ for ( int i = 0, n = nr; i < n; i++ ) {
+ // read range start
+ int s = in.readTTFUShort();
+ // read range end
+ int e = in.readTTFUShort();
+ // read range coverage (mapping) index
+ int m = in.readTTFUShort();
+ // dump info if debugging
+ if (log.isDebugEnabled()) {
+ log.debug(label + " range[" + i + "]: [" + s + "," + e + "]: " + m );
+ }
+ entries.add ( new GlyphCoverageTable.MappingRange ( s, e, m ) );
+ }
+ return GlyphCoverageTable.createCoverageTable ( entries );
+ }
+
+ private GlyphCoverageTable readCoverageTable(String label, long tableOffset) throws IOException {
+ GlyphCoverageTable gct;
+ long cp = in.getCurrentPos();
+ in.seekSet(tableOffset);
+ // read coverage table format
+ int cf = in.readTTFUShort();
+ if ( cf == 1 ) {
+ gct = readCoverageTableFormat1 ( label, tableOffset, cf );
+ } else if ( cf == 2 ) {
+ gct = readCoverageTableFormat2 ( label, tableOffset, cf );
+ } else {
+ throw new AdvancedTypographicTableFormatException ( "unsupported coverage table format: " + cf );
+ }
+ in.seekSet ( cp );
+ return gct;
+ }
+
+ private GlyphClassTable readClassDefTableFormat1(String label, long tableOffset, int classFormat) throws IOException {
+ List entries = new java.util.ArrayList();
+ in.seekSet(tableOffset);
+ // skip over format (already known)
+ in.skip ( 2 );
+ // read start glyph
+ int sg = in.readTTFUShort();
+ entries.add ( Integer.valueOf(sg) );
+ // read glyph count
+ int ng = in.readTTFUShort();
+ // read glyph classes
+ int[] ca = new int[ng];
+ for ( int i = 0, n = ng; i < n; i++ ) {
+ int gc = in.readTTFUShort();
+ ca[i] = gc;
+ entries.add ( Integer.valueOf(gc) );
+ }
+ // dump info if debugging
+ if (log.isDebugEnabled()) {
+ log.debug(label + " glyph classes: " + toString(ca) );
+ }
+ return GlyphClassTable.createClassTable ( entries );
+ }
+
+ private GlyphClassTable readClassDefTableFormat2(String label, long tableOffset, int classFormat) throws IOException {
+ List entries = new java.util.ArrayList();
+ in.seekSet(tableOffset);
+ // skip over format (already known)
+ in.skip ( 2 );
+ // read range record count
+ int nr = in.readTTFUShort();
+ for ( int i = 0, n = nr; i < n; i++ ) {
+ // read range start
+ int s = in.readTTFUShort();
+ // read range end
+ int e = in.readTTFUShort();
+ // read range glyph class (mapping) index
+ int m = in.readTTFUShort();
+ // dump info if debugging
+ if (log.isDebugEnabled()) {
+ log.debug(label + " range[" + i + "]: [" + s + "," + e + "]: " + m );
+ }
+ entries.add ( new GlyphClassTable.MappingRange ( s, e, m ) );
+ }
+ return GlyphClassTable.createClassTable ( entries );
+ }
+
+ private GlyphClassTable readClassDefTable(String label, long tableOffset) throws IOException {
+ GlyphClassTable gct;
+ long cp = in.getCurrentPos();
+ in.seekSet(tableOffset);
+ // read class table format
+ int cf = in.readTTFUShort();
+ if ( cf == 1 ) {
+ gct = readClassDefTableFormat1 ( label, tableOffset, cf );
+ } else if ( cf == 2 ) {
+ gct = readClassDefTableFormat2 ( label, tableOffset, cf );
+ } else {
+ throw new AdvancedTypographicTableFormatException ( "unsupported class definition table format: " + cf );
+ }
+ in.seekSet ( cp );
+ return gct;
+ }
+
+ private void readSingleSubTableFormat1(int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException {
+ String tableTag = "GSUB";
+ in.seekSet(subtableOffset);
+ // skip over format (already known)
+ in.skip ( 2 );
+ // read coverage offset
+ int co = in.readTTFUShort();
+ // read delta glyph
+ int dg = in.readTTFShort();
+ // dump info if debugging
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " single substitution subtable format: " + subtableFormat + " (delta)" );
+ log.debug(tableTag + " single substitution coverage table offset: " + co );
+ log.debug(tableTag + " single substitution delta: " + dg );
+ }
+ // read coverage table
+ seMapping = readCoverageTable ( tableTag + " single substitution coverage", subtableOffset + co );
+ seEntries.add ( Integer.valueOf ( dg ) );
+ }
+
+ private void readSingleSubTableFormat2(int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException {
+ String tableTag = "GSUB";
+ in.seekSet(subtableOffset);
+ // skip over format (already known)
+ in.skip ( 2 );
+ // read coverage offset
+ int co = in.readTTFUShort();
+ // read glyph count
+ int ng = in.readTTFUShort();
+ // dump info if debugging
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " single substitution subtable format: " + subtableFormat + " (mapped)" );
+ log.debug(tableTag + " single substitution coverage table offset: " + co );
+ log.debug(tableTag + " single substitution glyph count: " + ng );
+ }
+ // read coverage table
+ seMapping = readCoverageTable ( tableTag + " single substitution coverage", subtableOffset + co );
+ // read glyph substitutions
+ int[] gsa = new int[ng];
+ for ( int i = 0, n = ng; i < n; i++ ) {
+ int gs = in.readTTFUShort();
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " single substitution glyph[" + i + "]: " + gs );
+ }
+ gsa[i] = gs;
+ seEntries.add ( Integer.valueOf ( gs ) );
+ }
+ }
+
+ private int readSingleSubTable(int lookupType, int lookupFlags, long subtableOffset) throws IOException {
+ in.seekSet(subtableOffset);
+ // read substitution subtable format
+ int sf = in.readTTFUShort();
+ if ( sf == 1 ) {
+ readSingleSubTableFormat1 ( lookupType, lookupFlags, subtableOffset, sf );
+ } else if ( sf == 2 ) {
+ readSingleSubTableFormat2 ( lookupType, lookupFlags, subtableOffset, sf );
+ } else {
+ throw new AdvancedTypographicTableFormatException ( "unsupported single substitution subtable format: " + sf );
+ }
+ return sf;
+ }
+
+ private void readMultipleSubTableFormat1(int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException {
+ String tableTag = "GSUB";
+ in.seekSet(subtableOffset);
+ // skip over format (already known)
+ in.skip ( 2 );
+ // read coverage offset
+ int co = in.readTTFUShort();
+ // read sequence count
+ int ns = in.readTTFUShort();
+ // dump info if debugging
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " multiple substitution subtable format: " + subtableFormat + " (mapped)" );
+ log.debug(tableTag + " multiple substitution coverage table offset: " + co );
+ log.debug(tableTag + " multiple substitution sequence count: " + ns );
+ }
+ // read coverage table
+ seMapping = readCoverageTable ( tableTag + " multiple substitution coverage", subtableOffset + co );
+ // read sequence table offsets
+ int[] soa = new int[ns];
+ for ( int i = 0, n = ns; i < n; i++ ) {
+ soa[i] = in.readTTFUShort();
+ }
+ // read sequence tables
+ int[][] gsa = new int [ ns ] [];
+ for ( int i = 0, n = ns; i < n; i++ ) {
+ int so = soa[i];
+ int[] ga;
+ if ( so > 0 ) {
+ in.seekSet(subtableOffset + so);
+ // read glyph count
+ int ng = in.readTTFUShort();
+ ga = new int[ng];
+ for ( int j = 0; j < ng; j++ ) {
+ ga[j] = in.readTTFUShort();
+ }
+ } else {
+ ga = null;
+ }
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " multiple substitution sequence[" + i + "]: " + toString ( ga ) );
+ }
+ gsa [ i ] = ga;
+ }
+ seEntries.add ( gsa );
+ }
+
+ private int readMultipleSubTable(int lookupType, int lookupFlags, long subtableOffset) throws IOException {
+ in.seekSet(subtableOffset);
+ // read substitution subtable format
+ int sf = in.readTTFUShort();
+ if ( sf == 1 ) {
+ readMultipleSubTableFormat1 ( lookupType, lookupFlags, subtableOffset, sf );
+ } else {
+ throw new AdvancedTypographicTableFormatException ( "unsupported multiple substitution subtable format: " + sf );
+ }
+ return sf;
+ }
+
+ private void readAlternateSubTableFormat1(int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException {
+ String tableTag = "GSUB";
+ in.seekSet(subtableOffset);
+ // skip over format (already known)
+ in.skip ( 2 );
+ // read coverage offset
+ int co = in.readTTFUShort();
+ // read alternate set count
+ int ns = in.readTTFUShort();
+ // dump info if debugging
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " alternate substitution subtable format: " + subtableFormat + " (mapped)" );
+ log.debug(tableTag + " alternate substitution coverage table offset: " + co );
+ log.debug(tableTag + " alternate substitution alternate set count: " + ns );
+ }
+ // read coverage table
+ seMapping = readCoverageTable ( tableTag + " alternate substitution coverage", subtableOffset + co );
+ // read alternate set table offsets
+ int[] soa = new int[ns];
+ for ( int i = 0, n = ns; i < n; i++ ) {
+ soa[i] = in.readTTFUShort();
+ }
+ // read alternate set tables
+ for ( int i = 0, n = ns; i < n; i++ ) {
+ int so = soa[i];
+ in.seekSet(subtableOffset + so);
+ // read glyph count
+ int ng = in.readTTFUShort();
+ int[] ga = new int[ng];
+ for ( int j = 0; j < ng; j++ ) {
+ int gs = in.readTTFUShort();
+ ga[j] = gs;
+ }
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " alternate substitution alternate set[" + i + "]: " + toString ( ga ) );
+ }
+ seEntries.add ( ga );
+ }
+ }
+
+ private int readAlternateSubTable(int lookupType, int lookupFlags, long subtableOffset) throws IOException {
+ in.seekSet(subtableOffset);
+ // read substitution subtable format
+ int sf = in.readTTFUShort();
+ if ( sf == 1 ) {
+ readAlternateSubTableFormat1 ( lookupType, lookupFlags, subtableOffset, sf );
+ } else {
+ throw new AdvancedTypographicTableFormatException ( "unsupported alternate substitution subtable format: " + sf );
+ }
+ return sf;
+ }
+
+ private void readLigatureSubTableFormat1(int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException {
+ String tableTag = "GSUB";
+ in.seekSet(subtableOffset);
+ // skip over format (already known)
+ in.skip ( 2 );
+ // read coverage offset
+ int co = in.readTTFUShort();
+ // read ligature set count
+ int ns = in.readTTFUShort();
+ // dump info if debugging
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " ligature substitution subtable format: " + subtableFormat + " (mapped)" );
+ log.debug(tableTag + " ligature substitution coverage table offset: " + co );
+ log.debug(tableTag + " ligature substitution ligature set count: " + ns );
+ }
+ // read coverage table
+ seMapping = readCoverageTable ( tableTag + " ligature substitution coverage", subtableOffset + co );
+ // read ligature set table offsets
+ int[] soa = new int[ns];
+ for ( int i = 0, n = ns; i < n; i++ ) {
+ soa[i] = in.readTTFUShort();
+ }
+ // read ligature set tables
+ for ( int i = 0, n = ns; i < n; i++ ) {
+ int so = soa[i];
+ in.seekSet(subtableOffset + so);
+ // read ligature table count
+ int nl = in.readTTFUShort();
+ int[] loa = new int[nl];
+ for ( int j = 0; j < nl; j++ ) {
+ loa[j] = in.readTTFUShort();
+ }
+ List ligs = new java.util.ArrayList();
+ for ( int j = 0; j < nl; j++ ) {
+ int lo = loa[j];
+ in.seekSet(subtableOffset + so + lo);
+ // read ligature glyph id
+ int lg = in.readTTFUShort();
+ // read ligature (input) component count
+ int nc = in.readTTFUShort();
+ int[] ca = new int [ nc - 1 ];
+ // read ligature (input) component glyph ids
+ for ( int k = 0; k < nc - 1; k++ ) {
+ ca[k] = in.readTTFUShort();
+ }
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " ligature substitution ligature set[" + i + "]: ligature(" + lg + "), components: " + toString ( ca ) );
+ }
+ ligs.add ( new GlyphSubstitutionTable.Ligature ( lg, ca ) );
+ }
+ seEntries.add ( new GlyphSubstitutionTable.LigatureSet ( ligs ) );
+ }
+ }
+
+ private int readLigatureSubTable(int lookupType, int lookupFlags, long subtableOffset) throws IOException {
+ in.seekSet(subtableOffset);
+ // read substitution subtable format
+ int sf = in.readTTFUShort();
+ if ( sf == 1 ) {
+ readLigatureSubTableFormat1 ( lookupType, lookupFlags, subtableOffset, sf );
+ } else {
+ throw new AdvancedTypographicTableFormatException ( "unsupported ligature substitution subtable format: " + sf );
+ }
+ return sf;
+ }
+
+ private GlyphTable.RuleLookup[] readRuleLookups(int numLookups, String header) throws IOException {
+ GlyphTable.RuleLookup[] la = new GlyphTable.RuleLookup [ numLookups ];
+ for ( int i = 0, n = numLookups; i < n; i++ ) {
+ int sequenceIndex = in.readTTFUShort();
+ int lookupIndex = in.readTTFUShort();
+ la [ i ] = new GlyphTable.RuleLookup ( sequenceIndex, lookupIndex );
+ // dump info if debugging and header is non-null
+ if ( log.isDebugEnabled() && ( header != null ) ) {
+ log.debug(header + "lookup[" + i + "]: " + la[i]);
+ }
+ }
+ return la;
+ }
+
+ private void readContextualSubTableFormat1(int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException {
+ String tableTag = "GSUB";
+ in.seekSet(subtableOffset);
+ // skip over format (already known)
+ in.skip ( 2 );
+ // read coverage offset
+ int co = in.readTTFUShort();
+ // read rule set count
+ int nrs = in.readTTFUShort();
+ // read rule set offsets
+ int[] rsoa = new int [ nrs ];
+ for ( int i = 0; i < nrs; i++ ) {
+ rsoa [ i ] = in.readTTFUShort();
+ }
+ // dump info if debugging
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " contextual substitution format: " + subtableFormat + " (glyphs)" );
+ log.debug(tableTag + " contextual substitution coverage table offset: " + co );
+ log.debug(tableTag + " contextual substitution rule set count: " + nrs );
+ for ( int i = 0; i < nrs; i++ ) {
+ log.debug(tableTag + " contextual substitution rule set offset[" + i + "]: " + rsoa[i] );
+ }
+ }
+ // read coverage table
+ GlyphCoverageTable ct;
+ if ( co > 0 ) {
+ ct = readCoverageTable ( tableTag + " contextual substitution coverage", subtableOffset + co );
+ } else {
+ ct = null;
+ }
+ // read rule sets
+ GlyphTable.RuleSet[] rsa = new GlyphTable.RuleSet [ nrs ];
+ String header = null;
+ for ( int i = 0; i < nrs; i++ ) {
+ GlyphTable.RuleSet rs;
+ int rso = rsoa [ i ];
+ if ( rso > 0 ) {
+ // seek to rule set [ i ]
+ in.seekSet ( subtableOffset + rso );
+ // read rule count
+ int nr = in.readTTFUShort();
+ // read rule offsets
+ int[] roa = new int [ nr ];
+ GlyphTable.Rule[] ra = new GlyphTable.Rule [ nr ];
+ for ( int j = 0; j < nr; j++ ) {
+ roa [ j ] = in.readTTFUShort();
+ }
+ // read glyph sequence rules
+ for ( int j = 0; j < nr; j++ ) {
+ GlyphTable.GlyphSequenceRule r;
+ int ro = roa [ j ];
+ if ( ro > 0 ) {
+ // seek to rule [ j ]
+ in.seekSet ( subtableOffset + rso + ro );
+ // read glyph count
+ int ng = in.readTTFUShort();
+ // read rule lookup count
+ int nl = in.readTTFUShort();
+ // read glyphs
+ int[] glyphs = new int [ ng - 1 ];
+ for ( int k = 0, nk = glyphs.length; k < nk; k++ ) {
+ glyphs [ k ] = in.readTTFUShort();
+ }
+ // read rule lookups
+ if (log.isDebugEnabled()) {
+ header = tableTag + " contextual substitution lookups @rule[" + i + "][" + j + "]: ";
+ }
+ GlyphTable.RuleLookup[] lookups = readRuleLookups ( nl, header );
+ r = new GlyphTable.GlyphSequenceRule ( lookups, ng, glyphs );
+ } else {
+ r = null;
+ }
+ ra [ j ] = r;
+ }
+ rs = new GlyphTable.HomogeneousRuleSet ( ra );
+ } else {
+ rs = null;
+ }
+ rsa [ i ] = rs;
+ }
+ // store results
+ seMapping = ct;
+ seEntries.add ( rsa );
+ }
+
+ private void readContextualSubTableFormat2(int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException {
+ String tableTag = "GSUB";
+ in.seekSet(subtableOffset);
+ // skip over format (already known)
+ in.skip ( 2 );
+ // read coverage offset
+ int co = in.readTTFUShort();
+ // read class def table offset
+ int cdo = in.readTTFUShort();
+ // read class rule set count
+ int ngc = in.readTTFUShort();
+ // read class rule set offsets
+ int[] csoa = new int [ ngc ];
+ for ( int i = 0; i < ngc; i++ ) {
+ csoa [ i ] = in.readTTFUShort();
+ }
+ // dump info if debugging
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " contextual substitution format: " + subtableFormat + " (glyph classes)" );
+ log.debug(tableTag + " contextual substitution coverage table offset: " + co );
+ log.debug(tableTag + " contextual substitution class set count: " + ngc );
+ for ( int i = 0; i < ngc; i++ ) {
+ log.debug(tableTag + " contextual substitution class set offset[" + i + "]: " + csoa[i] );
+ }
+ }
+ // read coverage table
+ GlyphCoverageTable ct;
+ if ( co > 0 ) {
+ ct = readCoverageTable ( tableTag + " contextual substitution coverage", subtableOffset + co );
+ } else {
+ ct = null;
+ }
+ // read class definition table
+ GlyphClassTable cdt;
+ if ( cdo > 0 ) {
+ cdt = readClassDefTable ( tableTag + " contextual substitution class definition", subtableOffset + cdo );
+ } else {
+ cdt = null;
+ }
+ // read rule sets
+ GlyphTable.RuleSet[] rsa = new GlyphTable.RuleSet [ ngc ];
+ String header = null;
+ for ( int i = 0; i < ngc; i++ ) {
+ int cso = csoa [ i ];
+ GlyphTable.RuleSet rs;
+ if ( cso > 0 ) {
+ // seek to rule set [ i ]
+ in.seekSet ( subtableOffset + cso );
+ // read rule count
+ int nr = in.readTTFUShort();
+ // read rule offsets
+ int[] roa = new int [ nr ];
+ GlyphTable.Rule[] ra = new GlyphTable.Rule [ nr ];
+ for ( int j = 0; j < nr; j++ ) {
+ roa [ j ] = in.readTTFUShort();
+ }
+ // read glyph sequence rules
+ for ( int j = 0; j < nr; j++ ) {
+ int ro = roa [ j ];
+ GlyphTable.ClassSequenceRule r;
+ if ( ro > 0 ) {
+ // seek to rule [ j ]
+ in.seekSet ( subtableOffset + cso + ro );
+ // read glyph count
+ int ng = in.readTTFUShort();
+ // read rule lookup count
+ int nl = in.readTTFUShort();
+ // read classes
+ int[] classes = new int [ ng - 1 ];
+ for ( int k = 0, nk = classes.length; k < nk; k++ ) {
+ classes [ k ] = in.readTTFUShort();
+ }
+ // read rule lookups
+ if (log.isDebugEnabled()) {
+ header = tableTag + " contextual substitution lookups @rule[" + i + "][" + j + "]: ";
+ }
+ GlyphTable.RuleLookup[] lookups = readRuleLookups ( nl, header );
+ r = new GlyphTable.ClassSequenceRule ( lookups, ng, classes );
+ } else {
+ assert ro > 0 : "unexpected null subclass rule offset";
+ r = null;
+ }
+ ra [ j ] = r;
+ }
+ rs = new GlyphTable.HomogeneousRuleSet ( ra );
+ } else {
+ rs = null;
+ }
+ rsa [ i ] = rs;
+ }
+ // store results
+ seMapping = ct;
+ seEntries.add ( cdt );
+ seEntries.add ( Integer.valueOf ( ngc ) );
+ seEntries.add ( rsa );
+ }
+
+ private void readContextualSubTableFormat3(int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException {
+ String tableTag = "GSUB";
+ in.seekSet(subtableOffset);
+ // skip over format (already known)
+ in.skip ( 2 );
+ // read glyph (input sequence length) count
+ int ng = in.readTTFUShort();
+ // read substitution lookup count
+ int nl = in.readTTFUShort();
+ // read glyph coverage offsets, one per glyph input sequence length count
+ int[] gcoa = new int [ ng ];
+ for ( int i = 0; i < ng; i++ ) {
+ gcoa [ i ] = in.readTTFUShort();
+ }
+ // dump info if debugging
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " contextual substitution format: " + subtableFormat + " (glyph sets)" );
+ log.debug(tableTag + " contextual substitution glyph input sequence length count: " + ng );
+ log.debug(tableTag + " contextual substitution lookup count: " + nl );
+ for ( int i = 0; i < ng; i++ ) {
+ log.debug(tableTag + " contextual substitution coverage table offset[" + i + "]: " + gcoa[i] );
+ }
+ }
+ // read coverage tables
+ GlyphCoverageTable[] gca = new GlyphCoverageTable [ ng ];
+ for ( int i = 0; i < ng; i++ ) {
+ int gco = gcoa [ i ];
+ GlyphCoverageTable gct;
+ if ( gco > 0 ) {
+ gct = readCoverageTable ( tableTag + " contextual substitution coverage[" + i + "]", subtableOffset + gco );
+ } else {
+ gct = null;
+ }
+ gca [ i ] = gct;
+ }
+ // read rule lookups
+ String header = null;
+ if (log.isDebugEnabled()) {
+ header = tableTag + " contextual substitution lookups: ";
+ }
+ GlyphTable.RuleLookup[] lookups = readRuleLookups ( nl, header );
+ // construct rule, rule set, and rule set array
+ GlyphTable.Rule r = new GlyphTable.CoverageSequenceRule ( lookups, ng, gca );
+ GlyphTable.RuleSet rs = new GlyphTable.HomogeneousRuleSet ( new GlyphTable.Rule[] {r} );
+ GlyphTable.RuleSet[] rsa = new GlyphTable.RuleSet[] {rs};
+ // store results
+ assert ( gca != null ) && ( gca.length > 0 );
+ seMapping = gca[0];
+ seEntries.add ( rsa );
+ }
+
+ private int readContextualSubTable(int lookupType, int lookupFlags, long subtableOffset) throws IOException {
+ in.seekSet(subtableOffset);
+ // read substitution subtable format
+ int sf = in.readTTFUShort();
+ if ( sf == 1 ) {
+ readContextualSubTableFormat1 ( lookupType, lookupFlags, subtableOffset, sf );
+ } else if ( sf == 2 ) {
+ readContextualSubTableFormat2 ( lookupType, lookupFlags, subtableOffset, sf );
+ } else if ( sf == 3 ) {
+ readContextualSubTableFormat3 ( lookupType, lookupFlags, subtableOffset, sf );
+ } else {
+ throw new AdvancedTypographicTableFormatException ( "unsupported contextual substitution subtable format: " + sf );
+ }
+ return sf;
+ }
+
+ private void readChainedContextualSubTableFormat1(int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException {
+ String tableTag = "GSUB";
+ in.seekSet(subtableOffset);
+ // skip over format (already known)
+ in.skip ( 2 );
+ // read coverage offset
+ int co = in.readTTFUShort();
+ // read rule set count
+ int nrs = in.readTTFUShort();
+ // read rule set offsets
+ int[] rsoa = new int [ nrs ];
+ for ( int i = 0; i < nrs; i++ ) {
+ rsoa [ i ] = in.readTTFUShort();
+ }
+ // dump info if debugging
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " chained contextual substitution format: " + subtableFormat + " (glyphs)" );
+ log.debug(tableTag + " chained contextual substitution coverage table offset: " + co );
+ log.debug(tableTag + " chained contextual substitution rule set count: " + nrs );
+ for ( int i = 0; i < nrs; i++ ) {
+ log.debug(tableTag + " chained contextual substitution rule set offset[" + i + "]: " + rsoa[i] );
+ }
+ }
+ // read coverage table
+ GlyphCoverageTable ct;
+ if ( co > 0 ) {
+ ct = readCoverageTable ( tableTag + " chained contextual substitution coverage", subtableOffset + co );
+ } else {
+ ct = null;
+ }
+ // read rule sets
+ GlyphTable.RuleSet[] rsa = new GlyphTable.RuleSet [ nrs ];
+ String header = null;
+ for ( int i = 0; i < nrs; i++ ) {
+ GlyphTable.RuleSet rs;
+ int rso = rsoa [ i ];
+ if ( rso > 0 ) {
+ // seek to rule set [ i ]
+ in.seekSet ( subtableOffset + rso );
+ // read rule count
+ int nr = in.readTTFUShort();
+ // read rule offsets
+ int[] roa = new int [ nr ];
+ GlyphTable.Rule[] ra = new GlyphTable.Rule [ nr ];
+ for ( int j = 0; j < nr; j++ ) {
+ roa [ j ] = in.readTTFUShort();
+ }
+ // read glyph sequence rules
+ for ( int j = 0; j < nr; j++ ) {
+ GlyphTable.ChainedGlyphSequenceRule r;
+ int ro = roa [ j ];
+ if ( ro > 0 ) {
+ // seek to rule [ j ]
+ in.seekSet ( subtableOffset + rso + ro );
+ // read backtrack glyph count
+ int nbg = in.readTTFUShort();
+ // read backtrack glyphs
+ int[] backtrackGlyphs = new int [ nbg ];
+ for ( int k = 0, nk = backtrackGlyphs.length; k < nk; k++ ) {
+ backtrackGlyphs [ k ] = in.readTTFUShort();
+ }
+ // read input glyph count
+ int nig = in.readTTFUShort();
+ // read glyphs
+ int[] glyphs = new int [ nig - 1 ];
+ for ( int k = 0, nk = glyphs.length; k < nk; k++ ) {
+ glyphs [ k ] = in.readTTFUShort();
+ }
+ // read lookahead glyph count
+ int nlg = in.readTTFUShort();
+ // read lookahead glyphs
+ int[] lookaheadGlyphs = new int [ nlg ];
+ for ( int k = 0, nk = lookaheadGlyphs.length; k < nk; k++ ) {
+ lookaheadGlyphs [ k ] = in.readTTFUShort();
+ }
+ // read rule lookup count
+ int nl = in.readTTFUShort();
+ // read rule lookups
+ if (log.isDebugEnabled()) {
+ header = tableTag + " contextual substitution lookups @rule[" + i + "][" + j + "]: ";
+ }
+ GlyphTable.RuleLookup[] lookups = readRuleLookups ( nl, header );
+ r = new GlyphTable.ChainedGlyphSequenceRule ( lookups, nig, glyphs, backtrackGlyphs, lookaheadGlyphs );
+ } else {
+ r = null;
+ }
+ ra [ j ] = r;
+ }
+ rs = new GlyphTable.HomogeneousRuleSet ( ra );
+ } else {
+ rs = null;
+ }
+ rsa [ i ] = rs;
+ }
+ // store results
+ seMapping = ct;
+ seEntries.add ( rsa );
+ }
+
+ private void readChainedContextualSubTableFormat2(int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException {
+ String tableTag = "GSUB";
+ in.seekSet(subtableOffset);
+ // skip over format (already known)
+ in.skip ( 2 );
+ // read coverage offset
+ int co = in.readTTFUShort();
+ // read backtrack class def table offset
+ int bcdo = in.readTTFUShort();
+ // read input class def table offset
+ int icdo = in.readTTFUShort();
+ // read lookahead class def table offset
+ int lcdo = in.readTTFUShort();
+ // read class set count
+ int ngc = in.readTTFUShort();
+ // read class set offsets
+ int[] csoa = new int [ ngc ];
+ for ( int i = 0; i < ngc; i++ ) {
+ csoa [ i ] = in.readTTFUShort();
+ }
+ // dump info if debugging
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " chained contextual substitution format: " + subtableFormat + " (glyph classes)" );
+ log.debug(tableTag + " chained contextual substitution coverage table offset: " + co );
+ log.debug(tableTag + " chained contextual substitution class set count: " + ngc );
+ for ( int i = 0; i < ngc; i++ ) {
+ log.debug(tableTag + " chained contextual substitution class set offset[" + i + "]: " + csoa[i] );
+ }
+ }
+ // read coverage table
+ GlyphCoverageTable ct;
+ if ( co > 0 ) {
+ ct = readCoverageTable ( tableTag + " chained contextual substitution coverage", subtableOffset + co );
+ } else {
+ ct = null;
+ }
+ // read backtrack class definition table
+ GlyphClassTable bcdt;
+ if ( bcdo > 0 ) {
+ bcdt = readClassDefTable ( tableTag + " contextual substitution backtrack class definition", subtableOffset + bcdo );
+ } else {
+ bcdt = null;
+ }
+ // read input class definition table
+ GlyphClassTable icdt;
+ if ( icdo > 0 ) {
+ icdt = readClassDefTable ( tableTag + " contextual substitution input class definition", subtableOffset + icdo );
+ } else {
+ icdt = null;
+ }
+ // read lookahead class definition table
+ GlyphClassTable lcdt;
+ if ( lcdo > 0 ) {
+ lcdt = readClassDefTable ( tableTag + " contextual substitution lookahead class definition", subtableOffset + lcdo );
+ } else {
+ lcdt = null;
+ }
+ // read rule sets
+ GlyphTable.RuleSet[] rsa = new GlyphTable.RuleSet [ ngc ];
+ String header = null;
+ for ( int i = 0; i < ngc; i++ ) {
+ int cso = csoa [ i ];
+ GlyphTable.RuleSet rs;
+ if ( cso > 0 ) {
+ // seek to rule set [ i ]
+ in.seekSet ( subtableOffset + cso );
+ // read rule count
+ int nr = in.readTTFUShort();
+ // read rule offsets
+ int[] roa = new int [ nr ];
+ GlyphTable.Rule[] ra = new GlyphTable.Rule [ nr ];
+ for ( int j = 0; j < nr; j++ ) {
+ roa [ j ] = in.readTTFUShort();
+ }
+ // read glyph sequence rules
+ for ( int j = 0; j < nr; j++ ) {
+ int ro = roa [ j ];
+ GlyphTable.ChainedClassSequenceRule r;
+ if ( ro > 0 ) {
+ // seek to rule [ j ]
+ in.seekSet ( subtableOffset + cso + ro );
+ // read backtrack glyph class count
+ int nbc = in.readTTFUShort();
+ // read backtrack glyph classes
+ int[] backtrackClasses = new int [ nbc ];
+ for ( int k = 0, nk = backtrackClasses.length; k < nk; k++ ) {
+ backtrackClasses [ k ] = in.readTTFUShort();
+ }
+ // read input glyph class count
+ int nic = in.readTTFUShort();
+ // read input glyph classes
+ int[] classes = new int [ nic - 1 ];
+ for ( int k = 0, nk = classes.length; k < nk; k++ ) {
+ classes [ k ] = in.readTTFUShort();
+ }
+ // read lookahead glyph class count
+ int nlc = in.readTTFUShort();
+ // read lookahead glyph classes
+ int[] lookaheadClasses = new int [ nlc ];
+ for ( int k = 0, nk = lookaheadClasses.length; k < nk; k++ ) {
+ lookaheadClasses [ k ] = in.readTTFUShort();
+ }
+ // read rule lookup count
+ int nl = in.readTTFUShort();
+ // read rule lookups
+ if (log.isDebugEnabled()) {
+ header = tableTag + " contextual substitution lookups @rule[" + i + "][" + j + "]: ";
+ }
+ GlyphTable.RuleLookup[] lookups = readRuleLookups ( nl, header );
+ r = new GlyphTable.ChainedClassSequenceRule ( lookups, nic, classes, backtrackClasses, lookaheadClasses );
+ } else {
+ r = null;
+ }
+ ra [ j ] = r;
+ }
+ rs = new GlyphTable.HomogeneousRuleSet ( ra );
+ } else {
+ rs = null;
+ }
+ rsa [ i ] = rs;
+ }
+ // store results
+ seMapping = ct;
+ seEntries.add ( icdt );
+ seEntries.add ( bcdt );
+ seEntries.add ( lcdt );
+ seEntries.add ( Integer.valueOf ( ngc ) );
+ seEntries.add ( rsa );
+ }
+
+ private void readChainedContextualSubTableFormat3(int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException {
+ String tableTag = "GSUB";
+ in.seekSet(subtableOffset);
+ // skip over format (already known)
+ in.skip ( 2 );
+ // read backtrack glyph count
+ int nbg = in.readTTFUShort();
+ // read backtrack glyph coverage offsets
+ int[] bgcoa = new int [ nbg ];
+ for ( int i = 0; i < nbg; i++ ) {
+ bgcoa [ i ] = in.readTTFUShort();
+ }
+ // read input glyph count
+ int nig = in.readTTFUShort();
+ // read input glyph coverage offsets
+ int[] igcoa = new int [ nig ];
+ for ( int i = 0; i < nig; i++ ) {
+ igcoa [ i ] = in.readTTFUShort();
+ }
+ // read lookahead glyph count
+ int nlg = in.readTTFUShort();
+ // read lookahead glyph coverage offsets
+ int[] lgcoa = new int [ nlg ];
+ for ( int i = 0; i < nlg; i++ ) {
+ lgcoa [ i ] = in.readTTFUShort();
+ }
+ // read substitution lookup count
+ int nl = in.readTTFUShort();
+ // dump info if debugging
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " chained contextual substitution format: " + subtableFormat + " (glyph sets)" );
+ log.debug(tableTag + " chained contextual substitution backtrack glyph count: " + nbg );
+ for ( int i = 0; i < nbg; i++ ) {
+ log.debug(tableTag + " chained contextual substitution backtrack coverage table offset[" + i + "]: " + bgcoa[i] );
+ }
+ log.debug(tableTag + " chained contextual substitution input glyph count: " + nig );
+ for ( int i = 0; i < nig; i++ ) {
+ log.debug(tableTag + " chained contextual substitution input coverage table offset[" + i + "]: " + igcoa[i] );
+ }
+ log.debug(tableTag + " chained contextual substitution lookahead glyph count: " + nlg );
+ for ( int i = 0; i < nlg; i++ ) {
+ log.debug(tableTag + " chained contextual substitution lookahead coverage table offset[" + i + "]: " + lgcoa[i] );
+ }
+ log.debug(tableTag + " chained contextual substitution lookup count: " + nl );
+ }
+ // read backtrack coverage tables
+ GlyphCoverageTable[] bgca = new GlyphCoverageTable[nbg];
+ for ( int i = 0; i < nbg; i++ ) {
+ int bgco = bgcoa [ i ];
+ GlyphCoverageTable bgct;
+ if ( bgco > 0 ) {
+ bgct = readCoverageTable ( tableTag + " chained contextual substitution backtrack coverage[" + i + "]", subtableOffset + bgco );
+ } else {
+ bgct = null;
+ }
+ bgca[i] = bgct;
+ }
+ // read input coverage tables
+ GlyphCoverageTable[] igca = new GlyphCoverageTable[nig];
+ for ( int i = 0; i < nig; i++ ) {
+ int igco = igcoa [ i ];
+ GlyphCoverageTable igct;
+ if ( igco > 0 ) {
+ igct = readCoverageTable ( tableTag + " chained contextual substitution input coverage[" + i + "]", subtableOffset + igco );
+ } else {
+ igct = null;
+ }
+ igca[i] = igct;
+ }
+ // read lookahead coverage tables
+ GlyphCoverageTable[] lgca = new GlyphCoverageTable[nlg];
+ for ( int i = 0; i < nlg; i++ ) {
+ int lgco = lgcoa [ i ];
+ GlyphCoverageTable lgct;
+ if ( lgco > 0 ) {
+ lgct = readCoverageTable ( tableTag + " chained contextual substitution lookahead coverage[" + i + "]", subtableOffset + lgco );
+ } else {
+ lgct = null;
+ }
+ lgca[i] = lgct;
+ }
+ // read rule lookups
+ String header = null;
+ if (log.isDebugEnabled()) {
+ header = tableTag + " chained contextual substitution lookups: ";
+ }
+ GlyphTable.RuleLookup[] lookups = readRuleLookups ( nl, header );
+ // construct rule, rule set, and rule set array
+ GlyphTable.Rule r = new GlyphTable.ChainedCoverageSequenceRule ( lookups, nig, igca, bgca, lgca );
+ GlyphTable.RuleSet rs = new GlyphTable.HomogeneousRuleSet ( new GlyphTable.Rule[] {r} );
+ GlyphTable.RuleSet[] rsa = new GlyphTable.RuleSet[] {rs};
+ // store results
+ assert ( igca != null ) && ( igca.length > 0 );
+ seMapping = igca[0];
+ seEntries.add ( rsa );
+ }
+
+ private int readChainedContextualSubTable(int lookupType, int lookupFlags, long subtableOffset) throws IOException {
+ in.seekSet(subtableOffset);
+ // read substitution subtable format
+ int sf = in.readTTFUShort();
+ if ( sf == 1 ) {
+ readChainedContextualSubTableFormat1 ( lookupType, lookupFlags, subtableOffset, sf );
+ } else if ( sf == 2 ) {
+ readChainedContextualSubTableFormat2 ( lookupType, lookupFlags, subtableOffset, sf );
+ } else if ( sf == 3 ) {
+ readChainedContextualSubTableFormat3 ( lookupType, lookupFlags, subtableOffset, sf );
+ } else {
+ throw new AdvancedTypographicTableFormatException ( "unsupported chained contextual substitution subtable format: " + sf );
+ }
+ return sf;
+ }
+
+ private void readExtensionSubTableFormat1(int lookupType, int lookupFlags, int lookupSequence, int subtableSequence, long subtableOffset, int subtableFormat) throws IOException {
+ String tableTag = "GSUB";
+ in.seekSet(subtableOffset);
+ // skip over format (already known)
+ in.skip ( 2 );
+ // read extension lookup type
+ int lt = in.readTTFUShort();
+ // read extension offset
+ long eo = in.readTTFULong();
+ // dump info if debugging
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " extension substitution subtable format: " + subtableFormat );
+ log.debug(tableTag + " extension substitution lookup type: " + lt );
+ log.debug(tableTag + " extension substitution lookup table offset: " + eo );
+ }
+ // read referenced subtable from extended offset
+ readGSUBSubtable ( lt, lookupFlags, lookupSequence, subtableSequence, subtableOffset + eo );
+ }
+
+ private int readExtensionSubTable(int lookupType, int lookupFlags, int lookupSequence, int subtableSequence, long subtableOffset) throws IOException {
+ in.seekSet(subtableOffset);
+ // read substitution subtable format
+ int sf = in.readTTFUShort();
+ if ( sf == 1 ) {
+ readExtensionSubTableFormat1 ( lookupType, lookupFlags, lookupSequence, subtableSequence, subtableOffset, sf );
+ } else {
+ throw new AdvancedTypographicTableFormatException ( "unsupported extension substitution subtable format: " + sf );
+ }
+ return sf;
+ }
+
+ private void readReverseChainedSingleSubTableFormat1(int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException {
+ String tableTag = "GSUB";
+ in.seekSet(subtableOffset);
+ // skip over format (already known)
+ in.skip ( 2 );
+ // read coverage offset
+ int co = in.readTTFUShort();
+ // read backtrack glyph count
+ int nbg = in.readTTFUShort();
+ // read backtrack glyph coverage offsets
+ int[] bgcoa = new int [ nbg ];
+ for ( int i = 0; i < nbg; i++ ) {
+ bgcoa [ i ] = in.readTTFUShort();
+ }
+ // read lookahead glyph count
+ int nlg = in.readTTFUShort();
+ // read backtrack glyph coverage offsets
+ int[] lgcoa = new int [ nlg ];
+ for ( int i = 0; i < nlg; i++ ) {
+ lgcoa [ i ] = in.readTTFUShort();
+ }
+ // read substitution (output) glyph count
+ int ng = in.readTTFUShort();
+ // read substitution (output) glyphs
+ int[] glyphs = new int [ ng ];
+ for ( int i = 0, n = ng; i < n; i++ ) {
+ glyphs [ i ] = in.readTTFUShort();
+ }
+ // dump info if debugging
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " reverse chained contextual substitution format: " + subtableFormat );
+ log.debug(tableTag + " reverse chained contextual substitution coverage table offset: " + co );
+ log.debug(tableTag + " reverse chained contextual substitution backtrack glyph count: " + nbg );
+ for ( int i = 0; i < nbg; i++ ) {
+ log.debug(tableTag + " reverse chained contextual substitution backtrack coverage table offset[" + i + "]: " + bgcoa[i] );
+ }
+ log.debug(tableTag + " reverse chained contextual substitution lookahead glyph count: " + nlg );
+ for ( int i = 0; i < nlg; i++ ) {
+ log.debug(tableTag + " reverse chained contextual substitution lookahead coverage table offset[" + i + "]: " + lgcoa[i] );
+ }
+ log.debug(tableTag + " reverse chained contextual substitution glyphs: " + toString(glyphs) );
+ }
+ // read coverage table
+ GlyphCoverageTable ct = readCoverageTable ( tableTag + " reverse chained contextual substitution coverage", subtableOffset + co );
+ // read backtrack coverage tables
+ GlyphCoverageTable[] bgca = new GlyphCoverageTable[nbg];
+ for ( int i = 0; i < nbg; i++ ) {
+ int bgco = bgcoa[i];
+ GlyphCoverageTable bgct;
+ if ( bgco > 0 ) {
+ bgct = readCoverageTable ( tableTag + " reverse chained contextual substitution backtrack coverage[" + i + "]", subtableOffset + bgco );
+ } else {
+ bgct = null;
+ }
+ bgca[i] = bgct;
+ }
+ // read lookahead coverage tables
+ GlyphCoverageTable[] lgca = new GlyphCoverageTable[nlg];
+ for ( int i = 0; i < nlg; i++ ) {
+ int lgco = lgcoa[i];
+ GlyphCoverageTable lgct;
+ if ( lgco > 0 ) {
+ lgct = readCoverageTable ( tableTag + " reverse chained contextual substitution lookahead coverage[" + i + "]", subtableOffset + lgco );
+ } else {
+ lgct = null;
+ }
+ lgca[i] = lgct;
+ }
+ // store results
+ seMapping = ct;
+ seEntries.add ( bgca );
+ seEntries.add ( lgca );
+ seEntries.add ( glyphs );
+ }
+
+ private int readReverseChainedSingleSubTable(int lookupType, int lookupFlags, long subtableOffset) throws IOException {
+ in.seekSet(subtableOffset);
+ // read substitution subtable format
+ int sf = in.readTTFUShort();
+ if ( sf == 1 ) {
+ readReverseChainedSingleSubTableFormat1 ( lookupType, lookupFlags, subtableOffset, sf );
+ } else {
+ throw new AdvancedTypographicTableFormatException ( "unsupported reverse chained single substitution subtable format: " + sf );
+ }
+ return sf;
+ }
+
+ private void readGSUBSubtable(int lookupType, int lookupFlags, int lookupSequence, int subtableSequence, long subtableOffset) throws IOException {
+ initATSubState();
+ int subtableFormat = -1;
+ switch ( lookupType ) {
+ case GSUBLookupType.SINGLE:
+ subtableFormat = readSingleSubTable ( lookupType, lookupFlags, subtableOffset );
+ break;
+ case GSUBLookupType.MULTIPLE:
+ subtableFormat = readMultipleSubTable ( lookupType, lookupFlags, subtableOffset );
+ break;
+ case GSUBLookupType.ALTERNATE:
+ subtableFormat = readAlternateSubTable ( lookupType, lookupFlags, subtableOffset );
+ break;
+ case GSUBLookupType.LIGATURE:
+ subtableFormat = readLigatureSubTable ( lookupType, lookupFlags, subtableOffset );
+ break;
+ case GSUBLookupType.CONTEXTUAL:
+ subtableFormat = readContextualSubTable ( lookupType, lookupFlags, subtableOffset );
+ break;
+ case GSUBLookupType.CHAINED_CONTEXTUAL:
+ subtableFormat = readChainedContextualSubTable ( lookupType, lookupFlags, subtableOffset );
+ break;
+ case GSUBLookupType.REVERSE_CHAINED_SINGLE:
+ subtableFormat = readReverseChainedSingleSubTable ( lookupType, lookupFlags, subtableOffset );
+ break;
+ case GSUBLookupType.EXTENSION:
+ subtableFormat = readExtensionSubTable ( lookupType, lookupFlags, lookupSequence, subtableSequence, subtableOffset );
+ break;
+ default:
+ break;
+ }
+ extractSESubState ( GlyphTable.GLYPH_TABLE_TYPE_SUBSTITUTION, lookupType, lookupFlags, lookupSequence, subtableSequence, subtableFormat );
+ resetATSubState();
+ }
+
+ private GlyphPositioningTable.DeviceTable readPosDeviceTable(long subtableOffset, long deviceTableOffset) throws IOException {
+ long cp = in.getCurrentPos();
+ in.seekSet(subtableOffset + deviceTableOffset);
+ // read start size
+ int ss = in.readTTFUShort();
+ // read end size
+ int es = in.readTTFUShort();
+ // read delta format
+ int df = in.readTTFUShort();
+ int s1, m1, dm, dd, s2;
+ if ( df == 1 ) {
+ s1 = 14; m1 = 0x3; dm = 1; dd = 4; s2 = 2;
+ } else if ( df == 2 ) {
+ s1 = 12; m1 = 0xF; dm = 7; dd = 16; s2 = 4;
+ } else if ( df == 3 ) {
+ s1 = 8; m1 = 0xFF; dm = 127; dd = 256; s2 = 8;
+ } else {
+ 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++ ) {
+ int d = ( p >> s1 ) & m1;
+ if ( d > dm ) {
+ d -= dd;
+ }
+ if ( i < n ) {
+ da [ i++ ] = d;
+ } else {
+ break;
+ }
+ p <<= s2;
+ }
+ }
+ in.seekSet(cp);
+ return new GlyphPositioningTable.DeviceTable ( ss, es, da );
+ }
+
+ private GlyphPositioningTable.Value readPosValue(long subtableOffset, int valueFormat) throws IOException {
+ // XPlacement
+ int xp;
+ if ( ( valueFormat & GlyphPositioningTable.Value.X_PLACEMENT ) != 0 ) {
+ xp = ttf.convertTTFUnit2PDFUnit ( in.readTTFShort() );
+ } else {
+ xp = 0;
+ }
+ // YPlacement
+ int yp;
+ if ( ( valueFormat & GlyphPositioningTable.Value.Y_PLACEMENT ) != 0 ) {
+ yp = ttf.convertTTFUnit2PDFUnit ( in.readTTFShort() );
+ } else {
+ yp = 0;
+ }
+ // XAdvance
+ int xa;
+ if ( ( valueFormat & GlyphPositioningTable.Value.X_ADVANCE ) != 0 ) {
+ xa = ttf.convertTTFUnit2PDFUnit ( in.readTTFShort() );
+ } else {
+ xa = 0;
+ }
+ // YAdvance
+ int ya;
+ if ( ( valueFormat & GlyphPositioningTable.Value.Y_ADVANCE ) != 0 ) {
+ ya = ttf.convertTTFUnit2PDFUnit ( in.readTTFShort() );
+ } else {
+ ya = 0;
+ }
+ // XPlaDevice
+ GlyphPositioningTable.DeviceTable xpd;
+ if ( ( valueFormat & GlyphPositioningTable.Value.X_PLACEMENT_DEVICE ) != 0 ) {
+ int xpdo = in.readTTFUShort();
+ xpd = readPosDeviceTable ( subtableOffset, xpdo );
+ } else {
+ xpd = null;
+ }
+ // YPlaDevice
+ GlyphPositioningTable.DeviceTable ypd;
+ if ( ( valueFormat & GlyphPositioningTable.Value.Y_PLACEMENT_DEVICE ) != 0 ) {
+ int ypdo = in.readTTFUShort();
+ ypd = readPosDeviceTable ( subtableOffset, ypdo );
+ } else {
+ ypd = null;
+ }
+ // XAdvDevice
+ GlyphPositioningTable.DeviceTable xad;
+ if ( ( valueFormat & GlyphPositioningTable.Value.X_ADVANCE_DEVICE ) != 0 ) {
+ int xado = in.readTTFUShort();
+ xad = readPosDeviceTable ( subtableOffset, xado );
+ } else {
+ xad = null;
+ }
+ // YAdvDevice
+ GlyphPositioningTable.DeviceTable yad;
+ if ( ( valueFormat & GlyphPositioningTable.Value.Y_ADVANCE_DEVICE ) != 0 ) {
+ int yado = in.readTTFUShort();
+ yad = readPosDeviceTable ( subtableOffset, yado );
+ } else {
+ yad = null;
+ }
+ return new GlyphPositioningTable.Value ( xp, yp, xa, ya, xpd, ypd, xad, yad );
+ }
+
+ private void readSinglePosTableFormat1(int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException {
+ String tableTag = "GPOS";
+ in.seekSet(subtableOffset);
+ // skip over format (already known)
+ in.skip ( 2 );
+ // read coverage offset
+ int co = in.readTTFUShort();
+ // read value format
+ int vf = in.readTTFUShort();
+ // read value
+ GlyphPositioningTable.Value v = readPosValue ( subtableOffset, vf );
+ // dump info if debugging
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " single positioning subtable format: " + subtableFormat + " (delta)" );
+ log.debug(tableTag + " single positioning coverage table offset: " + co );
+ log.debug(tableTag + " single positioning value: " + v );
+ }
+ // read coverage table
+ GlyphCoverageTable ct = readCoverageTable ( tableTag + " single positioning coverage", subtableOffset + co );
+ // store results
+ seMapping = ct;
+ seEntries.add ( v );
+ }
+
+ private void readSinglePosTableFormat2(int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException {
+ String tableTag = "GPOS";
+ in.seekSet(subtableOffset);
+ // skip over format (already known)
+ in.skip ( 2 );
+ // read coverage offset
+ int co = in.readTTFUShort();
+ // read value format
+ int vf = in.readTTFUShort();
+ // read value count
+ int nv = in.readTTFUShort();
+ // dump info if debugging
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " single positioning subtable format: " + subtableFormat + " (mapped)" );
+ log.debug(tableTag + " single positioning coverage table offset: " + co );
+ log.debug(tableTag + " single positioning value count: " + nv );
+ }
+ // read coverage table
+ GlyphCoverageTable ct = readCoverageTable ( tableTag + " single positioning coverage", subtableOffset + co );
+ // read positioning values
+ GlyphPositioningTable.Value[] pva = new GlyphPositioningTable.Value[nv];
+ for ( int i = 0, n = nv; i < n; i++ ) {
+ GlyphPositioningTable.Value pv = readPosValue ( subtableOffset, vf );
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " single positioning value[" + i + "]: " + pv );
+ }
+ pva[i] = pv;
+ }
+ // store results
+ seMapping = ct;
+ seEntries.add ( pva );
+ }
+
+ private int readSinglePosTable(int lookupType, int lookupFlags, long subtableOffset) throws IOException {
+ in.seekSet(subtableOffset);
+ // read positionining subtable format
+ int sf = in.readTTFUShort();
+ if ( sf == 1 ) {
+ readSinglePosTableFormat1 ( lookupType, lookupFlags, subtableOffset, sf );
+ } else if ( sf == 2 ) {
+ readSinglePosTableFormat2 ( lookupType, lookupFlags, subtableOffset, sf );
+ } else {
+ throw new AdvancedTypographicTableFormatException ( "unsupported single positioning subtable format: " + sf );
+ }
+ return sf;
+ }
+
+ private GlyphPositioningTable.PairValues readPosPairValues(long subtableOffset, boolean hasGlyph, int vf1, int vf2) throws IOException {
+ // read glyph (if present)
+ int glyph;
+ if ( hasGlyph ) {
+ glyph = in.readTTFUShort();
+ } else {
+ glyph = 0;
+ }
+ // read first value (if present)
+ GlyphPositioningTable.Value v1;
+ if ( vf1 != 0 ) {
+ v1 = readPosValue ( subtableOffset, vf1 );
+ } else {
+ v1 = null;
+ }
+ // read second value (if present)
+ GlyphPositioningTable.Value v2;
+ if ( vf2 != 0 ) {
+ v2 = readPosValue ( subtableOffset, vf2 );
+ } else {
+ v2 = null;
+ }
+ return new GlyphPositioningTable.PairValues ( glyph, v1, v2 );
+ }
+
+ private GlyphPositioningTable.PairValues[] readPosPairSetTable(long subtableOffset, int pairSetTableOffset, int vf1, int vf2) throws IOException {
+ String tableTag = "GPOS";
+ long cp = in.getCurrentPos();
+ in.seekSet(subtableOffset + pairSetTableOffset);
+ // read pair values count
+ int npv = in.readTTFUShort();
+ // dump info if debugging
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " pair set table offset: " + pairSetTableOffset );
+ log.debug(tableTag + " pair set table values count: " + npv );
+ }
+ // read pair values
+ GlyphPositioningTable.PairValues[] pva = new GlyphPositioningTable.PairValues [ npv ];
+ for ( int i = 0, n = npv; i < n; i++ ) {
+ GlyphPositioningTable.PairValues pv = readPosPairValues ( subtableOffset, true, vf1, vf2 );
+ pva [ i ] = pv;
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " pair set table value[" + i + "]: " + pv);
+ }
+ }
+ in.seekSet(cp);
+ return pva;
+ }
+
+ private void readPairPosTableFormat1(int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException {
+ String tableTag = "GPOS";
+ in.seekSet(subtableOffset);
+ // skip over format (already known)
+ in.skip ( 2 );
+ // read coverage offset
+ int co = in.readTTFUShort();
+ // read value format for first glyph
+ int vf1 = in.readTTFUShort();
+ // read value format for second glyph
+ int vf2 = in.readTTFUShort();
+ // read number (count) of pair sets
+ int nps = in.readTTFUShort();
+ // dump info if debugging
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " pair positioning subtable format: " + subtableFormat + " (glyphs)" );
+ log.debug(tableTag + " pair positioning coverage table offset: " + co );
+ log.debug(tableTag + " pair positioning value format #1: " + vf1 );
+ log.debug(tableTag + " pair positioning value format #2: " + vf2 );
+ }
+ // read coverage table
+ GlyphCoverageTable ct = readCoverageTable ( tableTag + " pair positioning coverage", subtableOffset + co );
+ // read pair value matrix
+ GlyphPositioningTable.PairValues[][] pvm = new GlyphPositioningTable.PairValues [ nps ][];
+ for ( int i = 0, n = nps; i < n; i++ ) {
+ // read pair set offset
+ int pso = in.readTTFUShort();
+ // read pair set table at offset
+ pvm [ i ] = readPosPairSetTable ( subtableOffset, pso, vf1, vf2 );
+ }
+ // store results
+ seMapping = ct;
+ seEntries.add ( pvm );
+ }
+
+ private void readPairPosTableFormat2(int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException {
+ String tableTag = "GPOS";
+ in.seekSet(subtableOffset);
+ // skip over format (already known)
+ in.skip ( 2 );
+ // read coverage offset
+ int co = in.readTTFUShort();
+ // read value format for first glyph
+ int vf1 = in.readTTFUShort();
+ // read value format for second glyph
+ int vf2 = in.readTTFUShort();
+ // read class def 1 offset
+ int cd1o = in.readTTFUShort();
+ // read class def 2 offset
+ int cd2o = in.readTTFUShort();
+ // read number (count) of classes in class def 1 table
+ int nc1 = in.readTTFUShort();
+ // read number (count) of classes in class def 2 table
+ int nc2 = in.readTTFUShort();
+ // dump info if debugging
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " pair positioning subtable format: " + subtableFormat + " (glyph classes)" );
+ log.debug(tableTag + " pair positioning coverage table offset: " + co );
+ log.debug(tableTag + " pair positioning value format #1: " + vf1 );
+ log.debug(tableTag + " pair positioning value format #2: " + vf2 );
+ log.debug(tableTag + " pair positioning class def table #1 offset: " + cd1o );
+ log.debug(tableTag + " pair positioning class def table #2 offset: " + cd2o );
+ log.debug(tableTag + " pair positioning class #1 count: " + nc1 );
+ log.debug(tableTag + " pair positioning class #2 count: " + nc2 );
+ }
+ // read coverage table
+ GlyphCoverageTable ct = readCoverageTable ( tableTag + " pair positioning coverage", subtableOffset + co );
+ // read class definition table #1
+ GlyphClassTable cdt1 = readClassDefTable ( tableTag + " pair positioning class definition #1", subtableOffset + cd1o );
+ // read class definition table #2
+ GlyphClassTable cdt2 = readClassDefTable ( tableTag + " pair positioning class definition #2", subtableOffset + cd2o );
+ // read pair value matrix
+ GlyphPositioningTable.PairValues[][] pvm = new GlyphPositioningTable.PairValues [ nc1 ] [ nc2 ];
+ for ( int i = 0; i < nc1; i++ ) {
+ for ( int j = 0; j < nc2; j++ ) {
+ GlyphPositioningTable.PairValues pv = readPosPairValues ( subtableOffset, false, vf1, vf2 );
+ pvm [ i ] [ j ] = pv;
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " pair set table value[" + i + "][" + j + "]: " + pv);
+ }
+ }
+ }
+ // store results
+ seMapping = ct;
+ seEntries.add ( cdt1 );
+ seEntries.add ( cdt2 );
+ seEntries.add ( Integer.valueOf ( nc1 ) );
+ seEntries.add ( Integer.valueOf ( nc2 ) );
+ seEntries.add ( pvm );
+ }
+
+ private int readPairPosTable(int lookupType, int lookupFlags, long subtableOffset) throws IOException {
+ in.seekSet(subtableOffset);
+ // read positioning subtable format
+ int sf = in.readTTFUShort();
+ if ( sf == 1 ) {
+ readPairPosTableFormat1 ( lookupType, lookupFlags, subtableOffset, sf );
+ } else if ( sf == 2 ) {
+ readPairPosTableFormat2 ( lookupType, lookupFlags, subtableOffset, sf );
+ } else {
+ throw new AdvancedTypographicTableFormatException ( "unsupported pair positioning subtable format: " + sf );
+ }
+ return sf;
+ }
+
+ private GlyphPositioningTable.Anchor readPosAnchor(long anchorTableOffset) throws IOException {
+ GlyphPositioningTable.Anchor a;
+ long cp = in.getCurrentPos();
+ in.seekSet(anchorTableOffset);
+ // read anchor table format
+ int af = in.readTTFUShort();
+ if ( af == 1 ) {
+ // read x coordinate
+ int x = ttf.convertTTFUnit2PDFUnit ( in.readTTFShort() );
+ // read y coordinate
+ int y = ttf.convertTTFUnit2PDFUnit ( in.readTTFShort() );
+ a = new GlyphPositioningTable.Anchor ( x, y );
+ } else if ( af == 2 ) {
+ // read x coordinate
+ int x = ttf.convertTTFUnit2PDFUnit ( in.readTTFShort() );
+ // read y coordinate
+ int y = ttf.convertTTFUnit2PDFUnit ( in.readTTFShort() );
+ // read anchor point index
+ int ap = in.readTTFUShort();
+ a = new GlyphPositioningTable.Anchor ( x, y, ap );
+ } else if ( af == 3 ) {
+ // read x coordinate
+ int x = ttf.convertTTFUnit2PDFUnit ( in.readTTFShort() );
+ // read y coordinate
+ int y = ttf.convertTTFUnit2PDFUnit ( in.readTTFShort() );
+ // read x device table offset
+ int xdo = in.readTTFUShort();
+ // read y device table offset
+ int ydo = in.readTTFUShort();
+ // read x device table (if present)
+ GlyphPositioningTable.DeviceTable xd;
+ if ( xdo != 0 ) {
+ xd = readPosDeviceTable ( cp, xdo );
+ } else {
+ xd = null;
+ }
+ // read y device table (if present)
+ GlyphPositioningTable.DeviceTable yd;
+ if ( ydo != 0 ) {
+ yd = readPosDeviceTable ( cp, ydo );
+ } else {
+ yd = null;
+ }
+ a = new GlyphPositioningTable.Anchor ( x, y, xd, yd );
+ } else {
+ throw new AdvancedTypographicTableFormatException ( "unsupported positioning anchor format: " + af );
+ }
+ in.seekSet(cp);
+ return a;
+ }
+
+ private void readCursivePosTableFormat1(int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException {
+ String tableTag = "GPOS";
+ in.seekSet(subtableOffset);
+ // skip over format (already known)
+ in.skip ( 2 );
+ // read coverage offset
+ int co = in.readTTFUShort();
+ // read entry/exit count
+ int ec = in.readTTFUShort();
+ // dump info if debugging
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " cursive positioning subtable format: " + subtableFormat );
+ log.debug(tableTag + " cursive positioning coverage table offset: " + co );
+ log.debug(tableTag + " cursive positioning entry/exit count: " + ec );
+ }
+ // read coverage table
+ GlyphCoverageTable ct = readCoverageTable ( tableTag + " cursive positioning coverage", subtableOffset + co );
+ // read entry/exit records
+ GlyphPositioningTable.Anchor[] aa = new GlyphPositioningTable.Anchor [ ec * 2 ];
+ for ( int i = 0, n = ec; i < n; i++ ) {
+ // read entry anchor offset
+ int eno = in.readTTFUShort();
+ // read exit anchor offset
+ int exo = in.readTTFUShort();
+ // read entry anchor
+ GlyphPositioningTable.Anchor ena;
+ if ( eno > 0 ) {
+ ena = readPosAnchor ( subtableOffset + eno );
+ } else {
+ ena = null;
+ }
+ // read exit anchor
+ GlyphPositioningTable.Anchor exa;
+ if ( exo > 0 ) {
+ exa = readPosAnchor ( subtableOffset + exo );
+ } else {
+ exa = null;
+ }
+ aa [ ( i * 2 ) + 0 ] = ena;
+ aa [ ( i * 2 ) + 1 ] = exa;
+ if (log.isDebugEnabled()) {
+ if ( ena != null ) {
+ log.debug(tableTag + " cursive entry anchor [" + i + "]: " + ena );
+ }
+ if ( exa != null ) {
+ log.debug(tableTag + " cursive exit anchor [" + i + "]: " + exa );
+ }
+ }
+ }
+ // store results
+ seMapping = ct;
+ seEntries.add ( aa );
+ }
+
+ private int readCursivePosTable(int lookupType, int lookupFlags, long subtableOffset) throws IOException {
+ in.seekSet(subtableOffset);
+ // read positioning subtable format
+ int sf = in.readTTFUShort();
+ if ( sf == 1 ) {
+ readCursivePosTableFormat1 ( lookupType, lookupFlags, subtableOffset, sf );
+ } else {
+ throw new AdvancedTypographicTableFormatException ( "unsupported cursive positioning subtable format: " + sf );
+ }
+ return sf;
+ }
+
+ private void readMarkToBasePosTableFormat1(int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException {
+ String tableTag = "GPOS";
+ in.seekSet(subtableOffset);
+ // skip over format (already known)
+ in.skip ( 2 );
+ // read mark coverage offset
+ int mco = in.readTTFUShort();
+ // read base coverage offset
+ int bco = in.readTTFUShort();
+ // read mark class count
+ int nmc = in.readTTFUShort();
+ // read mark array offset
+ int mao = in.readTTFUShort();
+ // read base array offset
+ int bao = in.readTTFUShort();
+ // dump info if debugging
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " mark-to-base positioning subtable format: " + subtableFormat );
+ log.debug(tableTag + " mark-to-base positioning mark coverage table offset: " + mco );
+ log.debug(tableTag + " mark-to-base positioning base coverage table offset: " + bco );
+ log.debug(tableTag + " mark-to-base positioning mark class count: " + nmc );
+ log.debug(tableTag + " mark-to-base positioning mark array offset: " + mao );
+ log.debug(tableTag + " mark-to-base positioning base array offset: " + bao );
+ }
+ // read mark coverage table
+ GlyphCoverageTable mct = readCoverageTable ( tableTag + " mark-to-base positioning mark coverage", subtableOffset + mco );
+ // read base coverage table
+ GlyphCoverageTable bct = readCoverageTable ( tableTag + " mark-to-base positioning base coverage", subtableOffset + bco );
+ // read mark anchor array
+ // seek to mark array
+ in.seekSet(subtableOffset + mao);
+ // read mark count
+ int nm = in.readTTFUShort();
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " mark-to-base positioning mark count: " + nm );
+ }
+ // read mark anchor array, where i:{0...markCount}
+ GlyphPositioningTable.MarkAnchor[] maa = new GlyphPositioningTable.MarkAnchor [ nm ];
+ for ( int i = 0; i < nm; i++ ) {
+ // read mark class
+ int mc = in.readTTFUShort();
+ // read mark anchor offset
+ int ao = in.readTTFUShort();
+ GlyphPositioningTable.Anchor a;
+ if ( ao > 0 ) {
+ a = readPosAnchor ( subtableOffset + mao + ao );
+ } else {
+ a = null;
+ }
+ GlyphPositioningTable.MarkAnchor ma;
+ if ( a != null ) {
+ ma = new GlyphPositioningTable.MarkAnchor ( mc, a );
+ } else {
+ ma = null;
+ }
+ maa [ i ] = ma;
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " mark-to-base positioning mark anchor[" + i + "]: " + ma);
+ }
+
+ }
+ // read base anchor matrix
+ // seek to base array
+ in.seekSet(subtableOffset + bao);
+ // read base count
+ int nb = in.readTTFUShort();
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " mark-to-base positioning base count: " + nb );
+ }
+ // read anchor matrix, where i:{0...baseCount - 1}, j:{0...markClassCount - 1}
+ GlyphPositioningTable.Anchor[][] bam = new GlyphPositioningTable.Anchor [ nb ] [ nmc ];
+ for ( int i = 0; i < nb; i++ ) {
+ for ( int j = 0; j < nmc; j++ ) {
+ // read base anchor offset
+ int ao = in.readTTFUShort();
+ GlyphPositioningTable.Anchor a;
+ if ( ao > 0 ) {
+ a = readPosAnchor ( subtableOffset + bao + ao );
+ } else {
+ a = null;
+ }
+ bam [ i ] [ j ] = a;
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " mark-to-base positioning base anchor[" + i + "][" + j + "]: " + a);
+ }
+ }
+ }
+ // store results
+ seMapping = mct;
+ seEntries.add ( bct );
+ seEntries.add ( Integer.valueOf ( nmc ) );
+ seEntries.add ( maa );
+ seEntries.add ( bam );
+ }
+
+ private int readMarkToBasePosTable(int lookupType, int lookupFlags, long subtableOffset) throws IOException {
+ in.seekSet(subtableOffset);
+ // read positioning subtable format
+ int sf = in.readTTFUShort();
+ if ( sf == 1 ) {
+ readMarkToBasePosTableFormat1 ( lookupType, lookupFlags, subtableOffset, sf );
+ } else {
+ throw new AdvancedTypographicTableFormatException ( "unsupported mark-to-base positioning subtable format: " + sf );
+ }
+ return sf;
+ }
+
+ private void readMarkToLigaturePosTableFormat1(int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException {
+ String tableTag = "GPOS";
+ in.seekSet(subtableOffset);
+ // skip over format (already known)
+ in.skip ( 2 );
+ // read mark coverage offset
+ int mco = in.readTTFUShort();
+ // read ligature coverage offset
+ int lco = in.readTTFUShort();
+ // read mark class count
+ int nmc = in.readTTFUShort();
+ // read mark array offset
+ int mao = in.readTTFUShort();
+ // read ligature array offset
+ int lao = in.readTTFUShort();
+ // dump info if debugging
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " mark-to-ligature positioning subtable format: " + subtableFormat );
+ log.debug(tableTag + " mark-to-ligature positioning mark coverage table offset: " + mco );
+ log.debug(tableTag + " mark-to-ligature positioning ligature coverage table offset: " + lco );
+ log.debug(tableTag + " mark-to-ligature positioning mark class count: " + nmc );
+ log.debug(tableTag + " mark-to-ligature positioning mark array offset: " + mao );
+ log.debug(tableTag + " mark-to-ligature positioning ligature array offset: " + lao );
+ }
+ // read mark coverage table
+ GlyphCoverageTable mct = readCoverageTable ( tableTag + " mark-to-ligature positioning mark coverage", subtableOffset + mco );
+ // read ligature coverage table
+ GlyphCoverageTable lct = readCoverageTable ( tableTag + " mark-to-ligature positioning ligature coverage", subtableOffset + lco );
+ // read mark anchor array
+ // seek to mark array
+ in.seekSet(subtableOffset + mao);
+ // read mark count
+ int nm = in.readTTFUShort();
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " mark-to-ligature positioning mark count: " + nm );
+ }
+ // read mark anchor array, where i:{0...markCount}
+ GlyphPositioningTable.MarkAnchor[] maa = new GlyphPositioningTable.MarkAnchor [ nm ];
+ for ( int i = 0; i < nm; i++ ) {
+ // read mark class
+ int mc = in.readTTFUShort();
+ // read mark anchor offset
+ int ao = in.readTTFUShort();
+ GlyphPositioningTable.Anchor a;
+ if ( ao > 0 ) {
+ a = readPosAnchor ( subtableOffset + mao + ao );
+ } else {
+ a = null;
+ }
+ GlyphPositioningTable.MarkAnchor ma;
+ if ( a != null ) {
+ ma = new GlyphPositioningTable.MarkAnchor ( mc, a );
+ } else {
+ ma = null;
+ }
+ maa [ i ] = ma;
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " mark-to-ligature positioning mark anchor[" + i + "]: " + ma);
+ }
+ }
+ // read ligature anchor matrix
+ // seek to ligature array
+ in.seekSet(subtableOffset + lao);
+ // read ligature count
+ int nl = in.readTTFUShort();
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " mark-to-ligature positioning ligature count: " + nl );
+ }
+ // read ligature attach table offsets
+ int[] laoa = new int [ nl ];
+ for ( int i = 0; i < nl; i++ ) {
+ laoa [ i ] = in.readTTFUShort();
+ }
+ // iterate over ligature attach tables, recording maximum component count
+ int mxc = 0;
+ for ( int i = 0; i < nl; i++ ) {
+ int lato = laoa [ i ];
+ in.seekSet ( subtableOffset + lao + lato );
+ // read component count
+ int cc = in.readTTFUShort();
+ if ( cc > mxc ) {
+ mxc = cc;
+ }
+ }
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " mark-to-ligature positioning maximum component count: " + mxc );
+ }
+ // read anchor matrix, where i:{0...ligatureCount - 1}, j:{0...maxComponentCount - 1}, k:{0...markClassCount - 1}
+ GlyphPositioningTable.Anchor[][][] lam = new GlyphPositioningTable.Anchor [ nl ][][];
+ for ( int i = 0; i < nl; i++ ) {
+ int lato = laoa [ i ];
+ // seek to ligature attach table for ligature[i]
+ in.seekSet ( subtableOffset + lao + lato );
+ // read component count
+ int cc = in.readTTFUShort();
+ GlyphPositioningTable.Anchor[][] lcm = new GlyphPositioningTable.Anchor [ cc ] [ nmc ];
+ for ( int j = 0; j < cc; j++ ) {
+ for ( int k = 0; k < nmc; k++ ) {
+ // read ligature anchor offset
+ int ao = in.readTTFUShort();
+ GlyphPositioningTable.Anchor a;
+ if ( ao > 0 ) {
+ a = readPosAnchor ( subtableOffset + lao + lato + ao );
+ } else {
+ a = null;
+ }
+ lcm [ j ] [ k ] = a;
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " mark-to-ligature positioning ligature anchor[" + i + "][" + j + "][" + k + "]: " + a);
+ }
+ }
+ }
+ lam [ i ] = lcm;
+ }
+ // store results
+ seMapping = mct;
+ seEntries.add ( lct );
+ seEntries.add ( Integer.valueOf ( nmc ) );
+ seEntries.add ( Integer.valueOf ( mxc ) );
+ seEntries.add ( maa );
+ seEntries.add ( lam );
+ }
+
+ private int readMarkToLigaturePosTable(int lookupType, int lookupFlags, long subtableOffset) throws IOException {
+ in.seekSet(subtableOffset);
+ // read positioning subtable format
+ int sf = in.readTTFUShort();
+ if ( sf == 1 ) {
+ readMarkToLigaturePosTableFormat1 ( lookupType, lookupFlags, subtableOffset, sf );
+ } else {
+ throw new AdvancedTypographicTableFormatException ( "unsupported mark-to-ligature positioning subtable format: " + sf );
+ }
+ return sf;
+ }
+
+ private void readMarkToMarkPosTableFormat1(int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException {
+ String tableTag = "GPOS";
+ in.seekSet(subtableOffset);
+ // skip over format (already known)
+ in.skip ( 2 );
+ // read mark #1 coverage offset
+ int m1co = in.readTTFUShort();
+ // read mark #2 coverage offset
+ int m2co = in.readTTFUShort();
+ // read mark class count
+ int nmc = in.readTTFUShort();
+ // read mark #1 array offset
+ int m1ao = in.readTTFUShort();
+ // read mark #2 array offset
+ int m2ao = in.readTTFUShort();
+ // dump info if debugging
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " mark-to-mark positioning subtable format: " + subtableFormat );
+ log.debug(tableTag + " mark-to-mark positioning mark #1 coverage table offset: " + m1co );
+ log.debug(tableTag + " mark-to-mark positioning mark #2 coverage table offset: " + m2co );
+ log.debug(tableTag + " mark-to-mark positioning mark class count: " + nmc );
+ log.debug(tableTag + " mark-to-mark positioning mark #1 array offset: " + m1ao );
+ log.debug(tableTag + " mark-to-mark positioning mark #2 array offset: " + m2ao );
+ }
+ // read mark #1 coverage table
+ GlyphCoverageTable mct1 = readCoverageTable ( tableTag + " mark-to-mark positioning mark #1 coverage", subtableOffset + m1co );
+ // read mark #2 coverage table
+ GlyphCoverageTable mct2 = readCoverageTable ( tableTag + " mark-to-mark positioning mark #2 coverage", subtableOffset + m2co );
+ // read mark #1 anchor array
+ // seek to mark array
+ in.seekSet(subtableOffset + m1ao);
+ // read mark count
+ int nm1 = in.readTTFUShort();
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " mark-to-mark positioning mark #1 count: " + nm1 );
+ }
+ // read mark anchor array, where i:{0...mark1Count}
+ GlyphPositioningTable.MarkAnchor[] maa = new GlyphPositioningTable.MarkAnchor [ nm1 ];
+ for ( int i = 0; i < nm1; i++ ) {
+ // read mark class
+ int mc = in.readTTFUShort();
+ // read mark anchor offset
+ int ao = in.readTTFUShort();
+ GlyphPositioningTable.Anchor a;
+ if ( ao > 0 ) {
+ a = readPosAnchor ( subtableOffset + m1ao + ao );
+ } else {
+ a = null;
+ }
+ GlyphPositioningTable.MarkAnchor ma;
+ if ( a != null ) {
+ ma = new GlyphPositioningTable.MarkAnchor ( mc, a );
+ } else {
+ ma = null;
+ }
+ maa [ i ] = ma;
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " mark-to-mark positioning mark #1 anchor[" + i + "]: " + ma);
+ }
+ }
+ // read mark #2 anchor matrix
+ // seek to mark #2 array
+ in.seekSet(subtableOffset + m2ao);
+ // read mark #2 count
+ int nm2 = in.readTTFUShort();
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " mark-to-mark positioning mark #2 count: " + nm2 );
+ }
+ // read anchor matrix, where i:{0...mark2Count - 1}, j:{0...markClassCount - 1}
+ GlyphPositioningTable.Anchor[][] mam = new GlyphPositioningTable.Anchor [ nm2 ] [ nmc ];
+ for ( int i = 0; i < nm2; i++ ) {
+ for ( int j = 0; j < nmc; j++ ) {
+ // read mark anchor offset
+ int ao = in.readTTFUShort();
+ GlyphPositioningTable.Anchor a;
+ if ( ao > 0 ) {
+ a = readPosAnchor ( subtableOffset + m2ao + ao );
+ } else {
+ a = null;
+ }
+ mam [ i ] [ j ] = a;
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " mark-to-mark positioning mark #2 anchor[" + i + "][" + j + "]: " + a);
+ }
+ }
+ }
+ // store results
+ seMapping = mct1;
+ seEntries.add ( mct2 );
+ seEntries.add ( Integer.valueOf ( nmc ) );
+ seEntries.add ( maa );
+ seEntries.add ( mam );
+ }
+
+ private int readMarkToMarkPosTable(int lookupType, int lookupFlags, long subtableOffset) throws IOException {
+ in.seekSet(subtableOffset);
+ // read positioning subtable format
+ int sf = in.readTTFUShort();
+ if ( sf == 1 ) {
+ readMarkToMarkPosTableFormat1 ( lookupType, lookupFlags, subtableOffset, sf );
+ } else {
+ throw new AdvancedTypographicTableFormatException ( "unsupported mark-to-mark positioning subtable format: " + sf );
+ }
+ return sf;
+ }
+
+ private void readContextualPosTableFormat1(int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException {
+ String tableTag = "GPOS";
+ in.seekSet(subtableOffset);
+ // skip over format (already known)
+ in.skip ( 2 );
+ // read coverage offset
+ int co = in.readTTFUShort();
+ // read rule set count
+ int nrs = in.readTTFUShort();
+ // read rule set offsets
+ int[] rsoa = new int [ nrs ];
+ for ( int i = 0; i < nrs; i++ ) {
+ rsoa [ i ] = in.readTTFUShort();
+ }
+ // dump info if debugging
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " contextual positioning subtable format: " + subtableFormat + " (glyphs)" );
+ log.debug(tableTag + " contextual positioning coverage table offset: " + co );
+ log.debug(tableTag + " contextual positioning rule set count: " + nrs );
+ for ( int i = 0; i < nrs; i++ ) {
+ log.debug(tableTag + " contextual positioning rule set offset[" + i + "]: " + rsoa[i] );
+ }
+ }
+ // read coverage table
+ GlyphCoverageTable ct;
+ if ( co > 0 ) {
+ ct = readCoverageTable ( tableTag + " contextual positioning coverage", subtableOffset + co );
+ } else {
+ ct = null;
+ }
+ // read rule sets
+ GlyphTable.RuleSet[] rsa = new GlyphTable.RuleSet [ nrs ];
+ String header = null;
+ for ( int i = 0; i < nrs; i++ ) {
+ GlyphTable.RuleSet rs;
+ int rso = rsoa [ i ];
+ if ( rso > 0 ) {
+ // seek to rule set [ i ]
+ in.seekSet ( subtableOffset + rso );
+ // read rule count
+ int nr = in.readTTFUShort();
+ // read rule offsets
+ int[] roa = new int [ nr ];
+ GlyphTable.Rule[] ra = new GlyphTable.Rule [ nr ];
+ for ( int j = 0; j < nr; j++ ) {
+ roa [ j ] = in.readTTFUShort();
+ }
+ // read glyph sequence rules
+ for ( int j = 0; j < nr; j++ ) {
+ GlyphTable.GlyphSequenceRule r;
+ int ro = roa [ j ];
+ if ( ro > 0 ) {
+ // seek to rule [ j ]
+ in.seekSet ( subtableOffset + rso + ro );
+ // read glyph count
+ int ng = in.readTTFUShort();
+ // read rule lookup count
+ int nl = in.readTTFUShort();
+ // read glyphs
+ int[] glyphs = new int [ ng - 1 ];
+ for ( int k = 0, nk = glyphs.length; k < nk; k++ ) {
+ glyphs [ k ] = in.readTTFUShort();
+ }
+ // read rule lookups
+ if (log.isDebugEnabled()) {
+ header = tableTag + " contextual positioning lookups @rule[" + i + "][" + j + "]: ";
+ }
+ GlyphTable.RuleLookup[] lookups = readRuleLookups ( nl, header );
+ r = new GlyphTable.GlyphSequenceRule ( lookups, ng, glyphs );
+ } else {
+ r = null;
+ }
+ ra [ j ] = r;
+ }
+ rs = new GlyphTable.HomogeneousRuleSet ( ra );
+ } else {
+ rs = null;
+ }
+ rsa [ i ] = rs;
+ }
+ // store results
+ seMapping = ct;
+ seEntries.add ( rsa );
+ }
+
+ private void readContextualPosTableFormat2(int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException {
+ String tableTag = "GPOS";
+ in.seekSet(subtableOffset);
+ // skip over format (already known)
+ in.skip ( 2 );
+ // read coverage offset
+ int co = in.readTTFUShort();
+ // read class def table offset
+ int cdo = in.readTTFUShort();
+ // read class rule set count
+ int ngc = in.readTTFUShort();
+ // read class rule set offsets
+ int[] csoa = new int [ ngc ];
+ for ( int i = 0; i < ngc; i++ ) {
+ csoa [ i ] = in.readTTFUShort();
+ }
+ // dump info if debugging
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " contextual positioning subtable format: " + subtableFormat + " (glyph classes)" );
+ log.debug(tableTag + " contextual positioning coverage table offset: " + co );
+ log.debug(tableTag + " contextual positioning class set count: " + ngc );
+ for ( int i = 0; i < ngc; i++ ) {
+ log.debug(tableTag + " contextual positioning class set offset[" + i + "]: " + csoa[i] );
+ }
+ }
+ // read coverage table
+ GlyphCoverageTable ct;
+ if ( co > 0 ) {
+ ct = readCoverageTable ( tableTag + " contextual positioning coverage", subtableOffset + co );
+ } else {
+ ct = null;
+ }
+ // read class definition table
+ GlyphClassTable cdt;
+ if ( cdo > 0 ) {
+ cdt = readClassDefTable ( tableTag + " contextual positioning class definition", subtableOffset + cdo );
+ } else {
+ cdt = null;
+ }
+ // read rule sets
+ GlyphTable.RuleSet[] rsa = new GlyphTable.RuleSet [ ngc ];
+ String header = null;
+ for ( int i = 0; i < ngc; i++ ) {
+ int cso = csoa [ i ];
+ GlyphTable.RuleSet rs;
+ if ( cso > 0 ) {
+ // seek to rule set [ i ]
+ in.seekSet ( subtableOffset + cso );
+ // read rule count
+ int nr = in.readTTFUShort();
+ // read rule offsets
+ int[] roa = new int [ nr ];
+ GlyphTable.Rule[] ra = new GlyphTable.Rule [ nr ];
+ for ( int j = 0; j < nr; j++ ) {
+ roa [ j ] = in.readTTFUShort();
+ }
+ // read glyph sequence rules
+ for ( int j = 0; j < nr; j++ ) {
+ int ro = roa [ j ];
+ GlyphTable.ClassSequenceRule r;
+ if ( ro > 0 ) {
+ // seek to rule [ j ]
+ in.seekSet ( subtableOffset + cso + ro );
+ // read glyph count
+ int ng = in.readTTFUShort();
+ // read rule lookup count
+ int nl = in.readTTFUShort();
+ // read classes
+ int[] classes = new int [ ng - 1 ];
+ for ( int k = 0, nk = classes.length; k < nk; k++ ) {
+ classes [ k ] = in.readTTFUShort();
+ }
+ // read rule lookups
+ if (log.isDebugEnabled()) {
+ header = tableTag + " contextual positioning lookups @rule[" + i + "][" + j + "]: ";
+ }
+ GlyphTable.RuleLookup[] lookups = readRuleLookups ( nl, header );
+ r = new GlyphTable.ClassSequenceRule ( lookups, ng, classes );
+ } else {
+ r = null;
+ }
+ ra [ j ] = r;
+ }
+ rs = new GlyphTable.HomogeneousRuleSet ( ra );
+ } else {
+ rs = null;
+ }
+ rsa [ i ] = rs;
+ }
+ // store results
+ seMapping = ct;
+ seEntries.add ( cdt );
+ seEntries.add ( Integer.valueOf ( ngc ) );
+ seEntries.add ( rsa );
+ }
+
+ private void readContextualPosTableFormat3(int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException {
+ String tableTag = "GPOS";
+ in.seekSet(subtableOffset);
+ // skip over format (already known)
+ in.skip ( 2 );
+ // read glyph (input sequence length) count
+ int ng = in.readTTFUShort();
+ // read positioning lookup count
+ int nl = in.readTTFUShort();
+ // read glyph coverage offsets, one per glyph input sequence length count
+ int[] gcoa = new int [ ng ];
+ for ( int i = 0; i < ng; i++ ) {
+ gcoa [ i ] = in.readTTFUShort();
+ }
+ // dump info if debugging
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " contextual positioning subtable format: " + subtableFormat + " (glyph sets)" );
+ log.debug(tableTag + " contextual positioning glyph input sequence length count: " + ng );
+ log.debug(tableTag + " contextual positioning lookup count: " + nl );
+ for ( int i = 0; i < ng; i++ ) {
+ log.debug(tableTag + " contextual positioning coverage table offset[" + i + "]: " + gcoa[i] );
+ }
+ }
+ // read coverage tables
+ GlyphCoverageTable[] gca = new GlyphCoverageTable [ ng ];
+ for ( int i = 0; i < ng; i++ ) {
+ int gco = gcoa [ i ];
+ GlyphCoverageTable gct;
+ if ( gco > 0 ) {
+ gct = readCoverageTable ( tableTag + " contextual positioning coverage[" + i + "]", subtableOffset + gcoa[i] );
+ } else {
+ gct = null;
+ }
+ gca [ i ] = gct;
+ }
+ // read rule lookups
+ String header = null;
+ if (log.isDebugEnabled()) {
+ header = tableTag + " contextual positioning lookups: ";
+ }
+ GlyphTable.RuleLookup[] lookups = readRuleLookups ( nl, header );
+ // construct rule, rule set, and rule set array
+ GlyphTable.Rule r = new GlyphTable.CoverageSequenceRule ( lookups, ng, gca );
+ GlyphTable.RuleSet rs = new GlyphTable.HomogeneousRuleSet ( new GlyphTable.Rule[] {r} );
+ GlyphTable.RuleSet[] rsa = new GlyphTable.RuleSet[] {rs};
+ // store results
+ assert ( gca != null ) && ( gca.length > 0 );
+ seMapping = gca[0];
+ seEntries.add ( rsa );
+ }
+
+ private int readContextualPosTable(int lookupType, int lookupFlags, long subtableOffset) throws IOException {
+ in.seekSet(subtableOffset);
+ // read positioning subtable format
+ int sf = in.readTTFUShort();
+ if ( sf == 1 ) {
+ readContextualPosTableFormat1 ( lookupType, lookupFlags, subtableOffset, sf );
+ } else if ( sf == 2 ) {
+ readContextualPosTableFormat2 ( lookupType, lookupFlags, subtableOffset, sf );
+ } else if ( sf == 3 ) {
+ readContextualPosTableFormat3 ( lookupType, lookupFlags, subtableOffset, sf );
+ } else {
+ throw new AdvancedTypographicTableFormatException ( "unsupported contextual positioning subtable format: " + sf );
+ }
+ return sf;
+ }
+
+ private void readChainedContextualPosTableFormat1(int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException {
+ String tableTag = "GPOS";
+ in.seekSet(subtableOffset);
+ // skip over format (already known)
+ in.skip ( 2 );
+ // read coverage offset
+ int co = in.readTTFUShort();
+ // read rule set count
+ int nrs = in.readTTFUShort();
+ // read rule set offsets
+ int[] rsoa = new int [ nrs ];
+ for ( int i = 0; i < nrs; i++ ) {
+ rsoa [ i ] = in.readTTFUShort();
+ }
+ // dump info if debugging
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " chained contextual positioning subtable format: " + subtableFormat + " (glyphs)" );
+ log.debug(tableTag + " chained contextual positioning coverage table offset: " + co );
+ log.debug(tableTag + " chained contextual positioning rule set count: " + nrs );
+ for ( int i = 0; i < nrs; i++ ) {
+ log.debug(tableTag + " chained contextual positioning rule set offset[" + i + "]: " + rsoa[i] );
+ }
+ }
+ // read coverage table
+ GlyphCoverageTable ct;
+ if ( co > 0 ) {
+ ct = readCoverageTable ( tableTag + " chained contextual positioning coverage", subtableOffset + co );
+ } else {
+ ct = null;
+ }
+ // read rule sets
+ GlyphTable.RuleSet[] rsa = new GlyphTable.RuleSet [ nrs ];
+ String header = null;
+ for ( int i = 0; i < nrs; i++ ) {
+ GlyphTable.RuleSet rs;
+ int rso = rsoa [ i ];
+ if ( rso > 0 ) {
+ // seek to rule set [ i ]
+ in.seekSet ( subtableOffset + rso );
+ // read rule count
+ int nr = in.readTTFUShort();
+ // read rule offsets
+ int[] roa = new int [ nr ];
+ GlyphTable.Rule[] ra = new GlyphTable.Rule [ nr ];
+ for ( int j = 0; j < nr; j++ ) {
+ roa [ j ] = in.readTTFUShort();
+ }
+ // read glyph sequence rules
+ for ( int j = 0; j < nr; j++ ) {
+ GlyphTable.ChainedGlyphSequenceRule r;
+ int ro = roa [ j ];
+ if ( ro > 0 ) {
+ // seek to rule [ j ]
+ in.seekSet ( subtableOffset + rso + ro );
+ // read backtrack glyph count
+ int nbg = in.readTTFUShort();
+ // read backtrack glyphs
+ int[] backtrackGlyphs = new int [ nbg ];
+ for ( int k = 0, nk = backtrackGlyphs.length; k < nk; k++ ) {
+ backtrackGlyphs [ k ] = in.readTTFUShort();
+ }
+ // read input glyph count
+ int nig = in.readTTFUShort();
+ // read glyphs
+ int[] glyphs = new int [ nig - 1 ];
+ for ( int k = 0, nk = glyphs.length; k < nk; k++ ) {
+ glyphs [ k ] = in.readTTFUShort();
+ }
+ // read lookahead glyph count
+ int nlg = in.readTTFUShort();
+ // read lookahead glyphs
+ int[] lookaheadGlyphs = new int [ nlg ];
+ for ( int k = 0, nk = lookaheadGlyphs.length; k < nk; k++ ) {
+ lookaheadGlyphs [ k ] = in.readTTFUShort();
+ }
+ // read rule lookup count
+ int nl = in.readTTFUShort();
+ // read rule lookups
+ if (log.isDebugEnabled()) {
+ header = tableTag + " contextual positioning lookups @rule[" + i + "][" + j + "]: ";
+ }
+ GlyphTable.RuleLookup[] lookups = readRuleLookups ( nl, header );
+ r = new GlyphTable.ChainedGlyphSequenceRule ( lookups, nig, glyphs, backtrackGlyphs, lookaheadGlyphs );
+ } else {
+ r = null;
+ }
+ ra [ j ] = r;
+ }
+ rs = new GlyphTable.HomogeneousRuleSet ( ra );
+ } else {
+ rs = null;
+ }
+ rsa [ i ] = rs;
+ }
+ // store results
+ seMapping = ct;
+ seEntries.add ( rsa );
+ }
+
+ private void readChainedContextualPosTableFormat2(int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException {
+ String tableTag = "GPOS";
+ in.seekSet(subtableOffset);
+ // skip over format (already known)
+ in.skip ( 2 );
+ // read coverage offset
+ int co = in.readTTFUShort();
+ // read backtrack class def table offset
+ int bcdo = in.readTTFUShort();
+ // read input class def table offset
+ int icdo = in.readTTFUShort();
+ // read lookahead class def table offset
+ int lcdo = in.readTTFUShort();
+ // read class set count
+ int ngc = in.readTTFUShort();
+ // read class set offsets
+ int[] csoa = new int [ ngc ];
+ for ( int i = 0; i < ngc; i++ ) {
+ csoa [ i ] = in.readTTFUShort();
+ }
+ // dump info if debugging
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " chained contextual positioning subtable format: " + subtableFormat + " (glyph classes)" );
+ log.debug(tableTag + " chained contextual positioning coverage table offset: " + co );
+ log.debug(tableTag + " chained contextual positioning class set count: " + ngc );
+ for ( int i = 0; i < ngc; i++ ) {
+ log.debug(tableTag + " chained contextual positioning class set offset[" + i + "]: " + csoa[i] );
+ }
+ }
+ // read coverage table
+ GlyphCoverageTable ct;
+ if ( co > 0 ) {
+ ct = readCoverageTable ( tableTag + " chained contextual positioning coverage", subtableOffset + co );
+ } else {
+ ct = null;
+ }
+ // read backtrack class definition table
+ GlyphClassTable bcdt;
+ if ( bcdo > 0 ) {
+ bcdt = readClassDefTable ( tableTag + " contextual positioning backtrack class definition", subtableOffset + bcdo );
+ } else {
+ bcdt = null;
+ }
+ // read input class definition table
+ GlyphClassTable icdt;
+ if ( icdo > 0 ) {
+ icdt = readClassDefTable ( tableTag + " contextual positioning input class definition", subtableOffset + icdo );
+ } else {
+ icdt = null;
+ }
+ // read lookahead class definition table
+ GlyphClassTable lcdt;
+ if ( lcdo > 0 ) {
+ lcdt = readClassDefTable ( tableTag + " contextual positioning lookahead class definition", subtableOffset + lcdo );
+ } else {
+ lcdt = null;
+ }
+ // read rule sets
+ GlyphTable.RuleSet[] rsa = new GlyphTable.RuleSet [ ngc ];
+ String header = null;
+ for ( int i = 0; i < ngc; i++ ) {
+ int cso = csoa [ i ];
+ GlyphTable.RuleSet rs;
+ if ( cso > 0 ) {
+ // seek to rule set [ i ]
+ in.seekSet ( subtableOffset + cso );
+ // read rule count
+ int nr = in.readTTFUShort();
+ // read rule offsets
+ int[] roa = new int [ nr ];
+ GlyphTable.Rule[] ra = new GlyphTable.Rule [ nr ];
+ for ( int j = 0; j < nr; j++ ) {
+ roa [ j ] = in.readTTFUShort();
+ }
+ // read glyph sequence rules
+ for ( int j = 0; j < nr; j++ ) {
+ GlyphTable.ChainedClassSequenceRule r;
+ int ro = roa [ j ];
+ if ( ro > 0 ) {
+ // seek to rule [ j ]
+ in.seekSet ( subtableOffset + cso + ro );
+ // read backtrack glyph class count
+ int nbc = in.readTTFUShort();
+ // read backtrack glyph classes
+ int[] backtrackClasses = new int [ nbc ];
+ for ( int k = 0, nk = backtrackClasses.length; k < nk; k++ ) {
+ backtrackClasses [ k ] = in.readTTFUShort();
+ }
+ // read input glyph class count
+ int nic = in.readTTFUShort();
+ // read input glyph classes
+ int[] classes = new int [ nic - 1 ];
+ for ( int k = 0, nk = classes.length; k < nk; k++ ) {
+ classes [ k ] = in.readTTFUShort();
+ }
+ // read lookahead glyph class count
+ int nlc = in.readTTFUShort();
+ // read lookahead glyph classes
+ int[] lookaheadClasses = new int [ nlc ];
+ for ( int k = 0, nk = lookaheadClasses.length; k < nk; k++ ) {
+ lookaheadClasses [ k ] = in.readTTFUShort();
+ }
+ // read rule lookup count
+ int nl = in.readTTFUShort();
+ // read rule lookups
+ if (log.isDebugEnabled()) {
+ header = tableTag + " contextual positioning lookups @rule[" + i + "][" + j + "]: ";
+ }
+ GlyphTable.RuleLookup[] lookups = readRuleLookups ( nl, header );
+ r = new GlyphTable.ChainedClassSequenceRule ( lookups, nic, classes, backtrackClasses, lookaheadClasses );
+ } else {
+ r = null;
+ }
+ ra [ j ] = r;
+ }
+ rs = new GlyphTable.HomogeneousRuleSet ( ra );
+ } else {
+ rs = null;
+ }
+ rsa [ i ] = rs;
+ }
+ // store results
+ seMapping = ct;
+ seEntries.add ( icdt );
+ seEntries.add ( bcdt );
+ seEntries.add ( lcdt );
+ seEntries.add ( Integer.valueOf ( ngc ) );
+ seEntries.add ( rsa );
+ }
+
+ private void readChainedContextualPosTableFormat3(int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException {
+ String tableTag = "GPOS";
+ in.seekSet(subtableOffset);
+ // skip over format (already known)
+ in.skip ( 2 );
+ // read backtrack glyph count
+ int nbg = in.readTTFUShort();
+ // read backtrack glyph coverage offsets
+ int[] bgcoa = new int [ nbg ];
+ for ( int i = 0; i < nbg; i++ ) {
+ bgcoa [ i ] = in.readTTFUShort();
+ }
+ // read input glyph count
+ int nig = in.readTTFUShort();
+ // read backtrack glyph coverage offsets
+ int[] igcoa = new int [ nig ];
+ for ( int i = 0; i < nig; i++ ) {
+ igcoa [ i ] = in.readTTFUShort();
+ }
+ // read lookahead glyph count
+ int nlg = in.readTTFUShort();
+ // read backtrack glyph coverage offsets
+ int[] lgcoa = new int [ nlg ];
+ for ( int i = 0; i < nlg; i++ ) {
+ lgcoa [ i ] = in.readTTFUShort();
+ }
+ // read positioning lookup count
+ int nl = in.readTTFUShort();
+ // dump info if debugging
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " chained contextual positioning subtable format: " + subtableFormat + " (glyph sets)" );
+ log.debug(tableTag + " chained contextual positioning backtrack glyph count: " + nbg );
+ for ( int i = 0; i < nbg; i++ ) {
+ log.debug(tableTag + " chained contextual positioning backtrack coverage table offset[" + i + "]: " + bgcoa[i] );
+ }
+ log.debug(tableTag + " chained contextual positioning input glyph count: " + nig );
+ for ( int i = 0; i < nig; i++ ) {
+ log.debug(tableTag + " chained contextual positioning input coverage table offset[" + i + "]: " + igcoa[i] );
+ }
+ log.debug(tableTag + " chained contextual positioning lookahead glyph count: " + nlg );
+ for ( int i = 0; i < nlg; i++ ) {
+ log.debug(tableTag + " chained contextual positioning lookahead coverage table offset[" + i + "]: " + lgcoa[i] );
+ }
+ log.debug(tableTag + " chained contextual positioning lookup count: " + nl );
+ }
+ // read backtrack coverage tables
+ GlyphCoverageTable[] bgca = new GlyphCoverageTable[nbg];
+ for ( int i = 0; i < nbg; i++ ) {
+ int bgco = bgcoa [ i ];
+ GlyphCoverageTable bgct;
+ if ( bgco > 0 ) {
+ bgct = readCoverageTable ( tableTag + " chained contextual positioning backtrack coverage[" + i + "]", subtableOffset + bgco );
+ } else {
+ bgct = null;
+ }
+ bgca[i] = bgct;
+ }
+ // read input coverage tables
+ GlyphCoverageTable[] igca = new GlyphCoverageTable[nig];
+ for ( int i = 0; i < nig; i++ ) {
+ int igco = igcoa [ i ];
+ GlyphCoverageTable igct;
+ if ( igco > 0 ) {
+ igct = readCoverageTable ( tableTag + " chained contextual positioning input coverage[" + i + "]", subtableOffset + igco );
+ } else {
+ igct = null;
+ }
+ igca[i] = igct;
+ }
+ // read lookahead coverage tables
+ GlyphCoverageTable[] lgca = new GlyphCoverageTable[nlg];
+ for ( int i = 0; i < nlg; i++ ) {
+ int lgco = lgcoa [ i ];
+ GlyphCoverageTable lgct;
+ if ( lgco > 0 ) {
+ lgct = readCoverageTable ( tableTag + " chained contextual positioning lookahead coverage[" + i + "]", subtableOffset + lgco );
+ } else {
+ lgct = null;
+ }
+ lgca[i] = lgct;
+ }
+ // read rule lookups
+ String header = null;
+ if (log.isDebugEnabled()) {
+ header = tableTag + " chained contextual positioning lookups: ";
+ }
+ GlyphTable.RuleLookup[] lookups = readRuleLookups ( nl, header );
+ // construct rule, rule set, and rule set array
+ GlyphTable.Rule r = new GlyphTable.ChainedCoverageSequenceRule ( lookups, nig, igca, bgca, lgca );
+ GlyphTable.RuleSet rs = new GlyphTable.HomogeneousRuleSet ( new GlyphTable.Rule[] {r} );
+ GlyphTable.RuleSet[] rsa = new GlyphTable.RuleSet[] {rs};
+ // store results
+ assert ( igca != null ) && ( igca.length > 0 );
+ seMapping = igca[0];
+ seEntries.add ( rsa );
+ }
+
+ private int readChainedContextualPosTable(int lookupType, int lookupFlags, long subtableOffset) throws IOException {
+ in.seekSet(subtableOffset);
+ // read positioning subtable format
+ int sf = in.readTTFUShort();
+ if ( sf == 1 ) {
+ readChainedContextualPosTableFormat1 ( lookupType, lookupFlags, subtableOffset, sf );
+ } else if ( sf == 2 ) {
+ readChainedContextualPosTableFormat2 ( lookupType, lookupFlags, subtableOffset, sf );
+ } else if ( sf == 3 ) {
+ readChainedContextualPosTableFormat3 ( lookupType, lookupFlags, subtableOffset, sf );
+ } else {
+ throw new AdvancedTypographicTableFormatException ( "unsupported chained contextual positioning subtable format: " + sf );
+ }
+ return sf;
+ }
+
+ private void readExtensionPosTableFormat1(int lookupType, int lookupFlags, int lookupSequence, int subtableSequence, long subtableOffset, int subtableFormat) throws IOException {
+ String tableTag = "GPOS";
+ in.seekSet(subtableOffset);
+ // skip over format (already known)
+ in.skip ( 2 );
+ // read extension lookup type
+ int lt = in.readTTFUShort();
+ // read extension offset
+ long eo = in.readTTFULong();
+ // dump info if debugging
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " extension positioning subtable format: " + subtableFormat );
+ log.debug(tableTag + " extension positioning lookup type: " + lt );
+ log.debug(tableTag + " extension positioning lookup table offset: " + eo );
+ }
+ // read referenced subtable from extended offset
+ readGPOSSubtable ( lt, lookupFlags, lookupSequence, subtableSequence, subtableOffset + eo );
+ }
+
+ private int readExtensionPosTable(int lookupType, int lookupFlags, int lookupSequence, int subtableSequence, long subtableOffset) throws IOException {
+ in.seekSet(subtableOffset);
+ // read positioning subtable format
+ int sf = in.readTTFUShort();
+ if ( sf == 1 ) {
+ readExtensionPosTableFormat1 ( lookupType, lookupFlags, lookupSequence, subtableSequence, subtableOffset, sf );
+ } else {
+ throw new AdvancedTypographicTableFormatException ( "unsupported extension positioning subtable format: " + sf );
+ }
+ return sf;
+ }
+
+ private void readGPOSSubtable(int lookupType, int lookupFlags, int lookupSequence, int subtableSequence, long subtableOffset) throws IOException {
+ initATSubState();
+ int subtableFormat = -1;
+ switch ( lookupType ) {
+ case GPOSLookupType.SINGLE:
+ subtableFormat = readSinglePosTable ( lookupType, lookupFlags, subtableOffset );
+ break;
+ case GPOSLookupType.PAIR:
+ subtableFormat = readPairPosTable ( lookupType, lookupFlags, subtableOffset );
+ break;
+ case GPOSLookupType.CURSIVE:
+ subtableFormat = readCursivePosTable ( lookupType, lookupFlags, subtableOffset );
+ break;
+ case GPOSLookupType.MARK_TO_BASE:
+ subtableFormat = readMarkToBasePosTable ( lookupType, lookupFlags, subtableOffset );
+ break;
+ case GPOSLookupType.MARK_TO_LIGATURE:
+ subtableFormat = readMarkToLigaturePosTable ( lookupType, lookupFlags, subtableOffset );
+ break;
+ case GPOSLookupType.MARK_TO_MARK:
+ subtableFormat = readMarkToMarkPosTable ( lookupType, lookupFlags, subtableOffset );
+ break;
+ case GPOSLookupType.CONTEXTUAL:
+ subtableFormat = readContextualPosTable ( lookupType, lookupFlags, subtableOffset );
+ break;
+ case GPOSLookupType.CHAINED_CONTEXTUAL:
+ subtableFormat = readChainedContextualPosTable ( lookupType, lookupFlags, subtableOffset );
+ break;
+ case GPOSLookupType.EXTENSION:
+ subtableFormat = readExtensionPosTable ( lookupType, lookupFlags, lookupSequence, subtableSequence, subtableOffset );
+ break;
+ default:
+ break;
+ }
+ extractSESubState ( GlyphTable.GLYPH_TABLE_TYPE_POSITIONING, lookupType, lookupFlags, lookupSequence, subtableSequence, subtableFormat );
+ resetATSubState();
+ }
+
+ private void readLookupTable(String tableTag, int lookupSequence, long lookupTable) throws IOException {
+ boolean isGSUB = tableTag.equals ( "GSUB" );
+ boolean isGPOS = tableTag.equals ( "GPOS" );
+ in.seekSet(lookupTable);
+ // read lookup type
+ int lt = in.readTTFUShort();
+ // read lookup flags
+ int lf = in.readTTFUShort();
+ // read sub-table count
+ int ns = in.readTTFUShort();
+ // dump info if debugging
+ if (log.isDebugEnabled()) {
+ String lts;
+ if ( isGSUB ) {
+ lts = GSUBLookupType.toString ( lt );
+ } else if ( isGPOS ) {
+ lts = GPOSLookupType.toString ( lt );
+ } else {
+ lts = "?";
+ }
+ log.debug(tableTag + " lookup table type: " + lt + " (" + lts + ")" );
+ log.debug(tableTag + " lookup table flags: " + lf + " (" + LookupFlag.toString ( lf ) + ")" );
+ log.debug(tableTag + " lookup table subtable count: " + ns );
+ }
+ // read subtable offsets
+ int[] soa = new int[ns];
+ for ( int i = 0; i < ns; i++ ) {
+ int so = in.readTTFUShort();
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " lookup table subtable offset: " + so );
+ }
+ soa[i] = so;
+ }
+ // read mark filtering set
+ if ( ( lf & LookupFlag.USE_MARK_FILTERING_SET ) != 0 ) {
+ // read mark filtering set
+ int fs = in.readTTFUShort();
+ // dump info if debugging
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " lookup table mark filter set: " + fs );
+ }
+ }
+ // read subtables
+ for ( int i = 0; i < ns; i++ ) {
+ int so = soa[i];
+ if ( isGSUB ) {
+ readGSUBSubtable ( lt, lf, lookupSequence, i, lookupTable + so );
+ } else if ( isGPOS ) {
+ readGPOSSubtable ( lt, lf, lookupSequence, i, lookupTable + so );
+ }
+ }
+ }
+
+ private void readLookupList(String tableTag, long lookupList) throws IOException {
+ in.seekSet(lookupList);
+ // read lookup record count
+ int nl = in.readTTFUShort();
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " lookup list record count: " + nl );
+ }
+ if ( nl > 0 ) {
+ int[] loa = new int[nl];
+ // read lookup records
+ for ( int i = 0, n = nl; i < n; i++ ) {
+ int lo = in.readTTFUShort();
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " lookup table offset: " + lo );
+ }
+ loa[i] = lo;
+ }
+ // read lookup tables
+ for ( int i = 0, n = nl; i < n; i++ ) {
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " lookup index: " + i );
+ }
+ readLookupTable ( tableTag, i, lookupList + loa [ i ] );
+ }
+ }
+ }
+
+ /**
+ * Read the common layout tables (used by GSUB and GPOS).
+ * @param tableTag tag of table being read
+ * @param scriptList offset to script list from beginning of font file
+ * @param featureList offset to feature list from beginning of font file
+ * @param lookupList offset to lookup list from beginning of font file
+ * @throws IOException In case of a I/O problem
+ */
+ private void readCommonLayoutTables(String tableTag, long scriptList, long featureList, long lookupList) throws IOException {
+ if ( scriptList > 0 ) {
+ readScriptList ( tableTag, scriptList );
+ }
+ if ( featureList > 0 ) {
+ readFeatureList ( tableTag, featureList );
+ }
+ if ( lookupList > 0 ) {
+ readLookupList ( tableTag, lookupList );
+ }
+ }
+
+ private void readGDEFClassDefTable(String tableTag, int lookupSequence, long subtableOffset) throws IOException {
+ initATSubState();
+ in.seekSet(subtableOffset);
+ // subtable is a bare class definition table
+ GlyphClassTable ct = readClassDefTable ( tableTag + " glyph class definition table", subtableOffset );
+ // store results
+ seMapping = ct;
+ // extract subtable
+ extractSESubState ( GlyphTable.GLYPH_TABLE_TYPE_DEFINITION, GDEFLookupType.GLYPH_CLASS, 0, lookupSequence, 0, 1 );
+ resetATSubState();
+ }
+
+ private void readGDEFAttachmentTable(String tableTag, int lookupSequence, long subtableOffset) throws IOException {
+ initATSubState();
+ in.seekSet(subtableOffset);
+ // read coverage offset
+ int co = in.readTTFUShort();
+ // dump info if debugging
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " attachment point coverage table offset: " + co );
+ }
+ // read coverage table
+ GlyphCoverageTable ct = readCoverageTable ( tableTag + " attachment point coverage", subtableOffset + co );
+ // store results
+ seMapping = ct;
+ // extract subtable
+ extractSESubState ( GlyphTable.GLYPH_TABLE_TYPE_DEFINITION, GDEFLookupType.ATTACHMENT_POINT, 0, lookupSequence, 0, 1 );
+ resetATSubState();
+ }
+
+ private void readGDEFLigatureCaretTable(String tableTag, int lookupSequence, long subtableOffset) throws IOException {
+ initATSubState();
+ in.seekSet(subtableOffset);
+ // read coverage offset
+ int co = in.readTTFUShort();
+ // read ligature glyph count
+ int nl = in.readTTFUShort();
+ // read ligature glyph table offsets
+ int[] lgto = new int [ nl ];
+ for ( int i = 0; i < nl; i++ ) {
+ lgto [ i ] = in.readTTFUShort();
+ }
+
+ // dump info if debugging
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " ligature caret coverage table offset: " + co );
+ log.debug(tableTag + " ligature caret ligature glyph count: " + nl );
+ for ( int i = 0; i < nl; i++ ) {
+ log.debug(tableTag + " ligature glyph table offset[" + i + "]: " + lgto[i] );
+ }
+ }
+ // read coverage table
+ GlyphCoverageTable ct = readCoverageTable ( tableTag + " ligature caret coverage", subtableOffset + co );
+ // store results
+ seMapping = ct;
+ // extract subtable
+ extractSESubState ( GlyphTable.GLYPH_TABLE_TYPE_DEFINITION, GDEFLookupType.LIGATURE_CARET, 0, lookupSequence, 0, 1 );
+ resetATSubState();
+ }
+
+ private void readGDEFMarkAttachmentTable(String tableTag, int lookupSequence, long subtableOffset) throws IOException {
+ initATSubState();
+ in.seekSet(subtableOffset);
+ // subtable is a bare class definition table
+ GlyphClassTable ct = readClassDefTable ( tableTag + " glyph class definition table", subtableOffset );
+ // store results
+ seMapping = ct;
+ // extract subtable
+ extractSESubState ( GlyphTable.GLYPH_TABLE_TYPE_DEFINITION, GDEFLookupType.MARK_ATTACHMENT, 0, lookupSequence, 0, 1 );
+ resetATSubState();
+ }
+
+ private void readGDEFMarkGlyphsTableFormat1(String tableTag, int lookupSequence, long subtableOffset, int subtableFormat) throws IOException {
+ initATSubState();
+ in.seekSet(subtableOffset);
+ // skip over format (already known)
+ in.skip ( 2 );
+ // read mark set class count
+ int nmc = in.readTTFUShort();
+ long[] mso = new long [ nmc ];
+ // read mark set coverage offsets
+ for ( int i = 0; i < nmc; i++ ) {
+ mso [ i ] = in.readTTFULong();
+ }
+ // dump info if debugging
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " mark set subtable format: " + subtableFormat + " (glyph sets)" );
+ log.debug(tableTag + " mark set class count: " + nmc );
+ for ( int i = 0; i < nmc; i++ ) {
+ log.debug(tableTag + " mark set coverage table offset[" + i + "]: " + mso[i] );
+ }
+ }
+ // read mark set coverage tables, one per class
+ GlyphCoverageTable[] msca = new GlyphCoverageTable[nmc];
+ for ( int i = 0; i < nmc; i++ ) {
+ msca[i] = readCoverageTable ( tableTag + " mark set coverage[" + i + "]", subtableOffset + mso[i] );
+ }
+ // create combined class table from per-class coverage tables
+ GlyphClassTable ct = GlyphClassTable.createClassTable ( Arrays.asList ( msca ) );
+ // store results
+ seMapping = ct;
+ // extract subtable
+ extractSESubState ( GlyphTable.GLYPH_TABLE_TYPE_DEFINITION, GDEFLookupType.MARK_ATTACHMENT, 0, lookupSequence, 0, 1 );
+ resetATSubState();
+ }
+
+ private void readGDEFMarkGlyphsTable(String tableTag, int lookupSequence, long subtableOffset) throws IOException {
+ in.seekSet(subtableOffset);
+ // read mark set subtable format
+ int sf = in.readTTFUShort();
+ if ( sf == 1 ) {
+ readGDEFMarkGlyphsTableFormat1 ( tableTag, lookupSequence, subtableOffset, sf );
+ } else {
+ throw new AdvancedTypographicTableFormatException ( "unsupported mark glyph sets subtable format: " + sf );
+ }
+ }
+
+ /**
+ * Read the GDEF table.
+ * @throws IOException In case of a I/O problem
+ */
+ private void readGDEF() throws IOException {
+ String tableTag = "GDEF";
+ // Initialize temporary state
+ initATState();
+ // Read glyph definition (GDEF) table
+ TTFDirTabEntry dirTab = ttf.getDirectoryEntry ( tableTag );
+ if ( gdef != null ) {
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + ": ignoring duplicate table");
+ }
+ } else if (dirTab != null) {
+ ttf.seekTab(in, tableTag, 0);
+ long version = in.readTTFULong();
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " version: " + ( version / 65536 ) + "." + ( version % 65536 ));
+ }
+ // glyph class definition table offset (may be null)
+ int cdo = in.readTTFUShort();
+ // attach point list offset (may be null)
+ int apo = in.readTTFUShort();
+ // ligature caret list offset (may be null)
+ int lco = in.readTTFUShort();
+ // mark attach class definition table offset (may be null)
+ int mao = in.readTTFUShort();
+ // mark glyph sets definition table offset (may be null)
+ int mgo;
+ if ( version >= 0x00010002 ) {
+ mgo = in.readTTFUShort();
+ } else {
+ mgo = 0;
+ }
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " glyph class definition table offset: " + cdo );
+ log.debug(tableTag + " attachment point list offset: " + apo );
+ log.debug(tableTag + " ligature caret list offset: " + lco );
+ log.debug(tableTag + " mark attachment class definition table offset: " + mao );
+ log.debug(tableTag + " mark glyph set definitions table offset: " + mgo );
+ }
+ // initialize subtable sequence number
+ int seqno = 0;
+ // obtain offset to start of gdef table
+ long to = dirTab.getOffset();
+ // (optionally) read glyph class definition subtable
+ if ( cdo != 0 ) {
+ readGDEFClassDefTable ( tableTag, seqno++, to + cdo );
+ }
+ // (optionally) read glyph attachment point subtable
+ if ( apo != 0 ) {
+ readGDEFAttachmentTable ( tableTag, seqno++, to + apo );
+ }
+ // (optionally) read ligature caret subtable
+ if ( lco != 0 ) {
+ readGDEFLigatureCaretTable ( tableTag, seqno++, to + lco );
+ }
+ // (optionally) read mark attachment class subtable
+ if ( mao != 0 ) {
+ readGDEFMarkAttachmentTable ( tableTag, seqno++, to + mao );
+ }
+ // (optionally) read mark glyph sets subtable
+ if ( mgo != 0 ) {
+ readGDEFMarkGlyphsTable ( tableTag, seqno++, to + mgo );
+ }
+ GlyphDefinitionTable gdef;
+ if ( ( gdef = constructGDEF() ) != null ) {
+ this.gdef = gdef;
+ }
+ }
+ }
+
+ /**
+ * Read the GSUB table.
+ * @throws IOException In case of a I/O problem
+ */
+ private void readGSUB() throws IOException {
+ String tableTag = "GSUB";
+ // Initialize temporary state
+ initATState();
+ // Read glyph substitution (GSUB) table
+ TTFDirTabEntry dirTab = ttf.getDirectoryEntry ( tableTag );
+ if ( gpos != null ) {
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + ": ignoring duplicate table");
+ }
+ } else if (dirTab != null) {
+ ttf.seekTab(in, tableTag, 0);
+ int version = in.readTTFLong();
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " version: " + ( version / 65536 ) + "." + ( version % 65536 ));
+ }
+ int slo = in.readTTFUShort();
+ int flo = in.readTTFUShort();
+ int llo = in.readTTFUShort();
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " script list offset: " + slo );
+ log.debug(tableTag + " feature list offset: " + flo );
+ log.debug(tableTag + " lookup list offset: " + llo );
+ }
+ long to = dirTab.getOffset();
+ readCommonLayoutTables ( tableTag, to + slo, to + flo, to + llo );
+ GlyphSubstitutionTable gsub;
+ if ( ( gsub = constructGSUB() ) != null ) {
+ this.gsub = gsub;
+ }
+ }
+ }
+
+ /**
+ * Read the GPOS table.
+ * @throws IOException In case of a I/O problem
+ */
+ private void readGPOS() throws IOException {
+ String tableTag = "GPOS";
+ // Initialize temporary state
+ initATState();
+ // Read glyph positioning (GPOS) table
+ TTFDirTabEntry dirTab = ttf.getDirectoryEntry ( tableTag );
+ if ( gpos != null ) {
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + ": ignoring duplicate table");
+ }
+ } else if (dirTab != null) {
+ ttf.seekTab(in, tableTag, 0);
+ int version = in.readTTFLong();
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " version: " + ( version / 65536 ) + "." + ( version % 65536 ));
+ }
+ int slo = in.readTTFUShort();
+ int flo = in.readTTFUShort();
+ int llo = in.readTTFUShort();
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " script list offset: " + slo );
+ log.debug(tableTag + " feature list offset: " + flo );
+ log.debug(tableTag + " lookup list offset: " + llo );
+ }
+ long to = dirTab.getOffset();
+ readCommonLayoutTables ( tableTag, to + slo, to + flo, to + llo );
+ GlyphPositioningTable gpos;
+ if ( ( gpos = constructGPOS() ) != null ) {
+ this.gpos = gpos;
+ }
+ }
+ }
+
+ /**
+ * Construct the (internal representation of the) GDEF table based on previously
+ * parsed state.
+ * @returns glyph definition table or null if insufficient or invalid state
+ */
+ private GlyphDefinitionTable constructGDEF() {
+ GlyphDefinitionTable gdef = null;
+ List subtables;
+ if ( ( subtables = constructGDEFSubtables() ) != null ) {
+ if ( subtables.size() > 0 ) {
+ gdef = new GlyphDefinitionTable ( subtables );
+ }
+ }
+ resetATState();
+ return gdef;
+ }
+
+ /**
+ * Construct the (internal representation of the) GSUB table based on previously
+ * parsed state.
+ * @returns glyph substitution table or null if insufficient or invalid state
+ */
+ private GlyphSubstitutionTable constructGSUB() {
+ GlyphSubstitutionTable gsub = null;
+ Map lookups;
+ if ( ( lookups = constructLookups() ) != null ) {
+ List subtables;
+ if ( ( subtables = constructGSUBSubtables() ) != null ) {
+ if ( ( lookups.size() > 0 ) && ( subtables.size() > 0 ) ) {
+ gsub = new GlyphSubstitutionTable ( gdef, lookups, subtables );
+ }
+ }
+ }
+ resetATState();
+ return gsub;
+ }
+
+ /**
+ * Construct the (internal representation of the) GPOS table based on previously
+ * parsed state.
+ * @returns glyph positioning table or null if insufficient or invalid state
+ */
+ private GlyphPositioningTable constructGPOS() {
+ GlyphPositioningTable gpos = null;
+ Map lookups;
+ if ( ( lookups = constructLookups() ) != null ) {
+ List subtables;
+ if ( ( subtables = constructGPOSSubtables() ) != null ) {
+ if ( ( lookups.size() > 0 ) && ( subtables.size() > 0 ) ) {
+ gpos = new GlyphPositioningTable ( gdef, lookups, subtables );
+ }
+ }
+ }
+ resetATState();
+ return gpos;
+ }
+
+ private void constructLookupsFeature ( Map lookups, String st, String lt, String fid ) {
+ Object[] fp = (Object[]) seFeatures.get ( fid );
+ if ( fp != null ) {
+ assert fp.length == 2;
+ String ft = (String) fp[0]; // feature tag
+ List/*<String>*/ lul = (List) fp[1]; // list of lookup table ids
+ if ( ( ft != null ) && ( lul != null ) && ( lul.size() > 0 ) ) {
+ GlyphTable.LookupSpec ls = new GlyphTable.LookupSpec ( st, lt, ft );
+ lookups.put ( ls, lul );
+ }
+ }
+ }
+
+ private void constructLookupsFeatures ( Map lookups, String st, String lt, List/*<String>*/ fids ) {
+ for ( Iterator fit = fids.iterator(); fit.hasNext();) {
+ String fid = (String) fit.next();
+ constructLookupsFeature ( lookups, st, lt, fid );
+ }
+ }
+
+ private void constructLookupsLanguage ( Map lookups, String st, String lt, Map/*<String,Object[2]>*/ languages ) {
+ Object[] lp = (Object[]) languages.get ( lt );
+ if ( lp != null ) {
+ assert lp.length == 2;
+ if ( lp[0] != null ) { // required feature id
+ constructLookupsFeature ( lookups, st, lt, (String) lp[0] );
+ }
+ if ( lp[1] != null ) { // non-required features ids
+ constructLookupsFeatures ( lookups, st, lt, (List) lp[1] );
+ }
+ }
+ }
+
+ private void constructLookupsLanguages ( Map lookups, String st, List/*<String>*/ ll, Map/*<String,Object[2]>*/ languages ) {
+ for ( Iterator lit = ll.iterator(); lit.hasNext();) {
+ String lt = (String) lit.next();
+ constructLookupsLanguage ( lookups, st, lt, languages );
+ }
+ }
+
+ private Map constructLookups() {
+ Map/*<GlyphTable.LookupSpec,List<String>>*/ lookups = new java.util.LinkedHashMap();
+ for ( Iterator sit = seScripts.keySet().iterator(); sit.hasNext();) {
+ String st = (String) sit.next();
+ Object[] sp = (Object[]) seScripts.get ( st );
+ if ( sp != null ) {
+ assert sp.length == 3;
+ Map/*<String,Object[2]>*/ languages = (Map) sp[2];
+ if ( sp[0] != null ) { // default language
+ constructLookupsLanguage ( lookups, st, (String) sp[0], languages );
+ }
+ if ( sp[1] != null ) { // non-default languages
+ constructLookupsLanguages ( lookups, st, (List) sp[1], languages );
+ }
+ }
+ }
+ return lookups;
+ }
+
+ private List constructGDEFSubtables() {
+ List/*<GlyphDefinitionSubtable>*/ subtables = new java.util.ArrayList();
+ if ( seSubtables != null ) {
+ for ( Iterator it = seSubtables.iterator(); it.hasNext();) {
+ Object[] stp = (Object[]) it.next();
+ GlyphSubtable st;
+ if ( ( st = constructGDEFSubtable ( stp ) ) != null ) {
+ subtables.add ( st );
+ }
+ }
+ }
+ return subtables;
+ }
+
+ private GlyphSubtable constructGDEFSubtable ( Object[] stp ) {
+ GlyphSubtable st = null;
+ assert ( stp != null ) && ( stp.length == 8 );
+ Integer tt = (Integer) stp[0]; // table type
+ Integer lt = (Integer) stp[1]; // lookup type
+ Integer ln = (Integer) stp[2]; // lookup sequence number
+ Integer lf = (Integer) stp[3]; // lookup flags
+ Integer sn = (Integer) stp[4]; // subtable sequence number
+ Integer sf = (Integer) stp[5]; // subtable format
+ GlyphMappingTable mapping = (GlyphMappingTable) stp[6];
+ List entries = (List) stp[7];
+ if ( tt.intValue() == GlyphTable.GLYPH_TABLE_TYPE_DEFINITION ) {
+ int type = GDEFLookupType.getSubtableType ( lt.intValue() );
+ String lid = "lu" + ln.intValue();
+ int sequence = sn.intValue();
+ int flags = lf.intValue();
+ int format = sf.intValue();
+ st = GlyphDefinitionTable.createSubtable ( type, lid, sequence, flags, format, mapping, entries );
+ }
+ return st;
+ }
+
+ private List constructGSUBSubtables() {
+ List/*<GlyphSubtable>*/ subtables = new java.util.ArrayList();
+ if ( seSubtables != null ) {
+ for ( Iterator it = seSubtables.iterator(); it.hasNext();) {
+ Object[] stp = (Object[]) it.next();
+ GlyphSubtable st;
+ if ( ( st = constructGSUBSubtable ( stp ) ) != null ) {
+ subtables.add ( st );
+ }
+ }
+ }
+ return subtables;
+ }
+
+ private GlyphSubtable constructGSUBSubtable ( Object[] stp ) {
+ GlyphSubtable st = null;
+ assert ( stp != null ) && ( stp.length == 8 );
+ Integer tt = (Integer) stp[0]; // table type
+ Integer lt = (Integer) stp[1]; // lookup type
+ Integer ln = (Integer) stp[2]; // lookup sequence number
+ Integer lf = (Integer) stp[3]; // lookup flags
+ Integer sn = (Integer) stp[4]; // subtable sequence number
+ Integer sf = (Integer) stp[5]; // subtable format
+ GlyphCoverageTable coverage = (GlyphCoverageTable) stp[6];
+ List entries = (List) stp[7];
+ if ( tt.intValue() == GlyphTable.GLYPH_TABLE_TYPE_SUBSTITUTION ) {
+ int type = GSUBLookupType.getSubtableType ( lt.intValue() );
+ String lid = "lu" + ln.intValue();
+ int sequence = sn.intValue();
+ int flags = lf.intValue();
+ int format = sf.intValue();
+ st = GlyphSubstitutionTable.createSubtable ( type, lid, sequence, flags, format, coverage, entries );
+ }
+ return st;
+ }
+
+ private List constructGPOSSubtables() {
+ List/*<GlyphSubtable>*/ subtables = new java.util.ArrayList();
+ if ( seSubtables != null ) {
+ for ( Iterator it = seSubtables.iterator(); it.hasNext();) {
+ Object[] stp = (Object[]) it.next();
+ GlyphSubtable st;
+ if ( ( st = constructGPOSSubtable ( stp ) ) != null ) {
+ subtables.add ( st );
+ }
+ }
+ }
+ return subtables;
+ }
+
+ private GlyphSubtable constructGPOSSubtable ( Object[] stp ) {
+ GlyphSubtable st = null;
+ assert ( stp != null ) && ( stp.length == 8 );
+ Integer tt = (Integer) stp[0]; // table type
+ Integer lt = (Integer) stp[1]; // lookup type
+ Integer ln = (Integer) stp[2]; // lookup sequence number
+ Integer lf = (Integer) stp[3]; // lookup flags
+ Integer sn = (Integer) stp[4]; // subtable sequence number
+ Integer sf = (Integer) stp[5]; // subtable format
+ GlyphCoverageTable coverage = (GlyphCoverageTable) stp[6];
+ List entries = (List) stp[7];
+ if ( tt.intValue() == GlyphTable.GLYPH_TABLE_TYPE_POSITIONING ) {
+ int type = GSUBLookupType.getSubtableType ( lt.intValue() );
+ String lid = "lu" + ln.intValue();
+ int sequence = sn.intValue();
+ int flags = lf.intValue();
+ int format = sf.intValue();
+ st = GlyphPositioningTable.createSubtable ( type, lid, sequence, flags, format, coverage, entries );
+ }
+ return st;
+ }
+
+ private void initATState() {
+ seScripts = new java.util.LinkedHashMap();
+ seLanguages = new java.util.LinkedHashMap();
+ seFeatures = new java.util.LinkedHashMap();
+ seSubtables = new java.util.ArrayList();
+ resetATSubState();
+ }
+
+ private void resetATState() {
+ seScripts = null;
+ seLanguages = null;
+ seFeatures = null;
+ seSubtables = null;
+ resetATSubState();
+ }
+
+ private void initATSubState() {
+ seMapping = null;
+ seEntries = new java.util.ArrayList();
+ }
+
+ private void extractSESubState ( int tableType, int lookupType, int lookupFlags, int lookupSequence, int subtableSequence, int subtableFormat ) {
+ if ( seEntries != null ) {
+ if ( ( tableType == GlyphTable.GLYPH_TABLE_TYPE_DEFINITION ) || ( seEntries.size() > 0 ) ) {
+ if ( seSubtables != null ) {
+ Integer tt = Integer.valueOf ( tableType );
+ Integer lt = Integer.valueOf ( lookupType );
+ Integer ln = Integer.valueOf ( lookupSequence );
+ Integer lf = Integer.valueOf ( lookupFlags );
+ Integer sn = Integer.valueOf ( subtableSequence );
+ Integer sf = Integer.valueOf ( subtableFormat );
+ seSubtables.add ( new Object[] { tt, lt, ln, lf, sn, sf, seMapping, seEntries } );
+ }
+ }
+ }
+ }
+
+ private void resetATSubState() {
+ seMapping = null;
+ seEntries = null;
+ }
+
+ private void resetATStateAll() {
+ resetATState();
+ gdef = null; gsub = null; gpos = null;
+ }
+
+ /** helper method for formatting an integer array for output */
+ private String toString ( int[] ia ) {
+ StringBuffer sb = new StringBuffer();
+ if ( ( ia == null ) || ( ia.length == 0 ) ) {
+ sb.append ( '-' );
+ } else {
+ boolean first = true;
+ for ( int i = 0; i < ia.length; i++ ) {
+ if ( ! first ) {
+ sb.append ( ' ' );
+ } else {
+ first = false;
+ }
+ sb.append ( ia[i] );
+ }
+ }
+ return sb.toString();
+ }
+
+}
diff --git a/src/java/org/apache/fop/complexscripts/fonts/Positionable.java b/src/java/org/apache/fop/complexscripts/fonts/Positionable.java
new file mode 100644
index 000000000..ce2da38c6
--- /dev/null
+++ b/src/java/org/apache/fop/complexscripts/fonts/Positionable.java
@@ -0,0 +1,58 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.complexscripts.fonts;
+
+// CSOFF: LineLengthCheck
+
+/**
+ * Optional interface which indicates that glyph positioning is supported and, if supported,
+ * can perform positioning.
+ * @author Glenn Adams
+ */
+public interface Positionable {
+
+ /**
+ * Determines if font performs glyph positioning.
+ * @return true if performs positioning
+ */
+ boolean performsPositioning();
+
+ /**
+ * Perform glyph positioning.
+ * @param cs character sequence to map to position offsets (advancement adjustments)
+ * @param script a script identifier
+ * @param language a language identifier
+ * @param fontSize font size
+ * @return array (sequence) of 4-tuples of placement [PX,PY] and advance [AX,AY] adjustments, in that order,
+ * with one 4-tuple for each element of glyph sequence, or null if no non-zero adjustment applies
+ */
+ int[][] performPositioning ( CharSequence cs, String script, String language, int fontSize );
+
+ /**
+ * Perform glyph positioning using an implied font size.
+ * @param cs character sequence to map to position offsets (advancement adjustments)
+ * @param script a script identifier
+ * @param language a language identifier
+ * @return array (sequence) of 4-tuples of placement [PX,PY] and advance [AX,AY] adjustments, in that order,
+ * with one 4-tuple for each element of glyph sequence, or null if no non-zero adjustment applies
+ */
+ int[][] performPositioning ( CharSequence cs, String script, String language );
+
+}
diff --git a/src/java/org/apache/fop/complexscripts/fonts/Substitutable.java b/src/java/org/apache/fop/complexscripts/fonts/Substitutable.java
new file mode 100644
index 000000000..1ff970a5e
--- /dev/null
+++ b/src/java/org/apache/fop/complexscripts/fonts/Substitutable.java
@@ -0,0 +1,63 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.complexscripts.fonts;
+
+// CSOFF: LineLengthCheck
+
+/**
+ * Optional interface which indicates that glyph substitution is supported and, if supported,
+ * can perform substitution.
+ * @author Glenn Adams
+ */
+public interface Substitutable {
+
+ /**
+ * Determines if font performs glyph substitution.
+ * @return true if performs substitution.
+ */
+ boolean performsSubstitution();
+
+ /**
+ * Perform substitutions on characters to effect glyph substitution. If some substitution is performed, it
+ * entails mapping from one or more input characters denoting textual character information to one or more
+ * output character codes denoting glyphs in this font, where the output character codes may make use of
+ * private character code values that have significance only for this font.
+ * @param cs character sequence to map to output font encoding character sequence
+ * @param script a script identifier
+ * @param language a language identifier
+ * @return output sequence (represented as a character sequence, where each character in the returned sequence
+ * denotes "font characters", i.e., character codes that map directly (1-1) to their associated glyphs
+ */
+ 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/complexscripts/scripts/ArabicScriptProcessor.java b/src/java/org/apache/fop/complexscripts/scripts/ArabicScriptProcessor.java
new file mode 100644
index 000000000..503ee2705
--- /dev/null
+++ b/src/java/org/apache/fop/complexscripts/scripts/ArabicScriptProcessor.java
@@ -0,0 +1,522 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.complexscripts.scripts;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import org.apache.fop.complexscripts.bidi.BidiClass;
+import org.apache.fop.complexscripts.bidi.BidiConstants;
+import org.apache.fop.complexscripts.fonts.GlyphDefinitionTable;
+import org.apache.fop.complexscripts.util.GlyphContextTester;
+import org.apache.fop.complexscripts.util.GlyphSequence;
+import org.apache.fop.complexscripts.util.ScriptContextTester;
+
+// CSOFF: AvoidNestedBlocksCheck
+// CSOFF: NoWhitespaceAfterCheck
+// CSOFF: InnerAssignmentCheck
+// CSOFF: SimplifyBooleanReturnCheck
+// CSOFF: LineLengthCheck
+
+/**
+ * <p>The <code>ArabicScriptProcessor</code> class implements a script processor for
+ * performing glyph substitution and positioning operations on content associated with the Arabic script.</p>
+ * @author Glenn Adams
+ */
+public class ArabicScriptProcessor extends DefaultScriptProcessor {
+
+ /** logging instance */
+ private static final Log log = LogFactory.getLog(ArabicScriptProcessor.class); // CSOK: ConstantNameCheck
+
+ /** features to use for substitutions */
+ private static final String[] gsubFeatures = // CSOK: ConstantNameCheck
+ {
+ "calt", // contextual alternates
+ "ccmp", // glyph composition/decomposition
+ "fina", // final (terminal) forms
+ "init", // initial forms
+ "isol", // isolated formas
+ "liga", // standard ligatures
+ "medi", // medial forms
+ "rlig" // required ligatures
+ };
+
+ /** features to use for positioning */
+ private static final String[] gposFeatures = // CSOK: ConstantNameCheck
+ {
+ "curs", // cursive positioning
+ "kern", // kerning
+ "mark", // mark to base or ligature positioning
+ "mkmk" // mark to mark positioning
+ };
+
+ private static class SubstitutionScriptContextTester implements ScriptContextTester {
+ private static Map/*<String,GlyphContextTester>*/ testerMap = new HashMap/*<String,GlyphContextTester>*/();
+ static {
+ testerMap.put ( "fina", new GlyphContextTester() {
+ public boolean test ( String script, String language, String feature, GlyphSequence gs, int index, int flags ) {
+ return inFinalContext ( script, language, feature, gs, index, flags );
+ }
+ } );
+ testerMap.put ( "init", new GlyphContextTester() {
+ public boolean test ( String script, String language, String feature, GlyphSequence gs, int index, int flags ) {
+ return inInitialContext ( script, language, feature, gs, index, flags );
+ }
+ } );
+ testerMap.put ( "isol", new GlyphContextTester() {
+ public boolean test ( String script, String language, String feature, GlyphSequence gs, int index, int flags ) {
+ return inIsolateContext ( script, language, feature, gs, index, flags );
+ }
+ } );
+ testerMap.put ( "liga", new GlyphContextTester() {
+ public boolean test ( String script, String language, String feature, GlyphSequence gs, int index, int flags ) {
+ return inLigatureContext ( script, language, feature, gs, index, flags );
+ }
+ } );
+ testerMap.put ( "medi", new GlyphContextTester() {
+ public boolean test ( String script, String language, String feature, GlyphSequence gs, int index, int flags ) {
+ return inMedialContext ( script, language, feature, gs, index, flags );
+ }
+ } );
+ }
+ public GlyphContextTester getTester ( String feature ) {
+ return (GlyphContextTester) testerMap.get ( feature );
+ }
+ }
+
+ private static class PositioningScriptContextTester implements ScriptContextTester {
+ private static Map/*<String,GlyphContextTester>*/ testerMap = new HashMap/*<String,GlyphContextTester>*/();
+ public GlyphContextTester getTester ( String feature ) {
+ return (GlyphContextTester) testerMap.get ( feature );
+ }
+ }
+
+ private final ScriptContextTester subContextTester;
+ private final ScriptContextTester posContextTester;
+
+ ArabicScriptProcessor ( String script ) {
+ super ( script );
+ this.subContextTester = new SubstitutionScriptContextTester();
+ this.posContextTester = new PositioningScriptContextTester();
+ }
+
+ /** {@inheritDoc} */
+ public String[] getSubstitutionFeatures() {
+ return gsubFeatures;
+ }
+
+ /** {@inheritDoc} */
+ public ScriptContextTester getSubstitutionContextTester() {
+ return subContextTester;
+ }
+
+ /** {@inheritDoc} */
+ public String[] getPositioningFeatures() {
+ return gposFeatures;
+ }
+
+ /** {@inheritDoc} */
+ public ScriptContextTester getPositioningContextTester() {
+ return posContextTester;
+ }
+
+ /** {@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, int flags ) {
+ GlyphSequence.CharAssociation a = gs.getAssociation ( index );
+ int[] ca = gs.getCharacterArray ( false );
+ int nc = gs.getCharacterCount();
+ if ( nc == 0 ) {
+ return false;
+ } else {
+ int s = a.getStart();
+ int e = a.getEnd();
+ if ( ! hasFinalPrecedingContext ( ca, nc, s, e ) ) {
+ return false;
+ } else if ( forcesFinalThisContext ( ca, nc, s, e ) ) {
+ return true;
+ } else if ( ! hasFinalFollowingContext ( ca, nc, s, e ) ) {
+ return false;
+ } else {
+ return true;
+ }
+ }
+ }
+
+ private static boolean inInitialContext ( String script, String language, String feature, GlyphSequence gs, int index, int flags ) {
+ GlyphSequence.CharAssociation a = gs.getAssociation ( index );
+ int[] ca = gs.getCharacterArray ( false );
+ int nc = gs.getCharacterCount();
+ if ( nc == 0 ) {
+ return false;
+ } 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;
+ }
+ }
+ }
+
+ private static boolean inIsolateContext ( String script, String language, String feature, GlyphSequence gs, int index, int flags ) {
+ GlyphSequence.CharAssociation a = gs.getAssociation ( index );
+ int nc = gs.getCharacterCount();
+ if ( nc == 0 ) {
+ return false;
+ } else if ( ( a.getStart() == 0 ) && ( a.getEnd() == nc ) ) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ private static boolean inLigatureContext ( String script, String language, String feature, GlyphSequence gs, int index, int flags ) {
+ GlyphSequence.CharAssociation a = gs.getAssociation ( index );
+ int[] ca = gs.getCharacterArray ( false );
+ int nc = gs.getCharacterCount();
+ if ( nc == 0 ) {
+ return false;
+ } else {
+ 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 inMedialContext ( String script, String language, String feature, GlyphSequence gs, int index, int flags ) {
+ GlyphSequence.CharAssociation a = gs.getAssociation ( index );
+ int[] ca = gs.getCharacterArray ( false );
+ int nc = gs.getCharacterCount();
+ if ( nc == 0 ) {
+ return false;
+ } else {
+ int s = a.getStart();
+ int e = a.getEnd();
+ if ( ! hasMedialPrecedingContext ( ca, nc, s, e ) ) {
+ return false;
+ } else if ( ! hasMedialThisContext ( ca, nc, s, e ) ) {
+ return false;
+ } else if ( ! hasMedialFollowingContext ( ca, nc, s, e ) ) {
+ return false;
+ } else {
+ return true;
+ }
+ }
+ }
+
+ private static boolean hasFinalPrecedingContext ( int[] ca, int nc, int s, int e ) {
+ int chp = 0;
+ int clp = 0;
+ for ( int i = s; i > 0; i-- ) {
+ int k = i - 1;
+ if ( ( k >= 0 ) && ( k < nc ) ) {
+ chp = ca [ k ];
+ clp = BidiClass.getBidiClass ( chp );
+ if ( clp != BidiConstants.NSM ) {
+ break;
+ }
+ }
+ }
+ if ( clp != BidiConstants.AL ) {
+ return false;
+ } else if ( hasIsolateInitial ( chp ) ) {
+ return false;
+ } else {
+ return true;
+ }
+ }
+
+ private static boolean forcesFinalThisContext ( int[] ca, int nc, int s, int e ) {
+ int chl = 0;
+ int cll = 0;
+ for ( int i = 0, n = e - s; i < n; i++ ) {
+ int k = n - i - 1;
+ int j = s + k;
+ if ( ( j >= 0 ) && ( j < nc ) ) {
+ chl = ca [ j ];
+ cll = BidiClass.getBidiClass ( chl );
+ if ( cll != BidiConstants.NSM ) {
+ break;
+ }
+ }
+ }
+ if ( cll != BidiConstants.AL ) {
+ return false;
+ }
+ if ( hasIsolateInitial ( chl ) ) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ private static boolean hasFinalFollowingContext ( int[] ca, int nc, int s, int e ) {
+ int chf = 0;
+ int clf = 0;
+ for ( int i = e, n = nc; i < n; i++ ) {
+ chf = ca [ i ];
+ clf = BidiClass.getBidiClass ( chf );
+ if ( clf != BidiConstants.NSM ) {
+ break;
+ }
+ }
+ if ( clf != BidiConstants.AL ) {
+ return true;
+ } else if ( hasIsolateFinal ( chf ) ) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ private static boolean hasInitialPrecedingContext ( int[] ca, int nc, int s, int e ) {
+ int chp = 0;
+ int clp = 0;
+ for ( int i = s; i > 0; i-- ) {
+ int k = i - 1;
+ if ( ( k >= 0 ) && ( k < nc ) ) {
+ chp = ca [ k ];
+ clp = BidiClass.getBidiClass ( chp );
+ if ( clp != BidiConstants.NSM ) {
+ break;
+ }
+ }
+ }
+ if ( clp != BidiConstants.AL ) {
+ return true;
+ } else if ( hasIsolateInitial ( chp ) ) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ private static boolean hasInitialFollowingContext ( int[] ca, int nc, int s, int e ) {
+ int chf = 0;
+ int clf = 0;
+ for ( int i = e, n = nc; i < n; i++ ) {
+ chf = ca [ i ];
+ clf = BidiClass.getBidiClass ( chf );
+ if ( clf != BidiConstants.NSM ) {
+ break;
+ }
+ }
+ if ( clf != BidiConstants.AL ) {
+ return false;
+ } else if ( hasIsolateFinal ( chf ) ) {
+ return false;
+ } else {
+ return true;
+ }
+ }
+
+ private static boolean hasMedialPrecedingContext ( int[] ca, int nc, int s, int e ) {
+ int chp = 0;
+ int clp = 0;
+ for ( int i = s; i > 0; i-- ) {
+ int k = i - 1;
+ if ( ( k >= 0 ) && ( k < nc ) ) {
+ chp = ca [ k ];
+ clp = BidiClass.getBidiClass ( chp );
+ if ( clp != BidiConstants.NSM ) {
+ break;
+ }
+ }
+ }
+ if ( clp != BidiConstants.AL ) {
+ return false;
+ } else if ( hasIsolateInitial ( chp ) ) {
+ return false;
+ } else {
+ return true;
+ }
+ }
+
+ private static boolean hasMedialThisContext ( int[] ca, int nc, int s, int e ) {
+ int chf = 0; // first non-NSM char in [s,e)
+ int clf = 0;
+ for ( int i = 0, n = e - s; i < n; i++ ) {
+ int k = s + i;
+ if ( ( k >= 0 ) && ( k < nc ) ) {
+ chf = ca [ s + i ];
+ clf = BidiClass.getBidiClass ( chf );
+ if ( clf != BidiConstants.NSM ) {
+ break;
+ }
+ }
+ }
+ if ( clf != BidiConstants.AL ) {
+ return false;
+ }
+ int chl = 0; // last non-NSM char in [s,e)
+ int cll = 0;
+ for ( int i = 0, n = e - s; i < n; i++ ) {
+ int k = n - i - 1;
+ int j = s + k;
+ if ( ( j >= 0 ) && ( j < nc ) ) {
+ chl = ca [ j ];
+ cll = BidiClass.getBidiClass ( chl );
+ if ( cll != BidiConstants.NSM ) {
+ break;
+ }
+ }
+ }
+ if ( cll != BidiConstants.AL ) {
+ return false;
+ }
+ if ( hasIsolateFinal ( chf ) ) {
+ return false;
+ } else if ( hasIsolateInitial ( chl ) ) {
+ return false;
+ } else {
+ return true;
+ }
+ }
+
+ private static boolean hasMedialFollowingContext ( int[] ca, int nc, int s, int e ) {
+ int chf = 0;
+ int clf = 0;
+ for ( int i = e, n = nc; i < n; i++ ) {
+ chf = ca [ i ];
+ clf = BidiClass.getBidiClass ( chf );
+ if ( clf != BidiConstants.NSM ) {
+ break;
+ }
+ }
+ if ( clf != BidiConstants.AL ) {
+ return false;
+ } else if ( hasIsolateFinal ( chf ) ) {
+ return false;
+ } else {
+ return true;
+ }
+ }
+
+ private static boolean hasLigaturePrecedingContext ( int[] ca, int nc, int s, int e ) {
+ return true;
+ }
+
+ private static boolean hasLigatureFollowingContext ( int[] ca, int nc, int s, int e ) {
+ int chf = 0;
+ int clf = 0;
+ for ( int i = e, n = nc; i < n; i++ ) {
+ chf = ca [ i ];
+ clf = BidiClass.getBidiClass ( chf );
+ if ( clf != BidiConstants.NSM ) {
+ break;
+ }
+ }
+ if ( clf == BidiConstants.AL ) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Ordered array of Unicode scalars designating those Arabic (Script) Letters
+ * which exhibit an isolated form in word initial position.
+ */
+ private static int[] isolatedInitials = {
+ 0x0621, // HAMZA
+ 0x0622, // ALEF WITH MADDA ABOVE
+ 0x0623, // ALEF WITH HAMZA ABOVE
+ 0x0624, // WAW WITH HAMZA ABOVE
+ 0x0625, // ALEF WITH HAMZA BELOWW
+ 0x0627, // ALEF
+ 0x062F, // DAL
+ 0x0630, // THAL
+ 0x0631, // REH
+ 0x0632, // ZAIN
+ 0x0648, // WAW
+ 0x0671, // ALEF WASLA
+ 0x0672, // ALEF WITH WAVY HAMZA ABOVE
+ 0x0673, // ALEF WITH WAVY HAMZA BELOW
+ 0x0675, // HIGH HAMZA ALEF
+ 0x0676, // HIGH HAMZA WAW
+ 0x0677, // U WITH HAMZA ABOVE
+ 0x0688, // DDAL
+ 0x0689, // DAL WITH RING
+ 0x068A, // DAL WITH DOT BELOW
+ 0x068B, // DAL WITH DOT BELOW AND SMALL TAH
+ 0x068C, // DAHAL
+ 0x068D, // DDAHAL
+ 0x068E, // DUL
+ 0x068F, // DUL WITH THREE DOTS ABOVE DOWNWARDS
+ 0x0690, // DUL WITH FOUR DOTS ABOVE
+ 0x0691, // RREH
+ 0x0692, // REH WITH SMALL V
+ 0x0693, // REH WITH RING
+ 0x0694, // REH WITH DOT BELOW
+ 0x0695, // REH WITH SMALL V BELOW
+ 0x0696, // REH WITH DOT BELOW AND DOT ABOVE
+ 0x0697, // REH WITH TWO DOTS ABOVE
+ 0x0698, // JEH
+ 0x0699, // REH WITH FOUR DOTS ABOVE
+ 0x06C4, // WAW WITH RING
+ 0x06C5, // KIRGHIZ OE
+ 0x06C6, // OE
+ 0x06C7, // U
+ 0x06C8, // YU
+ 0x06C9, // KIRGHIZ YU
+ 0x06CA, // WAW WITH TWO DOTS ABOVE
+ 0x06CB, // VE
+ 0x06CF, // WAW WITH DOT ABOVE
+ 0x06EE, // DAL WITH INVERTED V
+ 0x06EF // REH WITH INVERTED V
+ };
+
+ private static boolean hasIsolateInitial ( int ch ) {
+ return Arrays.binarySearch ( isolatedInitials, ch ) >= 0;
+ }
+
+ /**
+ * Ordered array of Unicode scalars designating those Arabic (Script) Letters
+ * which exhibit an isolated form in word final position.
+ */
+ private static int[] isolatedFinals = {
+ 0x0621 // HAMZA
+ };
+
+ private static boolean hasIsolateFinal ( int ch ) {
+ return Arrays.binarySearch ( isolatedFinals, ch ) >= 0;
+ }
+
+}
diff --git a/src/java/org/apache/fop/complexscripts/scripts/DefaultScriptProcessor.java b/src/java/org/apache/fop/complexscripts/scripts/DefaultScriptProcessor.java
new file mode 100644
index 000000000..ff70e6a67
--- /dev/null
+++ b/src/java/org/apache/fop/complexscripts/scripts/DefaultScriptProcessor.java
@@ -0,0 +1,144 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.complexscripts.scripts;
+
+import org.apache.fop.complexscripts.fonts.GlyphDefinitionTable;
+import org.apache.fop.complexscripts.util.GlyphSequence;
+import org.apache.fop.complexscripts.util.ScriptContextTester;
+
+// CSOFF: LineLengthCheck
+
+/**
+ * Default script processor, which enables default glyph composition/decomposition, common ligatures, localized forms
+ * and kerning.
+ *
+ * @author Glenn Adams
+ */
+public class DefaultScriptProcessor extends ScriptProcessor {
+
+ /** features to use for substitutions */
+ private static final String[] gsubFeatures = // CSOK: ConstantNameCheck
+ {
+ "ccmp", // glyph composition/decomposition
+ "liga", // common ligatures
+ "locl" // localized forms
+ };
+
+ /** features to use for positioning */
+ private static final String[] gposFeatures = // CSOK: ConstantNameCheck
+ {
+ "kern", // kerning
+ "mark", // mark to base or ligature positioning
+ "mkmk" // mark to mark positioning
+ };
+
+ DefaultScriptProcessor ( String script ) {
+ 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/complexscripts/scripts/DevanagariScriptProcessor.java b/src/java/org/apache/fop/complexscripts/scripts/DevanagariScriptProcessor.java
new file mode 100644
index 000000000..52f1d7284
--- /dev/null
+++ b/src/java/org/apache/fop/complexscripts/scripts/DevanagariScriptProcessor.java
@@ -0,0 +1,537 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.complexscripts.scripts;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import org.apache.fop.complexscripts.util.GlyphSequence;
+import org.apache.fop.complexscripts.util.ScriptContextTester;
+
+// CSOFF: AvoidNestedBlocksCheck
+// CSOFF: NoWhitespaceAfterCheck
+// CSOFF: WhitespaceAfter
+// CSOFF: InnerAssignmentCheck
+// CSOFF: SimplifyBooleanReturnCheck
+// CSOFF: LineLengthCheck
+
+/**
+ * <p>The <code>DevanagariScriptProcessor</code> class implements a script processor for
+ * performing glyph substitution and positioning operations on content associated with the Devanagari script.</p>
+ * @author Glenn Adams
+ */
+public class DevanagariScriptProcessor extends IndicScriptProcessor {
+
+ /** logging instance */
+ private static final Log log = LogFactory.getLog(DevanagariScriptProcessor.class); // CSOK: ConstantNameCheck
+
+ DevanagariScriptProcessor ( String script ) {
+ super ( script );
+ }
+
+ @Override
+ protected Class<? extends DevanagariSyllabizer> getSyllabizerClass() {
+ return DevanagariSyllabizer.class;
+ }
+
+ @Override
+ // find rightmost pre-base matra
+ protected int findPreBaseMatra ( GlyphSequence gs ) {
+ int ng = gs.getGlyphCount();
+ int lk = -1;
+ for ( int i = ng; i > 0; i-- ) {
+ int k = i - 1;
+ if ( containsPreBaseMatra ( gs, k ) ) {
+ lk = k;
+ break;
+ }
+ }
+ return lk;
+ }
+
+ @Override
+ // find leftmost pre-base matra target, starting from source
+ protected int findPreBaseMatraTarget ( GlyphSequence gs, int source ) {
+ int ng = gs.getGlyphCount();
+ int lk = -1;
+ for ( int i = ( source < ng ) ? source : ng; i > 0; i-- ) {
+ int k = i - 1;
+ if ( containsConsonant ( gs, k ) ) {
+ if ( containsHalfConsonant ( gs, k ) ) {
+ lk = k;
+ } else if ( lk == -1 ) {
+ lk = k;
+ } else {
+ break;
+ }
+ }
+ }
+ return lk;
+ }
+
+ private static boolean containsPreBaseMatra ( GlyphSequence gs, int k ) {
+ GlyphSequence.CharAssociation a = gs.getAssociation ( k );
+ int[] ca = gs.getCharacterArray ( false );
+ for ( int i = a.getStart(), e = a.getEnd(); i < e; i++ ) {
+ if ( isPreM ( ca [ i ] ) ) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private static boolean containsConsonant ( GlyphSequence gs, int k ) {
+ GlyphSequence.CharAssociation a = gs.getAssociation ( k );
+ int[] ca = gs.getCharacterArray ( false );
+ for ( int i = a.getStart(), e = a.getEnd(); i < e; i++ ) {
+ if ( isC ( ca [ i ] ) ) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private static boolean containsHalfConsonant ( GlyphSequence gs, int k ) {
+ Boolean half = (Boolean) gs.getAssociation ( k ) . getPredication ( "half" );
+ return ( half != null ) ? half.booleanValue() : false;
+ }
+
+ @Override
+ protected int findReph ( GlyphSequence gs ) {
+ int ng = gs.getGlyphCount();
+ int li = -1;
+ for ( int i = 0; i < ng; i++ ) {
+ if ( containsReph ( gs, i ) ) {
+ li = i;
+ break;
+ }
+ }
+ return li;
+ }
+
+ @Override
+ protected int findRephTarget ( GlyphSequence gs, int source ) {
+ int ng = gs.getGlyphCount();
+ int c1 = -1;
+ int c2 = -1;
+ // first candidate target is after first non-half consonant
+ for ( int i = 0; i < ng; i++ ) {
+ if ( ( i != source ) && containsConsonant ( gs, i ) ) {
+ if ( ! containsHalfConsonant ( gs, i ) ) {
+ c1 = i + 1;
+ break;
+ }
+ }
+ }
+ // second candidate target is after last non-prebase matra after first candidate or before first syllable or vedic mark
+ for ( int i = ( c1 >= 0 ) ? c1 : 0; i < ng; i++ ) {
+ if ( containsMatra ( gs, i ) && ! containsPreBaseMatra ( gs, i ) ) {
+ c2 = i + 1;
+ } else if ( containsOtherMark ( gs, i ) ) {
+ c2 = i;
+ break;
+ }
+ }
+ if ( c2 >= 0 ) {
+ return c2;
+ } else if ( c1 >= 0 ) {
+ return c1;
+ } else {
+ return source;
+ }
+ }
+
+ private static boolean containsReph ( GlyphSequence gs, int k ) {
+ Boolean rphf = (Boolean) gs.getAssociation ( k ) . getPredication ( "rphf" );
+ return ( rphf != null ) ? rphf.booleanValue() : false;
+ }
+
+ private static boolean containsMatra ( GlyphSequence gs, int k ) {
+ GlyphSequence.CharAssociation a = gs.getAssociation ( k );
+ int[] ca = gs.getCharacterArray ( false );
+ for ( int i = a.getStart(), e = a.getEnd(); i < e; i++ ) {
+ if ( isM ( ca [ i ] ) ) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private static boolean containsOtherMark ( GlyphSequence gs, int k ) {
+ GlyphSequence.CharAssociation a = gs.getAssociation ( k );
+ int[] ca = gs.getCharacterArray ( false );
+ for ( int i = a.getStart(), e = a.getEnd(); i < e; i++ ) {
+ switch ( typeOf ( ca [ i ] ) ) {
+ case C_T: // tone (e.g., udatta, anudatta)
+ case C_A: // accent (e.g., acute, grave)
+ case C_O: // other (e.g., candrabindu, anusvara, visarga, etc)
+ return true;
+ default:
+ break;
+ }
+ }
+ return false;
+ }
+
+ private static class DevanagariSyllabizer extends DefaultSyllabizer {
+ DevanagariSyllabizer ( String script, String language ) {
+ super ( script, language );
+ }
+ @Override
+ // | C ...
+ protected int findStartOfSyllable ( int[] ca, int s, int e ) {
+ if ( ( s < 0 ) || ( s >= e ) ) {
+ return -1;
+ } else {
+ while ( s < e ) {
+ int c = ca [ s ];
+ if ( isC ( c ) ) {
+ break;
+ } else {
+ s++;
+ }
+ }
+ return s;
+ }
+ }
+ @Override
+ // D* L? | ...
+ protected int findEndOfSyllable ( int[] ca, int s, int e ) {
+ if ( ( s < 0 ) || ( s >= e ) ) {
+ return -1;
+ } else {
+ int nd = 0;
+ int nl = 0;
+ int i;
+ // consume dead consonants
+ while ( ( i = isDeadConsonant ( ca, s, e ) ) > s ) {
+ s = i; nd++;
+ }
+ // consume zero or one live consonant
+ if ( ( i = isLiveConsonant ( ca, s, e ) ) > s ) {
+ s = i; nl++;
+ }
+ return ( ( nd > 0 ) || ( nl > 0 ) ) ? s : -1;
+ }
+ }
+ // D := ( C N? H )?
+ private int isDeadConsonant ( int[] ca, int s, int e ) {
+ if ( s < 0 ) {
+ return -1;
+ } else {
+ int c, i = 0;
+ int nc = 0, nh = 0;
+ do {
+ // C
+ if ( ( s + i ) < e ) {
+ c = ca [ s + i ];
+ if ( isC ( c ) ) {
+ i++;
+ nc++;
+ } else {
+ break;
+ }
+ }
+ // N?
+ if ( ( s + i ) < e ) {
+ c = ca [ s + 1 ];
+ if ( isN ( c ) ) {
+ i++;
+ }
+ }
+ // H
+ if ( ( s + i ) < e ) {
+ c = ca [ s + i ];
+ if ( isH ( c ) ) {
+ i++;
+ nh++;
+ } else {
+ break;
+ }
+ }
+ } while ( false );
+ return ( nc > 0 ) && ( nh > 0 ) ? s + i : -1;
+ }
+ }
+ // L := ( (C|V) N? X* )?; where X = ( MATRA | ACCENT MARK | TONE MARK | OTHER MARK )
+ private int isLiveConsonant ( int[] ca, int s, int e ) {
+ if ( s < 0 ) {
+ return -1;
+ } else {
+ int c, i = 0;
+ int nc = 0, nv = 0, nx = 0;
+ do {
+ // C
+ if ( ( s + i ) < e ) {
+ c = ca [ s + i ];
+ if ( isC ( c ) ) {
+ i++;
+ nc++;
+ } else if ( isV ( c ) ) {
+ i++;
+ nv++;
+ } else {
+ break;
+ }
+ }
+ // N?
+ if ( ( s + i ) < e ) {
+ c = ca [ s + i ];
+ if ( isN ( c ) ) {
+ i++;
+ }
+ }
+ // X*
+ while ( ( s + i ) < e ) {
+ c = ca [ s + i ];
+ if ( isX ( c ) ) {
+ i++;
+ nx++;
+ } else {
+ break;
+ }
+ }
+ } while ( false );
+ // if no X but has H, then ignore C|I
+ if ( nx == 0 ) {
+ if ( ( s + i ) < e ) {
+ c = ca [ s + i ];
+ if ( isH ( c ) ) {
+ if ( nc > 0 ) {
+ nc--;
+ } else if ( nv > 0 ) {
+ nv--;
+ }
+ }
+ }
+ }
+ return ( ( nc > 0 ) || ( nv > 0 ) ) ? s + i : -1;
+ }
+ }
+ }
+
+ // devanagari character types
+ static final short C_U = 0; // unassigned
+ static final short C_C = 1; // consonant
+ static final short C_V = 2; // vowel
+ static final short C_M = 3; // vowel sign (matra)
+ static final short C_S = 4; // symbol or sign
+ static final short C_T = 5; // tone mark
+ static final short C_A = 6; // accent mark
+ static final short C_P = 7; // punctuation
+ static final short C_D = 8; // digit
+ static final short C_H = 9; // halant (virama)
+ static final short C_O = 10; // other signs
+ static final short C_N = 0x0100; // nukta(ized)
+ static final short C_R = 0x0200; // reph(ized)
+ static final short C_PRE = 0x0400; // pre-base
+ static final short C_M_TYPE = 0x00FF; // type mask
+ static final short C_M_FLAGS = 0x7F00; // flag mask
+ // devanagari block range
+ static final int ccaStart = 0x0900; // first code point mapped by cca // CSOK: ConstantNameCheck
+ static final int ccaEnd = 0x0980; // last code point + 1 mapped by cca // CSOK: ConstantNameCheck
+ // devanagari character type lookups
+ static final short[] cca = { // CSOK: ConstantNameCheck
+ C_O, // 0x0900 // INVERTED CANDRABINDU
+ C_O, // 0x0901 // CANDRABINDU
+ C_O, // 0x0902 // ANUSVARA
+ C_O, // 0x0903 // VISARGA
+ C_V, // 0x0904 // SHORT A
+ C_V, // 0x0905 // A
+ C_V, // 0x0906 // AA
+ C_V, // 0x0907 // I
+ C_V, // 0x0908 // II
+ C_V, // 0x0909 // U
+ C_V, // 0x090A // UU
+ C_V, // 0x090B // VOCALIC R
+ C_V, // 0x090C // VOCALIC L
+ C_V, // 0x090D // CANDRA E
+ C_V, // 0x090E // SHORT E
+ C_V, // 0x090F // E
+ C_V, // 0x0910 // AI
+ C_V, // 0x0911 // CANDRA O
+ C_V, // 0x0912 // SHORT O
+ C_V, // 0x0913 // O
+ C_V, // 0x0914 // AU
+ C_C, // 0x0915 // KA
+ C_C, // 0x0916 // KHA
+ C_C, // 0x0917 // GA
+ C_C, // 0x0918 // GHA
+ C_C, // 0x0919 // NGA
+ C_C, // 0x091A // CA
+ C_C, // 0x091B // CHA
+ C_C, // 0x091C // JA
+ C_C, // 0x091D // JHA
+ C_C, // 0x091E // NYA
+ C_C, // 0x091F // TTA
+ C_C, // 0x0920 // TTHA
+ C_C, // 0x0921 // DDA
+ C_C, // 0x0922 // DDHA
+ C_C, // 0x0923 // NNA
+ C_C, // 0x0924 // TA
+ C_C, // 0x0925 // THA
+ C_C, // 0x0926 // DA
+ C_C, // 0x0927 // DHA
+ C_C, // 0x0928 // NA
+ C_C, // 0x0929 // NNNA
+ C_C, // 0x092A // PA
+ C_C, // 0x092B // PHA
+ C_C, // 0x092C // BA
+ C_C, // 0x092D // BHA
+ C_C, // 0x092E // MA
+ C_C, // 0x092F // YA
+ C_C|C_R, // 0x0930 // RA // CSOK: WhitespaceAround
+ C_C|C_R|C_N, // 0x0931 // RRA = 0930+093C // CSOK: WhitespaceAround
+ C_C, // 0x0932 // LA
+ C_C, // 0x0933 // LLA
+ C_C, // 0x0934 // LLLA
+ C_C, // 0x0935 // VA
+ C_C, // 0x0936 // SHA
+ C_C, // 0x0937 // SSA
+ C_C, // 0x0938 // SA
+ C_C, // 0x0939 // HA
+ C_M, // 0x093A // OE (KASHMIRI)
+ C_M, // 0x093B // OOE (KASHMIRI)
+ C_N, // 0x093C // NUKTA
+ C_S, // 0x093D // AVAGRAHA
+ C_M, // 0x093E // AA
+ C_M|C_PRE, // 0x093F // I // CSOK: WhitespaceAround
+ C_M, // 0x0940 // II
+ C_M, // 0x0941 // U
+ C_M, // 0x0942 // UU
+ C_M, // 0x0943 // VOCALIC R
+ C_M, // 0x0944 // VOCALIC RR
+ C_M, // 0x0945 // CANDRA E
+ C_M, // 0x0946 // SHORT E
+ C_M, // 0x0947 // E
+ C_M, // 0x0948 // AI
+ C_M, // 0x0949 // CANDRA O
+ C_M, // 0x094A // SHORT O
+ C_M, // 0x094B // O
+ C_M, // 0x094C // AU
+ C_H, // 0x094D // VIRAMA (HALANT)
+ C_M, // 0x094E // PRISHTHAMATRA E
+ C_M, // 0x094F // AW
+ C_S, // 0x0950 // OM
+ C_T, // 0x0951 // UDATTA
+ C_T, // 0x0952 // ANUDATTA
+ C_A, // 0x0953 // GRAVE
+ C_A, // 0x0954 // ACUTE
+ C_M, // 0x0955 // CANDRA LONG E
+ C_M, // 0x0956 // UE
+ C_M, // 0x0957 // UUE
+ C_C|C_N, // 0x0958 // QA // CSOK: WhitespaceAround
+ C_C|C_N, // 0x0959 // KHHA // CSOK: WhitespaceAround
+ C_C|C_N, // 0x095A // GHHA // CSOK: WhitespaceAround
+ C_C|C_N, // 0x095B // ZA // CSOK: WhitespaceAround
+ C_C|C_N, // 0x095C // DDDHA // CSOK: WhitespaceAround
+ C_C|C_N, // 0x095D // RHA // CSOK: WhitespaceAround
+ C_C|C_N, // 0x095E // FA // CSOK: WhitespaceAround
+ C_C|C_N, // 0x095F // YYA // CSOK: WhitespaceAround
+ C_V, // 0x0960 // VOCALIC RR
+ C_V, // 0x0961 // VOCALIC LL
+ C_M, // 0x0962 // VOCALIC RR
+ C_M, // 0x0963 // VOCALIC LL
+ C_P, // 0x0964 // DANDA
+ C_P, // 0x0965 // DOUBLE DANDA
+ C_D, // 0x0966 // ZERO
+ C_D, // 0x0967 // ONE
+ C_D, // 0x0968 // TWO
+ C_D, // 0x0969 // THREE
+ C_D, // 0x096A // FOUR
+ C_D, // 0x096B // FIVE
+ C_D, // 0x096C // SIX
+ C_D, // 0x096D // SEVEN
+ C_D, // 0x096E // EIGHT
+ C_D, // 0x096F // NINE
+ C_S, // 0x0970 // ABBREVIATION SIGN
+ C_S, // 0x0971 // HIGH SPACING DOT
+ C_V, // 0x0972 // CANDRA A (MARATHI)
+ C_V, // 0x0973 // OE (KASHMIRI)
+ C_V, // 0x0974 // OOE (KASHMIRI)
+ C_V, // 0x0975 // AW (KASHMIRI)
+ C_V, // 0x0976 // UE (KASHMIRI)
+ C_V, // 0x0977 // UUE (KASHMIRI)
+ C_U, // 0x0978 // UNASSIGNED
+ C_C, // 0x0979 // ZHA
+ C_C, // 0x097A // HEAVY YA
+ C_C, // 0x097B // GGAA (SINDHI)
+ C_C, // 0x097C // JJA (SINDHI)
+ C_C, // 0x097D // GLOTTAL STOP (LIMBU)
+ C_C, // 0x097E // DDDA (SINDHI)
+ C_C // 0x097F // BBA (SINDHI)
+ };
+ static int typeOf(int c) {
+ if ( ( c >= ccaStart ) && ( c < ccaEnd ) ) {
+ return cca [ c - ccaStart ] & C_M_TYPE;
+ } else {
+ return C_U;
+ }
+ }
+ static boolean isType(int c, int t) {
+ return typeOf ( c ) == t;
+ }
+ static boolean hasFlag(int c, int f) {
+ if ( ( c >= ccaStart ) && ( c < ccaEnd ) ) {
+ return ( cca [ c - ccaStart ] & f ) == f;
+ } else {
+ return false;
+ }
+ }
+ static boolean isC(int c) {
+ return isType(c,C_C);
+ }
+ static boolean isR(int c) {
+ return isType(c,C_C) && hasR(c);
+ }
+ static boolean isV(int c) {
+ return isType(c,C_V);
+ }
+ static boolean isN(int c) {
+ return c == 0x093C;
+ }
+ static boolean isH(int c) {
+ return c == 0x094D;
+ }
+ static boolean isM(int c) {
+ return isType(c,C_M);
+ }
+ static boolean isPreM(int c) {
+ return isType(c,C_M) && hasFlag(c,C_PRE);
+ }
+ static boolean isX(int c) {
+ switch ( typeOf ( c ) ) {
+ case C_M: // matra (combining vowel)
+ case C_A: // accent mark
+ case C_T: // tone mark
+ case C_O: // other (modifying) mark
+ return true;
+ default:
+ return false;
+ }
+ }
+ static boolean hasR(int c) {
+ return hasFlag(c,C_R);
+ }
+ static boolean hasN(int c) {
+ return hasFlag(c,C_N);
+ }
+
+}
diff --git a/src/java/org/apache/fop/complexscripts/scripts/GujaratiScriptProcessor.java b/src/java/org/apache/fop/complexscripts/scripts/GujaratiScriptProcessor.java
new file mode 100644
index 000000000..4aec56a65
--- /dev/null
+++ b/src/java/org/apache/fop/complexscripts/scripts/GujaratiScriptProcessor.java
@@ -0,0 +1,537 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.complexscripts.scripts;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import org.apache.fop.complexscripts.util.GlyphSequence;
+import org.apache.fop.complexscripts.util.ScriptContextTester;
+
+// CSOFF: AvoidNestedBlocksCheck
+// CSOFF: NoWhitespaceAfterCheck
+// CSOFF: WhitespaceAfter
+// CSOFF: InnerAssignmentCheck
+// CSOFF: SimplifyBooleanReturnCheck
+// CSOFF: LineLengthCheck
+
+/**
+ * <p>The <code>GujaratiScriptProcessor</code> class implements a script processor for
+ * performing glyph substitution and positioning operations on content associated with the Gujarati script.</p>
+ * @author Glenn Adams
+ */
+public class GujaratiScriptProcessor extends IndicScriptProcessor {
+
+ /** logging instance */
+ private static final Log log = LogFactory.getLog(GujaratiScriptProcessor.class); // CSOK: ConstantNameCheck
+
+ GujaratiScriptProcessor ( String script ) {
+ super ( script );
+ }
+
+ @Override
+ protected Class<? extends GujaratiSyllabizer> getSyllabizerClass() {
+ return GujaratiSyllabizer.class;
+ }
+
+ @Override
+ // find rightmost pre-base matra
+ protected int findPreBaseMatra ( GlyphSequence gs ) {
+ int ng = gs.getGlyphCount();
+ int lk = -1;
+ for ( int i = ng; i > 0; i-- ) {
+ int k = i - 1;
+ if ( containsPreBaseMatra ( gs, k ) ) {
+ lk = k;
+ break;
+ }
+ }
+ return lk;
+ }
+
+ @Override
+ // find leftmost pre-base matra target, starting from source
+ protected int findPreBaseMatraTarget ( GlyphSequence gs, int source ) {
+ int ng = gs.getGlyphCount();
+ int lk = -1;
+ for ( int i = ( source < ng ) ? source : ng; i > 0; i-- ) {
+ int k = i - 1;
+ if ( containsConsonant ( gs, k ) ) {
+ if ( containsHalfConsonant ( gs, k ) ) {
+ lk = k;
+ } else if ( lk == -1 ) {
+ lk = k;
+ } else {
+ break;
+ }
+ }
+ }
+ return lk;
+ }
+
+ private static boolean containsPreBaseMatra ( GlyphSequence gs, int k ) {
+ GlyphSequence.CharAssociation a = gs.getAssociation ( k );
+ int[] ca = gs.getCharacterArray ( false );
+ for ( int i = a.getStart(), e = a.getEnd(); i < e; i++ ) {
+ if ( isPreM ( ca [ i ] ) ) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private static boolean containsConsonant ( GlyphSequence gs, int k ) {
+ GlyphSequence.CharAssociation a = gs.getAssociation ( k );
+ int[] ca = gs.getCharacterArray ( false );
+ for ( int i = a.getStart(), e = a.getEnd(); i < e; i++ ) {
+ if ( isC ( ca [ i ] ) ) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private static boolean containsHalfConsonant ( GlyphSequence gs, int k ) {
+ Boolean half = (Boolean) gs.getAssociation ( k ) . getPredication ( "half" );
+ return ( half != null ) ? half.booleanValue() : false;
+ }
+
+ @Override
+ protected int findReph ( GlyphSequence gs ) {
+ int ng = gs.getGlyphCount();
+ int li = -1;
+ for ( int i = 0; i < ng; i++ ) {
+ if ( containsReph ( gs, i ) ) {
+ li = i;
+ break;
+ }
+ }
+ return li;
+ }
+
+ @Override
+ protected int findRephTarget ( GlyphSequence gs, int source ) {
+ int ng = gs.getGlyphCount();
+ int c1 = -1;
+ int c2 = -1;
+ // first candidate target is after first non-half consonant
+ for ( int i = 0; i < ng; i++ ) {
+ if ( ( i != source ) && containsConsonant ( gs, i ) ) {
+ if ( ! containsHalfConsonant ( gs, i ) ) {
+ c1 = i + 1;
+ break;
+ }
+ }
+ }
+ // second candidate target is after last non-prebase matra after first candidate or before first syllable or vedic mark
+ for ( int i = ( c1 >= 0 ) ? c1 : 0; i < ng; i++ ) {
+ if ( containsMatra ( gs, i ) && ! containsPreBaseMatra ( gs, i ) ) {
+ c2 = i + 1;
+ } else if ( containsOtherMark ( gs, i ) ) {
+ c2 = i;
+ break;
+ }
+ }
+ if ( c2 >= 0 ) {
+ return c2;
+ } else if ( c1 >= 0 ) {
+ return c1;
+ } else {
+ return source;
+ }
+ }
+
+ private static boolean containsReph ( GlyphSequence gs, int k ) {
+ Boolean rphf = (Boolean) gs.getAssociation ( k ) . getPredication ( "rphf" );
+ return ( rphf != null ) ? rphf.booleanValue() : false;
+ }
+
+ private static boolean containsMatra ( GlyphSequence gs, int k ) {
+ GlyphSequence.CharAssociation a = gs.getAssociation ( k );
+ int[] ca = gs.getCharacterArray ( false );
+ for ( int i = a.getStart(), e = a.getEnd(); i < e; i++ ) {
+ if ( isM ( ca [ i ] ) ) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private static boolean containsOtherMark ( GlyphSequence gs, int k ) {
+ GlyphSequence.CharAssociation a = gs.getAssociation ( k );
+ int[] ca = gs.getCharacterArray ( false );
+ for ( int i = a.getStart(), e = a.getEnd(); i < e; i++ ) {
+ switch ( typeOf ( ca [ i ] ) ) {
+ case C_T: // tone (e.g., udatta, anudatta)
+ case C_A: // accent (e.g., acute, grave)
+ case C_O: // other (e.g., candrabindu, anusvara, visarga, etc)
+ return true;
+ default:
+ break;
+ }
+ }
+ return false;
+ }
+
+ private static class GujaratiSyllabizer extends DefaultSyllabizer {
+ GujaratiSyllabizer ( String script, String language ) {
+ super ( script, language );
+ }
+ @Override
+ // | C ...
+ protected int findStartOfSyllable ( int[] ca, int s, int e ) {
+ if ( ( s < 0 ) || ( s >= e ) ) {
+ return -1;
+ } else {
+ while ( s < e ) {
+ int c = ca [ s ];
+ if ( isC ( c ) ) {
+ break;
+ } else {
+ s++;
+ }
+ }
+ return s;
+ }
+ }
+ @Override
+ // D* L? | ...
+ protected int findEndOfSyllable ( int[] ca, int s, int e ) {
+ if ( ( s < 0 ) || ( s >= e ) ) {
+ return -1;
+ } else {
+ int nd = 0;
+ int nl = 0;
+ int i;
+ // consume dead consonants
+ while ( ( i = isDeadConsonant ( ca, s, e ) ) > s ) {
+ s = i; nd++;
+ }
+ // consume zero or one live consonant
+ if ( ( i = isLiveConsonant ( ca, s, e ) ) > s ) {
+ s = i; nl++;
+ }
+ return ( ( nd > 0 ) || ( nl > 0 ) ) ? s : -1;
+ }
+ }
+ // D := ( C N? H )?
+ private int isDeadConsonant ( int[] ca, int s, int e ) {
+ if ( s < 0 ) {
+ return -1;
+ } else {
+ int c, i = 0;
+ int nc = 0, nh = 0;
+ do {
+ // C
+ if ( ( s + i ) < e ) {
+ c = ca [ s + i ];
+ if ( isC ( c ) ) {
+ i++;
+ nc++;
+ } else {
+ break;
+ }
+ }
+ // N?
+ if ( ( s + i ) < e ) {
+ c = ca [ s + 1 ];
+ if ( isN ( c ) ) {
+ i++;
+ }
+ }
+ // H
+ if ( ( s + i ) < e ) {
+ c = ca [ s + i ];
+ if ( isH ( c ) ) {
+ i++;
+ nh++;
+ } else {
+ break;
+ }
+ }
+ } while ( false );
+ return ( nc > 0 ) && ( nh > 0 ) ? s + i : -1;
+ }
+ }
+ // L := ( (C|V) N? X* )?; where X = ( MATRA | ACCENT MARK | TONE MARK | OTHER MARK )
+ private int isLiveConsonant ( int[] ca, int s, int e ) {
+ if ( s < 0 ) {
+ return -1;
+ } else {
+ int c, i = 0;
+ int nc = 0, nv = 0, nx = 0;
+ do {
+ // C
+ if ( ( s + i ) < e ) {
+ c = ca [ s + i ];
+ if ( isC ( c ) ) {
+ i++;
+ nc++;
+ } else if ( isV ( c ) ) {
+ i++;
+ nv++;
+ } else {
+ break;
+ }
+ }
+ // N?
+ if ( ( s + i ) < e ) {
+ c = ca [ s + i ];
+ if ( isN ( c ) ) {
+ i++;
+ }
+ }
+ // X*
+ while ( ( s + i ) < e ) {
+ c = ca [ s + i ];
+ if ( isX ( c ) ) {
+ i++;
+ nx++;
+ } else {
+ break;
+ }
+ }
+ } while ( false );
+ // if no X but has H, then ignore C|I
+ if ( nx == 0 ) {
+ if ( ( s + i ) < e ) {
+ c = ca [ s + i ];
+ if ( isH ( c ) ) {
+ if ( nc > 0 ) {
+ nc--;
+ } else if ( nv > 0 ) {
+ nv--;
+ }
+ }
+ }
+ }
+ return ( ( nc > 0 ) || ( nv > 0 ) ) ? s + i : -1;
+ }
+ }
+ }
+
+ // gujarati character types
+ static final short C_U = 0; // unassigned
+ static final short C_C = 1; // consonant
+ static final short C_V = 2; // vowel
+ static final short C_M = 3; // vowel sign (matra)
+ static final short C_S = 4; // symbol or sign
+ static final short C_T = 5; // tone mark
+ static final short C_A = 6; // accent mark
+ static final short C_P = 7; // punctuation
+ static final short C_D = 8; // digit
+ static final short C_H = 9; // halant (virama)
+ static final short C_O = 10; // other signs
+ static final short C_N = 0x0100; // nukta(ized)
+ static final short C_R = 0x0200; // reph(ized)
+ static final short C_PRE = 0x0400; // pre-base
+ static final short C_M_TYPE = 0x00FF; // type mask
+ static final short C_M_FLAGS = 0x7F00; // flag mask
+ // gujarati block range
+ static final int ccaStart = 0x0A80; // first code point mapped by cca // CSOK: ConstantNameCheck
+ static final int ccaEnd = 0x0B00; // last code point + 1 mapped by cca // CSOK: ConstantNameCheck
+ // gujarati character type lookups
+ static final short[] cca = { // CSOK: ConstantNameCheck
+ C_U, // 0x0A80 // UNASSIGNED
+ C_O, // 0x0A81 // CANDRABINDU
+ C_O, // 0x0A82 // ANUSVARA
+ C_O, // 0x0A83 // VISARGA
+ C_U, // 0x0A84 // UNASSIGNED
+ C_V, // 0x0A85 // A
+ C_V, // 0x0A86 // AA
+ C_V, // 0x0A87 // I
+ C_V, // 0x0A88 // II
+ C_V, // 0x0A89 // U
+ C_V, // 0x0A8A // UU
+ C_V, // 0x0A8B // VOCALIC R
+ C_V, // 0x0A8C // VOCALIC L
+ C_V, // 0x0A8D // CANDRA E
+ C_U, // 0x0A8E // UNASSIGNED
+ C_V, // 0x0A8F // E
+ C_V, // 0x0A90 // AI
+ C_V, // 0x0A91 // CANDRA O
+ C_U, // 0x0A92 // UNASSIGNED
+ C_V, // 0x0A93 // O
+ C_V, // 0x0A94 // AU
+ C_C, // 0x0A95 // KA
+ C_C, // 0x0A96 // KHA
+ C_C, // 0x0A97 // GA
+ C_C, // 0x0A98 // GHA
+ C_C, // 0x0A99 // NGA
+ C_C, // 0x0A9A // CA
+ C_C, // 0x0A9B // CHA
+ C_C, // 0x0A9C // JA
+ C_C, // 0x0A9D // JHA
+ C_C, // 0x0A9E // NYA
+ C_C, // 0x0A9F // TTA
+ C_C, // 0x0AA0 // TTHA
+ C_C, // 0x0AA1 // DDA
+ C_C, // 0x0AA2 // DDHA
+ C_C, // 0x0AA3 // NNA
+ C_C, // 0x0AA4 // TA
+ C_C, // 0x0AA5 // THA
+ C_C, // 0x0AA6 // DA
+ C_C, // 0x0AA7 // DHA
+ C_C, // 0x0AA8 // NA
+ C_U, // 0x0AA9 // UNASSIGNED
+ C_C, // 0x0AAA // PA
+ C_C, // 0x0AAB // PHA
+ C_C, // 0x0AAC // BA
+ C_C, // 0x0AAD // BHA
+ C_C, // 0x0AAE // MA
+ C_C, // 0x0AAF // YA
+ C_C|C_R, // 0x0AB0 // RA // CSOK: WhitespaceAround
+ C_U, // 0x0AB1 // UNASSIGNED
+ C_C, // 0x0AB2 // LA
+ C_C, // 0x0AB3 // LLA
+ C_U, // 0x0AB4 // UNASSIGNED
+ C_C, // 0x0AB5 // VA
+ C_C, // 0x0AB6 // SHA
+ C_C, // 0x0AB7 // SSA
+ C_C, // 0x0AB8 // SA
+ C_C, // 0x0AB9 // HA
+ C_U, // 0x0ABA // UNASSIGNED
+ C_U, // 0x0ABB // UNASSIGNED
+ C_N, // 0x0ABC // NUKTA
+ C_S, // 0x0ABD // AVAGRAHA
+ C_M, // 0x0ABE // AA
+ C_M|C_PRE, // 0x0ABF // I // CSOK: WhitespaceAround
+ C_M, // 0x0AC0 // II
+ C_M, // 0x0AC1 // U
+ C_M, // 0x0AC2 // UU
+ C_M, // 0x0AC3 // VOCALIC R
+ C_M, // 0x0AC4 // VOCALIC RR
+ C_M, // 0x0AC5 // CANDRA E
+ C_U, // 0x0AC6 // UNASSIGNED
+ C_M, // 0x0AC7 // E
+ C_M, // 0x0AC8 // AI
+ C_M, // 0x0AC9 // CANDRA O
+ C_U, // 0x0ACA // UNASSIGNED
+ C_M, // 0x0ACB // O
+ C_M, // 0x0ACC // AU
+ C_H, // 0x0ACD // VIRAMA (HALANT)
+ C_U, // 0x0ACE // UNASSIGNED
+ C_U, // 0x0ACF // UNASSIGNED
+ C_S, // 0x0AD0 // OM
+ C_U, // 0x0AD1 // UNASSIGNED
+ C_U, // 0x0AD2 // UNASSIGNED
+ C_U, // 0x0AD3 // UNASSIGNED
+ C_U, // 0x0AD4 // UNASSIGNED
+ C_U, // 0x0AD5 // UNASSIGNED
+ C_U, // 0x0AD6 // UNASSIGNED
+ C_U, // 0x0AD7 // UNASSIGNED
+ C_U, // 0x0AD8 // UNASSIGNED
+ C_U, // 0x0AD9 // UNASSIGNED
+ C_U, // 0x0ADA // UNASSIGNED
+ C_U, // 0x0ADB // UNASSIGNED
+ C_U, // 0x0ADC // UNASSIGNED
+ C_U, // 0x0ADD // UNASSIGNED
+ C_U, // 0x0ADE // UNASSIGNED
+ C_U, // 0x0ADF // UNASSIGNED
+ C_V, // 0x0AE0 // VOCALIC RR
+ C_V, // 0x0AE1 // VOCALIC LL
+ C_M, // 0x0AE2 // VOCALIC L
+ C_M, // 0x0AE3 // VOCALIC LL
+ C_U, // 0x0AE4 // UNASSIGNED
+ C_U, // 0x0AE5 // UNASSIGNED
+ C_D, // 0x0AE6 // ZERO
+ C_D, // 0x0AE7 // ONE
+ C_D, // 0x0AE8 // TWO
+ C_D, // 0x0AE9 // THREE
+ C_D, // 0x0AEA // FOUR
+ C_D, // 0x0AEB // FIVE
+ C_D, // 0x0AEC // SIX
+ C_D, // 0x0AED // SEVEN
+ C_D, // 0x0AEE // EIGHT
+ C_D, // 0x0AEF // NINE
+ C_U, // 0x0AF0 // UNASSIGNED
+ C_S, // 0x0AF1 // RUPEE SIGN
+ C_U, // 0x0AF2 // UNASSIGNED
+ C_U, // 0x0AF3 // UNASSIGNED
+ C_U, // 0x0AF4 // UNASSIGNED
+ C_U, // 0x0AF5 // UNASSIGNED
+ C_U, // 0x0AF6 // UNASSIGNED
+ C_U, // 0x0AF7 // UNASSIGNED
+ C_U, // 0x0AF8 // UNASSIGNED
+ C_U, // 0x0AF9 // UNASSIGNED
+ C_U, // 0x0AFA // UNASSIGNED
+ C_U, // 0x0AFB // UNASSIGNED
+ C_U, // 0x0AFC // UNASSIGNED
+ C_U, // 0x0AFD // UNASSIGNED
+ C_U, // 0x0AFE // UNASSIGNED
+ C_U // 0x0AFF // UNASSIGNED
+ };
+ static int typeOf(int c) {
+ if ( ( c >= ccaStart ) && ( c < ccaEnd ) ) {
+ return cca [ c - ccaStart ] & C_M_TYPE;
+ } else {
+ return C_U;
+ }
+ }
+ static boolean isType(int c, int t) {
+ return typeOf ( c ) == t;
+ }
+ static boolean hasFlag(int c, int f) {
+ if ( ( c >= ccaStart ) && ( c < ccaEnd ) ) {
+ return ( cca [ c - ccaStart ] & f ) == f;
+ } else {
+ return false;
+ }
+ }
+ static boolean isC(int c) {
+ return isType(c,C_C);
+ }
+ static boolean isR(int c) {
+ return isType(c,C_C) && hasR(c);
+ }
+ static boolean isV(int c) {
+ return isType(c,C_V);
+ }
+ static boolean isN(int c) {
+ return c == 0x0ABC;
+ }
+ static boolean isH(int c) {
+ return c == 0x0ACD;
+ }
+ static boolean isM(int c) {
+ return isType(c,C_M);
+ }
+ static boolean isPreM(int c) {
+ return isType(c,C_M) && hasFlag(c,C_PRE);
+ }
+ static boolean isX(int c) {
+ switch ( typeOf ( c ) ) {
+ case C_M: // matra (combining vowel)
+ case C_A: // accent mark
+ case C_T: // tone mark
+ case C_O: // other (modifying) mark
+ return true;
+ default:
+ return false;
+ }
+ }
+ static boolean hasR(int c) {
+ return hasFlag(c,C_R);
+ }
+ static boolean hasN(int c) {
+ return hasFlag(c,C_N);
+ }
+
+}
diff --git a/src/java/org/apache/fop/complexscripts/scripts/GurmukhiScriptProcessor.java b/src/java/org/apache/fop/complexscripts/scripts/GurmukhiScriptProcessor.java
new file mode 100644
index 000000000..1f1b56738
--- /dev/null
+++ b/src/java/org/apache/fop/complexscripts/scripts/GurmukhiScriptProcessor.java
@@ -0,0 +1,543 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.complexscripts.scripts;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import org.apache.fop.complexscripts.fonts.GlyphDefinitionTable;
+import org.apache.fop.complexscripts.util.GlyphSequence;
+import org.apache.fop.complexscripts.util.ScriptContextTester;
+
+// CSOFF: AvoidNestedBlocksCheck
+// CSOFF: NoWhitespaceAfterCheck
+// CSOFF: WhitespaceAfter
+// CSOFF: InnerAssignmentCheck
+// CSOFF: SimplifyBooleanReturnCheck
+// CSOFF: LineLengthCheck
+
+/**
+ * <p>The <code>GurmukhiScriptProcessor</code> class implements a script processor for
+ * performing glyph substitution and positioning operations on content associated with the Gurmukhi script.</p>
+ * @author Glenn Adams
+ */
+public class GurmukhiScriptProcessor extends IndicScriptProcessor {
+
+ /** logging instance */
+ private static final Log log = LogFactory.getLog(GurmukhiScriptProcessor.class); // CSOK: ConstantNameCheck
+
+ GurmukhiScriptProcessor ( String script ) {
+ super ( script );
+ }
+
+ @Override
+ protected Class<? extends GurmukhiSyllabizer> getSyllabizerClass() {
+ return GurmukhiSyllabizer.class;
+ }
+
+ @Override
+ // find rightmost pre-base matra
+ protected int findPreBaseMatra ( GlyphSequence gs ) {
+ int ng = gs.getGlyphCount();
+ int lk = -1;
+ for ( int i = ng; i > 0; i-- ) {
+ int k = i - 1;
+ if ( containsPreBaseMatra ( gs, k ) ) {
+ lk = k;
+ break;
+ }
+ }
+ return lk;
+ }
+
+ @Override
+ // find leftmost pre-base matra target, starting from source
+ protected int findPreBaseMatraTarget ( GlyphSequence gs, int source ) {
+ int ng = gs.getGlyphCount();
+ int lk = -1;
+ for ( int i = ( source < ng ) ? source : ng; i > 0; i-- ) {
+ int k = i - 1;
+ if ( containsConsonant ( gs, k ) ) {
+ if ( containsHalfConsonant ( gs, k ) ) {
+ lk = k;
+ } else if ( lk == -1 ) {
+ lk = k;
+ } else {
+ break;
+ }
+ }
+ }
+ return lk;
+ }
+
+ private static boolean containsPreBaseMatra ( GlyphSequence gs, int k ) {
+ GlyphSequence.CharAssociation a = gs.getAssociation ( k );
+ int[] ca = gs.getCharacterArray ( false );
+ for ( int i = a.getStart(), e = a.getEnd(); i < e; i++ ) {
+ if ( isPreM ( ca [ i ] ) ) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private static boolean containsConsonant ( GlyphSequence gs, int k ) {
+ GlyphSequence.CharAssociation a = gs.getAssociation ( k );
+ int[] ca = gs.getCharacterArray ( false );
+ for ( int i = a.getStart(), e = a.getEnd(); i < e; i++ ) {
+ if ( isC ( ca [ i ] ) ) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private static boolean containsHalfConsonant ( GlyphSequence gs, int k ) {
+ Boolean half = (Boolean) gs.getAssociation ( k ) . getPredication ( "half" );
+ return ( half != null ) ? half.booleanValue() : false;
+ }
+
+ @Override
+ protected int findReph ( GlyphSequence gs ) {
+ int ng = gs.getGlyphCount();
+ int li = -1;
+ for ( int i = 0; i < ng; i++ ) {
+ if ( containsReph ( gs, i ) ) {
+ li = i;
+ break;
+ }
+ }
+ return li;
+ }
+
+ @Override
+ protected int findRephTarget ( GlyphSequence gs, int source ) {
+ int ng = gs.getGlyphCount();
+ int c1 = -1;
+ int c2 = -1;
+ // first candidate target is after first non-half consonant
+ for ( int i = 0; i < ng; i++ ) {
+ if ( ( i != source ) && containsConsonant ( gs, i ) ) {
+ if ( ! containsHalfConsonant ( gs, i ) ) {
+ c1 = i + 1;
+ break;
+ }
+ }
+ }
+ // second candidate target is after last non-prebase matra after first candidate or before first syllable or vedic mark
+ for ( int i = ( c1 >= 0 ) ? c1 : 0; i < ng; i++ ) {
+ if ( containsMatra ( gs, i ) && ! containsPreBaseMatra ( gs, i ) ) {
+ c2 = i + 1;
+ } else if ( containsOtherMark ( gs, i ) ) {
+ c2 = i;
+ break;
+ }
+ }
+ if ( c2 >= 0 ) {
+ return c2;
+ } else if ( c1 >= 0 ) {
+ return c1;
+ } else {
+ return source;
+ }
+ }
+
+ private static boolean containsReph ( GlyphSequence gs, int k ) {
+ Boolean rphf = (Boolean) gs.getAssociation ( k ) . getPredication ( "rphf" );
+ return ( rphf != null ) ? rphf.booleanValue() : false;
+ }
+
+ private static boolean containsMatra ( GlyphSequence gs, int k ) {
+ GlyphSequence.CharAssociation a = gs.getAssociation ( k );
+ int[] ca = gs.getCharacterArray ( false );
+ for ( int i = a.getStart(), e = a.getEnd(); i < e; i++ ) {
+ if ( isM ( ca [ i ] ) ) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private static boolean containsOtherMark ( GlyphSequence gs, int k ) {
+ GlyphSequence.CharAssociation a = gs.getAssociation ( k );
+ int[] ca = gs.getCharacterArray ( false );
+ for ( int i = a.getStart(), e = a.getEnd(); i < e; i++ ) {
+ switch ( typeOf ( ca [ i ] ) ) {
+ case C_T: // tone (e.g., udatta, anudatta)
+ case C_A: // accent (e.g., acute, grave)
+ case C_O: // other (e.g., candrabindu, anusvara, visarga, etc)
+ return true;
+ default:
+ break;
+ }
+ }
+ return false;
+ }
+
+ private static class GurmukhiSyllabizer extends DefaultSyllabizer {
+ GurmukhiSyllabizer ( String script, String language ) {
+ super ( script, language );
+ }
+ @Override
+ // | C ...
+ protected int findStartOfSyllable ( int[] ca, int s, int e ) {
+ if ( ( s < 0 ) || ( s >= e ) ) {
+ return -1;
+ } else {
+ while ( s < e ) {
+ int c = ca [ s ];
+ if ( isC ( c ) ) {
+ break;
+ } else {
+ s++;
+ }
+ }
+ return s;
+ }
+ }
+ @Override
+ // D* L? | ...
+ protected int findEndOfSyllable ( int[] ca, int s, int e ) {
+ if ( ( s < 0 ) || ( s >= e ) ) {
+ return -1;
+ } else {
+ int nd = 0;
+ int nl = 0;
+ int i;
+ // consume dead consonants
+ while ( ( i = isDeadConsonant ( ca, s, e ) ) > s ) {
+ s = i; nd++;
+ }
+ // consume zero or one live consonant
+ if ( ( i = isLiveConsonant ( ca, s, e ) ) > s ) {
+ s = i; nl++;
+ }
+ return ( ( nd > 0 ) || ( nl > 0 ) ) ? s : -1;
+ }
+ }
+ // D := ( C N? H )?
+ private int isDeadConsonant ( int[] ca, int s, int e ) {
+ if ( s < 0 ) {
+ return -1;
+ } else {
+ int c, i = 0;
+ int nc = 0, nh = 0;
+ do {
+ // C
+ if ( ( s + i ) < e ) {
+ c = ca [ s + i ];
+ if ( isC ( c ) ) {
+ i++;
+ nc++;
+ } else {
+ break;
+ }
+ }
+ // N?
+ if ( ( s + i ) < e ) {
+ c = ca [ s + 1 ];
+ if ( isN ( c ) ) {
+ i++;
+ }
+ }
+ // H
+ if ( ( s + i ) < e ) {
+ c = ca [ s + i ];
+ if ( isH ( c ) ) {
+ i++;
+ nh++;
+ } else {
+ break;
+ }
+ }
+ } while ( false );
+ return ( nc > 0 ) && ( nh > 0 ) ? s + i : -1;
+ }
+ }
+ // L := ( (C|V) N? X* )?; where X = ( MATRA | ACCENT MARK | TONE MARK | OTHER MARK )
+ private int isLiveConsonant ( int[] ca, int s, int e ) {
+ if ( s < 0 ) {
+ return -1;
+ } else {
+ int c, i = 0;
+ int nc = 0, nv = 0, nx = 0;
+ do {
+ // C
+ if ( ( s + i ) < e ) {
+ c = ca [ s + i ];
+ if ( isC ( c ) ) {
+ i++;
+ nc++;
+ } else if ( isV ( c ) ) {
+ i++;
+ nv++;
+ } else {
+ break;
+ }
+ }
+ // N?
+ if ( ( s + i ) < e ) {
+ c = ca [ s + i ];
+ if ( isN ( c ) ) {
+ i++;
+ }
+ }
+ // X*
+ while ( ( s + i ) < e ) {
+ c = ca [ s + i ];
+ if ( isX ( c ) ) {
+ i++;
+ nx++;
+ } else {
+ break;
+ }
+ }
+ } while ( false );
+ // if no X but has H, then ignore C|I
+ if ( nx == 0 ) {
+ if ( ( s + i ) < e ) {
+ c = ca [ s + i ];
+ if ( isH ( c ) ) {
+ if ( nc > 0 ) {
+ nc--;
+ } else if ( nv > 0 ) {
+ nv--;
+ }
+ }
+ }
+ }
+ return ( ( nc > 0 ) || ( nv > 0 ) ) ? s + i : -1;
+ }
+ }
+ }
+
+ // gurmukhi character types
+ static final short C_U = 0; // unassigned
+ static final short C_C = 1; // consonant
+ static final short C_V = 2; // vowel
+ static final short C_M = 3; // vowel sign (matra)
+ static final short C_S = 4; // symbol or sign
+ static final short C_T = 5; // tone mark
+ static final short C_A = 6; // accent mark
+ static final short C_P = 7; // punctuation
+ static final short C_D = 8; // digit
+ static final short C_H = 9; // halant (virama)
+ static final short C_O = 10; // other signs
+ static final short C_N = 0x0100; // nukta(ized)
+ static final short C_R = 0x0200; // reph(ized)
+ static final short C_PRE = 0x0400; // pre-base
+ static final short C_M_TYPE = 0x00FF; // type mask
+ static final short C_M_FLAGS = 0x7F00; // flag mask
+ // gurmukhi block range
+ static final int ccaStart = 0x0A00; // first code point mapped by cca // CSOK: ConstantNameCheck
+ static final int ccaEnd = 0x0A80; // last code point + 1 mapped by cca // CSOK: ConstantNameCheck
+ // gurmukhi character type lookups
+ static final short[] cca = { // CSOK: ConstantNameCheck
+ C_U, // 0x0A00 // UNASSIGNED
+ C_O, // 0x0A01 // ADAK BINDI
+ C_O, // 0x0A02 // BINDI
+ C_O, // 0x0A03 // VISARGA
+ C_U, // 0x0A04 // UNASSIGNED
+ C_V, // 0x0A05 // A
+ C_V, // 0x0A06 // AA
+ C_V, // 0x0A07 // I
+ C_V, // 0x0A08 // II
+ C_V, // 0x0A09 // U
+ C_V, // 0x0A0A // UU
+ C_U, // 0x0A0B // UNASSIGNED
+ C_U, // 0x0A0C // UNASSIGNED
+ C_U, // 0x0A0D // UNASSIGNED
+ C_U, // 0x0A0E // UNASSIGNED
+ C_V, // 0x0A0F // E
+ C_V, // 0x0A10 // AI
+ C_U, // 0x0A11 // UNASSIGNED
+ C_U, // 0x0A12 // UNASSIGNED
+ C_V, // 0x0A13 // O
+ C_V, // 0x0A14 // AU
+ C_C, // 0x0A15 // KA
+ C_C, // 0x0A16 // KHA
+ C_C, // 0x0A17 // GA
+ C_C, // 0x0A18 // GHA
+ C_C, // 0x0A19 // NGA
+ C_C, // 0x0A1A // CA
+ C_C, // 0x0A1B // CHA
+ C_C, // 0x0A1C // JA
+ C_C, // 0x0A1D // JHA
+ C_C, // 0x0A1E // NYA
+ C_C, // 0x0A1F // TTA
+ C_C, // 0x0A20 // TTHA
+ C_C, // 0x0A21 // DDA
+ C_C, // 0x0A22 // DDHA
+ C_C, // 0x0A23 // NNA
+ C_C, // 0x0A24 // TA
+ C_C, // 0x0A25 // THA
+ C_C, // 0x0A26 // DA
+ C_C, // 0x0A27 // DHA
+ C_C, // 0x0A28 // NA
+ C_U, // 0x0A29 // UNASSIGNED
+ C_C, // 0x0A2A // PA
+ C_C, // 0x0A2B // PHA
+ C_C, // 0x0A2C // BA
+ C_C, // 0x0A2D // BHA
+ C_C, // 0x0A2E // MA
+ C_C, // 0x0A2F // YA
+ C_C|C_R, // 0x0A30 // RA // CSOK: WhitespaceAround
+ C_U, // 0x0A31 // UNASSIGNED
+ C_C, // 0x0A32 // LA
+ C_C, // 0x0A33 // LLA
+ C_U, // 0x0A34 // UNASSIGNED
+ C_C, // 0x0A35 // VA
+ C_C, // 0x0A36 // SHA
+ C_U, // 0x0A37 // UNASSIGNED
+ C_C, // 0x0A38 // SA
+ C_C, // 0x0A39 // HA
+ C_U, // 0x0A3A // UNASSIGNED
+ C_U, // 0x0A3B // UNASSIGNED
+ C_N, // 0x0A3C // NUKTA
+ C_U, // 0x0A3D // UNASSIGNED
+ C_M, // 0x0A3E // AA
+ C_M|C_PRE, // 0x0A3F // I // CSOK: WhitespaceAround
+ C_M, // 0x0A40 // II
+ C_M, // 0x0A41 // U
+ C_M, // 0x0A42 // UU
+ C_U, // 0x0A43 // UNASSIGNED
+ C_U, // 0x0A44 // UNASSIGNED
+ C_U, // 0x0A45 // UNASSIGNED
+ C_U, // 0x0A46 // UNASSIGNED
+ C_M, // 0x0A47 // EE
+ C_M, // 0x0A48 // AI
+ C_U, // 0x0A49 // UNASSIGNED
+ C_U, // 0x0A4A // UNASSIGNED
+ C_M, // 0x0A4B // OO
+ C_M, // 0x0A4C // AU
+ C_H, // 0x0A4D // VIRAMA (HALANT)
+ C_U, // 0x0A4E // UNASSIGNED
+ C_U, // 0x0A4F // UNASSIGNED
+ C_U, // 0x0A50 // UNASSIGNED
+ C_T, // 0x0A51 // UDATTA
+ C_U, // 0x0A52 // UNASSIGNED
+ C_U, // 0x0A53 // UNASSIGNED
+ C_U, // 0x0A54 // UNASSIGNED
+ C_U, // 0x0A55 // UNASSIGNED
+ C_U, // 0x0A56 // UNASSIGNED
+ C_U, // 0x0A57 // UNASSIGNED
+ C_U, // 0x0A58 // UNASSIGNED
+ C_C|C_N, // 0x0A59 // KHHA // CSOK: WhitespaceAround
+ C_C|C_N, // 0x0A5A // GHHA // CSOK: WhitespaceAround
+ C_C|C_N, // 0x0A5B // ZA // CSOK: WhitespaceAround
+ C_C|C_N, // 0x0A5C // RRA // CSOK: WhitespaceAround
+ C_U, // 0x0A5D // UNASSIGNED
+ C_C|C_N, // 0x0A5E // FA // CSOK: WhitespaceAround
+ C_U, // 0x0A5F // UNASSIGNED
+ C_U, // 0x0A60 // UNASSIGNED
+ C_U, // 0x0A61 // UNASSIGNED
+ C_U, // 0x0A62 // UNASSIGNED
+ C_U, // 0x0A63 // UNASSIGNED
+ C_U, // 0x0A64 // UNASSIGNED
+ C_U, // 0x0A65 // UNASSIGNED
+ C_D, // 0x0A66 // ZERO
+ C_D, // 0x0A67 // ONE
+ C_D, // 0x0A68 // TWO
+ C_D, // 0x0A69 // THREE
+ C_D, // 0x0A6A // FOUR
+ C_D, // 0x0A6B // FIVE
+ C_D, // 0x0A6C // SIX
+ C_D, // 0x0A6D // SEVEN
+ C_D, // 0x0A6E // EIGHT
+ C_D, // 0x0A6F // NINE
+ C_O, // 0x0A70 // TIPPI
+ C_O, // 0x0A71 // ADDAK
+ C_V, // 0x0A72 // IRI
+ C_V, // 0x0A73 // URA
+ C_S, // 0x0A74 // EK ONKAR
+ C_O, // 0x0A75 // YAKASH
+ C_U, // 0x0A76 // UNASSIGNED
+ C_U, // 0x0A77 // UNASSIGNED
+ C_U, // 0x0A78 // UNASSIGNED
+ C_U, // 0x0A79 // UNASSIGNED
+ C_U, // 0x0A7A // UNASSIGNED
+ C_U, // 0x0A7B // UNASSIGNED
+ C_U, // 0x0A7C // UNASSIGNED
+ C_U, // 0x0A7D // UNASSIGNED
+ C_U, // 0x0A7E // UNASSIGNED
+ C_U // 0x0A7F // UNASSIGNED
+ };
+ static int typeOf(int c) {
+ if ( ( c >= ccaStart ) && ( c < ccaEnd ) ) {
+ return cca [ c - ccaStart ] & C_M_TYPE;
+ } else {
+ return C_U;
+ }
+ }
+ static boolean isType(int c, int t) {
+ return typeOf ( c ) == t;
+ }
+ static boolean hasFlag(int c, int f) {
+ if ( ( c >= ccaStart ) && ( c < ccaEnd ) ) {
+ return ( cca [ c - ccaStart ] & f ) == f;
+ } else {
+ return false;
+ }
+ }
+ static boolean isC(int c) {
+ return isType(c,C_C);
+ }
+ static boolean isR(int c) {
+ return isType(c,C_C) && hasR(c);
+ }
+ static boolean isV(int c) {
+ return isType(c,C_V);
+ }
+ static boolean isN(int c) {
+ return c == 0x0A3C;
+ }
+ static boolean isH(int c) {
+ return c == 0x0A4D;
+ }
+ static boolean isM(int c) {
+ return isType(c,C_M);
+ }
+ static boolean isPreM(int c) {
+ return isType(c,C_M) && hasFlag(c,C_PRE);
+ }
+ static boolean isX(int c) {
+ switch ( typeOf ( c ) ) {
+ case C_M: // matra (combining vowel)
+ case C_A: // accent mark
+ case C_T: // tone mark
+ case C_O: // other (modifying) mark
+ return true;
+ default:
+ return false;
+ }
+ }
+ static boolean hasR(int c) {
+ return hasFlag(c,C_R);
+ }
+ static boolean hasN(int c) {
+ return hasFlag(c,C_N);
+ }
+
+ @Override
+ public GlyphSequence reorderCombiningMarks ( GlyphDefinitionTable gdef, GlyphSequence gs, int[][] gpa, String script, String language ) {
+ return super.reorderCombiningMarks ( gdef, gs, gpa, script, language );
+ }
+
+}
diff --git a/src/java/org/apache/fop/complexscripts/scripts/IndicScriptProcessor.java b/src/java/org/apache/fop/complexscripts/scripts/IndicScriptProcessor.java
new file mode 100644
index 000000000..92ed31501
--- /dev/null
+++ b/src/java/org/apache/fop/complexscripts/scripts/IndicScriptProcessor.java
@@ -0,0 +1,589 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.complexscripts.scripts;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.Vector;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import org.apache.fop.complexscripts.fonts.GlyphTable;
+import org.apache.fop.complexscripts.util.CharScript;
+import org.apache.fop.complexscripts.util.GlyphContextTester;
+import org.apache.fop.complexscripts.util.GlyphSequence;
+import org.apache.fop.complexscripts.util.ScriptContextTester;
+
+// CSOFF: AvoidNestedBlocksCheck
+// CSOFF: NoWhitespaceAfterCheck
+// CSOFF: InnerAssignmentCheck
+// CSOFF: SimplifyBooleanReturnCheck
+// CSOFF: EmptyForIteratorPadCheck
+// CSOFF: WhitespaceAfterCheck
+// CSOFF: ParameterNumberCheck
+// CSOFF: LineLengthCheck
+
+/**
+ * <p>The <code>IndicScriptProcessor</code> class implements a script processor for
+ * performing glyph substitution and positioning operations on content associated with the Indic script.</p>
+ * @author Glenn Adams
+ */
+public class IndicScriptProcessor extends DefaultScriptProcessor {
+
+ /** logging instance */
+ private static final Log log = LogFactory.getLog(IndicScriptProcessor.class); // CSOK: ConstantNameCheck
+
+ /** required features to use for substitutions */
+ private static final String[] gsubReqFeatures = // CSOK: ConstantNameCheck
+ {
+ "abvf", // above base forms
+ "abvs", // above base substitutions
+ "akhn", // akhand
+ "blwf", // below base forms
+ "blws", // below base substitutions
+ "ccmp", // glyph composition/decomposition
+ "cjct", // conjunct forms
+ "clig", // contextual ligatures
+ "half", // half forms
+ "haln", // halant forms
+ "locl", // localized forms
+ "nukt", // nukta forms
+ "pref", // pre-base forms
+ "pres", // pre-base substitutions
+ "pstf", // post-base forms
+ "psts", // post-base substitutions
+ "rkrf", // rakar forms
+ "rphf", // reph form
+ "vatu" // vattu variants
+ };
+
+ /** optional features to use for substitutions */
+ private static final String[] gsubOptFeatures = // CSOK: ConstantNameCheck
+ {
+ "afrc", // alternative fractions
+ "calt", // contextual alternatives
+ "dlig" // discretionary ligatures
+ };
+
+ /** required features to use for positioning */
+ private static final String[] gposReqFeatures = // CSOK: ConstantNameCheck
+ {
+ "abvm", // above base marks
+ "blwm", // below base marks
+ "dist", // distance (adjustment)
+ "kern" // kerning
+ };
+
+ /** required features to use for positioning */
+ private static final String[] gposOptFeatures = // CSOK: ConstantNameCheck
+ {
+ };
+
+ private static class SubstitutionScriptContextTester implements ScriptContextTester {
+ private static Map/*<String,GlyphContextTester>*/ testerMap = new HashMap/*<String,GlyphContextTester>*/();
+ public GlyphContextTester getTester ( String feature ) {
+ return (GlyphContextTester) testerMap.get ( feature );
+ }
+ }
+
+ private static class PositioningScriptContextTester implements ScriptContextTester {
+ private static Map/*<String,GlyphContextTester>*/ testerMap = new HashMap/*<String,GlyphContextTester>*/();
+ public GlyphContextTester getTester ( String feature ) {
+ return (GlyphContextTester) testerMap.get ( feature );
+ }
+ }
+
+ /**
+ * Make script specific flavor of Indic script processor.
+ * @param script tag
+ * @return script processor instance
+ */
+ public static ScriptProcessor makeProcessor ( String script ) {
+ switch ( CharScript.scriptCodeFromTag ( script ) ) {
+ case CharScript.SCRIPT_DEVANAGARI:
+ case CharScript.SCRIPT_DEVANAGARI_2:
+ return new DevanagariScriptProcessor ( script );
+ case CharScript.SCRIPT_GUJARATI:
+ case CharScript.SCRIPT_GUJARATI_2:
+ return new GujaratiScriptProcessor ( script );
+ case CharScript.SCRIPT_GURMUKHI:
+ case CharScript.SCRIPT_GURMUKHI_2:
+ return new GurmukhiScriptProcessor ( script );
+ // [TBD] implement other script processors
+ default:
+ return new IndicScriptProcessor ( script );
+ }
+ }
+
+ private final ScriptContextTester subContextTester;
+ private final ScriptContextTester posContextTester;
+
+ IndicScriptProcessor ( String script ) {
+ super ( script );
+ this.subContextTester = new SubstitutionScriptContextTester();
+ this.posContextTester = new PositioningScriptContextTester();
+ }
+
+ /** {@inheritDoc} */
+ public String[] getSubstitutionFeatures() {
+ return gsubReqFeatures;
+ }
+
+ /** {@inheritDoc} */
+ public String[] getOptionalSubstitutionFeatures() {
+ return gsubOptFeatures;
+ }
+
+ /** {@inheritDoc} */
+ public ScriptContextTester getSubstitutionContextTester() {
+ return subContextTester;
+ }
+
+ /** {@inheritDoc} */
+ public String[] getPositioningFeatures() {
+ return gposReqFeatures;
+ }
+
+ /** {@inheritDoc} */
+ public String[] getOptionalPositioningFeatures() {
+ return gposOptFeatures;
+ }
+
+ /** {@inheritDoc} */
+ public ScriptContextTester getPositioningContextTester() {
+ return posContextTester;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public GlyphSequence substitute ( GlyphSequence gs, String script, String language, GlyphTable.UseSpec[] usa, ScriptContextTester sct ) {
+ assert usa != null;
+ // 1. syllabize
+ GlyphSequence[] sa = syllabize ( gs, script, language );
+ // 2. process each syllable
+ for ( int i = 0, n = sa.length; i < n; i++ ) {
+ GlyphSequence s = sa [ i ];
+ // apply basic shaping subs
+ for ( int j = 0, m = usa.length; j < m; j++ ) {
+ GlyphTable.UseSpec us = usa [ j ];
+ if ( isBasicShapingUse ( us ) ) {
+ s.setPredications ( true );
+ s = us.substitute ( s, script, language, sct );
+ }
+ }
+ // reorder pre-base matra
+ s = reorderPreBaseMatra ( s );
+ // reorder reph
+ s = reorderReph ( s );
+ // apply presentation subs
+ for ( int j = 0, m = usa.length; j < m; j++ ) {
+ GlyphTable.UseSpec us = usa [ j ];
+ if ( isPresentationUse ( us ) ) {
+ s.setPredications ( true );
+ s = us.substitute ( s, script, language, sct );
+ }
+ }
+ // record result
+ sa [ i ] = s;
+ }
+ // 3. return reassembled substituted syllables
+ return unsyllabize ( gs, sa );
+ }
+
+ /**
+ * Get script specific syllabizer class.
+ * @return a syllabizer class object or null
+ */
+ protected Class<? extends Syllabizer> getSyllabizerClass() {
+ return null;
+ }
+
+ private GlyphSequence[] syllabize ( GlyphSequence gs, String script, String language ) {
+ return Syllabizer.getSyllabizer ( script, language, getSyllabizerClass() ) . syllabize ( gs );
+ }
+
+ private GlyphSequence unsyllabize ( GlyphSequence gs, GlyphSequence[] sa ) {
+ return GlyphSequence.join ( gs, sa );
+ }
+
+ private static Set<String> basicShapingFeatures;
+ private static final String[] basicShapingFeatureStrings = { // CSOK: ConstantNameCheck
+ "abvf",
+ "akhn",
+ "blwf",
+ "cjct",
+ "half",
+ "locl",
+ "nukt",
+ "pref",
+ "pstf",
+ "rkrf",
+ "rphf",
+ "vatu",
+ };
+ static {
+ basicShapingFeatures = new HashSet<String>();
+ for ( String s : basicShapingFeatureStrings ) {
+ basicShapingFeatures.add ( s );
+ }
+ }
+ private boolean isBasicShapingUse ( GlyphTable.UseSpec us ) {
+ assert us != null;
+ if ( basicShapingFeatures != null ) {
+ return basicShapingFeatures.contains ( us.getFeature() );
+ } else {
+ return false;
+ }
+ }
+
+ private static Set<String> presentationFeatures;
+ private static final String[] presentationFeatureStrings = { // CSOK: ConstantNameCheck
+ "abvs",
+ "blws",
+ "calt",
+ "haln",
+ "pres",
+ "psts",
+ };
+ static {
+ presentationFeatures = new HashSet<String>();
+ for ( String s : presentationFeatureStrings ) {
+ presentationFeatures.add ( s );
+ }
+ }
+ private boolean isPresentationUse ( GlyphTable.UseSpec us ) {
+ assert us != null;
+ if ( presentationFeatures != null ) {
+ return presentationFeatures.contains ( us.getFeature() );
+ } else {
+ return false;
+ }
+ }
+
+ private GlyphSequence reorderPreBaseMatra ( GlyphSequence gs ) {
+ int source;
+ if ( ( source = findPreBaseMatra ( gs ) ) >= 0 ) {
+ int target;
+ if ( ( target = findPreBaseMatraTarget ( gs, source ) ) >= 0 ) {
+ if ( target != source ) {
+ gs = reorder ( gs, source, target );
+ }
+ }
+ }
+ return gs;
+ }
+
+ /**
+ * Find pre-base matra in sequence.
+ * @param gs input sequence
+ * @return index of pre-base matra or -1 if not found
+ */
+ protected int findPreBaseMatra ( GlyphSequence gs ) {
+ return -1;
+ }
+
+ /**
+ * Find pre-base matra target in sequence.
+ * @param gs input sequence
+ * @param source index of pre-base matra
+ * @return index of pre-base matra target or -1
+ */
+ protected int findPreBaseMatraTarget ( GlyphSequence gs, int source ) {
+ return -1;
+ }
+
+ private GlyphSequence reorderReph ( GlyphSequence gs ) {
+ int source;
+ if ( ( source = findReph ( gs ) ) >= 0 ) {
+ int target;
+ if ( ( target = findRephTarget ( gs, source ) ) >= 0 ) {
+ if ( target != source ) {
+ gs = reorder ( gs, source, target );
+ }
+ }
+ }
+ return gs;
+ }
+
+ /**
+ * Find reph in sequence.
+ * @param gs input sequence
+ * @return index of reph or -1 if not found
+ */
+ protected int findReph ( GlyphSequence gs ) {
+ return -1;
+ }
+
+ /**
+ * Find reph target in sequence.
+ * @param gs input sequence
+ * @param source index of reph
+ * @return index of reph target or -1
+ */
+ protected int findRephTarget ( GlyphSequence gs, int source ) {
+ return -1;
+ }
+
+ private GlyphSequence reorder ( GlyphSequence gs, int source, int target ) {
+ return GlyphSequence.reorder ( gs, source, 1, target );
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean position ( GlyphSequence gs, String script, String language, int fontSize, GlyphTable.UseSpec[] usa, int[] widths, int[][] adjustments, ScriptContextTester sct ) {
+ boolean adjusted = super.position ( gs, script, language, fontSize, usa, widths, adjustments, sct );
+ return adjusted;
+ }
+
+ /** Abstract syllabizer. */
+ protected abstract static class Syllabizer {
+ private String script;
+ private String language;
+ Syllabizer ( String script, String language ) {
+ this.script = script;
+ this.language = language;
+ }
+ /**
+ * Subdivide glyph sequence GS into syllabic segments each represented by a distinct
+ * output glyph sequence.
+ * @param gs input glyph sequence
+ * @return segmented syllabic glyph sequences
+ */
+ abstract GlyphSequence[] syllabize ( GlyphSequence gs );
+ /** {@inheritDoc} */
+ public int hashCode() {
+ int hc = 0;
+ hc = 7 * hc + ( hc ^ script.hashCode() );
+ hc = 11 * hc + ( hc ^ language.hashCode() );
+ return hc;
+ }
+ /** {@inheritDoc} */
+ public boolean equals ( Object o ) {
+ if ( o instanceof Syllabizer ) {
+ Syllabizer s = (Syllabizer) o;
+ if ( ! s.script.equals ( script ) ) {
+ return false;
+ } else if ( ! s.language.equals ( language ) ) {
+ return false;
+ } else {
+ return true;
+ }
+ } else {
+ return false;
+ }
+ }
+ /** {@inheritDoc} */
+ public int compareTo ( Object o ) {
+ int d;
+ if ( o instanceof Syllabizer ) {
+ Syllabizer s = (Syllabizer) o;
+ if ( ( d = script.compareTo ( s.script ) ) == 0 ) {
+ d = language.compareTo ( s.language );
+ }
+ } else {
+ d = -1;
+ }
+ return d;
+ }
+ private static Map<String,Syllabizer> syllabizers = new HashMap<String,Syllabizer>();
+ static Syllabizer getSyllabizer ( String script, String language, Class<? extends Syllabizer> syllabizerClass ) {
+ String sid = makeSyllabizerId ( script, language );
+ Syllabizer s = syllabizers.get ( sid );
+ if ( s == null ) {
+ if ( ( s = makeSyllabizer ( script, language, syllabizerClass ) ) == null ) {
+ s = new DefaultSyllabizer ( script, language );
+ }
+ syllabizers.put ( sid, s );
+ }
+ return s;
+ }
+ static String makeSyllabizerId ( String script, String language ) {
+ return script + ":" + language;
+ }
+ static Syllabizer makeSyllabizer ( String script, String language, Class<? extends Syllabizer> syllabizerClass ) {
+ Syllabizer s;
+ try {
+ Constructor<? extends Syllabizer> cf = syllabizerClass.getDeclaredConstructor ( new Class[] { String.class, String.class } );
+ s = (Syllabizer) cf.newInstance ( script, language );
+ } catch ( NoSuchMethodException e ) {
+ s = null;
+ } catch ( InstantiationException e ) {
+ s = null;
+ } catch ( IllegalAccessException e ) {
+ s = null;
+ } catch ( InvocationTargetException e ) {
+ s = null;
+ }
+ return s;
+ }
+ }
+
+ /** Default syllabizer. */
+ protected static class DefaultSyllabizer extends Syllabizer {
+ DefaultSyllabizer ( String script, String language ) {
+ super ( script, language );
+ }
+ /** {@inheritDoc} */
+ @Override
+ GlyphSequence[] syllabize ( GlyphSequence gs ) {
+ int[] ca = gs.getCharacterArray ( false );
+ int nc = gs.getCharacterCount();
+ if ( nc == 0 ) {
+ return new GlyphSequence[] { gs };
+ } else {
+ return segmentize ( gs, segmentize ( ca, nc ) );
+ }
+ }
+ /**
+ * Construct array of segements from original character array (associated with original glyph sequence)
+ * @param ca input character sequence
+ * @param nc number of characters in sequence
+ * @return array of syllable segments
+ */
+ protected Segment[] segmentize ( int[] ca, int nc ) {
+ Vector<Segment> sv = new Vector<Segment> ( nc );
+ for ( int s = 0, e = nc; s < e; ) {
+ int i;
+ if ( ( i = findStartOfSyllable ( ca, s, e ) ) > s ) {
+ // from s to i is non-syllable segment
+ sv.add ( new Segment ( s, i, Segment.OTHER ) );
+ s = i; // move s to start of syllable
+ } else if ( i > s ) {
+ // from s to e is non-syllable segment
+ sv.add ( new Segment ( s, e, Segment.OTHER ) );
+ s = e; // move s to end of input sequence
+ }
+ if ( ( i = findEndOfSyllable ( ca, s, e ) ) > s ) {
+ // from s to i is syllable segment
+ sv.add ( new Segment ( s, i, Segment.SYLLABLE ) );
+ s = i; // move s to end of syllable
+ } else {
+ // from s to e is non-syllable segment
+ sv.add ( new Segment ( s, e, Segment.OTHER ) );
+ s = e; // move s to end of input sequence
+ }
+ }
+ return sv.toArray ( new Segment [ sv.size() ] );
+ }
+ /**
+ * Construct array of glyph sequences from original glyph sequence and segment array.
+ * @param gs original input glyph sequence
+ * @param sa segment array
+ * @return array of glyph sequences each belonging to an (ordered) segment in SA
+ */
+ protected GlyphSequence[] segmentize ( GlyphSequence gs, Segment[] sa ) {
+ int ng = gs.getGlyphCount();
+ int[] ga = gs.getGlyphArray ( false );
+ GlyphSequence.CharAssociation[] aa = gs.getAssociations ( 0, -1 );
+ Vector<GlyphSequence> nsv = new Vector<GlyphSequence>();
+ for ( int i = 0, ns = sa.length; i < ns; i++ ) {
+ Segment s = sa [ i ];
+ Vector<Integer> ngv = new Vector<Integer> ( ng );
+ Vector<GlyphSequence.CharAssociation> nav = new Vector<GlyphSequence.CharAssociation> ( ng );
+ for ( int j = 0; j < ng; j++ ) {
+ GlyphSequence.CharAssociation ca = aa [ j ];
+ if ( ca.contained ( s.getOffset(), s.getCount() ) ) {
+ ngv.add ( ga [ j ] );
+ nav.add ( ca );
+ }
+ }
+ if ( ngv.size() > 0 ) {
+ nsv.add ( new GlyphSequence ( gs, null, toIntArray ( ngv ), null, null, nav.toArray ( new GlyphSequence.CharAssociation [ nav.size() ] ), null ) );
+ }
+ }
+ if ( nsv.size() > 0 ) {
+ return nsv.toArray ( new GlyphSequence [ nsv.size() ] );
+ } else {
+ return new GlyphSequence[] { gs };
+ }
+ }
+ /**
+ * Find start of syllable in character array, starting at S, ending at E.
+ * @param ca character array
+ * @param s start index
+ * @param e end index
+ * @return index of start or E if no start found
+ */
+ protected int findStartOfSyllable ( int[] ca, int s, int e ) {
+ return e;
+ }
+ /**
+ * Find end of syllable in character array, starting at S, ending at E.
+ * @param ca character array
+ * @param s start index
+ * @param e end index
+ * @return index of start or S if no end found
+ */
+ protected int findEndOfSyllable ( int[] ca, int s, int e ) {
+ return s;
+ }
+ private static int[] toIntArray ( Vector<Integer> iv ) {
+ int ni = iv.size();
+ int[] ia = new int [ iv.size() ];
+ for ( int i = 0, n = ni; i < n; i++ ) {
+ ia [ i ] = (int) iv.get ( i );
+ }
+ return ia;
+ }
+ }
+
+ /** Syllabic segment. */
+ protected static class Segment {
+
+ static final int OTHER = 0; // other (non-syllable) characters
+ static final int SYLLABLE = 1; // (orthographic) syllable
+
+ private int start;
+ private int end;
+ private int type;
+
+ Segment ( int start, int end, int type ) {
+ this.start = start;
+ this.end = end;
+ this.type = type;
+ }
+
+ int getStart() {
+ return start;
+ }
+
+ int getEnd() {
+ return end;
+ }
+
+ int getOffset() {
+ return start;
+ }
+
+ int getCount() {
+ return end - start;
+ }
+
+ int getType() {
+ return type;
+ }
+ }
+}
diff --git a/src/java/org/apache/fop/complexscripts/scripts/ScriptProcessor.java b/src/java/org/apache/fop/complexscripts/scripts/ScriptProcessor.java
new file mode 100644
index 000000000..4f6feffee
--- /dev/null
+++ b/src/java/org/apache/fop/complexscripts/scripts/ScriptProcessor.java
@@ -0,0 +1,234 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.complexscripts.scripts;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.fop.complexscripts.fonts.GlyphDefinitionTable;
+import org.apache.fop.complexscripts.fonts.GlyphPositioningTable;
+import org.apache.fop.complexscripts.fonts.GlyphSubstitutionTable;
+import org.apache.fop.complexscripts.fonts.GlyphTable;
+import org.apache.fop.complexscripts.util.CharScript;
+import org.apache.fop.complexscripts.util.GlyphSequence;
+import org.apache.fop.complexscripts.util.ScriptContextTester;
+
+// CSOFF: InnerAssignmentCheck
+// CSOFF: LineLengthCheck
+// CSOFF: ParameterNumberCheck
+
+/**
+ * Abstract script processor base class for which an implementation of the substitution and positioning methods
+ * must be supplied.
+ * @author Glenn Adams
+ */
+public abstract class ScriptProcessor {
+
+ private final String script;
+
+ private static Map<String, ScriptProcessor> processors = new HashMap<String, ScriptProcessor>();
+
+ /**
+ * Instantiate a script processor.
+ * @param script a script identifier
+ */
+ protected ScriptProcessor ( String script ) {
+ if ( ( script == null ) || ( script.length() == 0 ) ) {
+ throw new IllegalArgumentException ( "script must be non-empty string" );
+ } else {
+ this.script = script;
+ }
+ }
+
+ /** @return script identifier */
+ public final String getScript() {
+ return script;
+ }
+
+ /**
+ * 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
+ */
+ public abstract ScriptContextTester getSubstitutionContextTester();
+
+ /**
+ * Perform substitution processing using a specific set of lookup tables.
+ * @param gsub the glyph substitution table that applies
+ * @param gs an input glyph sequence
+ * @param script a script identifier
+ * @param language a language identifier
+ * @param lookups a mapping from lookup specifications to glyph subtables to use for substitution processing
+ * @return the substituted (output) glyph sequence
+ */
+ public final GlyphSequence substitute ( GlyphSubstitutionTable gsub, GlyphSequence gs, String script, String language, Map/*<LookupSpec,List<LookupTable>>>*/ lookups ) {
+ return substitute ( gs, script, language, assembleLookups ( gsub, getSubstitutionFeatures(), lookups ), getSubstitutionContextTester() );
+ }
+
+ /**
+ * Perform substitution processing using a specific set of ordered glyph table use specifications.
+ * @param gs an input glyph sequence
+ * @param script a script identifier
+ * @param language a language identifier
+ * @param usa an ordered array of glyph table use specs
+ * @param sct a script specific context tester (or null)
+ * @return the substituted (output) glyph sequence
+ */
+ 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 ];
+ gs = us.substitute ( gs, script, language, sct );
+ }
+ return gs;
+ }
+
+ /**
+ * 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
+ */
+ public abstract ScriptContextTester getPositioningContextTester();
+
+ /**
+ * Perform positioning processing using a specific set of lookup tables.
+ * @param gpos the glyph positioning table that applies
+ * @param gs an input glyph sequence
+ * @param script a script identifier
+ * @param language a language identifier
+ * @param fontSize size in device units
+ * @param lookups a mapping from lookup specifications to glyph subtables to use for positioning processing
+ * @param widths array of default advancements for each glyph
+ * @param adjustments accumulated adjustments array (sequence) of 4-tuples of placement [PX,PY] and advance [AX,AY] adjustments, in that order,
+ * with one 4-tuple for each element of glyph sequence
+ * @return true if some adjustment is not zero; otherwise, false
+ */
+ public final boolean position ( GlyphPositioningTable gpos, GlyphSequence gs, String script, String language, int fontSize, Map/*<LookupSpec,List<LookupTable>>*/ lookups, int[] widths, int[][] adjustments ) {
+ return position ( gs, script, language, fontSize, assembleLookups ( gpos, getPositioningFeatures(), lookups ), widths, adjustments, getPositioningContextTester() );
+ }
+
+ /**
+ * Perform positioning processing using a specific set of ordered glyph table use specifications.
+ * @param gs an input glyph sequence
+ * @param script a script identifier
+ * @param language a language identifier
+ * @param fontSize size in device units
+ * @param usa an ordered array of glyph table use specs
+ * @param widths array of default advancements for each glyph in font
+ * @param adjustments accumulated adjustments array (sequence) of 4-tuples of placement [PX,PY] and advance [AX,AY] adjustments, in that order,
+ * with one 4-tuple for each element of glyph sequence
+ * @param sct a script specific context tester (or null)
+ * @return true if some adjustment is not zero; otherwise, false
+ */
+ 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++ ) {
+ GlyphTable.UseSpec us = usa [ i ];
+ if ( us.position ( gs, script, language, fontSize, widths, adjustments, sct ) ) {
+ adjusted = true;
+ }
+ }
+ return adjusted;
+ }
+
+ /**
+ * Assemble ordered array of lookup table use specifications according to the specified features and candidate lookups,
+ * where the order of the array is in accordance to the order of the applicable lookup list.
+ * @param table the governing glyph table
+ * @param features array of feature identifiers to apply
+ * @param lookups a mapping from lookup specifications to lists of look tables from which to select lookup tables according to the specified features
+ * @return ordered array of assembled lookup table use specifications
+ */
+ public final GlyphTable.UseSpec[] assembleLookups ( GlyphTable table, String[] features, Map/*<LookupSpec,List<LookupTable>>*/ lookups ) {
+ return table.assembleLookups ( features, lookups );
+ }
+
+ /**
+ * Obtain script processor instance associated with specified script.
+ * @param script a script identifier
+ * @return a script processor instance or null if none found
+ */
+ public static synchronized ScriptProcessor getInstance ( String script ) {
+ ScriptProcessor sp = null;
+ assert processors != null;
+ if ( ( sp = processors.get ( script ) ) == null ) {
+ processors.put ( script, sp = createProcessor ( script ) );
+ }
+ return sp;
+ }
+
+ // [TBD] - rework to provide more configurable binding between script name and script processor constructor
+ private static ScriptProcessor createProcessor ( String script ) {
+ ScriptProcessor sp = null;
+ int sc = CharScript.scriptCodeFromTag ( script );
+ if ( sc == CharScript.SCRIPT_ARABIC ) {
+ sp = new ArabicScriptProcessor ( script );
+ } else if ( CharScript.isIndicScript ( sc ) ) {
+ sp = IndicScriptProcessor.makeProcessor ( script );
+ } else {
+ sp = new DefaultScriptProcessor ( script );
+ }
+ return sp;
+ }
+
+}
diff --git a/src/java/org/apache/fop/complexscripts/util/CharMirror.java b/src/java/org/apache/fop/complexscripts/util/CharMirror.java
new file mode 100644
index 000000000..bb1d1587f
--- /dev/null
+++ b/src/java/org/apache/fop/complexscripts/util/CharMirror.java
@@ -0,0 +1,715 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.complexscripts.util;
+
+import java.util.Arrays;
+
+/**
+ * Mirror related utilities.
+ * @author Glenn Adams
+ */
+public final class CharMirror {
+
+ private CharMirror() {
+ }
+
+ /**
+ * Mirror characters that are designated as having the bidi mirrorred property.
+ * @param s a string whose characters are to be mirrored
+ * @return the resulting string
+ */
+ public static String mirror ( String s ) {
+ StringBuffer sb = new StringBuffer ( s );
+ for ( int i = 0, n = sb.length(); i < n; i++ ) {
+ sb.setCharAt ( i, (char) mirror ( sb.charAt ( i ) ) );
+ }
+ return sb.toString();
+ }
+
+ private static int[] mirroredCharacters = {
+ 0x0028,
+ 0x0029,
+ 0x003C,
+ 0x003E,
+ 0x005B,
+ 0x005D,
+ 0x007B,
+ 0x007D,
+ 0x00AB,
+ 0x00BB,
+ 0x0F3A,
+ 0x0F3B,
+ 0x0F3C,
+ 0x0F3D,
+ 0x169B,
+ 0x169C,
+ 0x2039,
+ 0x203A,
+ 0x2045,
+ 0x2046,
+ 0x207D,
+ 0x207E,
+ 0x208D,
+ 0x208E,
+ 0x2208,
+ 0x2209,
+ 0x220A,
+ 0x220B,
+ 0x220C,
+ 0x220D,
+ 0x2215,
+ 0x223C,
+ 0x223D,
+ 0x2243,
+ 0x2252,
+ 0x2253,
+ 0x2254,
+ 0x2255,
+ 0x2264,
+ 0x2265,
+ 0x2266,
+ 0x2267,
+ 0x2268,
+ 0x2269,
+ 0x226A,
+ 0x226B,
+ 0x226E,
+ 0x226F,
+ 0x2270,
+ 0x2271,
+ 0x2272,
+ 0x2273,
+ 0x2274,
+ 0x2275,
+ 0x2276,
+ 0x2277,
+ 0x2278,
+ 0x2279,
+ 0x227A,
+ 0x227B,
+ 0x227C,
+ 0x227D,
+ 0x227E,
+ 0x227F,
+ 0x2280,
+ 0x2281,
+ 0x2282,
+ 0x2283,
+ 0x2284,
+ 0x2285,
+ 0x2286,
+ 0x2287,
+ 0x2288,
+ 0x2289,
+ 0x228A,
+ 0x228B,
+ 0x228F,
+ 0x2290,
+ 0x2291,
+ 0x2292,
+ 0x2298,
+ 0x22A2,
+ 0x22A3,
+ 0x22A6,
+ 0x22A8,
+ 0x22A9,
+ 0x22AB,
+ 0x22B0,
+ 0x22B1,
+ 0x22B2,
+ 0x22B3,
+ 0x22B4,
+ 0x22B5,
+ 0x22B6,
+ 0x22B7,
+ 0x22C9,
+ 0x22CA,
+ 0x22CB,
+ 0x22CC,
+ 0x22CD,
+ 0x22D0,
+ 0x22D1,
+ 0x22D6,
+ 0x22D7,
+ 0x22D8,
+ 0x22D9,
+ 0x22DA,
+ 0x22DB,
+ 0x22DC,
+ 0x22DD,
+ 0x22DE,
+ 0x22DF,
+ 0x22E0,
+ 0x22E1,
+ 0x22E2,
+ 0x22E3,
+ 0x22E4,
+ 0x22E5,
+ 0x22E6,
+ 0x22E7,
+ 0x22E8,
+ 0x22E9,
+ 0x22EA,
+ 0x22EB,
+ 0x22EC,
+ 0x22ED,
+ 0x22F0,
+ 0x22F1,
+ 0x22F2,
+ 0x22F3,
+ 0x22F4,
+ 0x22F6,
+ 0x22F7,
+ 0x22FA,
+ 0x22FB,
+ 0x22FC,
+ 0x22FD,
+ 0x22FE,
+ 0x2308,
+ 0x2309,
+ 0x230A,
+ 0x230B,
+ 0x2329,
+ 0x232A,
+ 0x2768,
+ 0x2769,
+ 0x276A,
+ 0x276B,
+ 0x276C,
+ 0x276D,
+ 0x276E,
+ 0x276F,
+ 0x2770,
+ 0x2771,
+ 0x2772,
+ 0x2773,
+ 0x2774,
+ 0x2775,
+ 0x27C3,
+ 0x27C4,
+ 0x27C5,
+ 0x27C6,
+ 0x27C8,
+ 0x27C9,
+ 0x27D5,
+ 0x27D6,
+ 0x27DD,
+ 0x27DE,
+ 0x27E2,
+ 0x27E3,
+ 0x27E4,
+ 0x27E5,
+ 0x27E6,
+ 0x27E7,
+ 0x27E8,
+ 0x27E9,
+ 0x27EA,
+ 0x27EB,
+ 0x27EC,
+ 0x27ED,
+ 0x27EE,
+ 0x27EF,
+ 0x2983,
+ 0x2984,
+ 0x2985,
+ 0x2986,
+ 0x2987,
+ 0x2988,
+ 0x2989,
+ 0x298A,
+ 0x298B,
+ 0x298C,
+ 0x298D,
+ 0x298E,
+ 0x298F,
+ 0x2990,
+ 0x2991,
+ 0x2992,
+ 0x2993,
+ 0x2994,
+ 0x2995,
+ 0x2996,
+ 0x2997,
+ 0x2998,
+ 0x29B8,
+ 0x29C0,
+ 0x29C1,
+ 0x29C4,
+ 0x29C5,
+ 0x29CF,
+ 0x29D0,
+ 0x29D1,
+ 0x29D2,
+ 0x29D4,
+ 0x29D5,
+ 0x29D8,
+ 0x29D9,
+ 0x29DA,
+ 0x29DB,
+ 0x29F5,
+ 0x29F8,
+ 0x29F9,
+ 0x29FC,
+ 0x29FD,
+ 0x2A2B,
+ 0x2A2C,
+ 0x2A2D,
+ 0x2A2E,
+ 0x2A34,
+ 0x2A35,
+ 0x2A3C,
+ 0x2A3D,
+ 0x2A64,
+ 0x2A65,
+ 0x2A79,
+ 0x2A7A,
+ 0x2A7D,
+ 0x2A7E,
+ 0x2A7F,
+ 0x2A80,
+ 0x2A81,
+ 0x2A82,
+ 0x2A83,
+ 0x2A84,
+ 0x2A8B,
+ 0x2A8C,
+ 0x2A91,
+ 0x2A92,
+ 0x2A93,
+ 0x2A94,
+ 0x2A95,
+ 0x2A96,
+ 0x2A97,
+ 0x2A98,
+ 0x2A99,
+ 0x2A9A,
+ 0x2A9B,
+ 0x2A9C,
+ 0x2AA1,
+ 0x2AA2,
+ 0x2AA6,
+ 0x2AA7,
+ 0x2AA8,
+ 0x2AA9,
+ 0x2AAA,
+ 0x2AAB,
+ 0x2AAC,
+ 0x2AAD,
+ 0x2AAF,
+ 0x2AB0,
+ 0x2AB3,
+ 0x2AB4,
+ 0x2AC3,
+ 0x2AC4,
+ 0x2AC5,
+ 0x2AC6,
+ 0x2ACD,
+ 0x2ACE,
+ 0x2ACF,
+ 0x2AD0,
+ 0x2AD1,
+ 0x2AD2,
+ 0x2AD3,
+ 0x2AD4,
+ 0x2AD5,
+ 0x2AD6,
+ 0x2ADE,
+ 0x2AE3,
+ 0x2E02,
+ 0x2E03,
+ 0x2E04,
+ 0x2E05,
+ 0x2E09,
+ 0x2E0A,
+ 0x2E0C,
+ 0x2E0D,
+ 0x2E1C,
+ 0x2E1D,
+ 0x2E20,
+ 0x2E21,
+ 0x2E22,
+ 0x2E23,
+ 0x2E24,
+ 0x2E25,
+ 0x2E26,
+ 0x300E,
+ 0x300F,
+ 0x3010,
+ 0x3011,
+ 0x3014,
+ 0x3015,
+ 0x3016,
+ 0x3017,
+ 0x3018,
+ 0x3019,
+ 0x301A,
+ 0x301B,
+ 0xFE59,
+ 0xFE5A,
+ 0xFF3B,
+ 0xFF3D,
+ 0xFF5B,
+ 0xFF5D,
+ 0xFF5F,
+ 0xFF60,
+ 0xFF62,
+ 0xFF63
+ };
+
+ private static int[] mirroredCharactersMapping = {
+ 0x0029,
+ 0x0028,
+ 0x003E,
+ 0x003C,
+ 0x005D,
+ 0x005B,
+ 0x007D,
+ 0x007B,
+ 0x00BB,
+ 0x00AB,
+ 0x0F3B,
+ 0x0F3A,
+ 0x0F3D,
+ 0x0F3C,
+ 0x169C,
+ 0x169B,
+ 0x203A,
+ 0x2039,
+ 0x2046,
+ 0x2045,
+ 0x207E,
+ 0x207D,
+ 0x208E,
+ 0x208D,
+ 0x220B,
+ 0x220C,
+ 0x220D,
+ 0x2208,
+ 0x2209,
+ 0x220A,
+ 0x29F5,
+ 0x223D,
+ 0x223C,
+ 0x22CD,
+ 0x2253,
+ 0x2252,
+ 0x2255,
+ 0x2254,
+ 0x2265,
+ 0x2264,
+ 0x2267,
+ 0x2266,
+ 0x2269,
+ 0x2268,
+ 0x226B,
+ 0x226A,
+ 0x226F,
+ 0x226E,
+ 0x2271,
+ 0x2270,
+ 0x2273,
+ 0x2272,
+ 0x2275,
+ 0x2274,
+ 0x2277,
+ 0x2276,
+ 0x2279,
+ 0x2278,
+ 0x227B,
+ 0x227A,
+ 0x227D,
+ 0x227C,
+ 0x227F,
+ 0x227E,
+ 0x2281,
+ 0x2280,
+ 0x2283,
+ 0x2282,
+ 0x2285,
+ 0x2284,
+ 0x2287,
+ 0x2286,
+ 0x2289,
+ 0x2288,
+ 0x228B,
+ 0x228A,
+ 0x2290,
+ 0x228F,
+ 0x2292,
+ 0x2291,
+ 0x29B8,
+ 0x22A3,
+ 0x22A2,
+ 0x2ADE,
+ 0x2AE4,
+ 0x2AE3,
+ 0x2AE5,
+ 0x22B1,
+ 0x22B0,
+ 0x22B3,
+ 0x22B2,
+ 0x22B5,
+ 0x22B4,
+ 0x22B7,
+ 0x22B6,
+ 0x22CA,
+ 0x22C9,
+ 0x22CC,
+ 0x22CB,
+ 0x2243,
+ 0x22D1,
+ 0x22D0,
+ 0x22D7,
+ 0x22D6,
+ 0x22D9,
+ 0x22D8,
+ 0x22DB,
+ 0x22DA,
+ 0x22DD,
+ 0x22DC,
+ 0x22DF,
+ 0x22DE,
+ 0x22E1,
+ 0x22E0,
+ 0x22E3,
+ 0x22E2,
+ 0x22E5,
+ 0x22E4,
+ 0x22E7,
+ 0x22E6,
+ 0x22E9,
+ 0x22E8,
+ 0x22EB,
+ 0x22EA,
+ 0x22ED,
+ 0x22EC,
+ 0x22F1,
+ 0x22F0,
+ 0x22FA,
+ 0x22FB,
+ 0x22FC,
+ 0x22FD,
+ 0x22FE,
+ 0x22F2,
+ 0x22F3,
+ 0x22F4,
+ 0x22F6,
+ 0x22F7,
+ 0x2309,
+ 0x2308,
+ 0x230B,
+ 0x230A,
+ 0x232A,
+ 0x2329,
+ 0x2769,
+ 0x2768,
+ 0x276B,
+ 0x276A,
+ 0x276D,
+ 0x276C,
+ 0x276F,
+ 0x276E,
+ 0x2771,
+ 0x2770,
+ 0x2773,
+ 0x2772,
+ 0x2775,
+ 0x2774,
+ 0x27C4,
+ 0x27C3,
+ 0x27C6,
+ 0x27C5,
+ 0x27C9,
+ 0x27C8,
+ 0x27D6,
+ 0x27D5,
+ 0x27DE,
+ 0x27DD,
+ 0x27E3,
+ 0x27E2,
+ 0x27E5,
+ 0x27E4,
+ 0x27E7,
+ 0x27E6,
+ 0x27E9,
+ 0x27E8,
+ 0x27EB,
+ 0x27EA,
+ 0x27ED,
+ 0x27EC,
+ 0x27EF,
+ 0x27EE,
+ 0x2984,
+ 0x2983,
+ 0x2986,
+ 0x2985,
+ 0x2988,
+ 0x2987,
+ 0x298A,
+ 0x2989,
+ 0x298C,
+ 0x298B,
+ 0x2990,
+ 0x298F,
+ 0x298E,
+ 0x298D,
+ 0x2992,
+ 0x2991,
+ 0x2994,
+ 0x2993,
+ 0x2996,
+ 0x2995,
+ 0x2998,
+ 0x2997,
+ 0x2298,
+ 0x29C1,
+ 0x29C0,
+ 0x29C5,
+ 0x29C4,
+ 0x29D0,
+ 0x29CF,
+ 0x29D2,
+ 0x29D1,
+ 0x29D5,
+ 0x29D4,
+ 0x29D9,
+ 0x29D8,
+ 0x29DB,
+ 0x29DA,
+ 0x2215,
+ 0x29F9,
+ 0x29F8,
+ 0x29FD,
+ 0x29FC,
+ 0x2A2C,
+ 0x2A2B,
+ 0x2A2E,
+ 0x2A2D,
+ 0x2A35,
+ 0x2A34,
+ 0x2A3D,
+ 0x2A3C,
+ 0x2A65,
+ 0x2A64,
+ 0x2A7A,
+ 0x2A79,
+ 0x2A7E,
+ 0x2A7D,
+ 0x2A80,
+ 0x2A7F,
+ 0x2A82,
+ 0x2A81,
+ 0x2A84,
+ 0x2A83,
+ 0x2A8C,
+ 0x2A8B,
+ 0x2A92,
+ 0x2A91,
+ 0x2A94,
+ 0x2A93,
+ 0x2A96,
+ 0x2A95,
+ 0x2A98,
+ 0x2A97,
+ 0x2A9A,
+ 0x2A99,
+ 0x2A9C,
+ 0x2A9B,
+ 0x2AA2,
+ 0x2AA1,
+ 0x2AA7,
+ 0x2AA6,
+ 0x2AA9,
+ 0x2AA8,
+ 0x2AAB,
+ 0x2AAA,
+ 0x2AAD,
+ 0x2AAC,
+ 0x2AB0,
+ 0x2AAF,
+ 0x2AB4,
+ 0x2AB3,
+ 0x2AC4,
+ 0x2AC3,
+ 0x2AC6,
+ 0x2AC5,
+ 0x2ACE,
+ 0x2ACD,
+ 0x2AD0,
+ 0x2ACF,
+ 0x2AD2,
+ 0x2AD1,
+ 0x2AD4,
+ 0x2AD3,
+ 0x2AD6,
+ 0x2AD5,
+ 0x22A6,
+ 0x22A9,
+ 0x2E03,
+ 0x2E02,
+ 0x2E05,
+ 0x2E04,
+ 0x2E0A,
+ 0x2E09,
+ 0x2E0D,
+ 0x2E0C,
+ 0x2E1D,
+ 0x2E1C,
+ 0x2E21,
+ 0x2E20,
+ 0x2E23,
+ 0x2E22,
+ 0x2E25,
+ 0x2E24,
+ 0x2E27,
+ 0x300F,
+ 0x300E,
+ 0x3011,
+ 0x3010,
+ 0x3015,
+ 0x3014,
+ 0x3017,
+ 0x3016,
+ 0x3019,
+ 0x3018,
+ 0x301B,
+ 0x301A,
+ 0xFE5A,
+ 0xFE59,
+ 0xFF3D,
+ 0xFF3B,
+ 0xFF5D,
+ 0xFF5B,
+ 0xFF60,
+ 0xFF5F,
+ 0xFF63,
+ 0xFF62
+ };
+
+ private static int mirror ( int c ) {
+ int i = Arrays.binarySearch ( mirroredCharacters, c );
+ if ( i < 0 ) {
+ return c;
+ } else {
+ return mirroredCharactersMapping [ i ];
+ }
+ }
+
+}
diff --git a/src/java/org/apache/fop/complexscripts/util/CharScript.java b/src/java/org/apache/fop/complexscripts/util/CharScript.java
new file mode 100644
index 000000000..bcce31327
--- /dev/null
+++ b/src/java/org/apache/fop/complexscripts/util/CharScript.java
@@ -0,0 +1,930 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.complexscripts.util;
+
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.fop.util.CharUtilities;
+
+// CSOFF: AvoidNestedBlocksCheck
+// CSOFF: InnerAssignmentCheck
+// CSOFF: LineLengthCheck
+// CSOFF: SimplifyBooleanReturnCheck
+// CSOFF: WhitespaceAfterCheck
+
+/**
+ * Script related utilities.
+ * @author Glenn Adams
+ */
+public final class CharScript {
+
+ //
+ // 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.
+ //
+ /** hebrew script constant */
+ public static final int SCRIPT_HEBREW = 125; // 'hebr'
+ /** mongolian script constant */
+ public static final int SCRIPT_MONGOLIAN = 145; // 'mong'
+ /** arabic script constant */
+ public static final int SCRIPT_ARABIC = 160; // 'arab'
+ /** greek script constant */
+ public static final int SCRIPT_GREEK = 200; // 'grek'
+ /** latin script constant */
+ public static final int SCRIPT_LATIN = 215; // 'latn'
+ /** cyrillic script constant */
+ public static final int SCRIPT_CYRILLIC = 220; // 'cyrl'
+ /** georgian script constant */
+ public static final int SCRIPT_GEORGIAN = 240; // 'geor'
+ /** bopomofo script constant */
+ public static final int SCRIPT_BOPOMOFO = 285; // 'bopo'
+ /** hangul script constant */
+ 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 */
+ public static final int SCRIPT_BURMESE = 350; // 'mymr'
+ /** thai script constant */
+ public static final int SCRIPT_THAI = 352; // 'thai'
+ /** khmer script constant */
+ public static final int SCRIPT_KHMER = 355; // 'khmr'
+ /** lao script constant */
+ public static final int SCRIPT_LAO = 356; // 'laoo'
+ /** hiragana script constant */
+ public static final int SCRIPT_HIRAGANA = 410; // 'hira'
+ /** ethiopic script constant */
+ public static final int SCRIPT_ETHIOPIC = 430; // 'ethi'
+ /** han script constant */
+ public static final int SCRIPT_HAN = 500; // 'hani'
+ /** katakana script constant */
+ public static final int SCRIPT_KATAKANA = 410; // 'kana'
+ /** math script constant */
+ public static final int SCRIPT_MATH = 995; // 'zmth'
+ /** symbol script constant */
+ public static final int SCRIPT_SYMBOL = 996; // 'zsym'
+ /** undetermined script constant */
+ public static final int SCRIPT_UNDETERMINED = 998; // 'zyyy'
+ /** uncoded script constant */
+ public static final int SCRIPT_UNCODED = 999; // 'zzzz'
+
+ /**
+ * A static (class) parameter indicating whether V2 indic shaping
+ * rules apply or not, with default being <code>true</code>.
+ */
+ private static final boolean useV2Indic = true; // CSOK: ConstantNameCheck
+
+ private CharScript() {
+ }
+
+ /**
+ * Determine if character c is punctuation.
+ * @param c a character represented as a unicode scalar value
+ * @return true if character is punctuation
+ */
+ public static boolean isPunctuation ( int c ) {
+ if ( ( c >= 0x0021 ) && ( c <= 0x002F ) ) { // basic latin punctuation
+ return true;
+ } else if ( ( c >= 0x003A ) && ( c <= 0x0040 ) ) { // basic latin punctuation
+ return true;
+ } else if ( ( c >= 0x005F ) && ( c <= 0x0060 ) ) { // basic latin punctuation
+ return true;
+ } else if ( ( c >= 0x007E ) && ( c <= 0x007E ) ) { // basic latin punctuation
+ return true;
+ } else if ( ( c >= 0x007E ) && ( c <= 0x007E ) ) { // basic latin punctuation
+ return true;
+ } else if ( ( c >= 0x00A1 ) && ( c <= 0x00BF ) ) { // latin supplement punctuation
+ return true;
+ } else if ( ( c >= 0x00D7 ) && ( c <= 0x00D7 ) ) { // latin supplement punctuation
+ return true;
+ } else if ( ( c >= 0x00F7 ) && ( c <= 0x00F7 ) ) { // latin supplement punctuation
+ return true;
+ } else if ( ( c >= 0x2000 ) && ( c <= 0x206F ) ) { // general punctuation
+ return true;
+ } else { // [TBD] - not complete
+ return false;
+ }
+ }
+
+ /**
+ * Determine if character c is a digit.
+ * @param c a character represented as a unicode scalar value
+ * @return true if character is a digit
+ */
+ public static boolean isDigit ( int c ) {
+ if ( ( c >= 0x0030 ) && ( c <= 0x0039 ) ) { // basic latin digits
+ return true;
+ } else { // [TBD] - not complete
+ return false;
+ }
+ }
+
+ /**
+ * Determine if character c belong to the hebrew script.
+ * @param c a character represented as a unicode scalar value
+ * @return true if character belongs to hebrew script
+ */
+ public static boolean isHebrew ( int c ) {
+ if ( ( c >= 0x0590 ) && ( c <= 0x05FF ) ) { // hebrew block
+ return true;
+ } else if ( ( c >= 0xFB00 ) && ( c <= 0xFB4F ) ) { // hebrew presentation forms block
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Determine if character c belong to the mongolian script.
+ * @param c a character represented as a unicode scalar value
+ * @return true if character belongs to mongolian script
+ */
+ public static boolean isMongolian ( int c ) {
+ if ( ( c >= 0x1800 ) && ( c <= 0x18AF ) ) { // mongolian block
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Determine if character c belong to the arabic script.
+ * @param c a character represented as a unicode scalar value
+ * @return true if character belongs to arabic script
+ */
+ public static boolean isArabic ( int c ) {
+ if ( ( c >= 0x0600 ) && ( c <= 0x06FF ) ) { // arabic block
+ return true;
+ } else if ( ( c >= 0x0750 ) && ( c <= 0x077F ) ) { // arabic supplement block
+ return true;
+ } else if ( ( c >= 0xFB50 ) && ( c <= 0xFDFF ) ) { // arabic presentation forms a block
+ return true;
+ } else if ( ( c >= 0xFE70 ) && ( c <= 0xFEFF ) ) { // arabic presentation forms b block
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Determine if character c belong to the greek script.
+ * @param c a character represented as a unicode scalar value
+ * @return true if character belongs to greek script
+ */
+ public static boolean isGreek ( int c ) {
+ 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;
+ }
+ }
+
+ /**
+ * Determine if character c belong to the latin script.
+ * @param c a character represented as a unicode scalar value
+ * @return true if character belongs to latin script
+ */
+ public static boolean isLatin ( int c ) {
+ if ( ( c >= 0x0041 ) && ( c <= 0x005A ) ) { // basic latin upper case
+ return true;
+ } else if ( ( c >= 0x0061 ) && ( c <= 0x007A ) ) { // basic latin lower case
+ return true;
+ } else if ( ( c >= 0x00C0 ) && ( c <= 0x00D6 ) ) { // latin supplement upper case
+ return true;
+ } else if ( ( c >= 0x00D8 ) && ( c <= 0x00DF ) ) { // latin supplement upper case
+ return true;
+ } else if ( ( c >= 0x00E0 ) && ( c <= 0x00F6 ) ) { // latin supplement lower case
+ return true;
+ } else if ( ( c >= 0x00F8 ) && ( c <= 0x00FF ) ) { // latin supplement lower case
+ return true;
+ } else if ( ( c >= 0x0100 ) && ( c <= 0x017F ) ) { // latin extended a
+ return true;
+ } else if ( ( c >= 0x0180 ) && ( c <= 0x024F ) ) { // latin extended b
+ return true;
+ } else if ( ( c >= 0x1E00 ) && ( c <= 0x1EFF ) ) { // latin extended additional
+ return true;
+ } else if ( ( c >= 0x2C60 ) && ( c <= 0x2C7F ) ) { // latin extended c
+ return true;
+ } else if ( ( c >= 0xA720 ) && ( c <= 0xA7FF ) ) { // latin extended d
+ return true;
+ } else if ( ( c >= 0xFB00 ) && ( c <= 0xFB0F ) ) { // latin ligatures
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Determine if character c belong to the cyrillic script.
+ * @param c a character represented as a unicode scalar value
+ * @return true if character belongs to cyrillic script
+ */
+ public static boolean isCyrillic ( int c ) {
+ 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;
+ }
+ }
+
+ /**
+ * Determine if character c belong to the georgian script.
+ * @param c a character represented as a unicode scalar value
+ * @return true if character belongs to georgian script
+ */
+ public static boolean isGeorgian ( int c ) {
+ if ( ( c >= 0x10A0 ) && ( c <= 0x10FF ) ) { // georgian block
+ return true;
+ } else if ( ( c >= 0x2D00 ) && ( c <= 0x2D2F ) ) { // georgian supplement block
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Determine if character c belong to the hangul script.
+ * @param c a character represented as a unicode scalar value
+ * @return true if character belongs to hangul script
+ */
+ public static boolean isHangul ( int c ) {
+ if ( ( c >= 0x1100 ) && ( c <= 0x11FF ) ) { // hangul jamo
+ return true;
+ } else if ( ( c >= 0x3130 ) && ( c <= 0x318F ) ) { // hangul compatibility jamo
+ return true;
+ } else if ( ( c >= 0xA960 ) && ( c <= 0xA97F ) ) { // hangul jamo extended a
+ return true;
+ } else if ( ( c >= 0xAC00 ) && ( c <= 0xD7A3 ) ) { // hangul syllables
+ return true;
+ } else if ( ( c >= 0xD7B0 ) && ( c <= 0xD7FF ) ) { // hangul jamo extended a
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Determine if character c belong to the gurmukhi script.
+ * @param c a character represented as a unicode scalar value
+ * @return true if character belongs to gurmukhi script
+ */
+ public static boolean isGurmukhi ( int c ) {
+ if ( ( c >= 0x0A00 ) && ( c <= 0x0A7F ) ) { // gurmukhi block
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Determine if character c belong to the devanagari script.
+ * @param c a character represented as a unicode scalar value
+ * @return true if character belongs to devanagari script
+ */
+ public static boolean isDevanagari ( int c ) {
+ if ( ( c >= 0x0900 ) && ( c <= 0x097F ) ) { // devangari block
+ return true;
+ } else if ( ( c >= 0xA8E0 ) && ( c <= 0xA8FF ) ) { // devangari extended block
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Determine if character c belong to the gujarati script.
+ * @param c a character represented as a unicode scalar value
+ * @return true if character belongs to gujarati script
+ */
+ public static boolean isGujarati ( int c ) {
+ if ( ( c >= 0x0A80 ) && ( c <= 0x0AFF ) ) { // gujarati block
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Determine if character c belong to the bengali script.
+ * @param c a character represented as a unicode scalar value
+ * @return true if character belongs to bengali script
+ */
+ public static boolean isBengali ( int c ) {
+ if ( ( c >= 0x0980 ) && ( c <= 0x09FF ) ) { // bengali block
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Determine if character c belong to the oriya script.
+ * @param c a character represented as a unicode scalar value
+ * @return true if character belongs to oriya script
+ */
+ public static boolean isOriya ( int c ) {
+ if ( ( c >= 0x0B00 ) && ( c <= 0x0B7F ) ) { // oriya block
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Determine if character c belong to the tibetan script.
+ * @param c a character represented as a unicode scalar value
+ * @return true if character belongs to tibetan script
+ */
+ public static boolean isTibetan ( int c ) {
+ if ( ( c >= 0x0F00 ) && ( c <= 0x0FFF ) ) { // tibetan block
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Determine if character c belong to the telugu script.
+ * @param c a character represented as a unicode scalar value
+ * @return true if character belongs to telugu script
+ */
+ public static boolean isTelugu ( int c ) {
+ 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;
+ }
+ }
+
+ /**
+ * Determine if character c belong to the tamil script.
+ * @param c a character represented as a unicode scalar value
+ * @return true if character belongs to tamil script
+ */
+ public static boolean isTamil ( int c ) {
+ if ( ( c >= 0x0B80 ) && ( c <= 0x0BFF ) ) { // tamil block
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Determine if character c belong to the malayalam script.
+ * @param c a character represented as a unicode scalar value
+ * @return true if character belongs to malayalam script
+ */
+ public static boolean isMalayalam ( int c ) {
+ if ( ( c >= 0x0D00 ) && ( c <= 0x0D7F ) ) { // malayalam block
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Determine if character c belong to the sinhalese script.
+ * @param c a character represented as a unicode scalar value
+ * @return true if character belongs to sinhalese script
+ */
+ public static boolean isSinhalese ( int c ) {
+ if ( ( c >= 0x0D80 ) && ( c <= 0x0DFF ) ) { // sinhala block
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Determine if character c belong to the burmese script.
+ * @param c a character represented as a unicode scalar value
+ * @return true if character belongs to burmese script
+ */
+ public static boolean isBurmese ( int c ) {
+ 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;
+ }
+ }
+
+ /**
+ * Determine if character c belong to the thai script.
+ * @param c a character represented as a unicode scalar value
+ * @return true if character belongs to thai script
+ */
+ public static boolean isThai ( int c ) {
+ if ( ( c >= 0x0E00 ) && ( c <= 0x0E7F ) ) { // thai block
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Determine if character c belong to the khmer script.
+ * @param c a character represented as a unicode scalar value
+ * @return true if character belongs to khmer script
+ */
+ public static boolean isKhmer ( int c ) {
+ if ( ( c >= 0x1780 ) && ( c <= 0x17FF ) ) { // khmer block
+ return true;
+ } else if ( ( c >= 0x19E0 ) && ( c <= 0x19FF ) ) { // khmer symbols block
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Determine if character c belong to the lao script.
+ * @param c a character represented as a unicode scalar value
+ * @return true if character belongs to lao script
+ */
+ public static boolean isLao ( int c ) {
+ if ( ( c >= 0x0E80 ) && ( c <= 0x0EFF ) ) { // lao block
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Determine if character c belong to the ethiopic (amharic) script.
+ * @param c a character represented as a unicode scalar value
+ * @return true if character belongs to ethiopic (amharic) script
+ */
+ public static boolean isEthiopic ( int c ) {
+ 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;
+ }
+ }
+
+ /**
+ * Determine if character c belong to the han (unified cjk) script.
+ * @param c a character represented as a unicode scalar value
+ * @return true if character belongs to han (unified cjk) script
+ */
+ public static boolean isHan ( int c ) {
+ if ( ( c >= 0x3400 ) && ( c <= 0x4DBF ) ) {
+ return true; // cjk unified ideographs extension a
+ } else if ( ( c >= 0x4E00 ) && ( c <= 0x9FFF ) ) {
+ return true; // cjk unified ideographs
+ } else if ( ( c >= 0xF900 ) && ( c <= 0xFAFF ) ) {
+ return true; // cjk compatibility ideographs
+ } else if ( ( c >= 0x20000 ) && ( c <= 0x2A6DF ) ) {
+ return true; // cjk unified ideographs extension b
+ } else if ( ( c >= 0x2A700 ) && ( c <= 0x2B73F ) ) {
+ return true; // cjk unified ideographs extension c
+ } else if ( ( c >= 0x2F800 ) && ( c <= 0x2FA1F ) ) {
+ return true; // cjk compatibility ideographs supplement
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Determine if character c belong to the bopomofo script.
+ * @param c a character represented as a unicode scalar value
+ * @return true if character belongs to bopomofo script
+ */
+ public static boolean isBopomofo ( int c ) {
+ if ( ( c >= 0x3100 ) && ( c <= 0x312F ) ) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Determine if character c belong to the hiragana script.
+ * @param c a character represented as a unicode scalar value
+ * @return true if character belongs to hiragana script
+ */
+ public static boolean isHiragana ( int c ) {
+ if ( ( c >= 0x3040 ) && ( c <= 0x309F ) ) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Determine if character c belong to the katakana script.
+ * @param c a character represented as a unicode scalar value
+ * @return true if character belongs to katakana script
+ */
+ public static boolean isKatakana ( int c ) {
+ if ( ( c >= 0x30A0 ) && ( c <= 0x30FF ) ) {
+ return true;
+ } else if ( ( c >= 0x31F0 ) && ( c <= 0x31FF ) ) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Obtain ISO15924 numeric script code of character. If script is not or cannot be determined,
+ * then the script code 998 ('zyyy') is returned.
+ * @param c the character to obtain script
+ * @return an ISO15924 script code
+ */
+ public static int scriptOf ( int c ) { // [TBD] - needs optimization!!!
+ if ( CharUtilities.isAnySpace ( c ) ) {
+ return SCRIPT_UNDETERMINED;
+ } else if ( isPunctuation ( c ) ) {
+ return SCRIPT_UNDETERMINED;
+ } else if ( isDigit ( c ) ) {
+ return SCRIPT_UNDETERMINED;
+ } else if ( isLatin ( c ) ) {
+ return SCRIPT_LATIN;
+ } else if ( isCyrillic ( c ) ) {
+ return SCRIPT_CYRILLIC;
+ } else if ( isGreek ( c ) ) {
+ return SCRIPT_GREEK;
+ } else if ( isHan ( c ) ) {
+ return SCRIPT_HAN;
+ } else if ( isBopomofo ( c ) ) {
+ return SCRIPT_BOPOMOFO;
+ } else if ( isKatakana ( c ) ) {
+ return SCRIPT_KATAKANA;
+ } else if ( isHiragana ( c ) ) {
+ return SCRIPT_HIRAGANA;
+ } else if ( isHangul ( c ) ) {
+ return SCRIPT_HANGUL;
+ } else if ( isArabic ( c ) ) {
+ return SCRIPT_ARABIC;
+ } else if ( isHebrew ( c ) ) {
+ return SCRIPT_HEBREW;
+ } else if ( isMongolian ( c ) ) {
+ return SCRIPT_MONGOLIAN;
+ } else if ( isGeorgian ( c ) ) {
+ return SCRIPT_GEORGIAN;
+ } else if ( isGurmukhi ( c ) ) {
+ return useV2IndicRules ( SCRIPT_GURMUKHI );
+ } else if ( isDevanagari ( c ) ) {
+ return useV2IndicRules ( SCRIPT_DEVANAGARI );
+ } else if ( isGujarati ( c ) ) {
+ return useV2IndicRules ( SCRIPT_GUJARATI );
+ } else if ( isBengali ( c ) ) {
+ return useV2IndicRules ( SCRIPT_BENGALI );
+ } else if ( isOriya ( c ) ) {
+ return useV2IndicRules ( SCRIPT_ORIYA );
+ } else if ( isTibetan ( c ) ) {
+ return SCRIPT_TIBETAN;
+ } else if ( isTelugu ( c ) ) {
+ return useV2IndicRules ( SCRIPT_TELUGU );
+ } else if ( isKannada ( c ) ) {
+ return useV2IndicRules ( SCRIPT_KANNADA );
+ } else if ( isTamil ( c ) ) {
+ return useV2IndicRules ( SCRIPT_TAMIL );
+ } else if ( isMalayalam ( c ) ) {
+ return useV2IndicRules ( SCRIPT_MALAYALAM );
+ } else if ( isSinhalese ( c ) ) {
+ return SCRIPT_SINHALESE;
+ } else if ( isBurmese ( c ) ) {
+ return SCRIPT_BURMESE;
+ } else if ( isThai ( c ) ) {
+ return SCRIPT_THAI;
+ } else if ( isKhmer ( c ) ) {
+ return SCRIPT_KHMER;
+ } else if ( isLao ( c ) ) {
+ return SCRIPT_LAO;
+ } else if ( isEthiopic ( c ) ) {
+ return SCRIPT_ETHIOPIC;
+ } else {
+ return SCRIPT_UNDETERMINED;
+ }
+ }
+
+ /**
+ * 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
+ * ('zyyy') is returned.
+ * @param cs the character sequence
+ * @return a (possibly empty) array of script codes
+ */
+ public static int[] scriptsOf ( CharSequence cs ) {
+ Set s = new HashSet();
+ for ( int i = 0, n = cs.length(); i < n; i++ ) {
+ s.add ( Integer.valueOf ( scriptOf ( cs.charAt ( i ) ) ) );
+ }
+ int[] sa = new int [ s.size() ];
+ int ns = 0;
+ for ( Iterator it = s.iterator(); it.hasNext();) {
+ sa [ ns++ ] = ( (Integer) it.next() ) .intValue();
+ }
+ Arrays.sort ( sa );
+ return sa;
+ }
+
+ /**
+ * Determine the dominant script of a character sequence.
+ * @param cs the character sequence
+ * @return the dominant script or SCRIPT_UNDETERMINED
+ */
+ public static int dominantScript ( CharSequence cs ) {
+ Map m = new HashMap();
+ for ( int i = 0, n = cs.length(); i < n; i++ ) {
+ int c = cs.charAt ( i );
+ int s = scriptOf ( c );
+ Integer k = Integer.valueOf ( s );
+ Integer v = (Integer) m.get ( k );
+ if ( v != null ) {
+ m.put ( k, Integer.valueOf ( v.intValue() + 1 ) );
+ } else {
+ m.put ( k, Integer.valueOf ( 0 ) );
+ }
+ }
+ int sMax = -1;
+ int cMax = -1;
+ for ( Iterator it = m.entrySet().iterator(); it.hasNext();) {
+ Map.Entry e = (Map.Entry) it.next();
+ Integer k = (Integer) e.getKey();
+ int s = k.intValue();
+ switch ( s ) {
+ case SCRIPT_UNDETERMINED:
+ case SCRIPT_UNCODED:
+ break;
+ default:
+ {
+ Integer v = (Integer) e.getValue();
+ assert v != null;
+ int c = v.intValue();
+ if ( c > cMax ) {
+ cMax = c; sMax = s;
+ }
+ break;
+ }
+ }
+ }
+ if ( sMax < 0 ) {
+ sMax = SCRIPT_UNDETERMINED;
+ }
+ return sMax;
+ }
+
+ /**
+ * 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 ) {
+ return isIndicScript ( scriptCodeFromTag ( script ) );
+ }
+
+ /**
+ * 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 code
+ * @return true if script code is a designated 'Indic' script
+ */
+ public static boolean isIndicScript ( int script ) {
+ switch ( 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 a script tag
+ */
+ public static String scriptTagFromCode ( int code ) {
+ Map<Integer,String> m = getScriptTagsMap();
+ if ( m != null ) {
+ String tag;
+ if ( ( tag = m.get ( Integer.valueOf ( code ) ) ) != null ) {
+ return tag;
+ } else {
+ return "";
+ }
+ } else {
+ return "";
+ }
+ }
+
+ /**
+ * 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<String,Integer> m = getScriptCodeMap();
+ if ( m != null ) {
+ Integer c;
+ if ( ( c = m.get ( tag ) ) != null ) {
+ return (int) c;
+ } else {
+ return SCRIPT_UNDETERMINED;
+ }
+ } else {
+ return SCRIPT_UNDETERMINED;
+ }
+ }
+
+ private static Map<Integer,String> scriptTagsMap = null;
+ private static Map<String,Integer> 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 makeScriptMaps() {
+ HashMap<Integer,String> tm = new HashMap<Integer,String>();
+ HashMap<String,Integer> cm = new HashMap<String,Integer>();
+ 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<Integer,String> getScriptTagsMap() {
+ if ( scriptTagsMap == null ) {
+ makeScriptMaps();
+ }
+ return scriptTagsMap;
+ }
+
+ private static Map<String,Integer> getScriptCodeMap() {
+ if ( scriptCodeMap == null ) {
+ makeScriptMaps();
+ }
+ return scriptCodeMap;
+ }
+
+}
diff --git a/src/java/org/apache/fop/complexscripts/util/DiscontinuousAssociationException.java b/src/java/org/apache/fop/complexscripts/util/DiscontinuousAssociationException.java
new file mode 100644
index 000000000..daade8ca6
--- /dev/null
+++ b/src/java/org/apache/fop/complexscripts/util/DiscontinuousAssociationException.java
@@ -0,0 +1,41 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.complexscripts.util;
+
+/**
+ * Exception thrown during when attempting to map glyphs to associated characters
+ * in the case that the associated characters do not represent a compact interval.
+ * @author Glenn Adams
+ */
+public class DiscontinuousAssociationException extends RuntimeException {
+ /**
+ * Instantiate discontinuous association exception
+ */
+ public DiscontinuousAssociationException() {
+ super();
+ }
+ /**
+ * Instantiate discontinuous association exception
+ * @param message a message string
+ */
+ public DiscontinuousAssociationException(String message) {
+ super(message);
+ }
+}
diff --git a/src/java/org/apache/fop/complexscripts/util/GlyphContextTester.java b/src/java/org/apache/fop/complexscripts/util/GlyphContextTester.java
new file mode 100644
index 000000000..6bdeb2298
--- /dev/null
+++ b/src/java/org/apache/fop/complexscripts/util/GlyphContextTester.java
@@ -0,0 +1,42 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.complexscripts.util;
+
+// CSOFF: LineLengthCheck
+
+/**
+ * Interface for testing the originating (source) character context of a glyph sequence.
+ * @author Glenn Adams
+ */
+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
+ * @param flags that apply to lookup in scope
+ * @return true if test is satisfied
+ */
+ boolean test ( String script, String language, String feature, GlyphSequence gs, int index, int flags );
+
+}
diff --git a/src/java/org/apache/fop/complexscripts/util/GlyphSequence.java b/src/java/org/apache/fop/complexscripts/util/GlyphSequence.java
new file mode 100644
index 000000000..0e256241d
--- /dev/null
+++ b/src/java/org/apache/fop/complexscripts/util/GlyphSequence.java
@@ -0,0 +1,1075 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.complexscripts.util;
+
+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
+
+/**
+ * A GlyphSequence encapsulates a sequence of character codes, a sequence of glyph codes,
+ * and a sequence of character associations, where, for each glyph in the sequence of glyph
+ * codes, there is a corresponding character association. Character associations server to
+ * relate the glyph codes in a glyph sequence to the specific characters in an original
+ * character code sequence with which the glyph codes are associated.
+ * @author Glenn Adams
+ */
+public class GlyphSequence implements Cloneable {
+
+ /** default character buffer capacity in case new character buffer is created */
+ private static final int DEFAULT_CHARS_CAPACITY = 8;
+
+ /** character buffer */
+ private IntBuffer characters;
+ /** glyph buffer */
+ private IntBuffer glyphs;
+ /** association list */
+ private List associations;
+ /** predications flag */
+ private boolean 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
+ * @param predications true if predications are enabled
+ */
+ public GlyphSequence ( IntBuffer characters, IntBuffer glyphs, List associations, boolean predications ) {
+ if ( characters == null ) {
+ characters = IntBuffer.allocate ( DEFAULT_CHARS_CAPACITY );
+ }
+ if ( glyphs == null ) {
+ glyphs = IntBuffer.allocate ( characters.capacity() );
+ }
+ if ( associations == null ) {
+ associations = makeIdentityAssociations ( characters.limit(), glyphs.limit() );
+ }
+ 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 );
+ }
+
+ /**
+ * Instantiate a glyph sequence using an existing glyph sequence, where the new glyph sequence shares
+ * the character array of the existing sequence (but not the buffer object), and creates new copies
+ * of glyphs buffer and association list.
+ * @param gs an existing glyph sequence
+ */
+ public GlyphSequence ( GlyphSequence gs ) {
+ this ( gs.characters.duplicate(), copyBuffer ( gs.glyphs ), copyAssociations ( gs.associations ), gs.predications );
+ }
+
+ /**
+ * Instantiate a glyph sequence using an existing glyph sequence, where the new glyph sequence shares
+ * the character array of the existing sequence (but not the buffer object), but uses the specified
+ * backtrack, input, and lookahead glyph arrays to populate the glyphs, and uses the specified
+ * of glyphs buffer and association list.
+ * backtrack, input, and lookahead association arrays to populate the associations.
+ * @param gs an existing glyph sequence
+ * @param bga backtrack glyph array
+ * @param iga input glyph array
+ * @param lga lookahead glyph array
+ * @param bal backtrack association list
+ * @param ial input association list
+ * @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 ), gs.predications );
+ }
+
+ /**
+ * Obtain reference to underlying character buffer.
+ * @return character buffer reference
+ */
+ public IntBuffer getCharacters() {
+ return characters;
+ }
+
+ /**
+ * Obtain array of characters. If <code>copy</code> is true, then
+ * a newly instantiated array is returned, otherwise a reference to
+ * the underlying buffer's array is returned. N.B. in case a reference
+ * to the undelying buffer's array is returned, the length
+ * of the array is not necessarily the number of characters in array.
+ * To determine the number of characters, use {@link #getCharacterCount}.
+ * @param copy true if to return a newly instantiated array of characters
+ * @return array of characters
+ */
+ public int[] getCharacterArray ( boolean copy ) {
+ if ( copy ) {
+ return toArray ( characters );
+ } else {
+ return characters.array();
+ }
+ }
+
+ /**
+ * Obtain the number of characters in character array, where
+ * each character constitutes a unicode scalar value.
+ * @return number of characters available in character array
+ */
+ public int getCharacterCount() {
+ return characters.limit();
+ }
+
+ /**
+ * Obtain glyph id at specified index.
+ * @param index to obtain glyph
+ * @return the glyph identifier of glyph at specified index
+ * @throws IndexOutOfBoundsException if index is less than zero
+ * or exceeds last valid position
+ */
+ public int getGlyph ( int index ) throws IndexOutOfBoundsException {
+ return glyphs.get ( index );
+ }
+
+ /**
+ * Set glyph id at specified index.
+ * @param index to set glyph
+ * @param gi glyph index
+ * @throws IndexOutOfBoundsException if index is greater or equal to
+ * the limit of the underlying glyph buffer
+ */
+ public void setGlyph ( int index, int gi ) throws IndexOutOfBoundsException {
+ if ( gi > 65535 ) {
+ gi = 65535;
+ }
+ glyphs.put ( index, gi );
+ }
+
+ /**
+ * Obtain reference to underlying glyph buffer.
+ * @return glyph buffer reference
+ */
+ public IntBuffer getGlyphs() {
+ return glyphs;
+ }
+
+ /**
+ * Obtain count glyphs starting at offset. If <code>count</code> is
+ * negative, then it is treated as if the number of available glyphs
+ * were specified.
+ * @param offset into glyph sequence
+ * @param count of glyphs to obtain starting at offset, or negative,
+ * indicating all avaialble glyphs starting at offset
+ * @return glyph array
+ */
+ public int[] getGlyphs ( int offset, int count ) {
+ int ng = getGlyphCount();
+ if ( offset < 0 ) {
+ offset = 0;
+ } else if ( offset > ng ) {
+ offset = ng;
+ }
+ if ( count < 0 ) {
+ count = ng - offset;
+ }
+ int[] ga = new int [ count ];
+ for ( int i = offset, n = offset + count, k = 0; i < n; i++ ) {
+ if ( k < ga.length ) {
+ ga [ k++ ] = glyphs.get ( i );
+ }
+ }
+ return ga;
+ }
+
+ /**
+ * Obtain array of glyphs. If <code>copy</code> is true, then
+ * a newly instantiated array is returned, otherwise a reference to
+ * the underlying buffer's array is returned. N.B. in case a reference
+ * to the undelying buffer's array is returned, the length
+ * of the array is not necessarily the number of glyphs in array.
+ * To determine the number of glyphs, use {@link #getGlyphCount}.
+ * @param copy true if to return a newly instantiated array of glyphs
+ * @return array of glyphs
+ */
+ public int[] getGlyphArray ( boolean copy ) {
+ if ( copy ) {
+ return toArray ( glyphs );
+ } else {
+ return glyphs.array();
+ }
+ }
+
+ /**
+ * Obtain the number of glyphs in glyphs array, where
+ * each glyph constitutes a font specific glyph index.
+ * @return number of glyphs available in character array
+ */
+ public int getGlyphCount() {
+ return glyphs.limit();
+ }
+
+ /**
+ * Obtain association at specified index.
+ * @param index into associations array
+ * @return glyph to character associations at specified index
+ * @throws IndexOutOfBoundsException if index is less than zero
+ * or exceeds last valid position
+ */
+ public CharAssociation getAssociation ( int index ) throws IndexOutOfBoundsException {
+ return (CharAssociation) associations.get ( index );
+ }
+
+ /**
+ * Obtain reference to underlying associations list.
+ * @return associations list
+ */
+ public List getAssociations() {
+ return associations;
+ }
+
+ /**
+ * Obtain count associations starting at offset.
+ * @param offset into glyph sequence
+ * @param count of associations to obtain starting at offset, or negative,
+ * indicating all avaialble associations starting at offset
+ * @return associations
+ */
+ public CharAssociation[] getAssociations ( int offset, int count ) {
+ int ng = getGlyphCount();
+ if ( offset < 0 ) {
+ offset = 0;
+ } else if ( offset > ng ) {
+ offset = ng;
+ }
+ if ( count < 0 ) {
+ count = ng - offset;
+ }
+ CharAssociation[] aa = new CharAssociation [ count ];
+ for ( int i = offset, n = offset + count, k = 0; i < n; i++ ) {
+ if ( k < aa.length ) {
+ aa [ k++ ] = (CharAssociation) associations.get ( i );
+ }
+ }
+ 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 <KEY,VALUE> 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
+ * @return zero if glyphs are the same, otherwise returns 1 or -1 according to whether this glyph sequence's
+ * glyphs are lexicographically greater or lesser than the glyphs in the specified string buffer
+ */
+ public int compareGlyphs ( IntBuffer gb ) {
+ int ng = getGlyphCount();
+ for ( int i = 0, n = gb.limit(); i < n; i++ ) {
+ if ( i < ng ) {
+ int g1 = glyphs.get ( i );
+ int g2 = gb.get ( i );
+ if ( g1 > g2 ) {
+ return 1;
+ } else if ( g1 < g2 ) {
+ return -1;
+ }
+ } else {
+ return -1; // this gb is a proper prefix of specified gb
+ }
+ }
+ return 0; // same lengths with no difference
+ }
+
+ /** {@inheritDoc} */
+ public Object clone() {
+ try {
+ GlyphSequence gs = (GlyphSequence) super.clone();
+ gs.characters = copyBuffer ( characters );
+ gs.glyphs = copyBuffer ( glyphs );
+ gs.associations = copyAssociations ( associations );
+ return gs;
+ } catch ( CloneNotSupportedException e ) {
+ return null;
+ }
+ }
+
+ /** {@inheritDoc} */
+ public String toString() {
+ StringBuffer sb = new StringBuffer();
+ sb.append ( '{' );
+ sb.append ( "chars = [" );
+ sb.append ( characters );
+ sb.append ( "], glyphs = [" );
+ sb.append ( glyphs );
+ sb.append ( "], associations = [" );
+ sb.append ( associations );
+ sb.append ( "]" );
+ sb.append ( '}' );
+ return sb.toString();
+ }
+
+ /**
+ * Determine if two arrays of glyphs are identical.
+ * @param ga1 first glyph array
+ * @param ga2 second glyph array
+ * @return true if arrays are botth null or both non-null and have identical elements
+ */
+ public static boolean sameGlyphs ( int[] ga1, int[] ga2 ) {
+ if ( ga1 == ga2 ) {
+ return true;
+ } else if ( ( ga1 == null ) || ( ga2 == null ) ) {
+ return false;
+ } else if ( ga1.length != ga2.length ) {
+ return false;
+ } else {
+ for ( int i = 0, n = ga1.length; i < n; i++ ) {
+ if ( ga1[i] != ga2[i] ) {
+ return false;
+ }
+ }
+ return true;
+ }
+ }
+
+ /**
+ * Concatenante glyph arrays.
+ * @param bga backtrack glyph array
+ * @param iga input glyph array
+ * @param lga lookahead glyph array
+ * @return new integer buffer containing concatenated glyphs
+ */
+ public static IntBuffer concatGlyphs ( int[] bga, int[] iga, int[] lga ) {
+ int ng = 0;
+ if ( bga != null ) {
+ ng += bga.length;
+ }
+ if ( iga != null ) {
+ ng += iga.length;
+ }
+ if ( lga != null ) {
+ ng += lga.length;
+ }
+ IntBuffer gb = IntBuffer.allocate ( ng );
+ if ( bga != null ) {
+ gb.put ( bga );
+ }
+ if ( iga != null ) {
+ gb.put ( iga );
+ }
+ if ( lga != null ) {
+ gb.put ( lga );
+ }
+ gb.flip();
+ return gb;
+ }
+
+ /**
+ * Concatenante association arrays.
+ * @param baa backtrack association array
+ * @param iaa input association array
+ * @param laa lookahead association array
+ * @return new list containing concatenated associations
+ */
+ public static List concatAssociations ( CharAssociation[] baa, CharAssociation[] iaa, CharAssociation[] laa ) {
+ int na = 0;
+ if ( baa != null ) {
+ na += baa.length;
+ }
+ if ( iaa != null ) {
+ na += iaa.length;
+ }
+ if ( laa != null ) {
+ na += laa.length;
+ }
+ if ( na > 0 ) {
+ List gl = new ArrayList ( na );
+ if ( baa != null ) {
+ for ( int i = 0; i < baa.length; i++ ) {
+ gl.add ( baa[i] );
+ }
+ }
+ if ( iaa != null ) {
+ for ( int i = 0; i < iaa.length; i++ ) {
+ gl.add ( iaa[i] );
+ }
+ }
+ if ( laa != null ) {
+ for ( int i = 0; i < laa.length; i++ ) {
+ gl.add ( laa[i] );
+ }
+ }
+ return gl;
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * 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();
+ int[] ia = new int[n];
+ ib.get ( ia, 0, n );
+ return ia;
+ } else {
+ return new int[0];
+ }
+ }
+
+ private static List makeIdentityAssociations ( int numChars, int numGlyphs ) {
+ int nc = numChars;
+ int ng = numGlyphs;
+ List av = new ArrayList ( ng );
+ for ( int i = 0, n = ng; i < n; i++ ) {
+ int k = ( i > nc ) ? nc : i;
+ av.add ( new CharAssociation ( i, ( k == nc ) ? 0 : 1 ) );
+ }
+ return av;
+ }
+
+ private static IntBuffer copyBuffer ( IntBuffer ib ) {
+ if ( ib != null ) {
+ int[] ia = new int [ ib.capacity() ];
+ int p = ib.position();
+ int l = ib.limit();
+ System.arraycopy ( ib.array(), 0, ia, 0, ia.length );
+ return IntBuffer.wrap ( ia, p, l - p );
+ } else {
+ return null;
+ }
+ }
+
+ private static List copyAssociations ( List ca ) {
+ if ( ca != null ) {
+ return new ArrayList ( ca );
+ } else {
+ return ca;
+ }
+ }
+
+ /**
+ * A structure class encapsulating an interval of characters
+ * expressed as an offset and count of Unicode scalar values (in
+ * an IntBuffer). A <code>CharAssociation</code> 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
+ * <code>CharAssociation</code> instance.
+ *
+ * A <code>CharAssociation</code> 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<String,Object> predications;
+
+ // class state
+ private static volatile Map<String,PredicationMerger> predicationMergers;
+
+ interface PredicationMerger {
+ Object merge ( String key, Object v1, Object v2 );
+ }
+
+ /**
+ * Instantiate a character association.
+ * @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)
+ */
+ public CharAssociation ( int offset, int count, int[] subIntervals ) {
+ this.offset = offset;
+ this.count = count;
+ this.subIntervals = ( ( subIntervals != null ) && ( subIntervals.length > 2 ) ) ? subIntervals : null;
+ }
+
+ /**
+ * Instantiate a non-disjoint 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)
+ */
+ public CharAssociation ( int offset, int count ) {
+ this ( offset, count, null );
+ }
+
+ /**
+ * Instantiate a non-disjoint character association.
+ * @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)
+ */
+ public CharAssociation ( int[] subIntervals ) {
+ this ( getSubIntervalsStart ( subIntervals ), getSubIntervalsLength ( subIntervals ), subIntervals );
+ }
+
+ /** @return offset (start of association interval) */
+ public int getOffset() {
+ return offset;
+ }
+
+ /** @return count (number of characer codes in association) */
+ public int getCount() {
+ return count;
+ }
+
+ /** @return start of association interval */
+ public int getStart() {
+ return getOffset();
+ }
+
+ /** @return end of association interval */
+ public int getEnd() {
+ return getOffset() + getCount();
+ }
+
+ /** @return true if association is disjoint */
+ public boolean isDisjoint() {
+ return subIntervals != null;
+ }
+
+ /** @return subintervals of disjoint association */
+ public int[] getSubIntervals() {
+ return subIntervals;
+ }
+
+ /** @return count of subintervals of disjoint association */
+ public int getSubIntervalCount() {
+ 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 <KEY,VALUE>.
+ * @param key predication key
+ * @param value predication value
+ */
+ public void setPredication ( String key, Object value ) {
+ if ( predications == null ) {
+ predications = new HashMap<String,Object>();
+ }
+ 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 <KEY,VALUE>.
+ * @param key predication key
+ * @param value predication value
+ */
+ public void mergePredication ( String key, Object value ) {
+ if ( predications == null ) {
+ predications = new HashMap<String,Object>();
+ }
+ 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 <code>PredicationMerger</code>
+ * 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<String,Object> e : ca.predications.entrySet() ) {
+ mergePredication ( e.getKey(), e.getValue() );
+ }
+ }
+ }
+
+ /** {@inheritDoc} */
+ public Object clone() {
+ try {
+ CharAssociation ca = (CharAssociation) super.clone();
+ if ( predications != null ) {
+ ca.predications = new HashMap<String,Object> ( 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<String,PredicationMerger>();
+ }
+ 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 <code>repeat</code> new associations.
+ * @param a association to replicate
+ * @param repeat count
+ * @return array of replicated associations
+ */
+ public static CharAssociation[] replicate ( CharAssociation a, int repeat ) {
+ CharAssociation[] aa = new CharAssociation [ repeat ];
+ for ( int i = 0, n = aa.length; i < n; i++ ) {
+ aa [ i ] = (CharAssociation) a.clone();
+ }
+ return aa;
+ }
+
+ /**
+ * Join (merge) multiple associations into a single, potentially disjoint
+ * association.
+ * @param aa array of associations to join
+ * @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 ) ) {
+ ca = new CharAssociation ( 0, 0 );
+ } else if ( ia.length == 2 ) {
+ int s = ia[0];
+ int e = ia[1];
+ ca = new CharAssociation ( s, e - s );
+ } else {
+ 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 ) {
+ int us = Integer.MAX_VALUE;
+ int ue = Integer.MIN_VALUE;
+ if ( ia != null ) {
+ for ( int i = 0, n = ia.length; i < n; i += 2 ) {
+ int s = ia [ i + 0 ];
+ int e = ia [ i + 1 ];
+ if ( s < us ) {
+ us = s;
+ }
+ if ( e > ue ) {
+ ue = e;
+ }
+ }
+ if ( ue < 0 ) {
+ ue = 0;
+ }
+ if ( us > ue ) {
+ us = ue;
+ }
+ }
+ return us;
+ }
+
+ private static int getSubIntervalsLength ( int[] ia ) {
+ int us = Integer.MAX_VALUE;
+ int ue = Integer.MIN_VALUE;
+ if ( ia != null ) {
+ for ( int i = 0, n = ia.length; i < n; i += 2 ) {
+ int s = ia [ i + 0 ];
+ int e = ia [ i + 1 ];
+ if ( s < us ) {
+ us = s;
+ }
+ if ( e > ue ) {
+ ue = e;
+ }
+ }
+ if ( ue < 0 ) {
+ ue = 0;
+ }
+ if ( us > ue ) {
+ us = ue;
+ }
+ }
+ return ue - us;
+ }
+
+ /**
+ * Extract sorted sub-intervals.
+ */
+ private static int[] extractIntervals ( CharAssociation[] aa ) {
+ int ni = 0;
+ for ( int i = 0, n = aa.length; i < n; i++ ) {
+ CharAssociation a = aa [ i ];
+ if ( a.isDisjoint() ) {
+ ni += a.getSubIntervalCount();
+ } else {
+ ni += 1;
+ }
+ }
+ int[] sa = new int [ ni ];
+ int[] ea = new int [ ni ];
+ for ( int i = 0, k = 0; i < aa.length; i++ ) {
+ CharAssociation a = aa [ i ];
+ if ( a.isDisjoint() ) {
+ int[] da = a.getSubIntervals();
+ for ( int j = 0; j < da.length; j += 2 ) {
+ sa [ k ] = da [ j + 0 ];
+ ea [ k ] = da [ j + 1 ];
+ k++;
+ }
+ } else {
+ sa [ k ] = a.getStart();
+ ea [ k ] = a.getEnd();
+ k++;
+ }
+ }
+ return sortIntervals ( sa, ea );
+ }
+
+ private static final int[] sortIncrements16 // CSOK: ConstantNameCheck
+ = { 1391376, 463792, 198768, 86961, 33936, 13776, 4592, 1968, 861, 336, 112, 48, 21, 7, 3, 1 };
+
+ private static final int[] sortIncrements03 // CSOK: ConstantNameCheck
+ = { 7, 3, 1 };
+
+ /**
+ * Sort sub-intervals using modified Shell Sort.
+ */
+ private static int[] sortIntervals ( int[] sa, int[] ea ) {
+ assert sa != null;
+ assert ea != null;
+ assert sa.length == ea.length;
+ int ni = sa.length;
+ int[] incr = ( ni < 21 ) ? sortIncrements03 : sortIncrements16;
+ for ( int k = 0; k < incr.length; k++ ) {
+ for ( int h = incr [ k ], i = h, n = ni, j; i < n; i++ ) {
+ int s1 = sa [ i ];
+ int e1 = ea [ i ];
+ for ( j = i; j >= h; j -= h) {
+ int s2 = sa [ j - h ];
+ int e2 = ea [ j - h ];
+ if ( s2 > s1 ) {
+ sa [ j ] = s2;
+ ea [ j ] = e2;
+ } else if ( ( s2 == s1 ) && ( e2 > e1 ) ) {
+ sa [ j ] = s2;
+ ea [ j ] = e2;
+ } else {
+ break;
+ }
+ }
+ sa [ j ] = s1;
+ ea [ j ] = e1;
+ }
+ }
+ int[] ia = new int [ ni * 2 ];
+ for ( int i = 0; i < ni; i++ ) {
+ ia [ ( i * 2 ) + 0 ] = sa [ i ];
+ ia [ ( i * 2 ) + 1 ] = ea [ i ];
+ }
+ return ia;
+ }
+
+ /**
+ * Merge overlapping and abutting sub-intervals.
+ */
+ private static int[] mergeIntervals ( int[] ia ) {
+ int ni = ia.length;
+ int i, n, nm, is, ie;
+ // count merged sub-intervals
+ for ( i = 0, n = ni, nm = 0, is = ie = -1; i < n; i += 2 ) {
+ int s = ia [ i + 0 ];
+ int e = ia [ i + 1 ];
+ if ( ( ie < 0 ) || ( s > ie ) ) {
+ is = s;
+ ie = e;
+ nm++;
+ } else if ( s >= is ) {
+ if ( e > ie ) {
+ ie = e;
+ }
+ }
+ }
+ int[] mi = new int [ nm * 2 ];
+ // populate merged sub-intervals
+ for ( i = 0, n = ni, nm = 0, is = ie = -1; i < n; i += 2 ) {
+ int s = ia [ i + 0 ];
+ int e = ia [ i + 1 ];
+ int k = nm * 2;
+ if ( ( ie < 0 ) || ( s > ie ) ) {
+ is = s;
+ ie = e;
+ mi [ k + 0 ] = is;
+ mi [ k + 1 ] = ie;
+ nm++;
+ } else if ( s >= is ) {
+ if ( e > ie ) {
+ ie = e;
+ }
+ mi [ k - 1 ] = ie;
+ }
+ }
+ return mi;
+ }
+
+ }
+
+}
diff --git a/src/java/org/apache/fop/complexscripts/util/GlyphTester.java b/src/java/org/apache/fop/complexscripts/util/GlyphTester.java
new file mode 100644
index 000000000..48d0444a0
--- /dev/null
+++ b/src/java/org/apache/fop/complexscripts/util/GlyphTester.java
@@ -0,0 +1,36 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.complexscripts.util;
+
+/**
+ * Interface for testing glyph properties according to glyph identifier.
+ * @author Glenn Adams
+ */
+public interface GlyphTester {
+
+ /**
+ * Perform a test on a glyph identifier.
+ * @param gi glyph identififer
+ * @param flags that apply to lookup in scope
+ * @return true if test is satisfied
+ */
+ boolean test ( int gi, int flags );
+
+}
diff --git a/src/java/org/apache/fop/complexscripts/util/NumberConverter.java b/src/java/org/apache/fop/complexscripts/util/NumberConverter.java
new file mode 100644
index 000000000..6d9831249
--- /dev/null
+++ b/src/java/org/apache/fop/complexscripts/util/NumberConverter.java
@@ -0,0 +1,1616 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.complexscripts.util;
+
+import java.util.ArrayList;
+import java.util.List;
+
+// CSOFF: LineLengthCheck
+// CSOFF: InnerAssignmentCheck
+// CSOFF: NoWhitespaceAfterCheck
+// CSOFF: AvoidNestedBlocksCheck
+
+/**
+ * Implementation of Number to String Conversion algorithm specified by
+ * XSL Transformations (XSLT) Version 2.0, W3C Recommendation, 23 January 2007.
+ *
+ * This algorithm differs from that specified in XSLT 1.0 in the following
+ * ways:
+ * <ul>
+ * <li>input numbers are greater than or equal to zero rather than greater than zero;</li>
+ * <li>introduces format tokens { w, W, Ww };</li>
+ * <li>introduces ordinal parameter to generate ordinal numbers;</li>
+ * </ul>
+ *
+ * Implementation Defaults and Limitations
+ * <ul>
+ * <li>If language parameter is unspecified (null or empty string), then the value
+ * of DEFAULT_LANGUAGE is used, which is defined below as "eng" (English).</li>
+ * <li>Only English, French, and Spanish word numerals are supported, and only if less than one trillion (1,000,000,000,000).</li>
+ * <li>Ordinal word numerals are supported for French and Spanish only when less than or equal to ten (10).</li>
+ * </ul>
+ *
+ * Implementation Notes
+ * <ul>
+ * <li>In order to handle format tokens outside the Unicode BMP, all processing is
+ * done in Unicode Scalar Values represented with Integer and Integer[]
+ * types. Without affecting behavior, this may be subsequently optimized to
+ * use int and int[] types.</li>
+ * <li>In order to communicate various sub-parameters, including ordinalization, a <em>features</em>
+ * is employed, which consists of comma separated name and optional value tokens, where name and value
+ * are separated by an equals '=' sign.</li>
+ * <li>Ordinal numbers are selected by specifying a word based format token in combination with a 'ordinal' feature with no value, in which case
+ * the features 'male' and 'female' may be used to specify gender for gender sensitive languages. For example, the feature string "ordinal,female"
+ * selects female ordinals.</li>
+ * </ul>
+ *
+ * @author Glenn Adams
+ */
+public class NumberConverter {
+
+ /** alphabetical */
+ public static final int LETTER_VALUE_ALPHABETIC = 1;
+ /** traditional */
+ public static final int LETTER_VALUE_TRADITIONAL = 2;
+
+ /** no token type */
+ private static final int TOKEN_NONE = 0;
+ /** alhphanumeric token type */
+ private static final int TOKEN_ALPHANUMERIC = 1;
+ /** nonalphanumeric token type */
+ private static final int TOKEN_NONALPHANUMERIC = 2;
+ /** default token */
+ private static final Integer[] DEFAULT_TOKEN = new Integer[] { (int) '1' };
+ /** default separator */
+ private static final Integer[] DEFAULT_SEPARATOR = new Integer[] { (int) '.' };
+ /** default language */
+ private static final String DEFAULT_LANGUAGE = "eng";
+
+ /** prefix token */
+ private Integer[] prefix;
+ /** suffix token */
+ private Integer[] suffix;
+ /** sequence of tokens, as parsed from format */
+ private Integer[][] tokens;
+ /** sequence of separators, as parsed from format */
+ private Integer[][] separators;
+ /** grouping separator */
+ private int groupingSeparator;
+ /** grouping size */
+ private int groupingSize;
+ /** letter value */
+ private int letterValue;
+ /** letter value system */
+ private String features;
+ /** language */
+ private String language;
+ /** country */
+ private String country;
+
+ /**
+ * Construct parameterized number converter.
+ * @param format format for the page number (may be null or empty, which is treated as null)
+ * @param groupingSeparator grouping separator (if zero, then no grouping separator applies)
+ * @param groupingSize grouping size (if zero or negative, then no grouping size applies)
+ * @param letterValue letter value (must be one of the above letter value enumeration values)
+ * @param features features (feature sub-parameters)
+ * @param language (may be null or empty, which is treated as null)
+ * @param country (may be null or empty, which is treated as null)
+ * @throws IllegalArgumentException if format is not a valid UTF-16 string (e.g., has unpaired surrogate)
+ */
+ public NumberConverter ( String format, int groupingSeparator, int groupingSize, int letterValue, String features, String language, String country )
+ throws IllegalArgumentException {
+ this.groupingSeparator = groupingSeparator;
+ this.groupingSize = groupingSize;
+ this.letterValue = letterValue;
+ this.features = features;
+ this.language = ( language != null ) ? language.toLowerCase() : null;
+ this.country = ( country != null ) ? country.toLowerCase() : null;
+ parseFormatTokens ( format );
+ }
+
+ /**
+ * Convert a number to string according to conversion parameters.
+ * @param number number to conver
+ * @return string representing converted number
+ */
+ public String convert ( long number ) {
+ List<Long> numbers = new ArrayList<Long>();
+ numbers.add ( number );
+ return convert ( numbers );
+ }
+
+ /**
+ * Convert list of numbers to string according to conversion parameters.
+ * @param numbers list of numbers to convert
+ * @return string representing converted list of numbers
+ */
+ public String convert ( List<Long> numbers ) {
+ List<Integer> scalars = new ArrayList<Integer>();
+ if ( prefix != null ) {
+ appendScalars ( scalars, prefix );
+ }
+ convertNumbers ( scalars, numbers );
+ if ( suffix != null ) {
+ appendScalars ( scalars, suffix );
+ }
+ return scalarsToString ( scalars );
+ }
+
+ private void parseFormatTokens ( String format ) throws IllegalArgumentException {
+ List<Integer[]> tokens = new ArrayList<Integer[]>();
+ List<Integer[]> separators = new ArrayList<Integer[]>();
+ if ( ( format == null ) || ( format.length() == 0 ) ) {
+ format = "1";
+ }
+ int tokenType = TOKEN_NONE;
+ List<Integer> token = new ArrayList<Integer>();
+ Integer[] ca = UTF32.toUTF32 ( format, 0, true );
+ for ( int i = 0, n = ca.length; i < n; i++ ) {
+ int c = ca[i];
+ int tokenTypeNew = isAlphaNumeric ( c ) ? TOKEN_ALPHANUMERIC : TOKEN_NONALPHANUMERIC;
+ if ( tokenTypeNew != tokenType ) {
+ if ( token.size() > 0 ) {
+ if ( tokenType == TOKEN_ALPHANUMERIC ) {
+ tokens.add ( token.toArray ( new Integer [ token.size() ] ) );
+ } else {
+ separators.add ( token.toArray ( new Integer [ token.size() ] ) );
+ }
+ token.clear();
+ }
+ tokenType = tokenTypeNew;
+ }
+ token.add ( c );
+ }
+ if ( token.size() > 0 ) {
+ if ( tokenType == TOKEN_ALPHANUMERIC ) {
+ tokens.add ( token.toArray ( new Integer [ token.size() ] ) );
+ } else {
+ separators.add ( token.toArray ( new Integer [ token.size() ] ) );
+ }
+ }
+ if ( ! separators.isEmpty() ) {
+ this.prefix = separators.remove ( 0 );
+ }
+ if ( ! separators.isEmpty() ) {
+ this.suffix = separators.remove ( separators.size() - 1 );
+ }
+ this.separators = separators.toArray ( new Integer [ separators.size() ] [] );
+ this.tokens = tokens.toArray ( new Integer [ tokens.size() ] [] );
+ }
+
+ private static boolean isAlphaNumeric ( int c ) {
+ switch ( Character.getType ( c ) ) {
+ case Character.DECIMAL_DIGIT_NUMBER: // Nd
+ case Character.LETTER_NUMBER: // Nl
+ case Character.OTHER_NUMBER: // No
+ case Character.UPPERCASE_LETTER: // Lu
+ case Character.LOWERCASE_LETTER: // Ll
+ case Character.TITLECASE_LETTER: // Lt
+ case Character.MODIFIER_LETTER: // Lm
+ case Character.OTHER_LETTER: // Lo
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ private void convertNumbers ( List<Integer> scalars, List<Long> numbers ) {
+ Integer[] tknLast = DEFAULT_TOKEN;
+ int tknIndex = 0;
+ int tknCount = tokens.length;
+ int sepIndex = 0;
+ int sepCount = separators.length;
+ int numIndex = 0;
+ for ( Long number : numbers ) {
+ Integer[] sep = null;
+ Integer[] tkn;
+ if ( tknIndex < tknCount ) {
+ if ( numIndex > 0 ) {
+ if ( sepIndex < sepCount ) {
+ sep = separators [ sepIndex++ ];
+ } else {
+ sep = DEFAULT_SEPARATOR;
+ }
+ }
+ tkn = tokens [ tknIndex++ ];
+ } else {
+ tkn = tknLast;
+ }
+ appendScalars ( scalars, convertNumber ( number, sep, tkn ) );
+ tknLast = tkn;
+ numIndex++;
+ }
+ }
+
+ private Integer[] convertNumber ( long number, Integer[] separator, Integer[] token ) {
+ List<Integer> sl = new ArrayList<Integer>();
+ if ( separator != null ) {
+ appendScalars ( sl, separator );
+ }
+ if ( token != null ) {
+ appendScalars ( sl, formatNumber ( number, token ) );
+ }
+ return sl.toArray ( new Integer [ sl.size() ] );
+ }
+
+ private Integer[] formatNumber ( long number, Integer[] token ) {
+ Integer[] fn = null;
+ assert token.length > 0;
+ if ( number < 0 ) {
+ throw new IllegalArgumentException ( "number must be non-negative" );
+ } else if ( token.length == 1 ) {
+ int s = token[0].intValue();
+ switch ( s ) {
+ case (int) '1':
+ {
+ fn = formatNumberAsDecimal ( number, (int) '1', 1 );
+ break;
+ }
+ case (int) 'W':
+ case (int) 'w':
+ {
+ fn = formatNumberAsWord ( number, ( s == (int) 'W' ) ? Character.UPPERCASE_LETTER : Character.LOWERCASE_LETTER );
+ break;
+ }
+ case (int) 'A': // handled as numeric sequence
+ case (int) 'a': // handled as numeric sequence
+ case (int) 'I': // handled as numeric special
+ case (int) 'i': // handled as numeric special
+ default:
+ {
+ if ( isStartOfDecimalSequence ( s ) ) {
+ fn = formatNumberAsDecimal ( number, s, 1 );
+ } else if ( isStartOfAlphabeticSequence ( s ) ) {
+ fn = formatNumberAsSequence ( number, s, getSequenceBase ( s ), null );
+ } else if ( isStartOfNumericSpecial ( s ) ) {
+ fn = formatNumberAsSpecial ( number, s );
+ } else {
+ fn = null;
+ }
+ break;
+ }
+ }
+ } else if ( ( token.length == 2 ) && ( token[0] == (int) 'W' ) && ( token[1] == (int) 'w' ) ) {
+ fn = formatNumberAsWord ( number, Character.TITLECASE_LETTER );
+ } else if ( isPaddedOne ( token ) ) {
+ int s = token [ token.length - 1 ].intValue();
+ fn = formatNumberAsDecimal ( number, s, token.length );
+ } else {
+ throw new IllegalArgumentException ( "invalid format token: \"" + UTF32.fromUTF32 ( token ) + "\"" );
+ }
+ if ( fn == null ) {
+ fn = formatNumber ( number, DEFAULT_TOKEN );
+ }
+ assert fn != null;
+ return fn;
+ }
+
+ /**
+ * Format NUMBER as decimal using characters denoting digits that start at ONE,
+ * adding one or more (zero) padding characters as needed to fill out field WIDTH.
+ * @param number to be formatted
+ * @param one unicode scalar value denoting numeric value 1
+ * @param width non-negative integer denoting field width of number, possible including padding
+ * @return formatted number as array of unicode scalars
+ */
+ private Integer[] formatNumberAsDecimal ( long number, int one, int width ) {
+ assert Character.getNumericValue ( one ) == 1;
+ assert Character.getNumericValue ( one - 1 ) == 0;
+ assert Character.getNumericValue ( one + 8 ) == 9;
+ List<Integer> sl = new ArrayList<Integer>();
+ int zero = one - 1;
+ while ( number > 0 ) {
+ long digit = number % 10;
+ sl.add ( 0, zero + (int) digit );
+ number = number / 10;
+ }
+ while ( width > sl.size() ) {
+ sl.add ( 0, zero );
+ }
+ if ( ( groupingSize != 0 ) && ( groupingSeparator != 0 ) ) {
+ sl = performGrouping ( sl, groupingSize, groupingSeparator );
+ }
+ return sl.toArray ( new Integer [ sl.size() ] );
+ }
+
+ private static List<Integer> performGrouping ( List<Integer> sl, int groupingSize, int groupingSeparator ) {
+ assert groupingSize > 0;
+ assert groupingSeparator != 0;
+ if ( sl.size() > groupingSize ) {
+ List<Integer> gl = new ArrayList<Integer>();
+ for ( int i = 0, n = sl.size(), g = 0; i < n; i++ ) {
+ int k = n - i - 1;
+ if ( g == groupingSize ) {
+ gl.add ( 0, groupingSeparator );
+ g = 1;
+ } else {
+ g++;
+ }
+ gl.add ( 0, sl.get ( k ) );
+ }
+ return gl;
+ } else {
+ return sl;
+ }
+ }
+
+
+ /**
+ * Format NUMBER as using sequence of characters that start at ONE, and
+ * having BASE radix.
+ * @param number to be formatted
+ * @param one unicode scalar value denoting start of sequence (numeric value 1)
+ * @param base number of elements in sequence
+ * @param map if non-null, then maps sequences indices to unicode scalars
+ * @return formatted number as array of unicode scalars
+ */
+ private Integer[] formatNumberAsSequence ( long number, int one, int base, int[] map ) {
+ assert base > 1;
+ assert ( map == null ) || ( map.length >= base );
+ List<Integer> sl = new ArrayList<Integer>();
+ if ( number == 0 ) {
+ return null;
+ } else {
+ long n = number;
+ while ( n > 0 ) {
+ int d = (int) ( ( n - 1 ) % (long) base );
+ int s = ( map != null ) ? map [ d ] : ( one + d );
+ sl.add ( 0, s );
+ n = ( n - 1 ) / base;
+ }
+ return sl.toArray ( new Integer [ sl.size() ] );
+ }
+ }
+
+ /**
+ * Format NUMBER as using special system that starts at ONE.
+ * @param number to be formatted
+ * @param one unicode scalar value denoting start of system (numeric value 1)
+ * @return formatted number as array of unicode scalars
+ */
+ private Integer[] formatNumberAsSpecial ( long number, int one ) {
+ SpecialNumberFormatter f = getSpecialFormatter ( one, letterValue, features, language, country );
+ if ( f != null ) {
+ return f.format ( number, one, letterValue, features, language, country );
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Format NUMBER as word according to TYPE, which must be either
+ * Character.UPPERCASE_LETTER, Character.LOWERCASE_LETTER, or
+ * Character.TITLECASE_LETTER. Makes use of this.language to
+ * determine language of word.
+ * @param number to be formatted
+ * @param caseType unicode character type for case conversion
+ * @return formatted number as array of unicode scalars
+ */
+ private Integer[] formatNumberAsWord ( long number, int caseType ) {
+ SpecialNumberFormatter f = null;
+ if ( isLanguage ( "eng" ) ) {
+ f = new EnglishNumberAsWordFormatter ( caseType );
+ } else if ( isLanguage ( "spa" ) ) {
+ f = new SpanishNumberAsWordFormatter ( caseType );
+ } else if ( isLanguage ( "fra" ) ) {
+ f = new FrenchNumberAsWordFormatter ( caseType );
+ } else {
+ f = new EnglishNumberAsWordFormatter ( caseType );
+ }
+ return f.format ( number, 0, letterValue, features, language, country );
+ }
+
+ private boolean isLanguage ( String iso3Code ) {
+ if ( language == null ) {
+ return false;
+ } else if ( language.equals ( iso3Code ) ) {
+ return true;
+ } else {
+ return isSameLanguage ( iso3Code, language );
+ }
+ }
+
+ private static String[][] equivalentLanguages = {
+ { "eng", "en" },
+ { "fra", "fre", "fr" },
+ { "spa", "es" },
+ };
+
+ private static boolean isSameLanguage ( String i3c, String lc ) {
+ for ( String[] el : equivalentLanguages ) {
+ assert el.length >= 2;
+ if ( el[0].equals ( i3c ) ) {
+ for ( int i = 0, n = el.length; i < n; i++ ) {
+ if ( el[i].equals ( lc ) ) {
+ return true;
+ }
+ }
+ return false;
+ }
+ }
+ return false;
+ }
+
+ private static boolean hasFeature ( String features, String feature ) {
+ if ( features != null ) {
+ assert feature != null;
+ assert feature.length() != 0;
+ String[] fa = features.split(",");
+ for ( String f : fa ) {
+ String[] fp = f.split("=");
+ assert fp.length > 0;
+ String fn = fp[0];
+ String fv = ( fp.length > 1 ) ? fp[1] : "";
+ if ( fn.equals ( feature ) ) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ /* not yet used
+ private static String getFeatureValue ( String features, String feature ) {
+ if ( features != null ) {
+ assert feature != null;
+ assert feature.length() != 0;
+ String[] fa = features.split(",");
+ for ( String f : fa ) {
+ String[] fp = f.split("=");
+ assert fp.length > 0;
+ String fn = fp[0];
+ String fv = ( fp.length > 1 ) ? fp[1] : "";
+ if ( fn.equals ( feature ) ) {
+ return fv;
+ }
+ }
+ }
+ return "";
+ }
+ */
+
+ private static void appendScalars ( List<Integer> scalars, Integer[] sa ) {
+ for ( Integer s : sa ) {
+ scalars.add ( s );
+ }
+ }
+
+ private static String scalarsToString ( List<Integer> scalars ) {
+ Integer[] sa = scalars.toArray ( new Integer [ scalars.size() ] );
+ return UTF32.fromUTF32 ( sa );
+ }
+
+ private static boolean isPaddedOne ( Integer[] token ) {
+ if ( getDecimalValue ( token [ token.length - 1 ] ) != 1 ) {
+ return false;
+ } else {
+ for ( int i = 0, n = token.length - 1; i < n; i++ ) {
+ if ( getDecimalValue ( token [ i ] ) != 0 ) {
+ return false;
+ }
+ }
+ return true;
+ }
+ }
+
+ private static int getDecimalValue ( Integer scalar ) {
+ int s = scalar.intValue();
+ if ( Character.getType ( s ) == Character.DECIMAL_DIGIT_NUMBER ) {
+ return Character.getNumericValue ( s );
+ } else {
+ return -1;
+ }
+ }
+
+ private static boolean isStartOfDecimalSequence ( int s ) {
+ return ( Character.getNumericValue ( s ) == 1 )
+ && ( Character.getNumericValue ( s - 1 ) == 0 )
+ && ( Character.getNumericValue ( s + 8 ) == 9 );
+ }
+
+ private static int[][] supportedAlphabeticSequences = {
+ { 'A', 26 }, // A...Z
+ { 'a', 26 }, // a...z
+ };
+
+ private static boolean isStartOfAlphabeticSequence ( int s ) {
+ for ( int[] ss : supportedAlphabeticSequences ) {
+ assert ss.length >= 2;
+ if ( ss[0] == s ) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private static int getSequenceBase ( int s ) {
+ for ( int[] ss : supportedAlphabeticSequences ) {
+ assert ss.length >= 2;
+ if ( ss[0] == s ) {
+ return ss[1];
+ }
+ }
+ return 0;
+ }
+
+ private static int[][] supportedSpecials = {
+ { 'I' }, // latin - uppercase roman numerals
+ { 'i' }, // latin - lowercase roman numerals
+ { '\u0391' }, // greek - uppercase isopsephry numerals
+ { '\u03B1' }, // greek - lowercase isopsephry numerals
+ { '\u05D0' }, // hebrew - gematria numerals
+ { '\u0623' }, // arabic - abjadi numberals
+ { '\u0627' }, // arabic - either abjadi or hijai alphabetic sequence
+ { '\u0E01' }, // thai - default alphabetic sequence
+ { '\u3042' }, // kana - hiragana (gojuon) - default alphabetic sequence
+ { '\u3044' }, // kana - hiragana (iroha)
+ { '\u30A2' }, // kana - katakana (gojuon) - default alphabetic sequence
+ { '\u30A4' }, // kana - katakana (iroha)
+ };
+
+ private static boolean isStartOfNumericSpecial ( int s ) {
+ for ( int[] ss : supportedSpecials ) {
+ assert ss.length >= 1;
+ if ( ss[0] == s ) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private SpecialNumberFormatter getSpecialFormatter ( int one, int letterValue, String features, String language, String country ) {
+ if ( one == (int) 'I' ) {
+ return new RomanNumeralsFormatter();
+ } else if ( one == (int) 'i' ) {
+ return new RomanNumeralsFormatter();
+ } else if ( one == (int) '\u0391' ) {
+ return new IsopsephryNumeralsFormatter();
+ } else if ( one == (int) '\u03B1' ) {
+ return new IsopsephryNumeralsFormatter();
+ } else if ( one == (int) '\u05D0' ) {
+ return new GematriaNumeralsFormatter();
+ } else if ( one == (int) '\u0623' ) {
+ return new ArabicNumeralsFormatter();
+ } else if ( one == (int) '\u0627' ) {
+ return new ArabicNumeralsFormatter();
+ } else if ( one == (int) '\u0E01' ) {
+ return new ThaiNumeralsFormatter();
+ } else if ( one == (int) '\u3042' ) {
+ return new KanaNumeralsFormatter();
+ } else if ( one == (int) '\u3044' ) {
+ return new KanaNumeralsFormatter();
+ } else if ( one == (int) '\u30A2' ) {
+ return new KanaNumeralsFormatter();
+ } else if ( one == (int) '\u30A4' ) {
+ return new KanaNumeralsFormatter();
+ } else {
+ return null;
+ }
+ }
+
+ private static Integer[] toUpperCase ( Integer[] sa ) {
+ assert sa != null;
+ for ( int i = 0, n = sa.length; i < n; i++ ) {
+ Integer s = sa [ i ];
+ sa [ i ] = Character.toUpperCase ( s );
+ }
+ return sa;
+ }
+
+ private static Integer[] toLowerCase ( Integer[] sa ) {
+ assert sa != null;
+ for ( int i = 0, n = sa.length; i < n; i++ ) {
+ Integer s = sa [ i ];
+ sa [ i ] = Character.toLowerCase ( s );
+ }
+ return sa;
+ }
+
+ /* not yet used
+ private static Integer[] toTitleCase ( Integer[] sa ) {
+ assert sa != null;
+ if ( sa.length > 0 ) {
+ sa [ 0 ] = Character.toTitleCase ( sa [ 0 ] );
+ }
+ return sa;
+ }
+ */
+
+ private static List<String> convertWordCase ( List<String> words, int caseType ) {
+ List<String> wl = new ArrayList<String>();
+ for ( String w : words ) {
+ wl.add ( convertWordCase ( w, caseType ) );
+ }
+ return wl;
+ }
+
+ private static String convertWordCase ( String word, int caseType ) {
+ if ( caseType == Character.UPPERCASE_LETTER ) {
+ return word.toUpperCase();
+ } else if ( caseType == Character.LOWERCASE_LETTER ) {
+ return word.toLowerCase();
+ } else if ( caseType == Character.TITLECASE_LETTER ) {
+ StringBuffer sb = new StringBuffer();
+ for ( int i = 0, n = word.length(); i < n; i++ ) {
+ String s = word.substring ( i, i + 1 );
+ if ( i == 0 ) {
+ sb.append ( s.toUpperCase() );
+ } else {
+ sb.append ( s.toLowerCase() );
+ }
+ }
+ return sb.toString();
+ } else {
+ return word;
+ }
+ }
+
+ private static String joinWords ( List<String> words, String separator ) {
+ StringBuffer sb = new StringBuffer();
+ for ( String w : words ) {
+ if ( sb.length() > 0 ) {
+ sb.append ( separator );
+ }
+ sb.append ( w );
+ }
+ return sb.toString();
+ }
+
+ /**
+ * Special number formatter.
+ */
+ interface SpecialNumberFormatter {
+ /**
+ * Format number with special numeral system.
+ * @param number to be formatted
+ * @param one unicode scalar value denoting numeric value 1
+ * @param letterValue letter value (must be one of the above letter value enumeration values)
+ * @param features features (feature sub-parameters)
+ * @param language denotes applicable language
+ * @param country denotes applicable country
+ * @return formatted number as array of unicode scalars
+ */
+ Integer[] format ( long number, int one, int letterValue, String features, String language, String country );
+ }
+
+ /**
+ * English Word Numerals
+ */
+ private static String[] englishWordOnes = { "zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine" };
+ private static String[] englishWordTeens = { "ten", "eleven", "twelve", "thirteen", "fourteen", "fifteen", "sixteen", "seventeen", "eighteen", "nineteen" };
+ private static String[] englishWordTens = { "", "ten", "twenty", "thirty", "forty", "fifty", "sixty", "seventy", "eighty", "ninety" };
+ private static String[] englishWordOthers = { "hundred", "thousand", "million", "billion" };
+ private static String[] englishWordOnesOrd = { "none", "first", "second", "third", "fourth", "fifth", "sixth", "seventh", "eighth", "ninth" };
+ private static String[] englishWordTeensOrd = { "tenth", "eleventh", "twelfth", "thirteenth", "fourteenth", "fifteenth", "sixteenth", "seventeenth", "eighteenth", "nineteenth" };
+ private static String[] englishWordTensOrd = { "", "tenth", "twentieth", "thirtieth", "fortieth", "fiftieth", "sixtieth", "seventieth", "eightieth", "ninetith" };
+ private static String[] englishWordOthersOrd = { "hundredth", "thousandth", "millionth", "billionth" };
+ private static class EnglishNumberAsWordFormatter implements SpecialNumberFormatter {
+ private int caseType = Character.UPPERCASE_LETTER;
+ EnglishNumberAsWordFormatter ( int caseType ) {
+ this.caseType = caseType;
+ }
+ @Override
+ public Integer[] format ( long number, int one, int letterValue, String features, String language, String country ) {
+ List<String> wl = new ArrayList<String>();
+ if ( number >= 1000000000000L ) {
+ return null;
+ } else {
+ boolean ordinal = hasFeature ( features, "ordinal" );
+ if ( number == 0 ) {
+ wl.add ( englishWordOnes [ 0 ] );
+ } else if ( ordinal && ( number < 10 ) ) {
+ wl.add ( englishWordOnesOrd [ (int) number ] );
+ } else {
+ int ones = (int) ( number % 1000 );
+ int thousands = (int) ( ( number / 1000 ) % 1000 );
+ int millions = (int) ( ( number / 1000000 ) % 1000 );
+ int billions = (int) ( ( number / 1000000000 ) % 1000 );
+ if ( billions > 0 ) {
+ wl = formatOnesInThousand ( wl, billions );
+ if ( ordinal && ( ( number % 1000000000 ) == 0 ) ) {
+ wl.add ( englishWordOthersOrd[3] );
+ } else {
+ wl.add ( englishWordOthers[3] );
+ }
+ }
+ if ( millions > 0 ) {
+ wl = formatOnesInThousand ( wl, millions );
+ if ( ordinal && ( ( number % 1000000 ) == 0 ) ) {
+ wl.add ( englishWordOthersOrd[2] );
+ } else {
+ wl.add ( englishWordOthers[2] );
+ }
+ }
+ if ( thousands > 0 ) {
+ wl = formatOnesInThousand ( wl, thousands );
+ if ( ordinal && ( ( number % 1000 ) == 0 ) ) {
+ wl.add ( englishWordOthersOrd[1] );
+ } else {
+ wl.add ( englishWordOthers[1] );
+ }
+ }
+ if ( ones > 0 ) {
+ wl = formatOnesInThousand ( wl, ones, ordinal );
+ }
+ }
+ wl = convertWordCase ( wl, caseType );
+ return UTF32.toUTF32 ( joinWords ( wl, " " ), 0, true );
+ }
+ }
+ private List<String> formatOnesInThousand ( List<String> wl, int number ) {
+ return formatOnesInThousand ( wl, number, false );
+ }
+ private List<String> formatOnesInThousand ( List<String> wl, int number, boolean ordinal ) {
+ assert number < 1000;
+ int ones = number % 10;
+ int tens = ( number / 10 ) % 10;
+ int hundreds = ( number / 100 ) % 10;
+ if ( hundreds > 0 ) {
+ wl.add ( englishWordOnes [ hundreds ] );
+ if ( ordinal && ( ( number % 100 ) == 0 ) ) {
+ wl.add ( englishWordOthersOrd[0] );
+ } else {
+ wl.add ( englishWordOthers[0] );
+ }
+ }
+ if ( tens > 0 ) {
+ if ( tens == 1 ) {
+ if ( ordinal ) {
+ wl.add ( englishWordTeensOrd [ ones ] );
+ } else {
+ wl.add ( englishWordTeens [ ones ] );
+ }
+ } else {
+ if ( ordinal && ( ones == 0 ) ) {
+ wl.add ( englishWordTensOrd [ tens ] );
+ } else {
+ wl.add ( englishWordTens [ tens ] );
+ }
+ if ( ones > 0 ) {
+ if ( ordinal ) {
+ wl.add ( englishWordOnesOrd [ ones ] );
+ } else {
+ wl.add ( englishWordOnes [ ones ] );
+ }
+ }
+ }
+ } else if ( ones > 0 ) {
+ if ( ordinal ) {
+ wl.add ( englishWordOnesOrd [ ones ] );
+ } else {
+ wl.add ( englishWordOnes [ ones ] );
+ }
+ }
+ return wl;
+ }
+ }
+
+ /**
+ * French Word Numerals
+ */
+ private static String[] frenchWordOnes = { "z\u00e9ro", "un", "deux", "trois", "quatre", "cinq", "six", "sept", "huit", "neuf" };
+ private static String[] frenchWordTeens = { "dix", "onze", "douze", "treize", "quatorze", "quinze", "seize", "dix-sept", "dix-huit", "dix-neuf" };
+ private static String[] frenchWordTens = { "", "dix", "vingt", "trente", "quarante", "cinquante", "soixante", "soixante-dix", "quatre-vingt", "quatre-vingt-dix" };
+ private static String[] frenchWordOthers = { "cent", "cents", "mille", "million", "millions", "milliard", "milliards" };
+ private static String[] frenchWordOnesOrdMale = { "premier", "deuxi\u00e8me", "troisi\u00e8me", "quatri\u00e8me", "cinqui\u00e8me", "sixi\u00e8me", "septi\u00e8me", "huiti\u00e8me", "neuvi\u00e8me", "dixi\u00e8me" };
+ private static String[] frenchWordOnesOrdFemale = { "premi\u00e8re", "deuxi\u00e8me", "troisi\u00e8me", "quatri\u00e8me", "cinqui\u00e8me", "sixi\u00e8me", "septi\u00e8me", "huiti\u00e8me", "neuvi\u00e8me", "dixi\u00e8me" };
+ private static class FrenchNumberAsWordFormatter implements SpecialNumberFormatter {
+ private int caseType = Character.UPPERCASE_LETTER;
+ FrenchNumberAsWordFormatter ( int caseType ) {
+ this.caseType = caseType;
+ }
+ @Override
+ public Integer[] format ( long number, int one, int letterValue, String features, String language, String country ) {
+ List<String> wl = new ArrayList<String>();
+ if ( number >= 1000000000000L ) {
+ return null;
+ } else {
+ boolean ordinal = hasFeature ( features, "ordinal" );
+ if ( number == 0 ) {
+ wl.add ( frenchWordOnes [ 0 ] );
+ } else if ( ordinal && ( number <= 10 ) ) {
+ boolean female = hasFeature ( features, "female" );
+ if ( female ) {
+ wl.add ( frenchWordOnesOrdFemale [ (int) number ] );
+ } else {
+ wl.add ( frenchWordOnesOrdMale [ (int) number ] );
+ }
+ } else {
+ int ones = (int) ( number % 1000 );
+ int thousands = (int) ( ( number / 1000 ) % 1000 );
+ int millions = (int) ( ( number / 1000000 ) % 1000 );
+ int billions = (int) ( ( number / 1000000000 ) % 1000 );
+ if ( billions > 0 ) {
+ wl = formatOnesInThousand ( wl, billions );
+ if ( billions == 1 ) {
+ wl.add ( frenchWordOthers[5] );
+ } else {
+ wl.add ( frenchWordOthers[6] );
+ }
+ }
+ if ( millions > 0 ) {
+ wl = formatOnesInThousand ( wl, millions );
+ if ( millions == 1 ) {
+ wl.add ( frenchWordOthers[3] );
+ } else {
+ wl.add ( frenchWordOthers[4] );
+ }
+ }
+ if ( thousands > 0 ) {
+ if ( thousands > 1 ) {
+ wl = formatOnesInThousand ( wl, thousands );
+ }
+ wl.add ( frenchWordOthers[2] );
+ }
+ if ( ones > 0 ) {
+ wl = formatOnesInThousand ( wl, ones );
+ }
+ }
+ wl = convertWordCase ( wl, caseType );
+ return UTF32.toUTF32 ( joinWords ( wl, " " ), 0, true );
+ }
+ }
+ private List<String> formatOnesInThousand ( List<String> wl, int number ) {
+ assert number < 1000;
+ int ones = number % 10;
+ int tens = ( number / 10 ) % 10;
+ int hundreds = ( number / 100 ) % 10;
+ if ( hundreds > 0 ) {
+ if ( hundreds > 1 ) {
+ wl.add ( frenchWordOnes [ hundreds ] );
+ }
+ if ( ( hundreds > 1 ) && ( tens == 0 ) && ( ones == 0 ) ) {
+ wl.add ( frenchWordOthers[1] );
+ } else {
+ wl.add ( frenchWordOthers[0] );
+ }
+ }
+ if ( tens > 0 ) {
+ if ( tens == 1 ) {
+ wl.add ( frenchWordTeens [ ones ] );
+ } else if ( tens < 7 ) {
+ if ( ones == 1 ) {
+ wl.add ( frenchWordTens [ tens ] );
+ wl.add ( "et" );
+ wl.add ( frenchWordOnes [ ones ] );
+ } else {
+ StringBuffer sb = new StringBuffer();
+ sb.append ( frenchWordTens [ tens ] );
+ if ( ones > 0 ) {
+ sb.append ( '-' );
+ sb.append ( frenchWordOnes [ ones ] );
+ }
+ wl.add ( sb.toString() );
+ }
+ } else if ( tens == 7 ) {
+ if ( ones == 1 ) {
+ wl.add ( frenchWordTens [ 6 ] );
+ wl.add ( "et" );
+ wl.add ( frenchWordTeens [ ones ] );
+ } else {
+ StringBuffer sb = new StringBuffer();
+ sb.append ( frenchWordTens [ 6 ] );
+ sb.append ( '-' );
+ sb.append ( frenchWordTeens [ ones ] );
+ wl.add ( sb.toString() );
+ }
+ } else if ( tens == 8 ) {
+ StringBuffer sb = new StringBuffer();
+ sb.append ( frenchWordTens [ tens ] );
+ if ( ones > 0 ) {
+ sb.append ( '-' );
+ sb.append ( frenchWordOnes [ ones ] );
+ } else {
+ sb.append ( 's' );
+ }
+ wl.add ( sb.toString() );
+ } else if ( tens == 9 ) {
+ StringBuffer sb = new StringBuffer();
+ sb.append ( frenchWordTens [ 8 ] );
+ sb.append ( '-' );
+ sb.append ( frenchWordTeens [ ones ] );
+ wl.add ( sb.toString() );
+ }
+ } else if ( ones > 0 ) {
+ wl.add ( frenchWordOnes [ ones ] );
+ }
+ return wl;
+ }
+ }
+
+ /**
+ * Spanish Word Numerals
+ */
+ private static String[] spanishWordOnes = { "cero", "uno", "dos", "tres", "cuatro", "cinco", "seise", "siete", "ocho", "nueve" };
+ private static String[] spanishWordTeens = { "diez", "once", "doce", "trece", "catorce", "quince", "diecis\u00e9is", "diecisiete", "dieciocho", "diecinueve" };
+ private static String[] spanishWordTweens = { "veinte", "veintiuno", "veintid\u00f3s", "veintitr\u00e9s", "veinticuatro", "veinticinco", "veintis\u00e9is", "veintisiete", "veintiocho", "veintinueve" };
+ private static String[] spanishWordTens = { "", "diez", "veinte", "treinta", "cuarenta", "cincuenta", "sesenta", "setenta", "ochenta", "noventa" };
+ private static String[] spanishWordHundreds = { "", "ciento", "doscientos", "trescientos", "cuatrocientos", "quinientos", "seiscientos", "setecientos", "ochocientos", "novecientos" };
+ private static String[] spanishWordOthers = { "un", "cien", "mil", "mill\u00f3n", "millones" };
+ private static String[] spanishWordOnesOrdMale = { "ninguno", "primero", "segundo", "tercero", "cuarto", "quinto", "sexto", "s\u00e9ptimo", "octavo", "novento", "d\u00e9cimo" };
+ private static String[] spanishWordOnesOrdFemale = { "ninguna", "primera", "segunda", "tercera", "cuarta", "quinta", "sexta", "s\u00e9ptima", "octava", "noventa", "d\u00e9cima" };
+ private static class SpanishNumberAsWordFormatter implements SpecialNumberFormatter {
+ private int caseType = Character.UPPERCASE_LETTER;
+ SpanishNumberAsWordFormatter ( int caseType ) {
+ this.caseType = caseType;
+ }
+ @Override
+ public Integer[] format ( long number, int one, int letterValue, String features, String language, String country ) {
+ List<String> wl = new ArrayList<String>();
+ if ( number >= 1000000000000L ) {
+ return null;
+ } else {
+ boolean ordinal = hasFeature ( features, "ordinal" );
+ if ( number == 0 ) {
+ wl.add ( spanishWordOnes [ 0 ] );
+ } else if ( ordinal && ( number <= 10 ) ) {
+ boolean female = hasFeature ( features, "female" );
+ if ( female ) {
+ wl.add ( spanishWordOnesOrdFemale [ (int) number ] );
+ } else {
+ wl.add ( spanishWordOnesOrdMale [ (int) number ] );
+ }
+ } else {
+ int ones = (int) ( number % 1000 );
+ int thousands = (int) ( ( number / 1000 ) % 1000 );
+ int millions = (int) ( ( number / 1000000 ) % 1000 );
+ int billions = (int) ( ( number / 1000000000 ) % 1000 );
+ if ( billions > 0 ) {
+ if ( billions > 1 ) {
+ wl = formatOnesInThousand ( wl, billions );
+ }
+ wl.add ( spanishWordOthers[2] );
+ wl.add ( spanishWordOthers[4] );
+ }
+ if ( millions > 0 ) {
+ if ( millions == 1 ) {
+ wl.add ( spanishWordOthers[0] );
+ } else {
+ wl = formatOnesInThousand ( wl, millions );
+ }
+ if ( millions > 1 ) {
+ wl.add ( spanishWordOthers[4] );
+ } else {
+ wl.add ( spanishWordOthers[3] );
+ }
+ }
+ if ( thousands > 0 ) {
+ if ( thousands > 1 ) {
+ wl = formatOnesInThousand ( wl, thousands );
+ }
+ wl.add ( spanishWordOthers[2] );
+ }
+ if ( ones > 0 ) {
+ wl = formatOnesInThousand ( wl, ones );
+ }
+ }
+ wl = convertWordCase ( wl, caseType );
+ return UTF32.toUTF32 ( joinWords ( wl, " " ), 0, true );
+ }
+ }
+ private List<String> formatOnesInThousand ( List<String> wl, int number ) {
+ assert number < 1000;
+ int ones = number % 10;
+ int tens = ( number / 10 ) % 10;
+ int hundreds = ( number / 100 ) % 10;
+ if ( hundreds > 0 ) {
+ if ( ( hundreds == 1 ) && ( tens == 0 ) && ( ones == 0 ) ) {
+ wl.add ( spanishWordOthers[1] );
+ } else {
+ wl.add ( spanishWordHundreds [ hundreds ] );
+ }
+ }
+ if ( tens > 0 ) {
+ if ( tens == 1 ) {
+ wl.add ( spanishWordTeens [ ones ] );
+ } else if ( tens == 2 ) {
+ wl.add ( spanishWordTweens [ ones ] );
+ } else {
+ wl.add ( spanishWordTens [ tens ] );
+ if ( ones > 0 ) {
+ wl.add ( "y" );
+ wl.add ( spanishWordOnes [ ones ] );
+ }
+ }
+ } else if ( ones > 0 ) {
+ wl.add ( spanishWordOnes [ ones ] );
+ }
+ return wl;
+ }
+ }
+
+ /**
+ * Roman (Latin) Numerals
+ */
+ private static int[] romanMapping = {
+ 100000,
+ 90000,
+ 50000,
+ 40000,
+ 10000,
+ 9000,
+ 5000,
+ 4000,
+ 1000,
+ 900,
+ 500,
+ 400,
+ 100,
+ 90,
+ 50,
+ 40,
+ 10,
+ 9,
+ 8,
+ 7,
+ 6,
+ 5,
+ 4,
+ 3,
+ 2,
+ 1
+ };
+ private static String[] romanStandardForms = {
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ "m",
+ "cm",
+ "d",
+ "cd",
+ "c",
+ "xc",
+ "l",
+ "xl",
+ "x",
+ "ix",
+ null,
+ null,
+ null,
+ "v",
+ "iv",
+ null,
+ null,
+ "i"
+ };
+ private static String[] romanLargeForms = {
+ "\u2188",
+ "\u2182\u2188",
+ "\u2187",
+ "\u2182\u2187",
+ "\u2182",
+ "\u2180\u2182",
+ "\u2181",
+ "\u2180\u2181",
+ "m",
+ "cm",
+ "d",
+ "cd",
+ "c",
+ "xc",
+ "l",
+ "xl",
+ "x",
+ "ix",
+ null,
+ null,
+ null,
+ "v",
+ "iv",
+ null,
+ null,
+ "i"
+ };
+ private static String[] romanNumberForms = {
+ "\u2188",
+ "\u2182\u2188",
+ "\u2187",
+ "\u2182\u2187",
+ "\u2182",
+ "\u2180\u2182",
+ "\u2181",
+ "\u2180\u2181",
+ "\u216F",
+ "\u216D\u216F",
+ "\u216E",
+ "\u216D\u216E",
+ "\u216D",
+ "\u2169\u216D",
+ "\u216C",
+ "\u2169\u216C",
+ "\u2169",
+ "\u2168",
+ "\u2167",
+ "\u2166",
+ "\u2165",
+ "\u2164",
+ "\u2163",
+ "\u2162",
+ "\u2161",
+ "\u2160"
+ };
+ private static class RomanNumeralsFormatter implements SpecialNumberFormatter {
+ @Override
+ public Integer[] format ( long number, int one, int letterValue, String features, String language, String country ) {
+ List<Integer> sl = new ArrayList<Integer>();
+ if ( number == 0 ) {
+ return null;
+ } else {
+ String[] forms;
+ int maxNumber;
+ if ( hasFeature ( features, "unicode-number-forms" ) ) {
+ forms = romanNumberForms;
+ maxNumber = 199999;
+ } else if ( hasFeature ( features, "large" ) ) {
+ forms = romanLargeForms;
+ maxNumber = 199999;
+ } else {
+ forms = romanStandardForms;
+ maxNumber = 4999;
+ }
+ if ( number > maxNumber ) {
+ return null;
+ } else {
+ while ( number > 0 ) {
+ for ( int i = 0, n = romanMapping.length; i < n; i++ ) {
+ int d = romanMapping [ i ];
+ if ( ( number >= d ) && ( forms [ i ] != null ) ) {
+ appendScalars ( sl, UTF32.toUTF32 ( forms [ i ], 0, true ) );
+ number = number - d;
+ break;
+ }
+ }
+ }
+ if ( one == (int) 'I' ) {
+ return toUpperCase ( sl.toArray ( new Integer [ sl.size() ] ) );
+ } else if ( one == (int) 'i' ) {
+ return toLowerCase ( sl.toArray ( new Integer [ sl.size() ] ) );
+ } else {
+ return null;
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Isopsephry (Greek) Numerals
+ */
+ private static class IsopsephryNumeralsFormatter implements SpecialNumberFormatter {
+ @Override
+ public Integer[] format ( long number, int one, int letterValue, String features, String language, String country ) {
+ return null;
+ }
+ }
+
+ /**
+ * Gematria (Hebrew) Numerals
+ */
+ private static int[] hebrewGematriaAlphabeticMap = {
+ // ones
+ 0x05D0, // ALEF
+ 0x05D1, // BET
+ 0x05D2, // GIMEL
+ 0x05D3, // DALET
+ 0x05D4, // HE
+ 0x05D5, // VAV
+ 0x05D6, // ZAYIN
+ 0x05D7, // HET
+ 0x05D8, // TET
+ // tens
+ 0x05D9, // YOD
+ 0x05DB, // KAF
+ 0x05DC, // LAMED
+ 0x05DE, // MEM
+ 0x05E0, // NUN
+ 0x05E1, // SAMEKH
+ 0x05E2, // AYIN
+ 0x05E4, // PE
+ 0x05E6, // TSADHI
+ // hundreds
+ 0x05E7, // QOF
+ 0x05E8, // RESH
+ 0x05E9, // SHIN
+ 0x05EA, // TAV
+ 0x05DA, // FINAL KAF
+ 0x05DD, // FINAL MEM
+ 0x05DF, // FINAL NUN
+ 0x05E3, // FINAL PE
+ 0x05E5, // FINAL TSADHI
+ };
+ private class GematriaNumeralsFormatter implements SpecialNumberFormatter {
+ @Override
+ public Integer[] format ( long number, int one, int letterValue, String features, String language, String country ) {
+ if ( one == 0x05D0 ) {
+ if ( letterValue == LETTER_VALUE_ALPHABETIC ) {
+ return formatNumberAsSequence ( number, one, hebrewGematriaAlphabeticMap.length, hebrewGematriaAlphabeticMap );
+ } else if ( letterValue == LETTER_VALUE_TRADITIONAL ) {
+ if ( ( number == 0 ) || ( number > 1999 ) ) {
+ return null;
+ } else {
+ return formatAsGematriaNumber ( number, features, language, country );
+ }
+ } else {
+ return null;
+ }
+ } else {
+ return null;
+ }
+ }
+ private Integer[] formatAsGematriaNumber ( long number, String features, String language, String country ) {
+ List<Integer> sl = new ArrayList<Integer>();
+ assert hebrewGematriaAlphabeticMap.length == 27;
+ assert hebrewGematriaAlphabeticMap[0] == 0x05D0; // ALEF
+ assert hebrewGematriaAlphabeticMap[21] == 0x05EA; // TAV
+ assert number != 0;
+ assert number < 2000;
+ int[] map = hebrewGematriaAlphabeticMap;
+ int thousands = (int) ( ( number / 1000 ) % 10 );
+ int hundreds = (int) ( ( number / 100 ) % 10 );
+ int tens = (int) ( ( number / 10 ) % 10 );
+ int ones = (int) ( ( number / 1 ) % 10 );
+ if ( thousands > 0 ) {
+ sl.add ( map [ 0 + ( thousands - 1 ) ] );
+ sl.add ( 0x05F3 );
+ }
+ if ( hundreds > 0 ) {
+ assert hundreds < 10;
+ if ( hundreds < 5 ) {
+ sl.add ( map [ 18 + ( hundreds - 1 ) ] );
+ } else if ( hundreds < 9 ) {
+ sl.add ( map [ 18 + ( 4 - 1 ) ] );
+ sl.add ( 0x05F4 );
+ sl.add ( map [ 18 + ( hundreds - 5 ) ] );
+ } else if ( hundreds == 9 ) {
+ sl.add ( map [ 18 + ( 4 - 1 ) ] );
+ sl.add ( map [ 18 + ( 4 - 1 ) ] );
+ sl.add ( 0x05F4 );
+ sl.add ( map [ 18 + ( hundreds - 9 ) ] );
+ }
+ }
+ if ( number == 15 ) {
+ sl.add ( map [ 9 - 1] );
+ sl.add ( 0x05F4 );
+ sl.add ( map [ 6 - 1] );
+ } else if ( number == 16 ) {
+ sl.add ( map [ 9 - 1 ] );
+ sl.add ( 0x05F4 );
+ sl.add ( map [ 7 - 1 ] );
+ } else {
+ if ( tens > 0 ) {
+ assert tens < 10;
+ sl.add ( map [ 9 + ( tens - 1 ) ] );
+ }
+ if ( ones > 0 ) {
+ assert ones < 10;
+ sl.add ( map [ 0 + ( ones - 1 ) ] );
+ }
+ }
+ return sl.toArray ( new Integer [ sl.size() ] );
+ }
+ }
+
+ /**
+ * Arabic Numerals
+ */
+ private static int[] arabicAbjadiAlphabeticMap = {
+ // ones
+ 0x0623, // ALEF WITH HAMZA ABOVE
+ 0x0628, // BEH
+ 0x062C, // JEEM
+ 0x062F, // DAL
+ 0x0647, // HEH
+ 0x0648, // WAW
+ 0x0632, // ZAIN
+ 0x062D, // HAH
+ 0x0637, // TAH
+ // tens
+ 0x0649, // ALEF MAQSURA
+ 0x0643, // KAF
+ 0x0644, // LAM
+ 0x0645, // MEEM
+ 0x0646, // NOON
+ 0x0633, // SEEN
+ 0x0639, // AIN
+ 0x0641, // FEH
+ 0x0635, // SAD
+ // hundreds
+ 0x0642, // QAF
+ 0x0631, // REH
+ 0x0634, // SHEEN
+ 0x062A, // TEH
+ 0x062B, // THEH
+ 0x062E, // KHAH
+ 0x0630, // THAL
+ 0x0636, // DAD
+ 0x0638, // ZAH
+ // thousands
+ 0x063A, // GHAIN
+ };
+ private static int[] arabicHijaiAlphabeticMap = {
+ 0x0623, // ALEF WITH HAMZA ABOVE
+ 0x0628, // BEH
+ 0x062A, // TEH
+ 0x062B, // THEH
+ 0x062C, // JEEM
+ 0x062D, // HAH
+ 0x062E, // KHAH
+ 0x062F, // DAL
+ 0x0630, // THAL
+ 0x0631, // REH
+ 0x0632, // ZAIN
+ 0x0633, // SEEN
+ 0x0634, // SHEEN
+ 0x0635, // SAD
+ 0x0636, // DAD
+ 0x0637, // TAH
+ 0x0638, // ZAH
+ 0x0639, // AIN
+ 0x063A, // GHAIN
+ 0x0641, // FEH
+ 0x0642, // QAF
+ 0x0643, // KAF
+ 0x0644, // LAM
+ 0x0645, // MEEM
+ 0x0646, // NOON
+ 0x0647, // HEH
+ 0x0648, // WAW
+ 0x0649, // ALEF MAQSURA
+ };
+ private class ArabicNumeralsFormatter implements SpecialNumberFormatter {
+ @Override
+ public Integer[] format ( long number, int one, int letterValue, String features, String language, String country ) {
+ if ( one == 0x0627 ) {
+ int[] map;
+ if ( letterValue == LETTER_VALUE_TRADITIONAL ) {
+ map = arabicAbjadiAlphabeticMap;
+ } else if ( letterValue == LETTER_VALUE_ALPHABETIC ) {
+ map = arabicHijaiAlphabeticMap;
+ } else {
+ map = arabicAbjadiAlphabeticMap;
+ }
+ return formatNumberAsSequence ( number, one, map.length, map );
+ } else if ( one == 0x0623 ) {
+ if ( ( number == 0 ) || ( number > 1999 ) ) {
+ return null;
+ } else {
+ return formatAsAbjadiNumber ( number, features, language, country );
+ }
+ } else {
+ return null;
+ }
+ }
+ private Integer[] formatAsAbjadiNumber ( long number, String features, String language, String country ) {
+ List<Integer> sl = new ArrayList<Integer>();
+ assert arabicAbjadiAlphabeticMap.length == 28;
+ assert arabicAbjadiAlphabeticMap[0] == 0x0623; // ALEF WITH HAMZA ABOVE
+ assert arabicAbjadiAlphabeticMap[27] == 0x063A; // GHAIN
+ assert number != 0;
+ assert number < 2000;
+ int[] map = arabicAbjadiAlphabeticMap;
+ int thousands = (int) ( ( number / 1000 ) % 10 );
+ int hundreds = (int) ( ( number / 100 ) % 10 );
+ int tens = (int) ( ( number / 10 ) % 10 );
+ int ones = (int) ( ( number / 1 ) % 10 );
+ if ( thousands > 0 ) {
+ assert thousands < 2;
+ sl.add ( map [ 27 + ( thousands - 1 ) ] );
+ }
+ if ( hundreds > 0 ) {
+ assert thousands < 10;
+ sl.add ( map [ 18 + ( hundreds - 1 ) ] );
+ }
+ if ( tens > 0 ) {
+ assert tens < 10;
+ sl.add ( map [ 9 + ( tens - 1 ) ] );
+ }
+ if ( ones > 0 ) {
+ assert ones < 10;
+ sl.add ( map [ 0 + ( ones - 1 ) ] );
+ }
+ return sl.toArray ( new Integer [ sl.size() ] );
+ }
+ }
+
+ /**
+ * Kana (Japanese) Numerals
+ */
+ private static int[] hiraganaGojuonAlphabeticMap = {
+ 0x3042, // A
+ 0x3044, // I
+ 0x3046, // U
+ 0x3048, // E
+ 0x304A, // O
+ 0x304B, // KA
+ 0x304D, // KI
+ 0x304F, // KU
+ 0x3051, // KE
+ 0x3053, // KO
+ 0x3055, // SA
+ 0x3057, // SI
+ 0x3059, // SU
+ 0x305B, // SE
+ 0x305D, // SO
+ 0x305F, // TA
+ 0x3061, // TI
+ 0x3064, // TU
+ 0x3066, // TE
+ 0x3068, // TO
+ 0x306A, // NA
+ 0x306B, // NI
+ 0x306C, // NU
+ 0x306D, // NE
+ 0x306E, // NO
+ 0x306F, // HA
+ 0x3072, // HI
+ 0x3075, // HU
+ 0x3078, // HE
+ 0x307B, // HO
+ 0x307E, // MA
+ 0x307F, // MI
+ 0x3080, // MU
+ 0x3081, // ME
+ 0x3082, // MO
+ 0x3084, // YA
+ 0x3086, // YU
+ 0x3088, // YO
+ 0x3089, // RA
+ 0x308A, // RI
+ 0x308B, // RU
+ 0x308C, // RE
+ 0x308D, // RO
+ 0x308F, // WA
+ 0x3090, // WI
+ 0x3091, // WE
+ 0x3092, // WO
+ 0x3093, // N
+ };
+ private static int[] katakanaGojuonAlphabeticMap = {
+ 0x30A2, // A
+ 0x30A4, // I
+ 0x30A6, // U
+ 0x30A8, // E
+ 0x30AA, // O
+ 0x30AB, // KA
+ 0x30AD, // KI
+ 0x30AF, // KU
+ 0x30B1, // KE
+ 0x30B3, // KO
+ 0x30B5, // SA
+ 0x30B7, // SI
+ 0x30B9, // SU
+ 0x30BB, // SE
+ 0x30BD, // SO
+ 0x30BF, // TA
+ 0x30C1, // TI
+ 0x30C4, // TU
+ 0x30C6, // TE
+ 0x30C8, // TO
+ 0x30CA, // NA
+ 0x30CB, // NI
+ 0x30CC, // NU
+ 0x30CD, // NE
+ 0x30CE, // NO
+ 0x30CF, // HA
+ 0x30D2, // HI
+ 0x30D5, // HU
+ 0x30D8, // HE
+ 0x30DB, // HO
+ 0x30DE, // MA
+ 0x30DF, // MI
+ 0x30E0, // MU
+ 0x30E1, // ME
+ 0x30E2, // MO
+ 0x30E4, // YA
+ 0x30E6, // YU
+ 0x30E8, // YO
+ 0x30E9, // RA
+ 0x30EA, // RI
+ 0x30EB, // RU
+ 0x30EC, // RE
+ 0x30ED, // RO
+ 0x30EF, // WA
+ 0x30F0, // WI
+ 0x30F1, // WE
+ 0x30F2, // WO
+ 0x30F3, // N
+ };
+ private class KanaNumeralsFormatter implements SpecialNumberFormatter {
+ @Override
+ public Integer[] format ( long number, int one, int letterValue, String features, String language, String country ) {
+ if ( ( one == 0x3042 ) && ( letterValue == LETTER_VALUE_ALPHABETIC ) ) {
+ return formatNumberAsSequence ( number, one, hiraganaGojuonAlphabeticMap.length, hiraganaGojuonAlphabeticMap );
+ } else if ( ( one == 0x30A2 ) && ( letterValue == LETTER_VALUE_ALPHABETIC ) ) {
+ return formatNumberAsSequence ( number, one, katakanaGojuonAlphabeticMap.length, katakanaGojuonAlphabeticMap );
+ } else {
+ return null;
+ }
+ }
+ }
+
+ /**
+ * Thai Numerals
+ */
+ private static int[] thaiAlphabeticMap = {
+ 0x0E01,
+ 0x0E02,
+ 0x0E03,
+ 0x0E04,
+ 0x0E05,
+ 0x0E06,
+ 0x0E07,
+ 0x0E08,
+ 0x0E09,
+ 0x0E0A,
+ 0x0E0B,
+ 0x0E0C,
+ 0x0E0D,
+ 0x0E0E,
+ 0x0E0F,
+ 0x0E10,
+ 0x0E11,
+ 0x0E12,
+ 0x0E13,
+ 0x0E14,
+ 0x0E15,
+ 0x0E16,
+ 0x0E17,
+ 0x0E18,
+ 0x0E19,
+ 0x0E1A,
+ 0x0E1B,
+ 0x0E1C,
+ 0x0E1D,
+ 0x0E1E,
+ 0x0E1F,
+ 0x0E20,
+ 0x0E21,
+ 0x0E22,
+ 0x0E23,
+ // 0x0E24, // RU - not used in modern sequence
+ 0x0E25,
+ // 0x0E26, // LU - not used in modern sequence
+ 0x0E27,
+ 0x0E28,
+ 0x0E29,
+ 0x0E2A,
+ 0x0E2B,
+ 0x0E2C,
+ 0x0E2D,
+ 0x0E2E,
+ };
+ private class ThaiNumeralsFormatter implements SpecialNumberFormatter {
+ @Override
+ public Integer[] format ( long number, int one, int letterValue, String features, String language, String country ) {
+ if ( ( one == 0x0E01 ) && ( letterValue == LETTER_VALUE_ALPHABETIC ) ) {
+ return formatNumberAsSequence ( number, one, thaiAlphabeticMap.length, thaiAlphabeticMap );
+ } else {
+ return null;
+ }
+ }
+ }
+
+}
diff --git a/src/java/org/apache/fop/complexscripts/util/ScriptContextTester.java b/src/java/org/apache/fop/complexscripts/util/ScriptContextTester.java
new file mode 100644
index 000000000..3f68b00e2
--- /dev/null
+++ b/src/java/org/apache/fop/complexscripts/util/ScriptContextTester.java
@@ -0,0 +1,35 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.complexscripts.util;
+
+/**
+ * Interface for providing script specific context testers.
+ * @author Glenn Adams
+ */
+public interface ScriptContextTester {
+
+ /**
+ * Obtain a glyph context tester for the specified feature.
+ * @param feature a feature identifier
+ * @return a glyph context tester or null if none available for the specified feature
+ */
+ GlyphContextTester getTester ( String feature );
+
+}
diff --git a/src/java/org/apache/fop/complexscripts/util/UTF32.java b/src/java/org/apache/fop/complexscripts/util/UTF32.java
new file mode 100644
index 000000000..9df2020f0
--- /dev/null
+++ b/src/java/org/apache/fop/complexscripts/util/UTF32.java
@@ -0,0 +1,128 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.complexscripts.util;
+
+import org.apache.fop.util.CharUtilities;
+
+// CSOFF: InnerAssignmentCheck
+
+/**
+ * UTF32 related utilities.
+ * @author Glenn Adams
+ */
+public final class UTF32 {
+
+ private UTF32() {
+ }
+
+ /**
+ * Convert Java string (UTF-16) to a Unicode scalar array (UTF-32).
+ * Note that if there are any non-BMP encoded characters present in the
+ * input, then the number of entries in the output array will be less
+ * than the number of elements in the input string. Any
+ * @param s input string
+ * @param substitution value to substitute for ill-formed surrogate
+ * @param errorOnSubstitution throw runtime exception (IllegalArgumentException) in
+ * case this argument is true and a substitution would be attempted
+ * @return output scalar array
+ * @throws IllegalArgumentException if substitution required and errorOnSubstitution
+ * is not false
+ */
+ public static Integer[] toUTF32 ( String s, int substitution, boolean errorOnSubstitution )
+ throws IllegalArgumentException {
+ int n;
+ if ( ( n = s.length() ) == 0 ) {
+ return new Integer[0];
+ } else {
+ Integer[] sa = new Integer [ n ];
+ int k = 0;
+ for ( int i = 0; i < n; i++ ) {
+ int c = (int) s.charAt(i);
+ if ( ( c >= 0xD800 ) && ( c < 0xE000 ) ) {
+ int s1 = c;
+ int s2 = ( ( i + 1 ) < n ) ? (int) s.charAt ( i + 1 ) : 0;
+ if ( s1 < 0xDC00 ) {
+ if ( ( s2 >= 0xDC00 ) && ( s2 < 0xE000 ) ) {
+ c = ( ( s1 - 0xD800 ) << 10 ) + ( s2 - 0xDC00 ) + 65536;
+ i++;
+ } else {
+ if ( errorOnSubstitution ) {
+ throw new IllegalArgumentException
+ ( "isolated high (leading) surrogate" );
+ } else {
+ c = substitution;
+ }
+ }
+ } else {
+ if ( errorOnSubstitution ) {
+ throw new IllegalArgumentException
+ ( "isolated low (trailing) surrogate" );
+ } else {
+ c = substitution;
+ }
+ }
+ }
+ sa[k++] = c;
+ }
+ if ( k == n ) {
+ return sa;
+ } else {
+ Integer[] na = new Integer [ k ];
+ System.arraycopy ( sa, 0, na, 0, k );
+ return na;
+ }
+ }
+ }
+
+ /**
+ * Convert a Unicode scalar array (UTF-32) a Java string (UTF-16).
+ * @param sa input scalar array
+ * @return output (UTF-16) string
+ * @throws IllegalArgumentException if an input scalar value is illegal,
+ * e.g., a surrogate or out of range
+ */
+ public static String fromUTF32 ( Integer[] sa ) throws IllegalArgumentException {
+ StringBuffer sb = new StringBuffer();
+ for ( int s : sa ) {
+ if ( s < 65535 ) {
+ if ( ( s < 0xD800 ) || ( s > 0xDFFF ) ) {
+ sb.append ( (char) s );
+ } else {
+ String ncr = CharUtilities.charToNCRef(s);
+ throw new IllegalArgumentException
+ ( "illegal scalar value 0x" + ncr.substring(2, ncr.length() - 1)
+ + "; cannot be UTF-16 surrogate" );
+ }
+ } else if ( s < 1114112 ) {
+ int s1 = ( ( ( s - 65536 ) >> 10 ) & 0x3FF ) + 0xD800;
+ int s2 = ( ( ( s - 65536 ) >> 0 ) & 0x3FF ) + 0xDC00;
+ sb.append ( (char) s1 );
+ sb.append ( (char) s2 );
+ } else {
+ String ncr = CharUtilities.charToNCRef(s);
+ throw new IllegalArgumentException
+ ( "illegal scalar value 0x" + ncr.substring(2, ncr.length() - 1)
+ + "; out of range for UTF-16" );
+ }
+ }
+ return sb.toString();
+ }
+
+}
diff --git a/src/java/org/apache/fop/fo/Constants.java b/src/java/org/apache/fop/fo/Constants.java
index a2b982269..2d10dcdd9 100644
--- a/src/java/org/apache/fop/fo/Constants.java
+++ b/src/java/org/apache/fop/fo/Constants.java
@@ -26,7 +26,7 @@ package org.apache.fop.fo;
* <li>Input and output formats</li>
* <li>Formatting objects (<em>FO_XXX</em>)</li>
* <li>Formatting properties (<em>PR_XXX</em>)</li>
- * <li>Enumerated values used in formatting properties (<em>EN_XXX</em>)</li>
+ * <li>Enumerated values used in formatting properties and traits (<em>EN_XXX</em>)</li>
* </ul>
*/
public interface Constants {
@@ -778,9 +778,15 @@ public interface Constants {
int PR_X_ALT_TEXT = 274;
/** Property constant - FOP proprietary prototype (in XSL-FO 2.0 Requirements) */
int PR_X_XML_BASE = 275;
+ /**
+ * Property constant - FOP proprietary extension (see NumberConverter) used
+ * to perform additional control over number conversion when generating page
+ * numbers.
+ */
+ int PR_X_NUMBER_CONVERSION_FEATURES = 276;
/** Number of property constants defined */
- int PROPERTY_COUNT = 275;
+ int PROPERTY_COUNT = 276;
// compound property constants
@@ -1208,6 +1214,16 @@ public interface Constants {
int EN_NO_LINK = 199;
/** Enumeration constant -- XSL 1.1 */
int EN_ALTERNATE = 200;
+ /** Enumeration constant -- for *-direction traits */
+ int EN_LR = 201; // left to right
+ /** Enumeration constant -- for *-direction traits */
+ int EN_RL = 202; // right to left
+ /** Enumeration constant -- for *-direction traits */
+ int EN_TB = 203; // top to bottom
+ /** Enumeration constant -- for *-direction traits */
+ int EN_BT = 204; // bottom to top
+ /** Enumeration constant */
+ int EN_TB_LR = 205; // for top-to-bottom, left-to-right writing mode
/** Number of enumeration constants defined */
- int ENUM_COUNT = 200;
+ int ENUM_COUNT = 205;
}
diff --git a/src/java/org/apache/fop/fo/FONode.java b/src/java/org/apache/fop/fo/FONode.java
index b63c49ae2..99d133f16 100644
--- a/src/java/org/apache/fop/fo/FONode.java
+++ b/src/java/org/apache/fop/fo/FONode.java
@@ -20,8 +20,10 @@
package org.apache.fop.fo;
// Java
+import java.util.Iterator;
import java.util.ListIterator;
import java.util.Map;
+import java.util.Stack;
import org.xml.sax.Attributes;
import org.xml.sax.Locator;
@@ -35,6 +37,7 @@ import org.apache.xmlgraphics.util.QName;
import org.apache.fop.accessibility.StructureTreeElement;
import org.apache.fop.apps.FOPException;
import org.apache.fop.apps.FOUserAgent;
+import org.apache.fop.complexscripts.bidi.DelimitedTextRange;
import org.apache.fop.fo.extensions.ExtensionAttachment;
import org.apache.fop.fo.extensions.ExtensionElementMapping;
import org.apache.fop.fo.extensions.InternalElementMapping;
@@ -865,6 +868,122 @@ public abstract class FONode implements Cloneable {
}
/**
+ * Determine if node has a delimited text range boundary. N.B. that we report
+ * this to be true by default, while specific subclasses override this method to report false.
+ * @param boundary one of {EN_BEFORE, EN_AFTER, or EN_BOTH} enumeration constants
+ * @return true if indicated boundary (or boundaries) constitute a delimited text range
+ * boundary.
+ */
+ public boolean isDelimitedTextRangeBoundary ( int boundary ) {
+ return true;
+ }
+
+ /**
+ * Collect the sequence of delimited text ranges, where each new
+ * range is pushed onto RANGES.
+ * @param ranges a stack of delimited text ranges
+ * @return the (possibly) updated stack of delimited text ranges
+ */
+ public Stack collectDelimitedTextRanges ( Stack ranges ) {
+ // if boundary before, then push new range
+ if ( isRangeBoundaryBefore() ) {
+ maybeNewRange ( ranges );
+ }
+ // get current range, if one exists
+ DelimitedTextRange currentRange;
+ if ( ranges.size() > 0 ) {
+ currentRange = (DelimitedTextRange) ranges.peek();
+ } else {
+ currentRange = null;
+ }
+ // proceses this node
+ ranges = collectDelimitedTextRanges ( ranges, currentRange );
+ // if boundary after, then push new range
+ if ( isRangeBoundaryAfter() ) {
+ maybeNewRange ( ranges );
+ }
+ return ranges;
+ }
+
+ /**
+ * Collect the sequence of delimited text ranges, where each new
+ * range is pushed onto RANGES, where default implementation collects ranges
+ * of child nodes.
+ * @param ranges a stack of delimited text ranges
+ * @param currentRange the current range or null (if none)
+ * @return the (possibly) updated stack of delimited text ranges
+ */
+ protected Stack collectDelimitedTextRanges ( Stack ranges, DelimitedTextRange currentRange ) {
+ for ( Iterator it = getChildNodes(); ( it != null ) && it.hasNext();) {
+ ranges = ( (FONode) it.next() ).collectDelimitedTextRanges ( ranges );
+ }
+ return ranges;
+ }
+
+ /**
+ * Determine if this node is a new bidi RANGE block item.
+ * @return true if this node is a new bidi RANGE block item
+ */
+ public boolean isBidiRangeBlockItem() {
+ return false;
+ }
+
+ /**
+ * Conditionally add a new delimited text range to RANGES, where new range is
+ * associated with current FONode. A new text range is added unless all of the following are
+ * true:
+ * <ul>
+ * <li>there exists a current range RCUR in RANGES</li>
+ * <li>RCUR is empty</li>
+ * <li>the node of the RCUR is the same node as FN or a descendent node of FN</li>
+ * </ul>
+ * @param ranges stack of delimited text ranges
+ * @return new range (if constructed and pushed onto stack) or current range (if any) or null
+ */
+ private DelimitedTextRange maybeNewRange ( Stack ranges ) {
+ DelimitedTextRange rCur = null; // current range (top of range stack)
+ DelimitedTextRange rNew = null; // new range to be pushed onto range stack
+ if ( ranges.empty() ) {
+ if ( isBidiRangeBlockItem() ) {
+ rNew = new DelimitedTextRange(this);
+ }
+ } else {
+ rCur = (DelimitedTextRange) ranges.peek();
+ if ( rCur != null ) {
+ if ( !rCur.isEmpty() || !isSelfOrDescendent ( rCur.getNode(), this ) ) {
+ rNew = new DelimitedTextRange(this);
+ }
+ }
+ }
+ if ( rNew != null ) {
+ ranges.push ( rNew );
+ } else {
+ rNew = rCur;
+ }
+ return rNew;
+ }
+
+ private boolean isRangeBoundaryBefore() {
+ return isDelimitedTextRangeBoundary ( Constants.EN_BEFORE );
+ }
+
+ private boolean isRangeBoundaryAfter() {
+ return isDelimitedTextRangeBoundary ( Constants.EN_AFTER );
+ }
+
+ /**
+ * Determine if node N2 is the same or a descendent of node N1.
+ */
+ private static boolean isSelfOrDescendent ( FONode n1, FONode n2 ) {
+ for ( FONode n = n2; n != null; n = n.getParent() ) {
+ if ( n == n1 ) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
* Base iterator interface over a FO's children
*/
public interface FONodeIterator extends ListIterator {
diff --git a/src/java/org/apache/fop/fo/FOPropertyMapping.java b/src/java/org/apache/fop/fo/FOPropertyMapping.java
index 172aa5329..b29571b09 100644
--- a/src/java/org/apache/fop/fo/FOPropertyMapping.java
+++ b/src/java/org/apache/fop/fo/FOPropertyMapping.java
@@ -632,7 +632,7 @@ public final class FOPropertyMapping implements Constants {
m.setDefault("black");
corr = new CorrespondingPropertyMaker(m);
corr.setCorresponding(PR_BORDER_TOP_COLOR, PR_BORDER_TOP_COLOR,
- PR_BORDER_RIGHT_COLOR);
+ PR_BORDER_RIGHT_COLOR, PR_BORDER_LEFT_COLOR);
corr.setRelative(true);
addPropertyMaker("border-before-color", m);
@@ -641,7 +641,7 @@ public final class FOPropertyMapping implements Constants {
m.useGeneric(genericBorderStyle);
corr = new CorrespondingPropertyMaker(m);
corr.setCorresponding(PR_BORDER_TOP_STYLE, PR_BORDER_TOP_STYLE,
- PR_BORDER_RIGHT_STYLE);
+ PR_BORDER_RIGHT_STYLE, PR_BORDER_LEFT_STYLE);
corr.setRelative(true);
addPropertyMaker("border-before-style", m);
@@ -651,7 +651,7 @@ public final class FOPropertyMapping implements Constants {
m.getSubpropMaker(CP_CONDITIONALITY).setDefault("discard");
corr = new CorrespondingPropertyMaker(m);
corr.setCorresponding(PR_BORDER_TOP_WIDTH, PR_BORDER_TOP_WIDTH,
- PR_BORDER_RIGHT_WIDTH);
+ PR_BORDER_RIGHT_WIDTH, PR_BORDER_LEFT_WIDTH);
corr.setRelative(true);
addPropertyMaker("border-before-width", m);
@@ -662,7 +662,7 @@ public final class FOPropertyMapping implements Constants {
m.setDefault("black");
corr = new CorrespondingPropertyMaker(m);
corr.setCorresponding(PR_BORDER_BOTTOM_COLOR, PR_BORDER_BOTTOM_COLOR,
- PR_BORDER_LEFT_COLOR);
+ PR_BORDER_LEFT_COLOR, PR_BORDER_RIGHT_COLOR);
corr.setRelative(true);
addPropertyMaker("border-after-color", m);
@@ -671,7 +671,7 @@ public final class FOPropertyMapping implements Constants {
m.useGeneric(genericBorderStyle);
corr = new CorrespondingPropertyMaker(m);
corr.setCorresponding(PR_BORDER_BOTTOM_STYLE, PR_BORDER_BOTTOM_STYLE,
- PR_BORDER_LEFT_STYLE);
+ PR_BORDER_LEFT_STYLE, PR_BORDER_RIGHT_STYLE);
corr.setRelative(true);
addPropertyMaker("border-after-style", m);
@@ -681,7 +681,7 @@ public final class FOPropertyMapping implements Constants {
m.getSubpropMaker(CP_CONDITIONALITY).setDefault("discard");
corr = new CorrespondingPropertyMaker(m);
corr.setCorresponding(PR_BORDER_BOTTOM_WIDTH, PR_BORDER_BOTTOM_WIDTH,
- PR_BORDER_LEFT_WIDTH);
+ PR_BORDER_LEFT_WIDTH, PR_BORDER_LEFT_WIDTH);
corr.setRelative(true);
addPropertyMaker("border-after-width", m);
@@ -692,7 +692,7 @@ public final class FOPropertyMapping implements Constants {
m.setDefault("black");
corr = new CorrespondingPropertyMaker(m);
corr.setCorresponding(PR_BORDER_LEFT_COLOR, PR_BORDER_RIGHT_COLOR,
- PR_BORDER_TOP_COLOR);
+ PR_BORDER_TOP_COLOR, PR_BORDER_TOP_COLOR);
corr.setRelative(true);
addPropertyMaker("border-start-color", m);
@@ -701,7 +701,7 @@ public final class FOPropertyMapping implements Constants {
m.useGeneric(genericBorderStyle);
corr = new CorrespondingPropertyMaker(m);
corr.setCorresponding(PR_BORDER_LEFT_STYLE, PR_BORDER_RIGHT_STYLE,
- PR_BORDER_TOP_STYLE);
+ PR_BORDER_TOP_STYLE, PR_BORDER_TOP_STYLE);
corr.setRelative(true);
addPropertyMaker("border-start-style", m);
@@ -711,7 +711,7 @@ public final class FOPropertyMapping implements Constants {
m.getSubpropMaker(CP_CONDITIONALITY).setDefault("discard");
corr = new CorrespondingPropertyMaker(m);
corr.setCorresponding(PR_BORDER_LEFT_WIDTH, PR_BORDER_RIGHT_WIDTH,
- PR_BORDER_TOP_WIDTH);
+ PR_BORDER_TOP_WIDTH, PR_BORDER_TOP_WIDTH);
corr.setRelative(true);
addPropertyMaker("border-start-width", m);
@@ -722,7 +722,7 @@ public final class FOPropertyMapping implements Constants {
m.setDefault("black");
corr = new CorrespondingPropertyMaker(m);
corr.setCorresponding(PR_BORDER_RIGHT_COLOR, PR_BORDER_LEFT_COLOR,
- PR_BORDER_BOTTOM_COLOR);
+ PR_BORDER_BOTTOM_COLOR, PR_BORDER_BOTTOM_COLOR);
corr.setRelative(true);
addPropertyMaker("border-end-color", m);
@@ -731,7 +731,7 @@ public final class FOPropertyMapping implements Constants {
m.useGeneric(genericBorderStyle);
corr = new CorrespondingPropertyMaker(m);
corr.setCorresponding(PR_BORDER_RIGHT_STYLE, PR_BORDER_LEFT_STYLE,
- PR_BORDER_BOTTOM_STYLE);
+ PR_BORDER_BOTTOM_STYLE, PR_BORDER_BOTTOM_STYLE);
corr.setRelative(true);
addPropertyMaker("border-end-style", m);
@@ -741,7 +741,7 @@ public final class FOPropertyMapping implements Constants {
m.getSubpropMaker(CP_CONDITIONALITY).setDefault("discard");
corr = new CorrespondingPropertyMaker(m);
corr.setCorresponding(PR_BORDER_RIGHT_WIDTH, PR_BORDER_LEFT_WIDTH,
- PR_BORDER_BOTTOM_WIDTH);
+ PR_BORDER_BOTTOM_WIDTH, PR_BORDER_BOTTOM_WIDTH);
corr.setRelative(true);
addPropertyMaker("border-end-width", m);
@@ -755,7 +755,7 @@ public final class FOPropertyMapping implements Constants {
m.addShorthand(generics[PR_BORDER]);
corr = new CorrespondingPropertyMaker(m);
corr.setCorresponding(PR_BORDER_BEFORE_COLOR, PR_BORDER_BEFORE_COLOR,
- PR_BORDER_START_COLOR);
+ PR_BORDER_START_COLOR, PR_BORDER_START_COLOR);
addPropertyMaker("border-top-color", m);
// border-top-style
@@ -766,7 +766,7 @@ public final class FOPropertyMapping implements Constants {
m.addShorthand(generics[PR_BORDER]);
corr = new CorrespondingPropertyMaker(m);
corr.setCorresponding(PR_BORDER_BEFORE_STYLE, PR_BORDER_BEFORE_STYLE,
- PR_BORDER_START_STYLE);
+ PR_BORDER_START_STYLE, PR_BORDER_START_STYLE);
addPropertyMaker("border-top-style", m);
// border-top-width
@@ -778,7 +778,7 @@ public final class FOPropertyMapping implements Constants {
bwm.addShorthand(generics[PR_BORDER]);
corr = new CorrespondingPropertyMaker(bwm);
corr.setCorresponding(PR_BORDER_BEFORE_WIDTH, PR_BORDER_BEFORE_WIDTH,
- PR_BORDER_START_WIDTH);
+ PR_BORDER_START_WIDTH, PR_BORDER_START_WIDTH);
addPropertyMaker("border-top-width", bwm);
// border-bottom-color
@@ -791,7 +791,7 @@ public final class FOPropertyMapping implements Constants {
m.addShorthand(generics[PR_BORDER]);
corr = new CorrespondingPropertyMaker(m);
corr.setCorresponding(PR_BORDER_AFTER_COLOR, PR_BORDER_AFTER_COLOR,
- PR_BORDER_END_COLOR);
+ PR_BORDER_END_COLOR, PR_BORDER_END_COLOR);
addPropertyMaker("border-bottom-color", m);
// border-bottom-style
@@ -802,7 +802,7 @@ public final class FOPropertyMapping implements Constants {
m.addShorthand(generics[PR_BORDER]);
corr = new CorrespondingPropertyMaker(m);
corr.setCorresponding(PR_BORDER_AFTER_STYLE, PR_BORDER_AFTER_STYLE,
- PR_BORDER_END_STYLE);
+ PR_BORDER_END_STYLE, PR_BORDER_END_STYLE);
addPropertyMaker("border-bottom-style", m);
// border-bottom-width
@@ -814,7 +814,7 @@ public final class FOPropertyMapping implements Constants {
bwm.addShorthand(generics[PR_BORDER]);
corr = new CorrespondingPropertyMaker(bwm);
corr.setCorresponding(PR_BORDER_AFTER_WIDTH, PR_BORDER_AFTER_WIDTH,
- PR_BORDER_END_WIDTH);
+ PR_BORDER_END_WIDTH, PR_BORDER_END_WIDTH);
addPropertyMaker("border-bottom-width", bwm);
// border-left-color
@@ -827,7 +827,7 @@ public final class FOPropertyMapping implements Constants {
m.addShorthand(generics[PR_BORDER]);
corr = new CorrespondingPropertyMaker(m);
corr.setCorresponding(PR_BORDER_START_COLOR, PR_BORDER_END_COLOR,
- PR_BORDER_AFTER_COLOR);
+ PR_BORDER_AFTER_COLOR, PR_BORDER_BEFORE_COLOR);
addPropertyMaker("border-left-color", m);
// border-left-style
@@ -838,7 +838,7 @@ public final class FOPropertyMapping implements Constants {
m.addShorthand(generics[PR_BORDER]);
corr = new CorrespondingPropertyMaker(m);
corr.setCorresponding(PR_BORDER_START_STYLE, PR_BORDER_END_STYLE,
- PR_BORDER_AFTER_STYLE);
+ PR_BORDER_AFTER_STYLE, PR_BORDER_BEFORE_STYLE);
addPropertyMaker("border-left-style", m);
// border-left-width
@@ -850,7 +850,7 @@ public final class FOPropertyMapping implements Constants {
bwm.addShorthand(generics[PR_BORDER]);
corr = new CorrespondingPropertyMaker(bwm);
corr.setCorresponding(PR_BORDER_START_WIDTH, PR_BORDER_END_WIDTH,
- PR_BORDER_AFTER_WIDTH);
+ PR_BORDER_AFTER_WIDTH, PR_BORDER_BEFORE_WIDTH);
addPropertyMaker("border-left-width", bwm);
// border-right-color
@@ -863,7 +863,7 @@ public final class FOPropertyMapping implements Constants {
m.addShorthand(generics[PR_BORDER]);
corr = new CorrespondingPropertyMaker(m);
corr.setCorresponding(PR_BORDER_END_COLOR, PR_BORDER_START_COLOR,
- PR_BORDER_BEFORE_COLOR);
+ PR_BORDER_BEFORE_COLOR, PR_BORDER_AFTER_COLOR);
addPropertyMaker("border-right-color", m);
// border-right-style
@@ -874,7 +874,7 @@ public final class FOPropertyMapping implements Constants {
m.addShorthand(generics[PR_BORDER]);
corr = new CorrespondingPropertyMaker(m);
corr.setCorresponding(PR_BORDER_END_STYLE, PR_BORDER_START_STYLE,
- PR_BORDER_BEFORE_STYLE);
+ PR_BORDER_BEFORE_STYLE, PR_BORDER_AFTER_STYLE);
addPropertyMaker("border-right-style", m);
// border-right-width
@@ -886,7 +886,7 @@ public final class FOPropertyMapping implements Constants {
bwm.addShorthand(generics[PR_BORDER]);
corr = new CorrespondingPropertyMaker(bwm);
corr.setCorresponding(PR_BORDER_END_WIDTH, PR_BORDER_START_WIDTH,
- PR_BORDER_BEFORE_WIDTH);
+ PR_BORDER_BEFORE_WIDTH, PR_BORDER_AFTER_WIDTH);
addPropertyMaker("border-right-width", bwm);
// padding-before
@@ -895,7 +895,7 @@ public final class FOPropertyMapping implements Constants {
m.getSubpropMaker(CP_CONDITIONALITY).setDefault("discard");
corr = new CorrespondingPropertyMaker(m);
corr.setCorresponding(PR_PADDING_TOP, PR_PADDING_TOP,
- PR_PADDING_RIGHT);
+ PR_PADDING_RIGHT, PR_PADDING_LEFT);
corr.setRelative(true);
addPropertyMaker("padding-before", m);
@@ -905,7 +905,7 @@ public final class FOPropertyMapping implements Constants {
m.getSubpropMaker(CP_CONDITIONALITY).setDefault("discard");
corr = new CorrespondingPropertyMaker(m);
corr.setCorresponding(PR_PADDING_BOTTOM, PR_PADDING_BOTTOM,
- PR_PADDING_LEFT);
+ PR_PADDING_LEFT, PR_PADDING_RIGHT);
corr.setRelative(true);
addPropertyMaker("padding-after", m);
@@ -915,7 +915,7 @@ public final class FOPropertyMapping implements Constants {
m.getSubpropMaker(CP_CONDITIONALITY).setDefault("discard");
corr = new CorrespondingPropertyMaker(m);
corr.setCorresponding(PR_PADDING_LEFT, PR_PADDING_RIGHT,
- PR_PADDING_TOP);
+ PR_PADDING_TOP, PR_PADDING_TOP);
corr.setRelative(true);
addPropertyMaker("padding-start", m);
@@ -925,7 +925,7 @@ public final class FOPropertyMapping implements Constants {
m.getSubpropMaker(CP_CONDITIONALITY).setDefault("discard");
corr = new CorrespondingPropertyMaker(m);
corr.setCorresponding(PR_PADDING_RIGHT, PR_PADDING_LEFT,
- PR_PADDING_BOTTOM);
+ PR_PADDING_BOTTOM, PR_PADDING_BOTTOM);
corr.setRelative(true);
addPropertyMaker("padding-end", m);
@@ -934,7 +934,7 @@ public final class FOPropertyMapping implements Constants {
m.useGeneric(genericPadding);
corr = new CorrespondingPropertyMaker(m);
corr.setCorresponding(PR_PADDING_BEFORE, PR_PADDING_BEFORE,
- PR_PADDING_START);
+ PR_PADDING_START, PR_PADDING_START);
addPropertyMaker("padding-top", m);
// padding-bottom
@@ -942,7 +942,7 @@ public final class FOPropertyMapping implements Constants {
m.useGeneric(genericPadding);
corr = new CorrespondingPropertyMaker(m);
corr.setCorresponding(PR_PADDING_AFTER, PR_PADDING_AFTER,
- PR_PADDING_END);
+ PR_PADDING_END, PR_PADDING_END);
addPropertyMaker("padding-bottom", m);
// padding-left
@@ -950,7 +950,7 @@ public final class FOPropertyMapping implements Constants {
m.useGeneric(genericPadding);
corr = new CorrespondingPropertyMaker(m);
corr.setCorresponding(PR_PADDING_START, PR_PADDING_END,
- PR_PADDING_AFTER);
+ PR_PADDING_AFTER, PR_PADDING_BEFORE);
addPropertyMaker("padding-left", m);
// padding-right
@@ -958,7 +958,7 @@ public final class FOPropertyMapping implements Constants {
m.useGeneric(genericPadding);
corr = new CorrespondingPropertyMaker(m);
corr.setCorresponding(PR_PADDING_END, PR_PADDING_START,
- PR_PADDING_BEFORE);
+ PR_PADDING_BEFORE, PR_PADDING_AFTER);
addPropertyMaker("padding-right", m);
}
@@ -1151,7 +1151,7 @@ public final class FOPropertyMapping implements Constants {
m = new SpaceProperty.Maker(PR_SPACE_BEFORE);
m.useGeneric(genericSpace);
corr = new SpacePropertyMaker(m);
- corr.setCorresponding(PR_MARGIN_TOP, PR_MARGIN_TOP, PR_MARGIN_RIGHT);
+ corr.setCorresponding(PR_MARGIN_TOP, PR_MARGIN_TOP, PR_MARGIN_RIGHT, PR_MARGIN_LEFT);
corr.setUseParent(false);
corr.setRelative(true);
addPropertyMaker("space-before", m);
@@ -1160,7 +1160,7 @@ public final class FOPropertyMapping implements Constants {
m = new SpaceProperty.Maker(PR_SPACE_AFTER);
m.useGeneric(genericSpace);
corr = new SpacePropertyMaker(m);
- corr.setCorresponding(PR_MARGIN_BOTTOM, PR_MARGIN_BOTTOM, PR_MARGIN_LEFT);
+ corr.setCorresponding(PR_MARGIN_BOTTOM, PR_MARGIN_BOTTOM, PR_MARGIN_LEFT, PR_MARGIN_RIGHT);
corr.setUseParent(false);
corr.setRelative(true);
addPropertyMaker("space-after", m);
@@ -1171,14 +1171,14 @@ public final class FOPropertyMapping implements Constants {
m.setDefault("0pt");
m.setPercentBase(LengthBase.CONTAINING_REFAREA_WIDTH);
IndentPropertyMaker sCorr = new IndentPropertyMaker(m);
- sCorr.setCorresponding(PR_MARGIN_LEFT, PR_MARGIN_RIGHT, PR_MARGIN_TOP);
+ sCorr.setCorresponding(PR_MARGIN_LEFT, PR_MARGIN_RIGHT, PR_MARGIN_TOP, PR_MARGIN_TOP);
sCorr.setUseParent(false);
sCorr.setRelative(true);
sCorr.setPaddingCorresponding(new int[] {
- PR_PADDING_LEFT, PR_PADDING_RIGHT, PR_PADDING_TOP
+ PR_PADDING_LEFT, PR_PADDING_RIGHT, PR_PADDING_TOP, PR_PADDING_TOP
});
sCorr.setBorderWidthCorresponding(new int[] {
- PR_BORDER_LEFT_WIDTH, PR_BORDER_RIGHT_WIDTH, PR_BORDER_TOP_WIDTH
+ PR_BORDER_LEFT_WIDTH, PR_BORDER_RIGHT_WIDTH, PR_BORDER_TOP_WIDTH, PR_BORDER_TOP_WIDTH
});
addPropertyMaker("start-indent", m);
@@ -1188,14 +1188,16 @@ public final class FOPropertyMapping implements Constants {
m.setDefault("0pt");
m.setPercentBase(LengthBase.CONTAINING_REFAREA_WIDTH);
IndentPropertyMaker eCorr = new IndentPropertyMaker(m);
- eCorr.setCorresponding(PR_MARGIN_RIGHT, PR_MARGIN_LEFT, PR_MARGIN_BOTTOM);
+ eCorr.setCorresponding(PR_MARGIN_RIGHT, PR_MARGIN_LEFT,
+ PR_MARGIN_BOTTOM, PR_MARGIN_BOTTOM);
eCorr.setUseParent(false);
eCorr.setRelative(true);
eCorr.setPaddingCorresponding(new int[] {
- PR_PADDING_RIGHT, PR_PADDING_LEFT, PR_PADDING_BOTTOM
+ PR_PADDING_RIGHT, PR_PADDING_LEFT, PR_PADDING_BOTTOM, PR_PADDING_BOTTOM
});
eCorr.setBorderWidthCorresponding(new int[] {
- PR_BORDER_RIGHT_WIDTH, PR_BORDER_LEFT_WIDTH, PR_BORDER_BOTTOM_WIDTH
+ PR_BORDER_RIGHT_WIDTH, PR_BORDER_LEFT_WIDTH,
+ PR_BORDER_BOTTOM_WIDTH, PR_BORDER_BOTTOM_WIDTH
});
addPropertyMaker("end-indent", m);
}
@@ -1352,10 +1354,10 @@ public final class FOPropertyMapping implements Constants {
m.addSubpropMaker(l);
pdim = new DimensionPropertyMaker(m);
- pdim.setCorresponding(PR_HEIGHT, PR_HEIGHT, PR_WIDTH);
+ pdim.setCorresponding(PR_HEIGHT, PR_HEIGHT, PR_WIDTH, PR_WIDTH);
pdim.setExtraCorresponding(new int[][] {
- {PR_MIN_HEIGHT, PR_MIN_HEIGHT, PR_MIN_WIDTH, },
- {PR_MAX_HEIGHT, PR_MAX_HEIGHT, PR_MAX_WIDTH, }
+ {PR_MIN_HEIGHT, PR_MIN_HEIGHT, PR_MIN_WIDTH, PR_MIN_WIDTH},
+ {PR_MAX_HEIGHT, PR_MAX_HEIGHT, PR_MAX_WIDTH, PR_MAX_WIDTH}
});
pdim.setRelative(true);
m.setCorresponding(pdim);
@@ -1419,10 +1421,10 @@ public final class FOPropertyMapping implements Constants {
pdim = new DimensionPropertyMaker(m);
pdim.setRelative(true);
- pdim.setCorresponding(PR_WIDTH, PR_WIDTH, PR_HEIGHT);
+ pdim.setCorresponding(PR_WIDTH, PR_WIDTH, PR_HEIGHT, PR_HEIGHT);
pdim.setExtraCorresponding(new int[][] {
- {PR_MIN_WIDTH, PR_MIN_WIDTH, PR_MIN_HEIGHT, },
- {PR_MAX_WIDTH, PR_MAX_WIDTH, PR_MAX_HEIGHT, }
+ {PR_MIN_WIDTH, PR_MIN_WIDTH, PR_MIN_HEIGHT, PR_MIN_HEIGHT },
+ {PR_MAX_WIDTH, PR_MAX_WIDTH, PR_MAX_HEIGHT, PR_MIN_HEIGHT }
});
m.setCorresponding(pdim);
addPropertyMaker("inline-progression-dimension", m);
@@ -1491,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;
@@ -1576,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");
@@ -1605,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) {
@@ -1620,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;
}
@@ -2171,6 +2222,12 @@ public final class FOPropertyMapping implements Constants {
m.addEnum("auto", getEnumProperty(EN_AUTO, "AUTO"));
m.setDefault("auto");
addPropertyMaker("letter-value", m);
+
+ // fox:alt-text, used for accessibility
+ m = new StringProperty.Maker(PR_X_NUMBER_CONVERSION_FEATURES);
+ m.setInherited(false);
+ m.setDefault("");
+ addPropertyMaker("fox:number-conversion-features", m);
}
private void createPaginationAndLayoutProperties() {
@@ -2512,6 +2569,7 @@ public final class FOPropertyMapping implements Constants {
m.addEnum("lr-tb", getEnumProperty(EN_LR_TB, "LR_TB"));
m.addEnum("rl-tb", getEnumProperty(EN_RL_TB, "RL_TB"));
m.addEnum("tb-rl", getEnumProperty(EN_TB_RL, "TB_RL"));
+ m.addEnum("tb-lr", getEnumProperty(EN_TB_LR, "TB_LR"));
m.addKeyword("lr", "lr-tb");
m.addKeyword("rl", "rl-tb");
m.addKeyword("tb", "tb-rl");
diff --git a/src/java/org/apache/fop/fo/FOText.java b/src/java/org/apache/fop/fo/FOText.java
index 43b58685f..5dfe0766b 100644
--- a/src/java/org/apache/fop/fo/FOText.java
+++ b/src/java/org/apache/fop/fo/FOText.java
@@ -21,12 +21,15 @@ package org.apache.fop.fo;
import java.awt.Color;
import java.nio.CharBuffer;
+import java.util.Map;
import java.util.NoSuchElementException;
+import java.util.Stack;
import org.xml.sax.Locator;
import org.apache.fop.accessibility.StructureTreeElement;
import org.apache.fop.apps.FOPException;
+import org.apache.fop.complexscripts.bidi.DelimitedTextRange;
import org.apache.fop.datatypes.Length;
import org.apache.fop.fo.flow.Block;
import org.apache.fop.fo.properties.CommonFont;
@@ -45,7 +48,8 @@ public class FOText extends FONode implements CharSequence {
/** the <code>CharBuffer</code> containing the text */
private CharBuffer charBuffer;
- /** properties relevant for #PCDATA */
+ // The value of FO traits (refined properties) that apply to #PCDATA
+ // (aka implicit sequence of fo:character)
private CommonFont commonFont;
private CommonHyphenation commonHyphenation;
private Color color;
@@ -58,6 +62,10 @@ public class FOText extends FONode implements CharSequence {
private Property wordSpacing;
private int wrapOption;
private Length baselineShift;
+ private String country;
+ private String language;
+ private String script;
+ // End of trait values
/**
* Points to the previous FOText object created within the current
@@ -82,6 +90,12 @@ public class FOText extends FONode implements CharSequence {
private StructureTreeElement structureTreeElement;
+ /* bidi levels */
+ private int[] bidiLevels;
+
+ /* advanced script processing state */
+ private Map/*<MapRange,String>*/ mappings;
+
private static final int IS_WORD_CHAR_FALSE = 0;
private static final int IS_WORD_CHAR_TRUE = 1;
private static final int IS_WORD_CHAR_MAYBE = 2;
@@ -98,21 +112,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());
}
/**
@@ -135,10 +159,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();
}
}
@@ -163,10 +187,16 @@ public class FOText extends FONode implements CharSequence {
this.wrapOption = pList.get(Constants.PR_WRAP_OPTION).getEnum();
this.textDecoration = pList.getTextDecorationProps();
this.baselineShift = pList.get(Constants.PR_BASELINE_SHIFT).getLength();
+ this.country = pList.get(Constants.PR_COUNTRY).getString();
+ this.language = pList.get(Constants.PR_LANGUAGE).getString();
+ this.script = pList.get(Constants.PR_SCRIPT).getString();
}
/** {@inheritDoc} */
protected void endOfNode() throws FOPException {
+ if ( charBuffer != null ) {
+ charBuffer.rewind();
+ }
super.endOfNode();
getFOEventHandler().characters(this);
}
@@ -187,20 +217,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;
}
}
@@ -243,13 +273,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));
@@ -374,25 +404,25 @@ public class FOText extends FONode implements CharSequence {
* @return The previous FOText node in this Block; null, if this is the
* first FOText in this Block.
*/
- public FOText getPrevFOTextThisBlock () {
- return prevFOTextThisBlock;
- }
+ //public FOText getPrevFOTextThisBlock () {
+ // return prevFOTextThisBlock;
+ //}
/**
* @return The next FOText node in this Block; null if this is the last
* FOText in this Block; null if subsequent FOText nodes have not yet been
* processed.
*/
- public FOText getNextFOTextThisBlock () {
- return nextFOTextThisBlock;
- }
+ //public FOText getNextFOTextThisBlock () {
+ // return nextFOTextThisBlock;
+ //}
/**
* @return The nearest ancestor block object which contains this FOText.
*/
- public Block getAncestorBlock () {
- return ancestorBlock;
- }
+ //public Block getAncestorBlock () {
+ // return ancestorBlock;
+ //}
/**
* Determines whether the input char should be considered part of a
@@ -485,6 +515,9 @@ public class FOText extends FONode implements CharSequence {
private boolean canRemove = false;
private boolean canReplace = false;
+ public TextCharIterator() {
+ }
+
/** {@inheritDoc} */
public boolean hasNext() {
return (this.currentPosition < charBuffer.limit());
@@ -556,67 +589,88 @@ public class FOText extends FONode implements CharSequence {
}
/**
- * @return the "color" property.
+ * @return the "color" trait.
*/
public Color getColor() {
return color;
}
/**
- * @return the "keep-together" property.
+ * @return the "keep-together" trait.
*/
public KeepProperty getKeepTogether() {
return keepTogether;
}
/**
- * @return the "letter-spacing" property.
+ * @return the "letter-spacing" trait.
*/
public Property getLetterSpacing() {
return letterSpacing;
}
/**
- * @return the "line-height" property.
+ * @return the "line-height" trait.
*/
public SpaceProperty getLineHeight() {
return lineHeight;
}
/**
- * @return the "white-space-treatment" property
+ * @return the "white-space-treatment" trait
*/
public int getWhitespaceTreatment() {
return whiteSpaceTreatment;
}
/**
- * @return the "word-spacing" property.
+ * @return the "word-spacing" trait.
*/
public Property getWordSpacing() {
return wordSpacing;
}
/**
- * @return the "wrap-option" property.
+ * @return the "wrap-option" trait.
*/
public int getWrapOption() {
return wrapOption;
}
- /** @return the "text-decoration" property. */
+ /** @return the "text-decoration" trait. */
public CommonTextDecoration getTextDecoration() {
return textDecoration;
}
- /** @return the baseline-shift property */
+ /** @return the baseline-shift trait */
public Length getBaseLineShift() {
return baselineShift;
}
+ /** @return the country trait */
+ public String getCountry() {
+ return country;
+ }
+
+ /** @return the language trait */
+ public String getLanguage() {
+ return language;
+ }
+
+ /** @return the script trait */
+ public String getScript() {
+ return script;
+ }
+
/** {@inheritDoc} */
public String toString() {
- return (this.charBuffer == null) ? "" : this.charBuffer.toString();
+ if ( charBuffer == null ) {
+ return "";
+ } else {
+ CharBuffer cb = charBuffer.duplicate();
+ cb.rewind();
+ return cb.toString();
+ }
}
/** {@inheritDoc} */
@@ -640,29 +694,34 @@ 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 <code>java.nio.CharBuffer</code>
*/
public void resetBuffer() {
- if (this.charBuffer != null) {
- this.charBuffer.rewind();
+ if (charBuffer != null) {
+ charBuffer.rewind();
}
}
@Override
+ public boolean isDelimitedTextRangeBoundary ( int boundary ) {
+ return false;
+ }
+
+ @Override
public void setStructureTreeElement(StructureTreeElement structureTreeElement) {
this.structureTreeElement = structureTreeElement;
}
@@ -672,4 +731,192 @@ public class FOText extends FONode implements CharSequence {
return structureTreeElement;
}
+ /**
+ * Set bidirectional level over interval [start,end).
+ * @param level the resolved level
+ * @param start the starting index of interval
+ * @param end the ending index of interval
+ */
+ public void setBidiLevel ( int level, int start, int end ) {
+ if ( start < end ) {
+ if ( bidiLevels == null ) {
+ bidiLevels = new int [ length() ];
+ }
+ for ( int i = start, n = end; i < n; i++ ) {
+ bidiLevels [ i ] = level;
+ }
+ if ( parent != null ) {
+ ( (FObj) parent ).setBidiLevel ( level );
+ }
+ } else {
+ assert start < end;
+ }
+ }
+
+ /**
+ * Obtain bidirectional level of each character
+ * represented by this FOText.
+ * @return a (possibly empty) array of bidi levels or null
+ * in case no bidi levels have been assigned
+ */
+ public int[] getBidiLevels() {
+ return bidiLevels;
+ }
+
+ /**
+ * Obtain bidirectional level of each character over
+ * interval [start,end).
+ * @param start the starting index of interval
+ * @param end the ending index of interval
+ * @return a (possibly empty) array of bidi levels or null
+ * in case no bidi levels have been assigned
+ */
+ public int[] getBidiLevels ( int start, int end ) {
+ if ( this.bidiLevels != null ) {
+ assert start <= end;
+ int n = end - start;
+ int[] bidiLevels = new int [ n ];
+ for ( int i = 0; i < n; i++ ) {
+ bidiLevels[i] = this.bidiLevels [ start + i ];
+ }
+ return bidiLevels;
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Obtain bidirectional level of character at
+ * specified position, which must be a non-negative integer
+ * less than the length of 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 non-negative integer
+ * or is greater than or equal to length
+ */
+ public int bidiLevelAt ( int position ) throws IndexOutOfBoundsException {
+ if ( ( position < 0 ) || ( position >= length() ) ) {
+ throw new IndexOutOfBoundsException();
+ } else if ( bidiLevels != null ) {
+ return bidiLevels [ position ];
+ } else {
+ return -1;
+ }
+ }
+
+ /**
+ * Add characters mapped by script substitution processing.
+ * @param start index in character buffer
+ * @param end index in character buffer
+ * @param mappedChars sequence of character codes denoting substituted characters
+ */
+ public void addMapping ( int start, int end, CharSequence mappedChars ) {
+ if ( mappings == null ) {
+ mappings = new java.util.HashMap();
+ }
+ mappings.put ( new MapRange ( start, end ), mappedChars.toString() );
+ }
+
+ /**
+ * Determine if characters over specific interval have a mapping.
+ * @param start index in character buffer
+ * @param end index in character buffer
+ * @return true if a mapping exist such that the mapping's interval is coincident to
+ * [start,end)
+ */
+ public boolean hasMapping ( int start, int end ) {
+ return ( mappings != null ) && ( mappings.containsKey ( new MapRange ( start, end ) ) );
+ }
+
+ /**
+ * Obtain mapping of characters over specific interval.
+ * @param start index in character buffer
+ * @param end index in character buffer
+ * @return a string of characters representing the mapping over the interval
+ * [start,end)
+ */
+ public String getMapping ( int start, int end ) {
+ if ( mappings != null ) {
+ return (String) mappings.get ( new MapRange ( start, end ) );
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Obtain length of mapping of characters over specific interval.
+ * @param start index in character buffer
+ * @param end index in character buffer
+ * @return the length of the mapping (if present) or zero
+ */
+ public int getMappingLength ( int start, int end ) {
+ if ( mappings != null ) {
+ return ( (String) mappings.get ( new MapRange ( start, end ) ) ) .length();
+ } else {
+ return 0;
+ }
+ }
+
+ /**
+ * Obtain bidirectional levels of mapping of characters over specific interval.
+ * @param start index in character buffer
+ * @param end index in character buffer
+ * @return a (possibly empty) array of bidi levels or null
+ * in case no bidi levels have been assigned
+ */
+ public int[] getMappingBidiLevels ( int start, int end ) {
+ if ( hasMapping ( start, end ) ) {
+ int nc = end - start;
+ int nm = getMappingLength ( start, end );
+ int[] la = getBidiLevels ( start, end );
+ if ( la == null ) {
+ return null;
+ } else if ( nm == nc ) { // mapping is same length as mapped range
+ return la;
+ } else if ( nm > nc ) { // mapping is longer than mapped range
+ int[] ma = new int [ nm ];
+ System.arraycopy ( la, 0, ma, 0, la.length );
+ for ( int i = la.length,
+ n = ma.length, l = ( i > 0 ) ? la [ i - 1 ] : 0; i < n; i++ ) {
+ ma [ i ] = l;
+ }
+ return ma;
+ } else { // mapping is shorter than mapped range
+ int[] ma = new int [ nm ];
+ System.arraycopy ( la, 0, ma, 0, ma.length );
+ return ma;
+ }
+ } else {
+ return getBidiLevels ( start, end );
+ }
+ }
+
+ @Override
+ protected Stack collectDelimitedTextRanges ( Stack ranges, DelimitedTextRange currentRange ) {
+ if ( currentRange != null ) {
+ currentRange.append ( charIterator(), this );
+ }
+ return ranges;
+ }
+
+ private static class MapRange {
+ private int start;
+ private int end;
+ MapRange(int start, int end) {
+ this.start = start;
+ this.end = end;
+ }
+ public int hashCode() {
+ return ( start * 31 ) + end;
+ }
+ public boolean equals ( Object o ) {
+ if ( o instanceof MapRange ) {
+ MapRange r = (MapRange) o;
+ return ( r.start == start ) && ( r.end == end );
+ } else {
+ return false;
+ }
+ }
+ }
+
}
diff --git a/src/java/org/apache/fop/fo/FObj.java b/src/java/org/apache/fop/fo/FObj.java
index 27d0711f2..f1d78acf1 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
@@ -118,9 +120,7 @@ public abstract class FObj extends FONode implements Constants {
throws FOPException {
setLocator(locator);
pList.addAttributesToList(attlist);
- if (!inMarker()
- || "marker".equals(elementName)) {
- pList.setWritingMode();
+ if (!inMarker() || "marker".equals(elementName)) {
bind(pList);
}
}
@@ -558,6 +558,63 @@ public abstract class FObj extends FONode implements Constants {
return "fo";
}
+ /** {@inheritDoc} */
+ public boolean isBidiRangeBlockItem() {
+ String ns = getNamespaceURI();
+ String ln = getLocalName();
+ return !isNeutralItem(ns, ln) && isBlockItem(ns, ln);
+ }
+
+ /**
+ * Recursively set resolved bidirectional level of FO (and its ancestors) if
+ * and only if it is non-negative and if either the current value is reset (-1)
+ * or the new value is less than the current value.
+ * @param bidiLevel a non-negative bidi embedding level
+ */
+ public void setBidiLevel(int bidiLevel) {
+ assert bidiLevel >= 0;
+ if ( bidiLevel >= 0 ) {
+ if ( ( this.bidiLevel < 0 ) || ( bidiLevel < this.bidiLevel ) ) {
+ this.bidiLevel = bidiLevel;
+ if ( parent != null ) {
+ FObj foParent = (FObj) parent;
+ int parentBidiLevel = foParent.getBidiLevel();
+ if ( ( parentBidiLevel < 0 ) || ( bidiLevel < parentBidiLevel ) ) {
+ foParent.setBidiLevel ( 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;
+ }
+
+ /**
+ * Obtain resolved bidirectional level of FO or nearest FO
+ * ancestor that has a resolved level.
+ * @return either a non-negative bidi embedding level or -1
+ * in case no bidi levels have been assigned to this FO or
+ * any ancestor
+ */
+ public int getBidiLevelRecursive() {
+ for ( FONode fn = this; fn != null; fn = fn.getParent() ) {
+ if ( fn instanceof FObj ) {
+ int level = ( (FObj) fn).getBidiLevel();
+ if ( level >= 0 ) {
+ return level;
+ }
+ }
+ }
+ return -1;
+ }
+
/**
* 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/PropertyList.java b/src/java/org/apache/fop/fo/PropertyList.java
index 65ebd40a2..0852c6e29 100644
--- a/src/java/org/apache/fop/fo/PropertyList.java
+++ b/src/java/org/apache/fop/fo/PropertyList.java
@@ -46,8 +46,6 @@ import org.apache.fop.fo.properties.PropertyMaker;
*/
public abstract class PropertyList {
- // writing-mode index
- private int writingMode;
private static boolean[] inheritableProperty;
@@ -220,58 +218,60 @@ public abstract class PropertyList {
}
/**
- * Set writing mode for this FO.
- * Use that from the nearest ancestor, including self, which generates
- * reference areas, or from root FO if no ancestor found.
- * @throws PropertyException ...
- */
- public void setWritingMode() throws PropertyException {
- FObj p = fobj.findNearestAncestorFObj();
- // If this is a reference area or the root, use the property value.
- if (fobj.generatesReferenceAreas() || p == null) {
- writingMode = get(Constants.PR_WRITING_MODE).getEnum();
- } else {
- // Otherwise get the writing mode value from the parent.
- writingMode = getParentPropertyList().getWritingMode();
- }
- }
-
- /**
- * Return the "writing-mode" property value.
- * @return the "writing-mode" property value.
- */
- public int getWritingMode() {
- return writingMode;
- }
-
-
- /**
- * Uses the stored writingMode.
+ * Select a writing mode dependent property ID based on value of writing mode property.
* @param lrtb the property ID to return under lrtb writingmode.
* @param rltb the property ID to return under rltb writingmode.
* @param tbrl the property ID to return under tbrl writingmode.
+ * @param tblr the property ID to return under tblr writingmode.
* @return one of the property IDs, depending on the writing mode.
*/
- public int getWritingMode(int lrtb, int rltb, int tbrl) {
- switch (writingMode) {
- case Constants.EN_LR_TB: return lrtb;
- case Constants.EN_RL_TB: return rltb;
- case Constants.EN_TB_RL: return tbrl;
+ public int selectFromWritingMode(int lrtb, int rltb, int tbrl, int tblr) {
+ int propID;
+ try {
+ switch (get(Constants.PR_WRITING_MODE).getEnum()) {
+ case Constants.EN_LR_TB:
+ propID = lrtb;
+ break;
+ case Constants.EN_RL_TB:
+ propID = rltb;
+ break;
+ case Constants.EN_TB_RL:
+ propID = tbrl;
+ break;
+ case Constants.EN_TB_LR:
+ propID = tblr;
+ break;
default:
- //nop
+ propID = -1;
+ break;
+ }
+ } catch ( PropertyException e ) {
+ propID = -1;
}
- return -1;
+ return propID;
}
private String addAttributeToList(Attributes attributes,
String attributeName) throws ValidationException {
String attributeValue = attributes.getValue(attributeName);
- convertAttributeToProperty(attributes, attributeName, attributeValue);
+ if ( attributeValue != null ) {
+ convertAttributeToProperty(attributes, attributeName, attributeValue);
+ }
return attributeValue;
}
/**
- * Adds the attributes, passed in by the parser to the PropertyList
+ * <p>Adds the attributes, passed in by the parser to the PropertyList.</p>
+ * <p>Note that certain attributes are given priority in terms of order of
+ * processing due to conversion dependencies, where the order is as follows:</p>
+ * <ol>
+ * <li>writing-mode</li>
+ * <li>column-number</li>
+ * <li>number-columns-spanned</li>
+ * <li>font</li>
+ * <li>font-size</li>
+ * <li><emph>all others in order of appearance</emph></li>
+ * </ol>
*
* @param attributes Collection of attributes passed to us from the parser.
* @throws ValidationException if there is an attribute that does not
@@ -280,6 +280,11 @@ public abstract class PropertyList {
public void addAttributesToList(Attributes attributes)
throws ValidationException {
/*
+ * Give writing-mode highest conversion priority.
+ */
+ addAttributeToList(attributes, "writing-mode");
+
+ /*
* If column-number/number-columns-spanned are specified, then we
* need them before all others (possible from-table-column() on any
* other property further in the list...
@@ -300,9 +305,9 @@ public abstract class PropertyList {
addAttributeToList(attributes, "font-size");
}
+ String attributeNS;
String attributeName;
String attributeValue;
- String attributeNS;
FopFactory factory = getFObj().getUserAgent().getFactory();
for (int i = 0; i < attributes.getLength(); i++) {
/* convert all attributes with the same namespace as the fo element
@@ -646,4 +651,3 @@ public abstract class PropertyList {
return CommonTextDecoration.createFromPropertyList(this);
}
}
-
diff --git a/src/java/org/apache/fop/fo/flow/AbstractGraphics.java b/src/java/org/apache/fop/fo/flow/AbstractGraphics.java
index d5fe2b500..3d93eaa8f 100644
--- a/src/java/org/apache/fop/fo/flow/AbstractGraphics.java
+++ b/src/java/org/apache/fop/fo/flow/AbstractGraphics.java
@@ -19,8 +19,11 @@
package org.apache.fop.fo.flow;
+import java.util.Stack;
+
import org.apache.fop.accessibility.StructureTreeElement;
import org.apache.fop.apps.FOPException;
+import org.apache.fop.complexscripts.bidi.DelimitedTextRange;
import org.apache.fop.datatypes.Length;
import org.apache.fop.fo.FONode;
import org.apache.fop.fo.FObj;
@@ -33,6 +36,7 @@ import org.apache.fop.fo.properties.KeepProperty;
import org.apache.fop.fo.properties.LengthRangeProperty;
import org.apache.fop.fo.properties.SpaceProperty;
import org.apache.fop.fo.properties.StructureTreeElementHolder;
+import org.apache.fop.util.CharUtilities;
/**
* Common base class for the <a href="http://www.w3.org/TR/xsl/#fo_instream-foreign-object">
@@ -77,8 +81,6 @@ public abstract class AbstractGraphics extends FObj
// private int scalingMethod;
// End of property values
-
-
/**
* constructs an instream-foreign-object object (called by Maker).
*
@@ -250,4 +252,18 @@ public abstract class AbstractGraphics extends FObj
/** @return the graphic's intrinsic alignment-adjust */
public abstract Length getIntrinsicAlignmentAdjust();
+
+ @Override
+ public boolean isDelimitedTextRangeBoundary ( int boundary ) {
+ return false;
+ }
+
+ @Override
+ protected Stack collectDelimitedTextRanges ( Stack ranges, DelimitedTextRange currentRange ) {
+ if ( currentRange != null ) {
+ currentRange.append ( CharUtilities.OBJECT_REPLACEMENT_CHARACTER, this );
+ }
+ return ranges;
+ }
+
}
diff --git a/src/java/org/apache/fop/fo/flow/AbstractPageNumberCitation.java b/src/java/org/apache/fop/fo/flow/AbstractPageNumberCitation.java
index 5a6893b55..ec3891632 100644
--- a/src/java/org/apache/fop/fo/flow/AbstractPageNumberCitation.java
+++ b/src/java/org/apache/fop/fo/flow/AbstractPageNumberCitation.java
@@ -20,12 +20,14 @@
package org.apache.fop.fo.flow;
import java.awt.Color;
+import java.util.Stack;
import org.xml.sax.Attributes;
import org.xml.sax.Locator;
import org.apache.fop.accessibility.StructureTreeElement;
import org.apache.fop.apps.FOPException;
+import org.apache.fop.complexscripts.bidi.DelimitedTextRange;
import org.apache.fop.datatypes.Length;
import org.apache.fop.fo.Constants;
import org.apache.fop.fo.FONode;
@@ -39,6 +41,7 @@ import org.apache.fop.fo.properties.CommonFont;
import org.apache.fop.fo.properties.CommonTextDecoration;
import org.apache.fop.fo.properties.SpaceProperty;
import org.apache.fop.fo.properties.StructureTreeElementHolder;
+import org.apache.fop.util.CharUtilities;
/**
* Common base class for the <a href="http://www.w3.org/TR/xsl/#fo_page-number-citation">
@@ -197,4 +200,17 @@ public abstract class AbstractPageNumberCitation extends FObj
return refId;
}
+ @Override
+ public boolean isDelimitedTextRangeBoundary ( int boundary ) {
+ return false;
+ }
+
+ @Override
+ protected Stack collectDelimitedTextRanges ( Stack ranges, DelimitedTextRange currentRange ) {
+ if ( currentRange != null ) {
+ currentRange.append ( CharUtilities.OBJECT_REPLACEMENT_CHARACTER, this );
+ }
+ return ranges;
+ }
+
}
diff --git a/src/java/org/apache/fop/fo/flow/BidiOverride.java b/src/java/org/apache/fop/fo/flow/BidiOverride.java
index 6e4a74379..d38df90c7 100644
--- a/src/java/org/apache/fop/fo/flow/BidiOverride.java
+++ b/src/java/org/apache/fop/fo/flow/BidiOverride.java
@@ -19,38 +19,37 @@
package org.apache.fop.fo.flow;
+import java.util.Iterator;
+import java.util.Stack;
+
import org.xml.sax.Locator;
import org.apache.fop.apps.FOPException;
+import org.apache.fop.complexscripts.bidi.DelimitedTextRange;
+import org.apache.fop.datatypes.Length;
+import org.apache.fop.fo.Constants;
import org.apache.fop.fo.FONode;
import org.apache.fop.fo.FObjMixed;
import org.apache.fop.fo.PropertyList;
import org.apache.fop.fo.ValidationException;
+import org.apache.fop.fo.properties.Property;
import org.apache.fop.fo.properties.SpaceProperty;
+import org.apache.fop.util.CharUtilities;
/**
* Class modelling the <a href="http://www.w3.org/TR/xsl/#fo_bidi-override">
* <code>fo:bidi-override</code></a> object.
*/
-public class BidiOverride extends FObjMixed {
-
- // used for FO validation
- private boolean blockOrInlineItemFound = false;
- private boolean canHaveBlockLevelChildren = true;
- // The value of properties relevant for fo:bidi-override.
- // private ToBeImplementedProperty prDirection;
- // private ToBeImplementedProperty prLetterSpacing;
- private SpaceProperty lineHeight;
- // private ToBeImplementedProperty prScoreSpaces;
- // private ToBeImplementedProperty prUnicodeBidi;
-
- // Unused but valid items, commented out for performance:
- // private CommonAural commonAural;
- // private CommonFont commonFont;
- // private CommonRelativePosition commonRelativePosition;
- // private Color prColor;
- // private SpaceProperty prWordSpacing;
- // End of property values
+public class BidiOverride extends Inline {
+
+ // The value of FO traits (refined properties) that apply to fo:bidi-override
+ // (that are not implemented by InlineLevel).
+ private Property letterSpacing;
+ private Property wordSpacing;
+ private int direction;
+ private int unicodeBidi;
+ // private int scoreSpaces;
+ // End of trait values
/**
* Base constructor
@@ -59,66 +58,35 @@ public class BidiOverride extends FObjMixed {
*/
public BidiOverride(FONode parent) {
super(parent);
-
- /* Check to see if this node can have block-level children.
- * See validateChildNode() below.
- */
- int lvlLeader = findAncestor(FO_LEADER);
- int lvlInCntr = findAncestor(FO_INLINE_CONTAINER);
- int lvlInline = findAncestor(FO_INLINE);
- int lvlFootnote = findAncestor(FO_FOOTNOTE);
-
- if (lvlLeader > 0) {
- if (lvlInCntr < 0 || (lvlInCntr > 0 && lvlInCntr > lvlLeader)) {
- canHaveBlockLevelChildren = false;
- }
- } else if (lvlInline > 0 && lvlFootnote == (lvlInline + 1)) {
- if (lvlInCntr < 0 || (lvlInCntr > 0 && lvlInCntr > lvlInline)) {
- canHaveBlockLevelChildren = false;
- }
- }
-
}
/** {@inheritDoc} */
public void bind(PropertyList pList) throws FOPException {
- // prDirection = pList.get(PR_DIRECTION);
- // prLetterSpacing = pList.get(PR_LETTER_SPACING);
- lineHeight = pList.get(PR_LINE_HEIGHT).getSpace();
- // prScoreSpaces = pList.get(PR_SCORE_SPACES);
- // prUnicodeBidi = pList.get(PR_UNICODE_BIDI);
+ super.bind(pList);
+ letterSpacing = pList.get(PR_LETTER_SPACING);
+ wordSpacing = pList.get(PR_WORD_SPACING);
+ direction = pList.get(PR_DIRECTION).getEnum();
+ unicodeBidi = pList.get(PR_UNICODE_BIDI).getEnum();
}
- /**
- * {@inheritDoc}
- * <br>XSL Content Model: marker* (#PCDATA|%inline;|%block;)*
- * <br><i>Additionally: "An fo:bidi-override that is a descendant of an fo:leader
- * or of the fo:inline child of an fo:footnote may not have block-level
- * children, unless it has a nearer ancestor that is an
- * fo:inline-container."</i>
- */
- protected void validateChildNode(Locator loc, String nsURI, String localName)
- throws ValidationException {
- if (FO_URI.equals(nsURI)) {
- if (localName.equals("marker")) {
- if (blockOrInlineItemFound) {
- nodesOutOfOrderError(loc, "fo:marker",
- "(#PCDATA|%inline;|%block;)");
- }
- } else if (!isBlockOrInlineItem(nsURI, localName)) {
- invalidChildError(loc, nsURI, localName);
- } else if (!canHaveBlockLevelChildren && isBlockItem(nsURI, localName)) {
- invalidChildError(loc, getParent().getName(), nsURI, getName(),
- "rule.bidiOverrideContent");
- } else {
- blockOrInlineItemFound = true;
- }
- }
+ /** @return the "letter-spacing" trait */
+ public Property getLetterSpacing() {
+ return letterSpacing;
+ }
+
+ /** @return the "word-spacing" trait */
+ public Property getWordSpacing() {
+ return wordSpacing;
}
- /** @return the "line-height" property */
- public SpaceProperty getLineHeight() {
- return lineHeight;
+ /** @return the "direction" trait */
+ public int getDirection() {
+ return direction;
+ }
+
+ /** @return the "unicodeBidi" trait */
+ public int getUnicodeBidi() {
+ return unicodeBidi;
}
/** {@inheritDoc} */
@@ -133,4 +101,32 @@ public class BidiOverride extends FObjMixed {
public int getNameId() {
return FO_BIDI_OVERRIDE;
}
+
+ @Override
+ protected Stack collectDelimitedTextRanges ( Stack ranges, DelimitedTextRange currentRange ) {
+ char pfx = 0;
+ char sfx = 0;
+ int unicodeBidi = getUnicodeBidi();
+ int direction = getDirection();
+ if ( unicodeBidi == Constants.EN_BIDI_OVERRIDE ) {
+ pfx = ( direction == Constants.EN_RTL ) ? CharUtilities.RLO : CharUtilities.LRO;
+ sfx = CharUtilities.PDF;
+ } else if ( unicodeBidi == Constants.EN_EMBED ) {
+ pfx = ( direction == Constants.EN_RTL ) ? CharUtilities.RLE : CharUtilities.LRE;
+ sfx = CharUtilities.PDF;
+ }
+ if ( currentRange != null ) {
+ if ( pfx != 0 ) {
+ currentRange.append ( pfx, this );
+ }
+ for ( Iterator it = getChildNodes(); ( it != null ) && it.hasNext();) {
+ ranges = ( (FONode) it.next() ).collectDelimitedTextRanges ( ranges );
+ }
+ if ( sfx != 0 ) {
+ currentRange.append ( sfx, this );
+ }
+ }
+ return ranges;
+ }
+
}
diff --git a/src/java/org/apache/fop/fo/flow/Block.java b/src/java/org/apache/fop/fo/flow/Block.java
index 84f1abc1b..d93821ca6 100644
--- a/src/java/org/apache/fop/fo/flow/Block.java
+++ b/src/java/org/apache/fop/fo/flow/Block.java
@@ -54,7 +54,7 @@ public class Block extends FObjMixed implements BreakPropertySet,
private boolean blockOrInlineItemFound = false;
private boolean initialPropertySetFound = false;
- // The value of properties relevant for fo:block.
+ // The value of FO traits (refined properties) that apply to fo:block.
private CommonAccessibility commonAccessibility;
private CommonBorderPaddingBackground commonBorderPaddingBackground;
private CommonFont commonFont;
@@ -91,7 +91,7 @@ public class Block extends FObjMixed implements BreakPropertySet,
// private Length textDepth;
// private Length textAltitude;
// private int visibility;
- // End of property values
+ // End of FO trait values
/**
* Base constructor
@@ -177,87 +177,87 @@ public class Block extends FObjMixed implements BreakPropertySet,
return commonHyphenation;
}
- /** @return the "break-after" property. */
+ /** @return the "break-after" trait. */
public int getBreakAfter() {
return breakAfter;
}
- /** @return the "break-before" property. */
+ /** @return the "break-before" trait. */
public int getBreakBefore() {
return breakBefore;
}
- /** @return the "hyphenation-ladder-count" property. */
+ /** @return the "hyphenation-ladder-count" trait. */
public Numeric getHyphenationLadderCount() {
return hyphenationLadderCount;
}
- /** @return the "keep-with-next" property. */
+ /** @return the "keep-with-next" trait. */
public KeepProperty getKeepWithNext() {
return keepWithNext;
}
- /** @return the "keep-with-previous" property. */
+ /** @return the "keep-with-previous" trait. */
public KeepProperty getKeepWithPrevious() {
return keepWithPrevious;
}
- /** @return the "keep-together" property. */
+ /** @return the "keep-together" trait. */
public KeepProperty getKeepTogether() {
return keepTogether;
}
- /** @return the "orphans" property. */
+ /** @return the "orphans" trait. */
public int getOrphans() {
return orphans.getValue();
}
- /** @return the "widows" property. */
+ /** @return the "widows" trait. */
public int getWidows() {
return widows.getValue();
}
- /** @return the "line-stacking-strategy" property. */
+ /** @return the "line-stacking-strategy" trait. */
public int getLineStackingStrategy() {
return lineStackingStrategy;
}
- /** @return the "color" property */
+ /** @return the "color" trait */
public Color getColor() {
return color;
}
- /** @return the "line-height" property */
+ /** @return the "line-height" trait */
public SpaceProperty getLineHeight() {
return lineHeight;
}
- /** @return the "span" property */
+ /** @return the "span" trait */
public int getSpan() {
return this.span;
}
- /** @return the "text-align" property */
+ /** @return the "text-align" trait */
public int getTextAlign() {
return textAlign;
}
- /** @return the "text-align-last" property */
+ /** @return the "text-align-last" trait */
public int getTextAlignLast() {
return textAlignLast;
}
- /** @return the "text-indent" property */
+ /** @return the "text-indent" trait */
public Length getTextIndent() {
return textIndent;
}
- /** @return the "last-line-end-indent" property */
+ /** @return the "last-line-end-indent" trait */
public Length getLastLineEndIndent() {
return lastLineEndIndent;
}
- /** @return the "wrap-option" property */
+ /** @return the "wrap-option" trait */
public int getWrapOption() {
return wrapOption;
}
@@ -295,17 +295,17 @@ public class Block extends FObjMixed implements BreakPropertySet,
}
}
- /** @return the "linefeed-treatment" property */
+ /** @return the "linefeed-treatment" trait */
public int getLinefeedTreatment() {
return linefeedTreatment;
}
- /** @return the "white-space-treatment" property */
+ /** @return the "white-space-treatment" trait */
public int getWhitespaceTreatment() {
return whiteSpaceTreatment;
}
- /** @return the "white-space-collapse" property */
+ /** @return the "white-space-collapse" trait */
public int getWhitespaceCollapse() {
return whiteSpaceCollapse;
}
@@ -315,17 +315,17 @@ public class Block extends FObjMixed implements BreakPropertySet,
return this.commonRelativePosition;
}
- /** @return the "hyphenation-keep" property */
+ /** @return the "hyphenation-keep" trait */
public int getHyphenationKeep() {
return this.hyphenationKeep;
}
- /** @return the "intrusion-displace" property */
+ /** @return the "intrusion-displace" trait */
public int getIntrusionDisplace() {
return this.intrusionDisplace;
}
- /** @return the "line-height-shift-adjustment" property */
+ /** @return the "line-height-shift-adjustment" trait */
public int getLineHeightShiftAdjustment() {
return this.lineHeightShiftAdjustment;
}
@@ -335,9 +335,9 @@ public class Block extends FObjMixed implements BreakPropertySet,
* {@link org.apache.fop.fo.Constants#EN_TRUE},
* {@link org.apache.fop.fo.Constants#EN_FALSE}
*/
- public int getDisableColumnBalancing() {
- return disableColumnBalancing;
- }
+ public int getDisableColumnBalancing() {
+ return disableColumnBalancing;
+ }
/** {@inheritDoc} */
@@ -350,10 +350,10 @@ public class Block extends FObjMixed implements BreakPropertySet,
return "block";
}
- /**
- * {@inheritDoc}
- * @return {@link org.apache.fop.fo.Constants#FO_BLOCK}
- */
+ /**
+ * {@inheritDoc}
+ * @return {@link org.apache.fop.fo.Constants#FO_BLOCK}
+ */
public int getNameId() {
return FO_BLOCK;
}
diff --git a/src/java/org/apache/fop/fo/flow/BlockContainer.java b/src/java/org/apache/fop/fo/flow/BlockContainer.java
index 57cb6e5c6..1dc43fd36 100644
--- a/src/java/org/apache/fop/fo/flow/BlockContainer.java
+++ b/src/java/org/apache/fop/fo/flow/BlockContainer.java
@@ -31,14 +31,18 @@ import org.apache.fop.fo.properties.CommonBorderPaddingBackground;
import org.apache.fop.fo.properties.CommonMarginBlock;
import org.apache.fop.fo.properties.KeepProperty;
import org.apache.fop.fo.properties.LengthRangeProperty;
+import org.apache.fop.traits.Direction;
+import org.apache.fop.traits.WritingMode;
+import org.apache.fop.traits.WritingModeTraits;
+import org.apache.fop.traits.WritingModeTraitsGetter;
import org.xml.sax.Locator;
/**
* Class modelling the <a href="http://www.w3.org/TR/xsl/#fo_block-container">
* <code>fo:block-container</code></a> object.
*/
-public class BlockContainer extends FObj implements BreakPropertySet {
- // The value of properties relevant for fo:block-container.
+public class BlockContainer extends FObj implements BreakPropertySet, WritingModeTraitsGetter {
+ // The value of FO traits (refined properties) that apply to fo:block-container.
private CommonAbsolutePosition commonAbsolutePosition;
private CommonBorderPaddingBackground commonBorderPaddingBackground;
private CommonMarginBlock commonMarginBlock;
@@ -55,11 +59,11 @@ public class BlockContainer extends FObj implements BreakPropertySet {
private Numeric referenceOrientation;
private int span;
private int disableColumnBalancing;
- private int writingMode;
+ private WritingModeTraits writingModeTraits;
// Unused but valid items, commented out for performance:
// private int intrusionDisplace;
// private Numeric zIndex;
- // End of property values
+ // End of FO trait values
/** used for FO validation */
private boolean blockItemFound = false;
@@ -92,7 +96,8 @@ public class BlockContainer extends FObj implements BreakPropertySet {
overflow = pList.get(PR_OVERFLOW).getEnum();
referenceOrientation = pList.get(PR_REFERENCE_ORIENTATION).getNumeric();
span = pList.get(PR_SPAN).getEnum();
- writingMode = pList.get(PR_WRITING_MODE).getEnum();
+ writingModeTraits = new WritingModeTraits
+ ( WritingMode.valueOf(pList.get(PR_WRITING_MODE).getEnum()) );
disableColumnBalancing = pList.get(PR_X_DISABLE_COLUMN_BALANCING).getEnum();
}
@@ -161,58 +166,58 @@ public class BlockContainer extends FObj implements BreakPropertySet {
}
/**
- * @return the "block-progression-dimension" property.
+ * @return the "block-progression-dimension" FO trait.
*/
public LengthRangeProperty getBlockProgressionDimension() {
return blockProgressionDimension;
}
- /** @return the "display-align" property. */
+ /** @return the "display-align" FO trait. */
public int getDisplayAlign() {
return displayAlign;
}
- /** @return the "break-after" property. */
+ /** @return the "break-after" FO trait. */
public int getBreakAfter() {
return breakAfter;
}
- /** @return the "break-before" property. */
+ /** @return the "break-before" FO trait. */
public int getBreakBefore() {
return breakBefore;
}
- /** @return the "keep-with-next" property. */
+ /** @return the "keep-with-next" FO trait. */
public KeepProperty getKeepWithNext() {
return keepWithNext;
}
- /** @return the "keep-with-previous" property. */
+ /** @return the "keep-with-previous" FO trait. */
public KeepProperty getKeepWithPrevious() {
return keepWithPrevious;
}
- /** @return the "keep-together" property. */
+ /** @return the "keep-together" FO trait. */
public KeepProperty getKeepTogether() {
return keepTogether;
}
- /** @return the "inline-progression-dimension" property */
+ /** @return the "inline-progression-dimension" FO trait */
public LengthRangeProperty getInlineProgressionDimension() {
return inlineProgressionDimension;
}
- /** @return the "overflow" property */
+ /** @return the "overflow" FO trait */
public int getOverflow() {
return overflow;
}
- /** @return the "reference-orientation" property */
+ /** @return the "reference-orientation" FO trait */
public int getReferenceOrientation() {
return referenceOrientation.getValue();
}
- /** @return the "span" property */
+ /** @return the "span" FO trait */
public int getSpan() {
return this.span;
}
@@ -226,10 +231,52 @@ public class BlockContainer extends FObj implements BreakPropertySet {
return disableColumnBalancing;
}
+ /**
+ * Obtain inline progression direction.
+ * @return the inline progression direction
+ */
+ public Direction getInlineProgressionDirection() {
+ return writingModeTraits.getInlineProgressionDirection();
+ }
+
+ /**
+ * Obtain block progression direction.
+ * @return the block progression direction
+ */
+ public Direction getBlockProgressionDirection() {
+ return writingModeTraits.getBlockProgressionDirection();
+ }
+
+ /**
+ * Obtain column progression direction.
+ * @return the column progression direction
+ */
+ public Direction getColumnProgressionDirection() {
+ return writingModeTraits.getColumnProgressionDirection();
+ }
- /** @return the "writing-mode" property */
- public int getWritingMode() {
- return writingMode;
+ /**
+ * Obtain row progression direction.
+ * @return the row progression direction
+ */
+ public Direction getRowProgressionDirection() {
+ return writingModeTraits.getRowProgressionDirection();
+ }
+
+ /**
+ * Obtain (baseline) shift direction.
+ * @return the (baseline) shift direction
+ */
+ public Direction getShiftDirection() {
+ return writingModeTraits.getShiftDirection();
+ }
+
+ /**
+ * Obtain writing mode.
+ * @return the writing mode
+ */
+ public WritingMode getWritingMode() {
+ return writingModeTraits.getWritingMode();
}
/** {@inheritDoc} */
diff --git a/src/java/org/apache/fop/fo/flow/Character.java b/src/java/org/apache/fop/fo/flow/Character.java
index c4de9fb72..f48ff289e 100644
--- a/src/java/org/apache/fop/fo/flow/Character.java
+++ b/src/java/org/apache/fop/fo/flow/Character.java
@@ -21,11 +21,13 @@ package org.apache.fop.fo.flow;
import java.awt.Color;
import java.util.NoSuchElementException;
+import java.util.Stack;
import org.xml.sax.Locator;
import org.apache.fop.accessibility.StructureTreeElement;
import org.apache.fop.apps.FOPException;
+import org.apache.fop.complexscripts.bidi.DelimitedTextRange;
import org.apache.fop.datatypes.Length;
import org.apache.fop.fo.CharIterator;
import org.apache.fop.fo.FONode;
@@ -233,6 +235,19 @@ public class Character extends FObj implements StructureTreeElementHolder {
return FO_CHARACTER;
}
+ @Override
+ public boolean isDelimitedTextRangeBoundary ( int boundary ) {
+ return false;
+ }
+
+ @Override
+ protected Stack collectDelimitedTextRanges ( Stack ranges, DelimitedTextRange currentRange ) {
+ if ( currentRange != null ) {
+ currentRange.append ( charIterator(), this );
+ }
+ return ranges;
+ }
+
private class FOCharIterator extends CharIterator {
private boolean bFirst = true;
diff --git a/src/java/org/apache/fop/fo/flow/InlineContainer.java b/src/java/org/apache/fop/fo/flow/InlineContainer.java
index d2422a24b..a52801c3f 100644
--- a/src/java/org/apache/fop/fo/flow/InlineContainer.java
+++ b/src/java/org/apache/fop/fo/flow/InlineContainer.java
@@ -33,6 +33,10 @@ import org.apache.fop.fo.properties.CommonMarginInline;
import org.apache.fop.fo.properties.KeepProperty;
import org.apache.fop.fo.properties.LengthRangeProperty;
import org.apache.fop.fo.properties.SpaceProperty;
+import org.apache.fop.traits.Direction;
+import org.apache.fop.traits.WritingMode;
+import org.apache.fop.traits.WritingModeTraits;
+import org.apache.fop.traits.WritingModeTraitsGetter;
/**
* Class modelling the <a href="http://www.w3.org/TR/xsl/#fo_inline-container">
@@ -40,7 +44,7 @@ import org.apache.fop.fo.properties.SpaceProperty;
*/
public class InlineContainer extends FObj {
- // The value of properties relevant for fo:inline-container.
+ // The value of FO traits (refined properties) that apply to fo:inline-container.
private Length alignmentAdjust;
private int alignmentBaseline;
private Length baselineShift;
@@ -54,7 +58,7 @@ public class InlineContainer extends FObj {
private SpaceProperty lineHeight;
private int overflow;
private Numeric referenceOrientation;
- private int writingMode;
+ private WritingModeTraits writingModeTraits;
// Unused but valid items, commented out for performance:
// private CommonRelativePosition commonRelativePosition;
// private int displayAlign;
@@ -62,7 +66,7 @@ public class InlineContainer extends FObj {
// private KeepProperty keepWithNext;
// private KeepProperty keepWithPrevious;
// private Length width;
- // End of property values
+ // End of FO trait values
/** used for FO validation */
private boolean blockItemFound = false;
@@ -92,7 +96,8 @@ public class InlineContainer extends FObj {
lineHeight = pList.get(PR_LINE_HEIGHT).getSpace();
overflow = pList.get(PR_OVERFLOW).getEnum();
referenceOrientation = pList.get(PR_REFERENCE_ORIENTATION).getNumeric();
- writingMode = pList.get(PR_WRITING_MODE).getEnum();
+ writingModeTraits = new WritingModeTraits
+ ( WritingMode.valueOf(pList.get(PR_WRITING_MODE).getEnum()) );
}
/**
@@ -121,27 +126,27 @@ public class InlineContainer extends FObj {
}
}
- /** @return the "alignment-adjust" property */
+ /** @return the "alignment-adjust" FO trait */
public Length getAlignmentAdjust() {
return alignmentAdjust;
}
- /** @return the "alignment-baseline" property */
+ /** @return the "alignment-baseline" FO trait */
public int getAlignmentBaseline() {
return alignmentBaseline;
}
- /** @return the "baseline-shift" property */
+ /** @return the "baseline-shift" FO trait */
public Length getBaselineShift() {
return baselineShift;
}
- /** @return the "block-progression-dimension" property */
+ /** @return the "block-progression-dimension" FO trait */
public LengthRangeProperty getBlockProgressionDimension() {
return blockProgressionDimension;
}
- /** @return the "clip" property */
+ /** @return the "clip" FO trait */
public int getClip() {
return clip;
}
@@ -156,39 +161,82 @@ public class InlineContainer extends FObj {
return this.commonMarginInline;
}
- /** @return the "dominant-baseline" property */
+ /** @return the "dominant-baseline" FO trait */
public int getDominantBaseline() {
return dominantBaseline;
}
- /** @return the "keep-together" property */
+ /** @return the "keep-together" FO trait */
public KeepProperty getKeepTogether() {
return keepTogether;
}
- /** @return the "inline-progression-dimension" property */
+ /** @return the "inline-progression-dimension" FO trait */
public LengthRangeProperty getInlineProgressionDimension() {
return inlineProgressionDimension;
}
- /** @return the "line-height" property */
+ /** @return the "line-height" FO trait */
public SpaceProperty getLineHeight() {
return lineHeight;
}
- /** @return the "overflow" property */
+ /** @return the "overflow" FO trait */
public int getOverflow() {
return overflow;
}
- /** @return the "reference-orientation" property */
+ /** @return the "reference-orientation" FO trait */
public int getReferenceOrientation() {
return referenceOrientation.getValue();
}
- /** @return the "writing-mode" property */
- public int getWritingMode() {
- return writingMode;
+ /**
+ * Obtain inline progression direction.
+ * @return the inline progression direction
+ */
+ public Direction getInlineProgressionDirection() {
+ return writingModeTraits.getInlineProgressionDirection();
+ }
+
+ /**
+ * Obtain block progression direction.
+ * @return the block progression direction
+ */
+ public Direction getBlockProgressionDirection() {
+ return writingModeTraits.getBlockProgressionDirection();
+ }
+
+ /**
+ * Obtain column progression direction.
+ * @return the column progression direction
+ */
+ public Direction getColumnProgressionDirection() {
+ return writingModeTraits.getColumnProgressionDirection();
+ }
+
+ /**
+ * Obtain row progression direction.
+ * @return the row progression direction
+ */
+ public Direction getRowProgressionDirection() {
+ return writingModeTraits.getRowProgressionDirection();
+ }
+
+ /**
+ * Obtain (baseline) shift direction.
+ * @return the (baseline) shift direction
+ */
+ public Direction getShiftDirection() {
+ return writingModeTraits.getShiftDirection();
+ }
+
+ /**
+ * Obtain writing mode.
+ * @return the writing mode
+ */
+ public WritingMode getWritingMode() {
+ return writingModeTraits.getWritingMode();
}
/** {@inheritDoc} */
@@ -204,4 +252,9 @@ public class InlineContainer extends FObj {
return FO_INLINE_CONTAINER;
}
+ @Override
+ public boolean isDelimitedTextRangeBoundary ( int boundary ) {
+ return false;
+ }
+
}
diff --git a/src/java/org/apache/fop/fo/flow/InlineLevel.java b/src/java/org/apache/fop/fo/flow/InlineLevel.java
index 0da769080..b0531f859 100644
--- a/src/java/org/apache/fop/fo/flow/InlineLevel.java
+++ b/src/java/org/apache/fop/fo/flow/InlineLevel.java
@@ -39,7 +39,7 @@ import org.apache.fop.fo.properties.SpaceProperty;
*/
public abstract class InlineLevel extends FObjMixed implements CommonAccessibilityHolder {
- // The value of properties relevant for inline-level FOs.
+ // The value of FO traits (refined properties) that apply to inline level FOs.
private CommonAccessibility commonAccessibility;
private CommonBorderPaddingBackground commonBorderPaddingBackground;
private CommonMarginInline commonMarginInline;
@@ -48,7 +48,7 @@ public abstract class InlineLevel extends FObjMixed implements CommonAccessibili
private KeepProperty keepWithNext;
private KeepProperty keepWithPrevious;
private SpaceProperty lineHeight;
- // End of property values
+ // End of trait values
/**
* Base constructor
@@ -92,24 +92,29 @@ public abstract class InlineLevel extends FObjMixed implements CommonAccessibili
return commonFont;
}
- /** @return the "color" property */
+ /** @return the "color" trait */
public Color getColor() {
return color;
}
- /** @return the "line-height" property */
+ /** @return the "line-height" trait */
public SpaceProperty getLineHeight() {
return lineHeight;
}
- /** @return the "keep-with-next" property */
+ /** @return the "keep-with-next" trait */
public KeepProperty getKeepWithNext() {
return keepWithNext;
}
- /** @return the "keep-with-previous" property */
+ /** @return the "keep-with-previous" trait */
public KeepProperty getKeepWithPrevious() {
return keepWithPrevious;
}
+ @Override
+ public boolean isDelimitedTextRangeBoundary ( int boundary ) {
+ return false;
+ }
+
}
diff --git a/src/java/org/apache/fop/fo/flow/Leader.java b/src/java/org/apache/fop/fo/flow/Leader.java
index f12930dde..ce166b114 100644
--- a/src/java/org/apache/fop/fo/flow/Leader.java
+++ b/src/java/org/apache/fop/fo/flow/Leader.java
@@ -19,18 +19,24 @@
package org.apache.fop.fo.flow;
+import java.util.Stack;
+
+import org.xml.sax.Locator;
+
import org.apache.fop.apps.FOPException;
+import org.apache.fop.complexscripts.bidi.DelimitedTextRange;
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;
+import org.apache.fop.util.CharUtilities;
/**
* Class modelling the <a href="http://www.w3.org/TR/xsl/#fo_leader">
* <code>fo:leader</code></a> object.
* The main property of <code>fo:leader</code> is leader-pattern.
* The following patterns are treated: rule, space, dots and use-content.
- * TODO implement validateChildNode()
*/
public class Leader extends InlineLevel {
// The value of properties relevant for fo:leader.
@@ -96,6 +102,28 @@ public class Leader extends InlineLevel {
// textShadow = pList.get(PR_TEXT_SHADOW);
}
+ /**
+ * {@inheritDoc}
+ * <br>XSL Content Model: (#PCDATA|%inline;)*
+ * <br><i>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."</i>
+ */
+ 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;
@@ -170,4 +198,17 @@ public class Leader extends InlineLevel {
super.endOfNode();
getFOEventHandler().endLeader(this);
}
+
+ @Override
+ protected Stack collectDelimitedTextRanges ( Stack ranges, DelimitedTextRange currentRange ) {
+ if ( currentRange != null ) {
+ if ( leaderPattern == EN_USECONTENT ) {
+ ranges = super.collectDelimitedTextRanges ( ranges, currentRange );
+ } else {
+ currentRange.append ( CharUtilities.OBJECT_REPLACEMENT_CHARACTER, this );
+ }
+ }
+ return ranges;
+ }
+
}
diff --git a/src/java/org/apache/fop/fo/flow/ListItem.java b/src/java/org/apache/fop/fo/flow/ListItem.java
index aa177777c..f61254874 100644
--- a/src/java/org/apache/fop/fo/flow/ListItem.java
+++ b/src/java/org/apache/fop/fo/flow/ListItem.java
@@ -19,9 +19,12 @@
package org.apache.fop.fo.flow;
+import java.util.Stack;
+
import org.xml.sax.Locator;
import org.apache.fop.apps.FOPException;
+import org.apache.fop.complexscripts.bidi.DelimitedTextRange;
import org.apache.fop.fo.FONode;
import org.apache.fop.fo.FObj;
import org.apache.fop.fo.PropertyList;
@@ -201,5 +204,19 @@ public class ListItem extends FObj implements BreakPropertySet, CommonAccessibil
public int getNameId() {
return FO_LIST_ITEM;
}
+
+ @Override
+ protected Stack collectDelimitedTextRanges ( Stack ranges, DelimitedTextRange currentRange ) {
+ ListItemLabel label = getLabel();
+ if ( label != null ) {
+ ranges = label.collectDelimitedTextRanges ( ranges );
+ }
+ ListItemBody body = getBody();
+ if ( body != null ) {
+ ranges = body.collectDelimitedTextRanges ( ranges );
+ }
+ return ranges;
+ }
+
}
diff --git a/src/java/org/apache/fop/fo/flow/PageNumber.java b/src/java/org/apache/fop/fo/flow/PageNumber.java
index 59169c2d3..fe3e80a75 100644
--- a/src/java/org/apache/fop/fo/flow/PageNumber.java
+++ b/src/java/org/apache/fop/fo/flow/PageNumber.java
@@ -199,4 +199,10 @@ public class PageNumber extends FObj
public int getNameId() {
return FO_PAGE_NUMBER;
}
+
+ @Override
+ public boolean isDelimitedTextRangeBoundary ( int boundary ) {
+ return false;
+ }
+
}
diff --git a/src/java/org/apache/fop/fo/flow/Wrapper.java b/src/java/org/apache/fop/fo/flow/Wrapper.java
index 6c5619252..1302e3134 100644
--- a/src/java/org/apache/fop/fo/flow/Wrapper.java
+++ b/src/java/org/apache/fop/fo/flow/Wrapper.java
@@ -135,5 +135,11 @@ public class Wrapper extends FObjMixed {
public int getNameId() {
return FO_WRAPPER;
}
+
+ @Override
+ public boolean isDelimitedTextRangeBoundary ( int boundary ) {
+ return false;
+ }
+
}
diff --git a/src/java/org/apache/fop/fo/flow/table/Table.java b/src/java/org/apache/fop/fo/flow/table/Table.java
index c78f9f959..b14326af5 100644
--- a/src/java/org/apache/fop/fo/flow/table/Table.java
+++ b/src/java/org/apache/fop/fo/flow/table/Table.java
@@ -48,7 +48,7 @@ import org.apache.fop.fo.properties.TableColLength;
public class Table extends TableFObj implements ColumnNumberManagerHolder, BreakPropertySet,
CommonAccessibilityHolder {
- /** properties */
+ // The value of FO traits (refined properties) that apply to fo:table.
private CommonAccessibility commonAccessibility;
private CommonBorderPaddingBackground commonBorderPaddingBackground;
private CommonMarginBlock commonMarginBlock;
@@ -64,11 +64,12 @@ public class Table extends TableFObj implements ColumnNumberManagerHolder, Break
private int tableLayout;
private int tableOmitFooterAtBreak;
private int tableOmitHeaderAtBreak;
+ private int writingMode;
// Unused but valid items, commented out for performance:
// private CommonAural commonAural;
// private CommonRelativePosition commonRelativePosition;
// private int intrusionDisplace;
- // private int writingMode;
+ // End of FO trait values
/** extension properties */
private Length widowContentLimit;
@@ -130,6 +131,7 @@ public class Table extends TableFObj implements ColumnNumberManagerHolder, Break
tableLayout = pList.get(PR_TABLE_LAYOUT).getEnum();
tableOmitFooterAtBreak = pList.get(PR_TABLE_OMIT_FOOTER_AT_BREAK).getEnum();
tableOmitHeaderAtBreak = pList.get(PR_TABLE_OMIT_HEADER_AT_BREAK).getEnum();
+ writingMode = pList.get(PR_WRITING_MODE).getEnum();
//Bind extension properties
widowContentLimit = pList.get(PR_X_WIDOW_CONTENT_LIMIT).getLength();
@@ -336,7 +338,6 @@ public class Table extends TableFObj implements ColumnNumberManagerHolder, Break
TableColumn implicitColumn = new TableColumn(this, true);
PropertyList pList = new StaticPropertyList(
implicitColumn, this.propList);
- pList.setWritingMode();
implicitColumn.bind(pList);
implicitColumn.setColumnWidth(new TableColLength(1.0, implicitColumn));
implicitColumn.setColumnNumber(colNumber);
@@ -433,14 +434,14 @@ public class Table extends TableFObj implements ColumnNumberManagerHolder, Break
}
/**
- * @return the "inline-progression-dimension" property.
+ * @return the "inline-progression-dimension" FO trait.
*/
public LengthRangeProperty getInlineProgressionDimension() {
return inlineProgressionDimension;
}
/**
- * @return the "block-progression-dimension" property.
+ * @return the "block-progression-dimension" FO trait.
*/
public LengthRangeProperty getBlockProgressionDimension() {
return blockProgressionDimension;
@@ -460,27 +461,27 @@ public class Table extends TableFObj implements ColumnNumberManagerHolder, Break
return commonBorderPaddingBackground;
}
- /** @return the "break-after" property. */
+ /** @return the "break-after" FO trait. */
public int getBreakAfter() {
return breakAfter;
}
- /** @return the "break-before" property. */
+ /** @return the "break-before" FO trait. */
public int getBreakBefore() {
return breakBefore;
}
- /** @return the "keep-with-next" property. */
+ /** @return the "keep-with-next" FO trait. */
public KeepProperty getKeepWithNext() {
return keepWithNext;
}
- /** @return the "keep-with-previous" property. */
+ /** @return the "keep-with-previous" FO trait. */
public KeepProperty getKeepWithPrevious() {
return keepWithPrevious;
}
- /** @return the "keep-together" property. */
+ /** @return the "keep-together" FO trait. */
public KeepProperty getKeepTogether() {
return keepTogether;
}
@@ -494,7 +495,7 @@ public class Table extends TableFObj implements ColumnNumberManagerHolder, Break
|| !getKeepTogether().getWithinColumn().isAuto();
}
- /** @return the "border-collapse" property. */
+ /** @return the "border-collapse" FO trait. */
public int getBorderCollapse() {
return borderCollapse;
}
@@ -504,17 +505,22 @@ public class Table extends TableFObj implements ColumnNumberManagerHolder, Break
return (getBorderCollapse() == EN_SEPARATE);
}
- /** @return the "border-separation" property. */
+ /** @return the "border-separation" FO trait. */
public LengthPairProperty getBorderSeparation() {
return borderSeparation;
}
- /** @return the "fox:widow-content-limit" extension property */
+ /** @return the "writing-mode" FO trait */
+ public int getWritingMode() {
+ return writingMode;
+ }
+
+ /** @return the "fox:widow-content-limit" extension FO trait */
public Length getWidowContentLimit() {
return widowContentLimit;
}
- /** @return the "fox:orphan-content-limit" extension property */
+ /** @return the "fox:orphan-content-limit" extension FO trait */
public Length getOrphanContentLimit() {
return orphanContentLimit;
}
diff --git a/src/java/org/apache/fop/fo/pagination/AbstractPageSequence.java b/src/java/org/apache/fop/fo/pagination/AbstractPageSequence.java
index 51b24b314..8640ab19b 100644
--- a/src/java/org/apache/fop/fo/pagination/AbstractPageSequence.java
+++ b/src/java/org/apache/fop/fo/pagination/AbstractPageSequence.java
@@ -43,6 +43,9 @@ public abstract class AbstractPageSequence extends FObj {
private char groupingSeparator;
private int groupingSize;
private Numeric referenceOrientation; //XSL 1.1
+ private String language;
+ private String country;
+ private String numberConversionFeatures;
// End of property values
private PageNumberGenerator pageNumberGenerator;
@@ -70,12 +73,16 @@ public abstract class AbstractPageSequence extends FObj {
groupingSeparator = pList.get(PR_GROUPING_SEPARATOR).getCharacter();
groupingSize = pList.get(PR_GROUPING_SIZE).getNumber().intValue();
referenceOrientation = pList.get(PR_REFERENCE_ORIENTATION).getNumeric();
+ language = pList.get(PR_LANGUAGE).getString();
+ country = pList.get(PR_COUNTRY).getString();
+ numberConversionFeatures = pList.get(PR_X_NUMBER_CONVERSION_FEATURES).getString();
}
/** {@inheritDoc} */
protected void startOfNode() throws FOPException {
this.pageNumberGenerator = new PageNumberGenerator(
- format, groupingSeparator, groupingSize, letterValue);
+ format, groupingSeparator, groupingSize, letterValue,
+ numberConversionFeatures, language, country);
}
diff --git a/src/java/org/apache/fop/fo/pagination/PageNumberGenerator.java b/src/java/org/apache/fop/fo/pagination/PageNumberGenerator.java
index 485fb67cc..ebd1919c4 100644
--- a/src/java/org/apache/fop/fo/pagination/PageNumberGenerator.java
+++ b/src/java/org/apache/fop/fo/pagination/PageNumberGenerator.java
@@ -19,84 +19,37 @@
package org.apache.fop.fo.pagination;
+import org.apache.fop.complexscripts.util.NumberConverter;
+
+// CSOFF: LineLengthCheck
+
/**
* This class uses the 'format', 'groupingSeparator', 'groupingSize',
* and 'letterValue' properties on fo:page-sequence to return a String
* corresponding to the supplied integer page number.
+ *
+ * In addition, (now) uses 'language' parameter and new 'fox:page-number-features'
+ * parameter to express applicable language and number conversion features.
+ *
+ * @author Glenn Adams (rewrite to use new NumberConverter utility)
+ * @see NumberConverter
*/
public class PageNumberGenerator {
- private String format;
- private char groupingSeparator;
- private int groupingSize;
- private int letterValue;
-
- // constants
- private static final int DECIMAL = 1; // '0*1'
- private static final int LOWERALPHA = 2; // 'a'
- private static final int UPPERALPHA = 3; // 'A'
- private static final int LOWERROMAN = 4; // 'i'
- private static final int UPPERROMAN = 5; // 'I'
-
- // flags
- private int formatType = DECIMAL;
- private int minPadding = 0; // for decimal formats
-
- // preloaded strings of zeros
- private String[] zeros = {
- "", "0", "00", "000", "0000", "00000"
- };
+ private NumberConverter converter;
/**
- * Main constructor. For further information on the parameters see the XSLT
- * specs (Number to String Conversion Attributes).
- * @param format format for the page number
- * @param groupingSeparator grouping separator
- * @param groupingSize grouping size
+ * Main constructor. For further information on the parameters see {@link NumberConverter}.
+ * @param format format for the page number (may be null or empty, which is treated as null)
+ * @param groupingSeparator grouping separator (if zero, then no grouping separator applies)
+ * @param groupingSize grouping size (if zero or negative, then no grouping size applies)
* @param letterValue letter value
+ * @param features features (feature sub-parameters)
+ * @param language (may be null or empty, which is treated as null)
+ * @param country (may be null or empty, which is treated as null)
*/
- public PageNumberGenerator(String format, char groupingSeparator,
- int groupingSize, int letterValue) {
- this.format = format;
- this.groupingSeparator = groupingSeparator;
- this.groupingSize = groupingSize;
- this.letterValue = letterValue;
-
- // the only accepted format strings are currently '0*1' 'a', 'A', 'i'
- // and 'I'
- int fmtLen = format.length();
- if (fmtLen == 1) {
- if (format.equals("1")) {
- formatType = DECIMAL;
- minPadding = 0;
- } else if (format.equals("a")) {
- formatType = LOWERALPHA;
- } else if (format.equals("A")) {
- formatType = UPPERALPHA;
- } else if (format.equals("i")) {
- formatType = LOWERROMAN;
- } else if (format.equals("I")) {
- formatType = UPPERROMAN;
- } else {
- // token not handled
- //getLogger().debug("'format' token not recognized; using '1'");
- formatType = DECIMAL;
- minPadding = 0;
- }
- } else {
- // only accepted token is '0+1'at this stage. Because of the
- // wonderful regular expression support in Java, we will resort to a
- // loop
- for (int i = 0; i < fmtLen - 1; i++) {
- if (format.charAt(i) != '0') {
- //getLogger().debug("'format' token not recognized; using '1'");
- formatType = DECIMAL;
- minPadding = 0;
- } else {
- minPadding = fmtLen - 1;
- }
- }
- }
+ public PageNumberGenerator ( String format, int groupingSeparator, int groupingSize, int letterValue, String features, String language, String country ) {
+ this.converter = new NumberConverter ( format, groupingSeparator, groupingSize, letterValue, features, language, country );
}
/**
@@ -104,70 +57,9 @@ public class PageNumberGenerator {
* @param number page number to format
* @return the formatted page number as a String
*/
- public String makeFormattedPageNumber(int number) {
- String pn = null;
- if (formatType == DECIMAL) {
- pn = Integer.toString(number);
- if (minPadding >= pn.length()) {
- int nz = minPadding - pn.length() + 1;
- pn = zeros[nz] + pn;
- }
- } else if ((formatType == LOWERROMAN) || (formatType == UPPERROMAN)) {
- pn = makeRoman(number);
- if (formatType == UPPERROMAN) {
- pn = pn.toUpperCase();
- }
- } else {
- // alphabetic
- pn = makeAlpha(number);
- if (formatType == UPPERALPHA) {
- pn = pn.toUpperCase();
- }
- }
- return pn;
+ public String makeFormattedPageNumber ( int number ) {
+ return converter.convert ( number );
}
- private String makeRoman(int num) {
- int[] arabic = {
- 1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1
- };
- String[] roman = {
- "m", "cm", "d", "cd", "c", "xc", "l", "xl", "x", "ix", "v", "iv",
- "i"
- };
-
- int i = 0;
- StringBuffer romanNumber = new StringBuffer();
-
- while (num > 0) {
- while (num >= arabic[i]) {
- num = num - arabic[i];
- romanNumber.append(roman[i]);
- }
- i = i + 1;
- }
- return romanNumber.toString();
- }
-
- private String makeAlpha(int num) {
- String letters = "abcdefghijklmnopqrstuvwxyz";
- StringBuffer alphaNumber = new StringBuffer();
-
- int base = 26;
- int rem = 0;
-
- num--;
- if (num < base) {
- alphaNumber.append(letters.charAt(num));
- } else {
- while (num >= base) {
- rem = num % base;
- alphaNumber.append(letters.charAt(rem));
- num = num / base;
- }
- alphaNumber.append(letters.charAt(num - 1));
- }
- return alphaNumber.reverse().toString();
- }
}
diff --git a/src/java/org/apache/fop/fo/pagination/PageSequence.java b/src/java/org/apache/fop/fo/pagination/PageSequence.java
index 1c3b33fa7..63114b507 100644
--- a/src/java/org/apache/fop/fo/pagination/PageSequence.java
+++ b/src/java/org/apache/fop/fo/pagination/PageSequence.java
@@ -19,28 +19,35 @@
package org.apache.fop.fo.pagination;
-// Java
import java.util.Map;
+import java.util.Stack;
import org.xml.sax.Locator;
import org.apache.fop.apps.FOPException;
+import org.apache.fop.complexscripts.bidi.DelimitedTextRange;
+import org.apache.fop.datatypes.Numeric;
import org.apache.fop.fo.FONode;
import org.apache.fop.fo.PropertyList;
import org.apache.fop.fo.ValidationException;
+import org.apache.fop.traits.Direction;
+import org.apache.fop.traits.WritingMode;
+import org.apache.fop.traits.WritingModeTraits;
+import org.apache.fop.traits.WritingModeTraitsGetter;
/**
* Class modelling the <a href="http://www.w3.org/TR/xsl/#fo_page-sequence">
* <code>fo:page-sequence</code></a> object.
*/
-public class PageSequence extends AbstractPageSequence {
+public class PageSequence extends AbstractPageSequence implements WritingModeTraitsGetter {
- // The value of properties relevant for fo:page-sequence.
+ // The value of FO traits (refined properties) that apply to fo:page-sequence.
private String country;
private String language;
private String masterReference;
- //private int writingMode; //XSL 1.1
- // End of property values
+ private Numeric referenceOrientation;
+ private WritingModeTraits writingModeTraits;
+ // End of trait values
// There doesn't seem to be anything in the spec requiring flows
// to be in the order given, only that they map to the regions
@@ -86,8 +93,9 @@ public class PageSequence extends AbstractPageSequence {
country = pList.get(PR_COUNTRY).getString();
language = pList.get(PR_LANGUAGE).getString();
masterReference = pList.get(PR_MASTER_REFERENCE).getString();
- //writingMode = pList.getWritingMode();
-
+ referenceOrientation = pList.get(PR_REFERENCE_ORIENTATION).getNumeric();
+ writingModeTraits = new WritingModeTraits
+ ( WritingMode.valueOf(pList.get(PR_WRITING_MODE).getEnum()) );
if (masterReference == null || masterReference.equals("")) {
missingPropertyError("master-reference");
}
@@ -292,8 +300,8 @@ public class PageSequence extends AbstractPageSequence {
}
/**
- * Get the value of the <code>master-reference</code> property.
- * @return the "master-reference" property
+ * Get the value of the <code>master-reference</code> trait.
+ * @return the "master-reference" trait
*/
public String getMasterReference() {
return masterReference;
@@ -313,22 +321,120 @@ public class PageSequence extends AbstractPageSequence {
}
/**
- * Get the value of the <code>country</code> property.
- * @return the country property value
+ * Get the value of the <code>country</code> trait.
+ * @return the country trait value
*/
public String getCountry() {
return this.country;
}
/**
- * Get the value of the <code>language</code> property.
- * @return the language property value
+ * Get the value of the <code>language</code> trait.
+ * @return the language trait value
*/
public String getLanguage() {
return this.language;
}
/**
+ * Get the value of the <code>reference-orientation</code> trait.
+ * @return the reference orientation trait value
+ */
+ public int getReferenceOrientation() {
+ if ( referenceOrientation != null ) {
+ return referenceOrientation.getValue();
+ } else {
+ return 0;
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public Direction getInlineProgressionDirection() {
+ if ( writingModeTraits != null ) {
+ return writingModeTraits.getInlineProgressionDirection();
+ } else {
+ return Direction.LR;
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public Direction getBlockProgressionDirection() {
+ if ( writingModeTraits != null ) {
+ return writingModeTraits.getBlockProgressionDirection();
+ } else {
+ return Direction.TB;
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public Direction getColumnProgressionDirection() {
+ if ( writingModeTraits != null ) {
+ return writingModeTraits.getColumnProgressionDirection();
+ } else {
+ return Direction.LR;
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public Direction getRowProgressionDirection() {
+ if ( writingModeTraits != null ) {
+ return writingModeTraits.getRowProgressionDirection();
+ } else {
+ return Direction.TB;
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public Direction getShiftDirection() {
+ if ( writingModeTraits != null ) {
+ return writingModeTraits.getShiftDirection();
+ } else {
+ return Direction.TB;
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public WritingMode getWritingMode() {
+ if ( writingModeTraits != null ) {
+ return writingModeTraits.getWritingMode();
+ } else {
+ return WritingMode.LR_TB;
+ }
+ }
+
+
+ @Override
+ protected Stack collectDelimitedTextRanges ( Stack ranges, DelimitedTextRange currentRange ) {
+ // collect ranges from static content flows
+ Map<String, FONode> flows = getFlowMap();
+ if ( flows != null ) {
+ for ( FONode fn : flows.values() ) {
+ if ( fn instanceof StaticContent ) {
+ ranges = ( (StaticContent) fn ).collectDelimitedTextRanges ( ranges );
+ }
+ }
+ }
+ // collect ranges in main flow
+ Flow main = getMainFlow();
+ if ( main != null ) {
+ ranges = main.collectDelimitedTextRanges ( ranges );
+ }
+ return ranges;
+ }
+
+ /**
* Releases a page-sequence's children after the page-sequence has been fully processed.
*/
public void releasePageSequence() {
diff --git a/src/java/org/apache/fop/fo/pagination/Region.java b/src/java/org/apache/fop/fo/pagination/Region.java
index a3c259aa7..16956b6dd 100644
--- a/src/java/org/apache/fop/fo/pagination/Region.java
+++ b/src/java/org/apache/fop/fo/pagination/Region.java
@@ -32,20 +32,21 @@ import org.apache.fop.fo.FObj;
import org.apache.fop.fo.PropertyList;
import org.apache.fop.fo.ValidationException;
import org.apache.fop.fo.properties.CommonBorderPaddingBackground;
+import org.apache.fop.traits.WritingMode;
/**
* This is an abstract base class for pagination regions.
*/
public abstract class Region extends FObj {
- // The value of properties relevant for fo:region
+ // The value of FO traits (refined properties) that apply to fo:region
private CommonBorderPaddingBackground commonBorderPaddingBackground;
// private ToBeImplementedProperty clip
private int displayAlign;
private int overflow;
private String regionName;
private Numeric referenceOrientation;
- private int writingMode;
- // End of property values
+ private WritingMode writingMode;
+ // End of FO trait values
/** the parent {@link SimplePageMaster} */
protected final SimplePageMaster layoutMaster;
@@ -68,7 +69,7 @@ public abstract class Region extends FObj {
overflow = pList.get(PR_OVERFLOW).getEnum();
regionName = pList.get(PR_REGION_NAME).getString();
referenceOrientation = pList.get(PR_REFERENCE_ORIENTATION).getNumeric();
- writingMode = pList.getWritingMode();
+ writingMode = WritingMode.valueOf(pList.get(PR_WRITING_MODE).getEnum());
// regions may have name, or default
if (regionName.equals("")) {
@@ -169,28 +170,28 @@ public abstract class Region extends FObj {
return commonBorderPaddingBackground;
}
- /** @return the "region-name" property. */
+ /** @return the "region-name" FO trait. */
public String getRegionName() {
return regionName;
}
- /** @return the "writing-mode" property. */
- public int getWritingMode() {
- return writingMode;
- }
-
- /** @return the "overflow" property. */
+ /** @return the "overflow" FO trait. */
public int getOverflow() {
return overflow;
}
- /** @return the display-align property. */
+ /** @return the display-align FO trait. */
public int getDisplayAlign() {
return displayAlign;
}
- /** @return the "reference-orientation" property. */
+ /** @return the "reference-orientation" FO trait. */
public int getReferenceOrientation() {
return referenceOrientation.getValue();
}
+
+ /** @return the "writing-mode" FO trait. */
+ public WritingMode getWritingMode() {
+ return writingMode;
+ }
}
diff --git a/src/java/org/apache/fop/fo/pagination/RegionAfter.java b/src/java/org/apache/fop/fo/pagination/RegionAfter.java
index bab5d46b8..3541c54be 100644
--- a/src/java/org/apache/fop/fo/pagination/RegionAfter.java
+++ b/src/java/org/apache/fop/fo/pagination/RegionAfter.java
@@ -23,6 +23,7 @@ package org.apache.fop.fo.pagination;
import java.awt.Rectangle;
// FOP
+import org.apache.fop.fo.Constants;
import org.apache.fop.fo.FONode;
import org.apache.fop.datatypes.FODimension;
import org.apache.fop.datatypes.LengthBase;
@@ -52,15 +53,22 @@ public class RegionAfter extends RegionBA {
PercentBaseContext pageHeightContext = getPageHeightContext(LengthBase.CUSTOM_BASE);
PercentBaseContext neighbourContext;
Rectangle vpRect;
- if (layoutMaster.getWritingMode() == EN_LR_TB
- || layoutMaster.getWritingMode() == EN_RL_TB) {
+
+ // [TBD] WRITING MODE ALERT
+ switch ( getWritingMode().getEnumValue() ) {
+ default:
+ case Constants.EN_LR_TB:
+ case Constants.EN_RL_TB:
neighbourContext = pageWidthContext;
vpRect = new Rectangle(0, reldims.bpd - getExtent().getValue(pageHeightContext)
, reldims.ipd, getExtent().getValue(pageHeightContext));
- } else {
+ break;
+ case Constants.EN_TB_LR:
+ case Constants.EN_TB_RL:
neighbourContext = pageHeightContext;
vpRect = new Rectangle(0, reldims.bpd - getExtent().getValue(pageWidthContext)
, getExtent().getValue(pageWidthContext), reldims.ipd);
+ break;
}
if (getPrecedence() == EN_FALSE) {
adjustIPD(vpRect, layoutMaster.getWritingMode(), neighbourContext);
diff --git a/src/java/org/apache/fop/fo/pagination/RegionBA.java b/src/java/org/apache/fop/fo/pagination/RegionBA.java
index 149e470cf..eac7723f5 100644
--- a/src/java/org/apache/fop/fo/pagination/RegionBA.java
+++ b/src/java/org/apache/fop/fo/pagination/RegionBA.java
@@ -26,6 +26,7 @@ import org.apache.fop.apps.FOPException;
import org.apache.fop.datatypes.PercentBaseContext;
import org.apache.fop.fo.FONode;
import org.apache.fop.fo.PropertyList;
+import org.apache.fop.traits.WritingMode;
/**
* Abstract base class for <a href="http://www.w3.org/TR/xsl/#fo_region-before">
@@ -70,7 +71,8 @@ public abstract class RegionBA extends SideRegion {
* @param wm writing mode
* @param siblingContext the context to use to resolve extent on siblings
*/
- protected void adjustIPD(Rectangle vpRefRect, int wm, PercentBaseContext siblingContext) {
+ protected void adjustIPD
+ ( Rectangle vpRefRect, WritingMode wm, PercentBaseContext siblingContext ) {
int offset = 0;
RegionStart start = (RegionStart) getSiblingRegion(FO_REGION_START);
if (start != null) {
@@ -81,8 +83,9 @@ public abstract class RegionBA extends SideRegion {
if (end != null) {
offset += end.getExtent().getValue(siblingContext);
}
+ // [TBD] WRITING MODE ALERT
if (offset > 0) {
- if (wm == EN_LR_TB || wm == EN_RL_TB) {
+ if (wm == WritingMode.LR_TB || wm == WritingMode.RL_TB) {
vpRefRect.width -= offset;
} else {
vpRefRect.height -= offset;
diff --git a/src/java/org/apache/fop/fo/pagination/RegionBefore.java b/src/java/org/apache/fop/fo/pagination/RegionBefore.java
index 4aa29ec90..295f6dc1c 100644
--- a/src/java/org/apache/fop/fo/pagination/RegionBefore.java
+++ b/src/java/org/apache/fop/fo/pagination/RegionBefore.java
@@ -23,6 +23,7 @@ package org.apache.fop.fo.pagination;
import java.awt.Rectangle;
// FOP
+import org.apache.fop.fo.Constants;
import org.apache.fop.datatypes.FODimension;
import org.apache.fop.datatypes.LengthBase;
import org.apache.fop.datatypes.PercentBaseContext;
@@ -57,13 +58,19 @@ public class RegionBefore extends RegionBA {
PercentBaseContext pageHeightContext = getPageHeightContext(LengthBase.CUSTOM_BASE);
PercentBaseContext neighbourContext;
Rectangle vpRect;
- if (layoutMaster.getWritingMode() == EN_LR_TB
- || layoutMaster.getWritingMode() == EN_RL_TB) {
+ // [TBD] WRITING MODE ALERT
+ switch ( getWritingMode().getEnumValue() ) {
+ default:
+ case Constants.EN_LR_TB:
+ case Constants.EN_RL_TB:
neighbourContext = pageWidthContext;
vpRect = new Rectangle(0, 0, reldims.ipd, getExtent().getValue(pageHeightContext));
- } else {
+ break;
+ case Constants.EN_TB_LR:
+ case Constants.EN_TB_RL:
neighbourContext = pageHeightContext;
vpRect = new Rectangle(0, 0, getExtent().getValue(pageWidthContext), reldims.ipd);
+ break;
}
if (getPrecedence() == EN_FALSE) {
adjustIPD(vpRect, layoutMaster.getWritingMode(), neighbourContext);
diff --git a/src/java/org/apache/fop/fo/pagination/RegionBody.java b/src/java/org/apache/fop/fo/pagination/RegionBody.java
index 5eed36061..9e6392337 100644
--- a/src/java/org/apache/fop/fo/pagination/RegionBody.java
+++ b/src/java/org/apache/fop/fo/pagination/RegionBody.java
@@ -28,9 +28,11 @@ import org.apache.fop.datatypes.Length;
import org.apache.fop.datatypes.LengthBase;
import org.apache.fop.datatypes.Numeric;
import org.apache.fop.datatypes.PercentBaseContext;
+import org.apache.fop.fo.Constants;
import org.apache.fop.fo.FONode;
import org.apache.fop.fo.PropertyList;
import org.apache.fop.fo.properties.CommonMarginBlock;
+import org.apache.fop.traits.WritingMode;
/**
* Class modelling the <a href="http://www.w3.org/TR/xsl/#fo_region-body">
@@ -113,12 +115,22 @@ public class RegionBody extends Region {
int start;
int end;
- if (layoutMaster.getWritingMode() == EN_LR_TB) { // Left-to-right
+ // [TBD] WRITING MODE ALERT
+ switch ( getWritingMode().getEnumValue() ) {
+ default:
+ case Constants.EN_LR_TB:
start = commonMarginBlock.marginLeft.getValue(pageWidthContext);
end = commonMarginBlock.marginRight.getValue(pageWidthContext);
- } else { // all other supported modes are right-to-left
+ break;
+ case Constants.EN_RL_TB:
start = commonMarginBlock.marginRight.getValue(pageWidthContext);
end = commonMarginBlock.marginLeft.getValue(pageWidthContext);
+ break;
+ case Constants.EN_TB_LR:
+ case Constants.EN_TB_RL:
+ start = commonMarginBlock.marginTop.getValue(pageWidthContext);
+ end = commonMarginBlock.marginBottom.getValue(pageWidthContext);
+ break;
}
int before = commonMarginBlock.spaceBefore.getOptimum(pageHeightContext)
.getLength().getValue(pageHeightContext);
diff --git a/src/java/org/apache/fop/fo/pagination/RegionEnd.java b/src/java/org/apache/fop/fo/pagination/RegionEnd.java
index 2533763a5..ce0258524 100644
--- a/src/java/org/apache/fop/fo/pagination/RegionEnd.java
+++ b/src/java/org/apache/fop/fo/pagination/RegionEnd.java
@@ -23,6 +23,7 @@ package org.apache.fop.fo.pagination;
import java.awt.Rectangle;
// FOP
+import org.apache.fop.fo.Constants;
import org.apache.fop.fo.FONode;
import org.apache.fop.datatypes.FODimension;
import org.apache.fop.datatypes.LengthBase;
@@ -52,18 +53,27 @@ public class RegionEnd extends RegionSE {
PercentBaseContext pageHeightContext = getPageHeightContext(LengthBase.CUSTOM_BASE);
PercentBaseContext neighbourContext;
Rectangle vpRect;
- if (layoutMaster.getWritingMode() == EN_LR_TB
- || layoutMaster.getWritingMode() == EN_RL_TB) {
+ // [TBD] WRITING MODE ALERT
+ switch ( getWritingMode().getEnumValue() ) {
+ default:
+ case Constants.EN_LR_TB:
neighbourContext = pageHeightContext;
vpRect = new Rectangle(reldims.ipd - getExtent().getValue(pageWidthContext), 0,
getExtent().getValue(pageWidthContext), reldims.bpd);
- } else {
+ 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
neighbourContext = pageWidthContext;
vpRect = new Rectangle(reldims.ipd - getExtent().getValue(pageHeightContext), 0,
reldims.bpd, getExtent().getValue(pageHeightContext));
+ break;
}
- adjustIPD(vpRect, layoutMaster.getWritingMode(), neighbourContext);
+ adjustIPD(vpRect, getWritingMode(), neighbourContext);
return vpRect;
}
diff --git a/src/java/org/apache/fop/fo/pagination/RegionSE.java b/src/java/org/apache/fop/fo/pagination/RegionSE.java
index f106e8a36..9fc2bdc27 100644
--- a/src/java/org/apache/fop/fo/pagination/RegionSE.java
+++ b/src/java/org/apache/fop/fo/pagination/RegionSE.java
@@ -26,6 +26,7 @@ import org.apache.fop.apps.FOPException;
import org.apache.fop.datatypes.PercentBaseContext;
import org.apache.fop.fo.FONode;
import org.apache.fop.fo.PropertyList;
+import org.apache.fop.traits.WritingMode;
/**
* Abstract base class for <a href="http://www.w3.org/TR/xsl/#fo_region-start">
@@ -61,7 +62,8 @@ public abstract class RegionSE extends SideRegion {
* @param wm writing mode
* @param siblingContext the context to use to resolve extent on siblings
*/
- protected void adjustIPD(Rectangle vpRefRect, int wm, PercentBaseContext siblingContext) {
+ protected void adjustIPD
+ ( Rectangle vpRefRect, WritingMode wm, PercentBaseContext siblingContext ) {
int offset = 0;
RegionBefore before = (RegionBefore) getSiblingRegion(FO_REGION_BEFORE);
if (before != null && before.getPrecedence() == EN_TRUE) {
@@ -72,8 +74,9 @@ public abstract class RegionSE extends SideRegion {
if (after != null && after.getPrecedence() == EN_TRUE) {
offset += after.getExtent().getValue(siblingContext);
}
+ // [TBD] WRITING MODE ALERT
if (offset > 0) {
- if (wm == EN_LR_TB || wm == EN_RL_TB) {
+ if (wm == WritingMode.LR_TB || wm == WritingMode.RL_TB) {
vpRefRect.height -= offset;
} else {
vpRefRect.width -= offset;
diff --git a/src/java/org/apache/fop/fo/pagination/RegionStart.java b/src/java/org/apache/fop/fo/pagination/RegionStart.java
index fdb423c51..c2fc3fe17 100644
--- a/src/java/org/apache/fop/fo/pagination/RegionStart.java
+++ b/src/java/org/apache/fop/fo/pagination/RegionStart.java
@@ -23,6 +23,7 @@ package org.apache.fop.fo.pagination;
import java.awt.Rectangle;
// FOP
+import org.apache.fop.fo.Constants;
import org.apache.fop.fo.FONode;
import org.apache.fop.datatypes.FODimension;
import org.apache.fop.datatypes.LengthBase;
@@ -52,13 +53,23 @@ public class RegionStart extends RegionSE {
PercentBaseContext pageHeightContext = getPageHeightContext(LengthBase.CUSTOM_BASE);
PercentBaseContext neighbourContext;
Rectangle vpRect;
- if (layoutMaster.getWritingMode() == EN_LR_TB
- || layoutMaster.getWritingMode() == EN_RL_TB) {
+ // [TBD] WRITING MODE ALERT
+ switch ( getWritingMode().getEnumValue() ) {
+ default:
+ case Constants.EN_LR_TB:
neighbourContext = pageHeightContext;
vpRect = new Rectangle(0, 0, getExtent().getValue(pageWidthContext), reldims.bpd);
- } else {
+ 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;
vpRect = new Rectangle(0, 0, reldims.bpd, getExtent().getValue(pageHeightContext));
+ break;
}
adjustIPD(vpRect, layoutMaster.getWritingMode(), neighbourContext);
return vpRect;
diff --git a/src/java/org/apache/fop/fo/pagination/SimplePageMaster.java b/src/java/org/apache/fop/fo/pagination/SimplePageMaster.java
index e38993487..79c8c3358 100644
--- a/src/java/org/apache/fop/fo/pagination/SimplePageMaster.java
+++ b/src/java/org/apache/fop/fo/pagination/SimplePageMaster.java
@@ -35,6 +35,7 @@ import org.apache.fop.fo.FObj;
import org.apache.fop.fo.PropertyList;
import org.apache.fop.fo.ValidationException;
import org.apache.fop.fo.properties.CommonMarginBlock;
+import org.apache.fop.traits.WritingMode;
/**
* Class modelling the <a href="http://www.w3.org/TR/xsl/#fo_simple-page-master">
@@ -43,14 +44,14 @@ import org.apache.fop.fo.properties.CommonMarginBlock;
* and attributes.
*/
public class SimplePageMaster extends FObj {
- // The value of properties relevant for fo:simple-page-master.
+ // The value of FO traits (refined properties) that apply to fo:simple-page-master.
private CommonMarginBlock commonMarginBlock;
private String masterName;
private Length pageHeight;
private Length pageWidth;
private Numeric referenceOrientation;
- private int writingMode;
- // End of property values
+ private WritingMode writingMode;
+ // End of FO trait values
/**
* Page regions (regionClass, Region)
@@ -80,7 +81,7 @@ public class SimplePageMaster extends FObj {
pageHeight = pList.get(PR_PAGE_HEIGHT).getLength();
pageWidth = pList.get(PR_PAGE_WIDTH).getLength();
referenceOrientation = pList.get(PR_REFERENCE_ORIENTATION).getNumeric();
- writingMode = pList.getWritingMode();
+ writingMode = WritingMode.valueOf(pList.get(PR_WRITING_MODE).getEnum());
if (masterName == null || masterName.equals("")) {
missingPropertyError("master-name");
@@ -269,31 +270,31 @@ public class SimplePageMaster extends FObj {
return commonMarginBlock;
}
- /** @return "master-name" property. */
+ /** @return "master-name" FO trait. */
public String getMasterName() {
return masterName;
}
- /** @return the "page-width" property. */
+ /** @return the "page-width" FO trait. */
public Length getPageWidth() {
return pageWidth;
}
- /** @return the "page-height" property. */
+ /** @return the "page-height" FO trait. */
public Length getPageHeight() {
return pageHeight;
}
- /** @return the "writing-mode" property. */
- public int getWritingMode() {
- return writingMode;
- }
-
- /** @return the "reference-orientation" property. */
+ /** @return the "reference-orientation" FO trait. */
public int getReferenceOrientation() {
return referenceOrientation.getValue();
}
+ /** @return the "writing-mode" FO trait. */
+ public WritingMode getWritingMode() {
+ return writingMode;
+ }
+
/** {@inheritDoc} */
public String getLocalName() {
return "simple-page-master";
diff --git a/src/java/org/apache/fop/fo/properties/CorrespondingPropertyMaker.java b/src/java/org/apache/fop/fo/properties/CorrespondingPropertyMaker.java
index 6183b9e56..b74c9dcfb 100644
--- a/src/java/org/apache/fop/fo/properties/CorrespondingPropertyMaker.java
+++ b/src/java/org/apache/fop/fo/properties/CorrespondingPropertyMaker.java
@@ -35,6 +35,8 @@ public class CorrespondingPropertyMaker {
protected int rltb;
/** corresponding property for tb-rl writing mode */
protected int tbrl;
+ /** corresponding property for tb-lr writing mode */
+ protected int tblr;
/** user parent property list */
protected boolean useParent;
private boolean relative;
@@ -48,17 +50,26 @@ public class CorrespondingPropertyMaker {
baseMaker.setCorresponding(this);
}
+ /**
+ * Set corresponding property values.
+ * @param lrtb a corresponding value
+ * @param rltb a corresponding value
+ * @param tbrl a corresponding value
+ * @param tblr a corresponding value
+ */
/**
* Set corresponding property identifiers.
* @param lrtb the property that corresponds with lr-tb writing mode
* @param rltb the property that corresponds with rl-tb writing mode
* @param tbrl the property that corresponds with tb-lr writing mode
+ * @param tblr the property that corresponds with tb-lr writing mode
*/
- public void setCorresponding(int lrtb, int rltb, int tbrl) {
+ public void setCorresponding(int lrtb, int rltb, int tbrl, int tblr) {
this.lrtb = lrtb;
this.rltb = rltb;
this.tbrl = tbrl;
+ this.tblr = tblr;
}
/**
@@ -72,7 +83,7 @@ public class CorrespondingPropertyMaker {
/**
* Set relative flag.
- * @param relative true if relative direction
+ * @param relative true if properties operate on a relative direction
*/
public void setRelative(boolean relative) {
this.relative = relative;
@@ -102,7 +113,7 @@ public class CorrespondingPropertyMaker {
PropertyList pList = getWMPropertyList(propertyList);
if (pList != null) {
- int correspondingId = pList.getWritingMode(lrtb, rltb, tbrl);
+ int correspondingId = pList.selectFromWritingMode(lrtb, rltb, tbrl, tblr);
if (pList.getExplicit(correspondingId) != null) {
return true;
@@ -126,7 +137,7 @@ public class CorrespondingPropertyMaker {
if (pList == null) {
return null;
}
- int correspondingId = pList.getWritingMode(lrtb, rltb, tbrl);
+ int correspondingId = pList.selectFromWritingMode(lrtb, rltb, tbrl, tblr);
Property p = propertyList.getExplicitOrShorthand(correspondingId);
if (p != null) {
diff --git a/src/java/org/apache/fop/fo/properties/DimensionPropertyMaker.java b/src/java/org/apache/fop/fo/properties/DimensionPropertyMaker.java
index 0bef5e916..13efa6f30 100644
--- a/src/java/org/apache/fop/fo/properties/DimensionPropertyMaker.java
+++ b/src/java/org/apache/fop/fo/properties/DimensionPropertyMaker.java
@@ -34,7 +34,7 @@ public class DimensionPropertyMaker extends CorrespondingPropertyMaker {
private int[][] extraCorresponding = null;
/**
- * Construct a dimension property maker.
+ * Instantiate a dimension property maker.
* @param baseMaker the base property maker
*/
public DimensionPropertyMaker(PropertyMaker baseMaker) {
@@ -43,9 +43,18 @@ public class DimensionPropertyMaker extends CorrespondingPropertyMaker {
/**
* Set extra correspondences.
- * @param extraCorresponding the extra correspondences
+ * @param extraCorresponding an array of four element integer arrays
*/
public void setExtraCorresponding(int[][] extraCorresponding) {
+ if ( extraCorresponding == null ) {
+ throw new NullPointerException();
+ }
+ for ( int i = 0; i < extraCorresponding.length; i++ ) {
+ int[] eca = extraCorresponding[i];
+ if ( ( eca == null ) || ( eca.length != 4 ) ) {
+ throw new IllegalArgumentException ( "bad sub-array @ [" + i + "]" );
+ }
+ }
this.extraCorresponding = extraCorresponding;
}
@@ -76,18 +85,20 @@ public class DimensionPropertyMaker extends CorrespondingPropertyMaker {
}
// Based on min-[width|height]
- int wmcorr = propertyList.getWritingMode(extraCorresponding[0][0],
+ int wmcorr = propertyList.selectFromWritingMode(extraCorresponding[0][0],
extraCorresponding[0][1],
- extraCorresponding[0][2]);
+ extraCorresponding[0][2],
+ extraCorresponding[0][3]);
Property subprop = propertyList.getExplicitOrShorthand(wmcorr);
if (subprop != null) {
baseMaker.setSubprop(p, Constants.CP_MINIMUM, subprop);
}
// Based on max-[width|height]
- wmcorr = propertyList.getWritingMode(extraCorresponding[1][0],
+ wmcorr = propertyList.selectFromWritingMode(extraCorresponding[1][0],
extraCorresponding[1][1],
- extraCorresponding[1][2]);
+ extraCorresponding[1][2],
+ extraCorresponding[1][3]);
subprop = propertyList.getExplicitOrShorthand(wmcorr);
// TODO: Don't set when NONE.
if (subprop != null) {
diff --git a/src/java/org/apache/fop/fo/properties/IndentPropertyMaker.java b/src/java/org/apache/fop/fo/properties/IndentPropertyMaker.java
index 19c4675ed..6b7c58a62 100644
--- a/src/java/org/apache/fop/fo/properties/IndentPropertyMaker.java
+++ b/src/java/org/apache/fop/fo/properties/IndentPropertyMaker.java
@@ -55,6 +55,9 @@ public class IndentPropertyMaker extends CorrespondingPropertyMaker {
* @param paddingCorresponding the corresping propids.
*/
public void setPaddingCorresponding(int[] paddingCorresponding) {
+ if ( ( paddingCorresponding == null ) || ( paddingCorresponding.length != 4 ) ) {
+ throw new IllegalArgumentException();
+ }
this.paddingCorresponding = paddingCorresponding;
}
@@ -63,6 +66,9 @@ public class IndentPropertyMaker extends CorrespondingPropertyMaker {
* @param borderWidthCorresponding the corresping propids.
*/
public void setBorderWidthCorresponding(int[] borderWidthCorresponding) {
+ if ( ( borderWidthCorresponding == null ) || ( borderWidthCorresponding.length != 4 ) ) {
+ throw new IllegalArgumentException();
+ }
this.borderWidthCorresponding = borderWidthCorresponding;
}
@@ -99,7 +105,7 @@ public class IndentPropertyMaker extends CorrespondingPropertyMaker {
Numeric padding = getCorresponding(paddingCorresponding, propertyList).getNumeric();
Numeric border = getCorresponding(borderWidthCorresponding, propertyList).getNumeric();
- int marginProp = pList.getWritingMode(lrtb, rltb, tbrl);
+ int marginProp = pList.selectFromWritingMode(lrtb, rltb, tbrl, tblr);
// Calculate the absolute margin.
if (propertyList.getExplicitOrShorthand(marginProp) == null) {
Property indent = propertyList.getExplicit(baseMaker.propId);
@@ -158,7 +164,7 @@ public class IndentPropertyMaker extends CorrespondingPropertyMaker {
Numeric padding = getCorresponding(paddingCorresponding, propertyList).getNumeric();
Numeric border = getCorresponding(borderWidthCorresponding, propertyList).getNumeric();
- int marginProp = pList.getWritingMode(lrtb, rltb, tbrl);
+ int marginProp = pList.selectFromWritingMode(lrtb, rltb, tbrl, tblr);
//Determine whether the nearest anscestor indent was specified through
//start-indent|end-indent or through a margin property.
@@ -208,7 +214,8 @@ public class IndentPropertyMaker extends CorrespondingPropertyMaker {
throws PropertyException {
PropertyList pList = getWMPropertyList(propertyList);
if (pList != null) {
- int wmcorr = pList.getWritingMode(corresponding[0], corresponding[1], corresponding[2]);
+ int wmcorr = pList.selectFromWritingMode
+ ( corresponding[0], corresponding[1], corresponding[2], corresponding[3] );
return propertyList.get(wmcorr);
} else {
return null;
diff --git a/src/java/org/apache/fop/fonts/BFEntry.java b/src/java/org/apache/fop/fonts/BFEntry.java
index e04b021e4..d3c7956ba 100644
--- a/src/java/org/apache/fop/fonts/BFEntry.java
+++ b/src/java/org/apache/fop/fonts/BFEntry.java
@@ -68,9 +68,15 @@ public class BFEntry {
@Override
public String toString() {
StringBuilder sb = new StringBuilder("BFEntry: ");
- sb.append("Unicode ").append(getUnicodeStart()).append("..").append(getUnicodeEnd());
- sb.append(" --> ").append(getGlyphStartIndex()).append("..");
- sb.append(getGlyphStartIndex() + getUnicodeEnd() - getUnicodeStart());
+ sb.append ( "{ UC[" );
+ sb.append ( unicodeStart );
+ sb.append ( ',' );
+ sb.append ( unicodeEnd );
+ sb.append ( "]: GC[" );
+ sb.append ( glyphStartIndex );
+ sb.append ( ',' );
+ sb.append ( glyphStartIndex + ( unicodeEnd - unicodeStart ) );
+ sb.append ( "] }" );
return sb.toString();
}
diff --git a/src/java/org/apache/fop/fonts/CustomFont.java b/src/java/org/apache/fop/fonts/CustomFont.java
index 4432fccef..c6b43fe98 100644
--- a/src/java/org/apache/fop/fonts/CustomFont.java
+++ b/src/java/org/apache/fop/fonts/CustomFont.java
@@ -60,6 +60,7 @@ public abstract class CustomFont extends Typeface
private Map<Integer, Map<Integer, Integer>> kerning;
private boolean useKerning = true;
+ private boolean useAdvanced = true;
/** {@inheritDoc} */
public String getFontName() {
@@ -285,6 +286,15 @@ public abstract class CustomFont extends Typeface
}
}
+ /**
+ * Used to determine if advanced typographic features are enabled.
+ * By default, this is false, but may be overridden by subclasses.
+ * @return true if enabled.
+ */
+ public boolean isAdvancedEnabled() {
+ return useAdvanced;
+ }
+
/* ---- MutableFont interface ---- */
/** {@inheritDoc} */
@@ -428,6 +438,13 @@ public abstract class CustomFont extends Typeface
}
/**
+ * {@inheritDoc}
+ */
+ public void setAdvancedEnabled(boolean enabled) {
+ this.useAdvanced = enabled;
+ }
+
+ /**
* Sets the font resolver. Needed for URI resolution.
* @param resolver the font resolver
*/
diff --git a/src/java/org/apache/fop/fonts/CustomFontCollection.java b/src/java/org/apache/fop/fonts/CustomFontCollection.java
index 9f98814a3..6e798a8f7 100644
--- a/src/java/org/apache/fop/fonts/CustomFontCollection.java
+++ b/src/java/org/apache/fop/fonts/CustomFontCollection.java
@@ -33,13 +33,14 @@ public class CustomFontCollection implements FontCollection {
* Main constructor.
* @param fontResolver a font resolver
* @param customFonts the list of custom fonts
+ * @param useComplexScriptFeatures true if complex script features enabled
*/
public CustomFontCollection(FontResolver fontResolver,
- List<EmbedFontInfo> customFonts) {
+ List<EmbedFontInfo> customFonts, boolean useComplexScriptFeatures) {
this.fontResolver = fontResolver;
if (this.fontResolver == null) {
//Ensure that we have minimal font resolution capabilities
- this.fontResolver = FontManager.createMinimalFontResolver();
+ this.fontResolver = FontManager.createMinimalFontResolver(useComplexScriptFeatures);
}
this.embedFontInfoList = customFonts;
}
diff --git a/src/java/org/apache/fop/fonts/EmbedFontInfo.java b/src/java/org/apache/fop/fonts/EmbedFontInfo.java
index b53cdfdd6..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;
@@ -37,6 +37,8 @@ public class EmbedFontInfo implements Serializable {
protected String embedFile;
/** false, to disable kerning */
protected boolean kerning;
+ /** false, to disable advanced typographic features */
+ protected boolean advanced;
/** the requested encoding mode for the font */
protected EncodingMode encodingMode = EncodingMode.AUTO;
@@ -52,17 +54,19 @@ public class EmbedFontInfo implements Serializable {
/**
* Main constructor
- * @param metricsFile Path to the xml file containing font metrics
- * @param kerning True if kerning should be enabled
- * @param fontTriplets List of font triplets to associate with this font
- * @param embedFile Path to the embeddable font file (may be null)
+ * @param metricsFile path to the xml file containing font metrics
+ * @param kerning true if kerning should be enabled
+ * @param advanced true if advanced typography features should be enabled
+ * @param fontTriplets list of font triplets to associate with this font
+ * @param embedFile path to the embeddable font file (may be null)
* @param subFontName the sub-fontname used for TrueType Collections (null otherwise)
*/
- public EmbedFontInfo(String metricsFile, boolean kerning,
+ public EmbedFontInfo(String metricsFile, boolean kerning, boolean advanced,
List<FontTriplet> fontTriplets, String embedFile, String subFontName) {
this.metricsFile = metricsFile;
this.embedFile = embedFile;
this.kerning = kerning;
+ this.advanced = advanced;
this.fontTriplets = fontTriplets;
this.subFontName = subFontName;
}
@@ -85,13 +89,21 @@ public class EmbedFontInfo implements Serializable {
/**
* Determines if kerning is enabled
- * @return True if enabled
+ * @return true if enabled
*/
public boolean getKerning() {
return kerning;
}
/**
+ * Determines if advanced typographic features are enabled
+ * @return true if enabled
+ */
+ public boolean getAdvanced() {
+ return advanced;
+ }
+
+ /**
* Returns the sub-font name of the font. This is primarily used for TrueType Collections
* to select one of the sub-fonts. For all other fonts, this is always null.
* @return the sub-font name (or null)
@@ -173,6 +185,7 @@ public class EmbedFontInfo implements Serializable {
public String toString() {
return "metrics-url=" + metricsFile + ", embed-url=" + embedFile
+ ", kerning=" + kerning
+ + ", advanced=" + advanced
+ ", enc-mode=" + encodingMode
+ ", font-triplet=" + fontTriplets
+ (getSubFontName() != null ? ", sub-font=" + getSubFontName() : "")
diff --git a/src/java/org/apache/fop/fonts/Font.java b/src/java/org/apache/fop/fonts/Font.java
index 8ca802234..ca387840a 100644
--- a/src/java/org/apache/fop/fonts/Font.java
+++ b/src/java/org/apache/fop/fonts/Font.java
@@ -25,11 +25,16 @@ import java.util.Map;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
+import org.apache.fop.complexscripts.fonts.Positionable;
+import org.apache.fop.complexscripts.fonts.Substitutable;
+
+// CSOFF: LineLengthCheck
+
/**
* This class holds font state information and provides access to the font
* metrics.
*/
-public class Font {
+public class Font implements Substitutable, Positionable {
/** Extra Bold font weight */
public static final int WEIGHT_EXTRA_BOLD = 800;
@@ -189,6 +194,30 @@ public class Font {
}
/**
+ * Returns the amount of kerning between two characters.
+ *
+ * The value returned measures in pt. So it is already adjusted for font size.
+ *
+ * @param ch1 first character
+ * @param ch2 second character
+ * @return the distance to adjust for kerning, 0 if there's no kerning
+ */
+ public int getKernValue(int ch1, int ch2) {
+ // TODO !BMP
+ if ( ch1 > 0x10000 ) {
+ return 0;
+ } else if ( ( ch1 >= 0xD800 ) && ( ch1 <= 0xE000 ) ) {
+ return 0;
+ } else if ( ch2 > 0x10000 ) {
+ return 0;
+ } else if ( ( ch2 >= 0xD800 ) && ( ch2 <= 0xE000 ) ) {
+ return 0;
+ } else {
+ return getKernValue ( (char) ch1, (char) ch2 );
+ }
+ }
+
+ /**
* Returns the width of a character
* @param charnum character to look up
* @return width of the character
@@ -241,8 +270,8 @@ public class Font {
*/
@Override
public String toString() {
- StringBuffer sbuf = new StringBuffer();
- sbuf.append('(');
+ StringBuffer sbuf = new StringBuffer(super.toString());
+ sbuf.append('{');
/*
sbuf.append(fontFamily);
sbuf.append(',');*/
@@ -254,7 +283,7 @@ public class Font {
sbuf.append(fontStyle);
sbuf.append(',');
sbuf.append(fontWeight);*/
- sbuf.append(')');
+ sbuf.append('}');
return sbuf.toString();
}
@@ -264,7 +293,7 @@ public class Font {
* This also performs some guessing on widths on various
* versions of space that might not exists in the font.
* @param c character to inspect
- * @return the width of the character
+ * @return the width of the character or -1 if no width available
*/
public int getCharWidth(char c) {
int width;
@@ -329,6 +358,23 @@ public class Font {
}
/**
+ * Helper method for getting the width of a unicode char
+ * from the current fontstate.
+ * This also performs some guessing on widths on various
+ * versions of space that might not exists in the font.
+ * @param c character to inspect
+ * @return the width of the character or -1 if no width available
+ */
+ public int getCharWidth(int c) {
+ if ( c < 0x10000 ) {
+ return getCharWidth ( (char) c );
+ } else {
+ // TODO !BMP
+ return -1;
+ }
+ }
+
+ /**
* Calculates the word width.
* @param word text to get width for
* @return the width of the text
@@ -347,6 +393,59 @@ public class Font {
return width;
}
-}
+ /** {@inheritDoc} */
+ public boolean performsSubstitution() {
+ if ( metric instanceof Substitutable ) {
+ Substitutable s = (Substitutable) metric;
+ return s.performsSubstitution();
+ } else {
+ return false;
+ }
+ }
+
+ /** {@inheritDoc} */
+ public CharSequence performSubstitution ( CharSequence cs, String script, String language ) {
+ if ( metric instanceof Substitutable ) {
+ Substitutable s = (Substitutable) metric;
+ return s.performSubstitution ( cs, script, language );
+ } else {
+ throw new UnsupportedOperationException();
+ }
+ }
+
+ /** {@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 ) {
+ Positionable p = (Positionable) metric;
+ return p.performsPositioning();
+ } else {
+ return false;
+ }
+ }
+ /** {@inheritDoc} */
+ public int[][] performPositioning ( CharSequence cs, String script, String language, int fontSize ) {
+ if ( metric instanceof Positionable ) {
+ Positionable p = (Positionable) metric;
+ return p.performPositioning ( cs, script, language, fontSize );
+ } else {
+ throw new UnsupportedOperationException();
+ }
+ }
+
+ /** {@inheritDoc} */
+ public int[][] performPositioning ( CharSequence cs, String script, String language ) {
+ return performPositioning ( cs, script, language, fontSize );
+ }
+
+}
diff --git a/src/java/org/apache/fop/fonts/FontInfoConfigurator.java b/src/java/org/apache/fop/fonts/FontInfoConfigurator.java
index 1f1116990..34b6ed1d0 100644
--- a/src/java/org/apache/fop/fonts/FontInfoConfigurator.java
+++ b/src/java/org/apache/fop/fonts/FontInfoConfigurator.java
@@ -253,20 +253,26 @@ public class FontInfoConfigurator {
}
boolean useKerning = fontCfg.getAttributeAsBoolean("kerning", true);
+ boolean useAdvanced = fontCfg.getAttributeAsBoolean("advanced", true);
EncodingMode encodingMode = EncodingMode.getEncodingMode(
fontCfg.getAttribute("encoding-mode", EncodingMode.AUTO.getName()));
EmbedFontInfo embedFontInfo
- = new EmbedFontInfo(metricsUrl, useKerning, tripletList, embedUrl, subFont);
+ = new EmbedFontInfo(metricsUrl, useKerning, useAdvanced, tripletList, embedUrl,
+ subFont);
embedFontInfo.setEncodingMode(encodingMode);
+ boolean skipCachedFont = false;
if (fontCache != null) {
if (!fontCache.containsFont(embedFontInfo)) {
fontCache.addFont(embedFontInfo);
+ } else {
+ skipCachedFont = true;
}
}
if (log.isDebugEnabled()) {
String embedFile = embedFontInfo.getEmbedFile();
- log.debug("Adding font " + (embedFile != null ? embedFile + ", " : "")
+ log.debug( ( skipCachedFont ? "Skipping (cached) font " : "Adding font " )
+ + (embedFile != null ? embedFile + ", " : "")
+ "metric file " + embedFontInfo.getMetricsFile());
for (int j = 0; j < tripletList.size(); ++j) {
FontTriplet triplet = tripletList.get(j);
diff --git a/src/java/org/apache/fop/fonts/FontLoader.java b/src/java/org/apache/fop/fonts/FontLoader.java
index 02c09a1a1..91b763939 100644
--- a/src/java/org/apache/fop/fonts/FontLoader.java
+++ b/src/java/org/apache/fop/fonts/FontLoader.java
@@ -43,31 +43,36 @@ public abstract class FontLoader {
protected static final Log log = LogFactory.getLog(FontLoader.class);
/** URI representing the font file */
- protected String fontFileURI = null;
+ protected String fontFileURI;
/** the FontResolver to use for font URI resolution */
- protected FontResolver resolver = null;
+ protected FontResolver resolver;
/** the loaded font */
- protected CustomFont returnFont = null;
+ protected CustomFont returnFont;
/** true if the font has been loaded */
- protected boolean loaded = false;
+ protected boolean loaded;
/** true if the font will be embedded, false if it will be referenced only. */
- protected boolean embedded = true;
- /** true if kerning information shall be loaded if available. */
- protected boolean useKerning = true;
+ protected boolean embedded;
+ /** true if kerning information false be loaded if available. */
+ protected boolean useKerning;
+ /** true if advanced typographic information shall be loaded if available. */
+ protected boolean useAdvanced;
/**
* Default constructor.
* @param fontFileURI the URI to the PFB file of a Type 1 font
* @param embedded indicates whether the font is embedded or referenced
* @param useKerning indicates whether kerning information shall be loaded if available
+ * @param useAdvanced indicates whether advanced typographic information shall be loaded if
+ * available
* @param resolver the font resolver used to resolve URIs
*/
public FontLoader(String fontFileURI, boolean embedded, boolean useKerning,
- FontResolver resolver) {
+ boolean useAdvanced, FontResolver resolver) {
this.fontFileURI = fontFileURI;
this.embedded = embedded;
this.useKerning = useKerning;
+ this.useAdvanced = useAdvanced;
this.resolver = resolver;
}
@@ -105,7 +110,7 @@ public abstract class FontLoader {
boolean embedded, EncodingMode encodingMode,
FontResolver resolver) throws IOException {
return loadFont(fontUrl.toExternalForm(), subFontName,
- embedded, encodingMode, true,
+ embedded, encodingMode, true, true,
resolver);
}
@@ -116,13 +121,15 @@ public abstract class FontLoader {
* @param embedded indicates whether the font is embedded or referenced
* @param encodingMode the requested encoding mode
* @param useKerning indicates whether kerning information should be loaded if available
+ * @param useAdvanced indicates whether advanced typographic information shall be loaded if
+ * available
* @param resolver the font resolver to use when resolving URIs
* @return the newly loaded font
* @throws IOException In case of an I/O error
*/
public static CustomFont loadFont(String fontFileURI, String subFontName,
boolean embedded, EncodingMode encodingMode, boolean useKerning,
- FontResolver resolver) throws IOException {
+ boolean useAdvanced, FontResolver resolver) throws IOException {
fontFileURI = fontFileURI.trim();
boolean type1 = isType1(fontFileURI);
FontLoader loader;
@@ -134,7 +141,7 @@ public abstract class FontLoader {
loader = new Type1FontLoader(fontFileURI, embedded, useKerning, resolver);
} else {
loader = new TTFFontLoader(fontFileURI, subFontName,
- embedded, encodingMode, useKerning, resolver);
+ embedded, encodingMode, useKerning, useAdvanced, resolver);
}
return loader.getFont();
}
diff --git a/src/java/org/apache/fop/fonts/FontManager.java b/src/java/org/apache/fop/fonts/FontManager.java
index 51516e231..9285c94be 100644
--- a/src/java/org/apache/fop/fonts/FontManager.java
+++ b/src/java/org/apache/fop/fonts/FontManager.java
@@ -224,16 +224,32 @@ public class FontManager {
getFontSubstitutions().adjustFontInfo(fontInfo);
}
- /** @return a new FontResolver to be used by the font subsystem */
- public static FontResolver createMinimalFontResolver() {
- return new FontResolver() {
-
- /** {@inheritDoc} */
- public Source resolve(String href) {
- //Minimal functionality here
- return new StreamSource(href);
- }
- };
+ /**
+ * Minimum implemenation of FontResolver.
+ */
+ public static class MinimalFontResolver implements FontResolver {
+ private boolean useComplexScriptFeatures;
+ MinimalFontResolver(boolean useComplexScriptFeatures) {
+ this.useComplexScriptFeatures = useComplexScriptFeatures;
+ }
+ /** {@inheritDoc} */
+ public Source resolve(String href) {
+ //Minimal functionality here
+ return new StreamSource(href);
+ }
+ /** {@inheritDoc} */
+ public boolean isComplexScriptFeaturesEnabled() {
+ return useComplexScriptFeatures;
+ }
+ }
+
+ /**
+ * Create minimal font resolver.
+ * @param useComplexScriptFeatures true if complex script features enabled
+ * @return a new FontResolver to be used by the font subsystem
+ */
+ public static FontResolver createMinimalFontResolver(boolean useComplexScriptFeatures) {
+ return new MinimalFontResolver ( useComplexScriptFeatures );
}
/**
diff --git a/src/java/org/apache/fop/fonts/FontManagerConfigurator.java b/src/java/org/apache/fop/fonts/FontManagerConfigurator.java
index 6935df7e3..40e163d42 100644
--- a/src/java/org/apache/fop/fonts/FontManagerConfigurator.java
+++ b/src/java/org/apache/fop/fonts/FontManagerConfigurator.java
@@ -98,6 +98,18 @@ public class FontManagerConfigurator {
}
}
+ // [GA] permit configuration control over base14 kerning; without this,
+ // there is no way for a user to enable base14 kerning other than by
+ // programmatic API;
+ if (cfg.getChild("base14-kerning", false) != null) {
+ try {
+ fontManager
+ .setBase14KerningEnabled(cfg.getChild("base14-kerning").getValueAsBoolean());
+ } catch (ConfigurationException e) {
+ LogUtil.handleException(log, e, true);
+ }
+ }
+
// global font configuration
Configuration fontsCfg = cfg.getChild("fonts", false);
if (fontsCfg != null) {
diff --git a/src/java/org/apache/fop/fonts/FontReader.java b/src/java/org/apache/fop/fonts/FontReader.java
index e9b88ec16..0f0348fc1 100644
--- a/src/java/org/apache/fop/fonts/FontReader.java
+++ b/src/java/org/apache/fop/fonts/FontReader.java
@@ -34,6 +34,7 @@ import org.xml.sax.Attributes;
import org.xml.sax.InputSource;
import org.xml.sax.Locator;
import org.xml.sax.SAXException;
+import org.xml.sax.SAXParseException;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.DefaultHandler;
@@ -52,7 +53,7 @@ import org.apache.fop.fonts.apps.TTFReader;
*/
public class FontReader extends DefaultHandler {
- private Locator locator = null;
+ // private Locator locator = null; // not used at present
private boolean isCID = false;
private CustomFont returnFont = null;
private MultiByteFont multiFont = null;
@@ -117,6 +118,14 @@ public class FontReader extends DefaultHandler {
}
/**
+ * Enable/disable use of advanced typographic features for the font
+ * @param enabled true to enable, false to disable
+ */
+ public void setAdvancedEnabled(boolean enabled) {
+ returnFont.setAdvancedEnabled(enabled);
+ }
+
+ /**
* Sets the font resolver. Needed for URI resolution.
* @param resolver the font resolver
*/
@@ -153,7 +162,7 @@ public class FontReader extends DefaultHandler {
* {@inheritDoc}
*/
public void setDocumentLocator(Locator locator) {
- this.locator = locator;
+ // this.locator = locator; // not used at present
}
/**
@@ -212,6 +221,7 @@ public class FontReader extends DefaultHandler {
currentKerning.put(new Integer(attributes.getValue("kpx2")),
new Integer(attributes.getValue("kern")));
}
+
}
private int getInt(String str) throws SAXException {
@@ -305,6 +315,5 @@ public class FontReader extends DefaultHandler {
public void characters(char[] ch, int start, int length) {
text.append(ch, start, length);
}
-}
-
+}
diff --git a/src/java/org/apache/fop/fonts/FontResolver.java b/src/java/org/apache/fop/fonts/FontResolver.java
index 1da2339f7..bd155482e 100644
--- a/src/java/org/apache/fop/fonts/FontResolver.java
+++ b/src/java/org/apache/fop/fonts/FontResolver.java
@@ -36,4 +36,10 @@ public interface FontResolver {
*/
Source resolve(String href);
+ /**
+ * Check whether complex script features are enabled.
+ * @return true if FOP is to use complex script features
+ */
+ boolean isComplexScriptFeaturesEnabled();
+
}
diff --git a/src/java/org/apache/fop/fonts/FontSetup.java b/src/java/org/apache/fop/fonts/FontSetup.java
index 935f695b1..ba31b2d50 100644
--- a/src/java/org/apache/fop/fonts/FontSetup.java
+++ b/src/java/org/apache/fop/fonts/FontSetup.java
@@ -57,9 +57,10 @@ public final class FontSetup {
/**
* Sets up a font info
* @param fontInfo font info
+ * @param base14Kerning true if base14 kerning applies
*/
- public static void setup(FontInfo fontInfo) {
- setup(fontInfo, null, null);
+ public static void setup(FontInfo fontInfo, boolean base14Kerning) {
+ setup(fontInfo, null, null, base14Kerning);
}
/**
@@ -71,10 +72,10 @@ public final class FontSetup {
* @param fontInfo the font info object to set up
* @param embedFontInfoList a list of EmbedFontInfo objects
* @param resolver the font resolver
+ * @param base14Kerning true if base14 kerning applies
*/
public static void setup(FontInfo fontInfo, List<EmbedFontInfo> embedFontInfoList,
- FontResolver resolver) {
- final boolean base14Kerning = false;
+ FontResolver resolver, boolean base14Kerning) {
fontInfo.addMetrics("F1", new Helvetica(base14Kerning));
fontInfo.addMetrics("F2", new HelveticaOblique(base14Kerning));
fontInfo.addMetrics("F3", new HelveticaBold(base14Kerning));
@@ -180,7 +181,7 @@ public final class FontSetup {
final int startNum = 15;
/* Add configured fonts */
- addConfiguredFonts(fontInfo, embedFontInfoList, startNum, resolver);
+ addConfiguredFonts(fontInfo, embedFontInfoList, startNum, resolver, base14Kerning);
}
/**
@@ -191,14 +192,17 @@ public final class FontSetup {
* @param resolver the font resolver
*/
private static void addConfiguredFonts(FontInfo fontInfo,
- List<EmbedFontInfo> embedFontInfoList, int num, FontResolver resolver) {
+ List<EmbedFontInfo> embedFontInfoList, int num, FontResolver resolver,
+ boolean base14Kerning) {
if (embedFontInfoList == null) {
return; //No fonts to process
}
if (resolver == null) {
//Ensure that we have minimal font resolution capabilities
- resolver = createMinimalFontResolver();
+ //None of the built-in base14 fonts have advanced typographic data
+ boolean useAdvanced = false;
+ resolver = createMinimalFontResolver(useAdvanced);
}
String internalName = null;
@@ -218,15 +222,31 @@ public final class FontSetup {
}
}
- /** @return a new FontResolver to be used by the font subsystem */
- public static FontResolver createMinimalFontResolver() {
- return new FontResolver() {
+ /**
+ * Minimum implemenation of FontResolver.
+ */
+ public static class MinimalFontResolver implements FontResolver {
+ private boolean useComplexScriptFeatures;
+ MinimalFontResolver(boolean useComplexScriptFeatures) {
+ this.useComplexScriptFeatures = useComplexScriptFeatures;
+ }
+ /** {@inheritDoc} */
+ public Source resolve(String href) {
+ //Minimal functionality here
+ return new StreamSource(href);
+ }
+ /** {@inheritDoc} */
+ public boolean isComplexScriptFeaturesEnabled() {
+ return useComplexScriptFeatures;
+ }
+ }
- /** {@inheritDoc} */
- public Source resolve(String href) {
- //Minimal functionality here
- return new StreamSource(href);
- }
- };
+ /**
+ * Create minimal font resolver.
+ * @param useComplexScriptFeatures true if complex script features enabled
+ * @return a new FontResolver to be used by the font subsystem
+ */
+ public static FontResolver createMinimalFontResolver(boolean useComplexScriptFeatures) {
+ return new MinimalFontResolver ( useComplexScriptFeatures );
}
}
diff --git a/src/java/org/apache/fop/fonts/LazyFont.java b/src/java/org/apache/fop/fonts/LazyFont.java
index f331a331e..dfd2367b2 100644
--- a/src/java/org/apache/fop/fonts/LazyFont.java
+++ b/src/java/org/apache/fop/fonts/LazyFont.java
@@ -29,28 +29,34 @@ import javax.xml.transform.stream.StreamSource;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
+
import org.apache.fop.apps.FOPException;
+import org.apache.fop.complexscripts.fonts.Positionable;
+import org.apache.fop.complexscripts.fonts.Substitutable;
+
+
import org.xml.sax.InputSource;
/**
* This class is used to defer the loading of a font until it is really used.
*/
-public class LazyFont extends Typeface implements FontDescriptor {
+public class LazyFont extends Typeface implements FontDescriptor, Substitutable, Positionable {
private static Log log = LogFactory.getLog(LazyFont.class);
- private String metricsFileName = null;
- private String fontEmbedPath = null;
- private boolean useKerning = false;
+ private String metricsFileName;
+ private String fontEmbedPath;
+ private boolean useKerning;
+ private boolean useAdvanced;
private EncodingMode encodingMode = EncodingMode.AUTO;
- private boolean embedded = true;
- private String subFontName = null;
+ private boolean embedded;
+ private String subFontName;
- private boolean isMetricsLoaded = false;
- private Typeface realFont = null;
- private FontDescriptor realFontDescriptor = null;
+ private boolean isMetricsLoaded;
+ private Typeface realFont;
+ private FontDescriptor realFontDescriptor;
- private FontResolver resolver = null;
+ private FontResolver resolver;
/**
* Main constructor
@@ -62,6 +68,11 @@ public class LazyFont extends Typeface implements FontDescriptor {
this.metricsFileName = fontInfo.getMetricsFile();
this.fontEmbedPath = fontInfo.getEmbedFile();
this.useKerning = fontInfo.getKerning();
+ if ( resolver != null ) {
+ this.useAdvanced = resolver.isComplexScriptFeaturesEnabled();
+ } else {
+ this.useAdvanced = fontInfo.getAdvanced();
+ }
this.encodingMode = fontInfo.getEncodingMode();
this.subFontName = fontInfo.getSubFontName();
this.embedded = fontInfo.isEmbedded();
@@ -70,9 +81,15 @@ public class LazyFont extends Typeface implements FontDescriptor {
/** {@inheritDoc} */
public String toString() {
- return ( "metrics-url=" + metricsFileName + ", embed-url=" + fontEmbedPath
- + ", kerning=" + useKerning );
- }
+ StringBuffer sbuf = new StringBuffer(super.toString());
+ sbuf.append('{');
+ sbuf.append("metrics-url=" + metricsFileName);
+ sbuf.append(",embed-url=" + fontEmbedPath);
+ sbuf.append(",kerning=" + useKerning);
+ sbuf.append(",advanced=" + useAdvanced);
+ sbuf.append('}');
+ return sbuf.toString();
+ }
private void load(boolean fail) {
if (!isMetricsLoaded) {
@@ -120,6 +137,7 @@ public class LazyFont extends Typeface implements FontDescriptor {
new URL(metricsFileName).openStream()));
}
reader.setKerningEnabled(useKerning);
+ reader.setAdvancedEnabled(useAdvanced);
if (this.embedded) {
reader.setFontEmbedPath(fontEmbedPath);
}
@@ -130,7 +148,7 @@ public class LazyFont extends Typeface implements FontDescriptor {
throw new RuntimeException("Cannot load font. No font URIs available.");
}
realFont = FontLoader.loadFont(fontEmbedPath, this.subFontName,
- this.embedded, this.encodingMode, useKerning, resolver);
+ this.embedded, this.encodingMode, useKerning, useAdvanced, resolver);
}
if (realFont instanceof FontDescriptor) {
realFontDescriptor = (FontDescriptor) realFont;
@@ -376,6 +394,84 @@ public class LazyFont extends Typeface implements FontDescriptor {
/**
* {@inheritDoc}
*/
+ public boolean performsSubstitution() {
+ load(true);
+ if ( realFontDescriptor instanceof Substitutable ) {
+ return ((Substitutable)realFontDescriptor).performsSubstitution();
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public CharSequence performSubstitution ( CharSequence cs, String script, String language ) {
+ load(true);
+ if ( realFontDescriptor instanceof Substitutable ) {
+ return ((Substitutable)realFontDescriptor).performSubstitution(cs, script, language);
+ } else {
+ return cs;
+ }
+ }
+
+ /**
+ * {@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}
+ */
+ public boolean performsPositioning() {
+ load(true);
+ if ( realFontDescriptor instanceof Positionable ) {
+ return ((Positionable)realFontDescriptor).performsPositioning();
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public int[][]
+ performPositioning ( CharSequence cs, String script, String language, int fontSize ) {
+ load(true);
+ if ( realFontDescriptor instanceof Positionable ) {
+ return ((Positionable)realFontDescriptor)
+ .performPositioning(cs, script, language, fontSize);
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public int[][]
+ performPositioning ( CharSequence cs, String script, String language ) {
+ load(true);
+ if ( realFontDescriptor instanceof Positionable ) {
+ return ((Positionable)realFontDescriptor)
+ .performPositioning(cs, script, language);
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
public boolean isSubsetEmbedded() {
load(true);
return realFont.isMultiByte();
diff --git a/src/java/org/apache/fop/fonts/MultiByteFont.java b/src/java/org/apache/fop/fonts/MultiByteFont.java
index 0d9897268..3b15662dc 100644
--- a/src/java/org/apache/fop/fonts/MultiByteFont.java
+++ b/src/java/org/apache/fop/fonts/MultiByteFont.java
@@ -19,14 +19,29 @@
package org.apache.fop.fonts;
-//Java
+import java.nio.CharBuffer;
+import java.nio.IntBuffer;
import java.util.Map;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import org.apache.fop.complexscripts.fonts.GlyphDefinitionTable;
+import org.apache.fop.complexscripts.fonts.GlyphPositioningTable;
+import org.apache.fop.complexscripts.fonts.GlyphSubstitutionTable;
+import org.apache.fop.complexscripts.fonts.Positionable;
+import org.apache.fop.complexscripts.fonts.Substitutable;
+import org.apache.fop.complexscripts.util.GlyphSequence;
+import org.apache.fop.util.CharUtilities;
/**
* Generic MultiByte (CID) font
*/
-public class MultiByteFont extends CIDFont {
+public class MultiByteFont extends CIDFont implements Substitutable, Positionable {
+
+ /** logging instance */
+ private static final Log log // CSOK: ConstantNameCheck
+ = LogFactory.getLog(MultiByteFont.class);
private String ttcName = null;
private String encoding = "Identity-H";
@@ -36,9 +51,28 @@ public class MultiByteFont extends CIDFont {
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 */
+ private GlyphDefinitionTable gdef;
+ 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
*/
@@ -132,13 +166,14 @@ public class MultiByteFont extends CIDFont {
* @param c the Unicode character index
* @return the glyph index (or 0 if the glyph is not available)
*/
- private int findGlyphIndex(char c) {
- int idx = (int)c;
+ // [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
@@ -148,6 +183,95 @@ public class MultiByteFont extends CIDFont {
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[]).
+ * @param gi glyph index
+ * @returns unicode scalar value
+ */
+ // [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 ];
+ int s = be.getGlyphStartIndex();
+ int e = s + ( be.getUnicodeEnd() - be.getUnicodeStart() );
+ if ( ( gi >= s ) && ( gi <= e ) ) {
+ cc = be.getUnicodeStart() + ( gi - s );
+ 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();
@@ -223,5 +347,220 @@ public class MultiByteFont extends CIDFont {
}
return subset.getSubsetChars();
}
+
+ /**
+ * Establishes the glyph definition table.
+ * @param gdef the glyph definition table to be used by this font
+ */
+ public void setGDEF ( GlyphDefinitionTable gdef ) {
+ if ( ( this.gdef == null ) || ( gdef == null ) ) {
+ this.gdef = gdef;
+ } else {
+ throw new IllegalStateException ( "font already associated with GDEF table" );
+ }
+ }
+
+ /**
+ * Obtain glyph definition table.
+ * @return glyph definition table or null if none is associated with font
+ */
+ public GlyphDefinitionTable getGDEF() {
+ return gdef;
+ }
+
+ /**
+ * Establishes the glyph substitution table.
+ * @param gsub the glyph substitution table to be used by this font
+ */
+ public void setGSUB ( GlyphSubstitutionTable gsub ) {
+ if ( ( this.gsub == null ) || ( gsub == null ) ) {
+ this.gsub = gsub;
+ } else {
+ throw new IllegalStateException ( "font already associated with GSUB table" );
+ }
+ }
+
+ /**
+ * Obtain glyph substitution table.
+ * @return glyph substitution table or null if none is associated with font
+ */
+ public GlyphSubstitutionTable getGSUB() {
+ return gsub;
+ }
+
+ /**
+ * Establishes the glyph positioning table.
+ * @param gpos the glyph positioning table to be used by this font
+ */
+ public void setGPOS ( GlyphPositioningTable gpos ) {
+ if ( ( this.gpos == null ) || ( gpos == null ) ) {
+ this.gpos = gpos;
+ } else {
+ throw new IllegalStateException ( "font already associated with GPOS table" );
+ }
+ }
+
+ /**
+ * Obtain glyph positioning table.
+ * @return glyph positioning table or null if none is associated with font
+ */
+ public GlyphPositioningTable getGPOS() {
+ return gpos;
+ }
+
+ /** {@inheritDoc} */
+ public boolean performsSubstitution() {
+ return gsub != null;
+ }
+
+ /** {@inheritDoc} */
+ public CharSequence performSubstitution ( CharSequence cs, String script, String language ) {
+ if ( gsub != null ) {
+ GlyphSequence igs = mapCharsToGlyphs ( cs );
+ GlyphSequence ogs = gsub.substitute ( igs, script, language );
+ CharSequence ocs = mapGlyphsToChars ( ogs );
+ return ocs;
+ } else {
+ return cs;
+ }
+ }
+
+ /** {@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;
+ }
+
+ /** {@inheritDoc} */
+ public int[][]
+ performPositioning ( CharSequence cs, String script, String language, int fontSize ) {
+ if ( gpos != null ) {
+ GlyphSequence gs = mapCharsToGlyphs ( cs );
+ int[][] adjustments = new int [ gs.getGlyphCount() ] [ 4 ];
+ if ( gpos.position ( gs, script, language, fontSize, this.width, adjustments ) ) {
+ return scaleAdjustments ( adjustments, fontSize );
+ } else {
+ return null;
+ }
+ } else {
+ return null;
+ }
+ }
+
+ /** {@inheritDoc} */
+ public int[][] performPositioning ( CharSequence cs, String script, String language ) {
+ throw new UnsupportedOperationException();
+ }
+
+
+ private int[][] scaleAdjustments ( int[][] adjustments, int fontSize ) {
+ if ( adjustments != null ) {
+ for ( int i = 0, n = adjustments.length; i < n; i++ ) {
+ int[] gpa = adjustments [ i ];
+ for ( int k = 0; k < 4; k++ ) {
+ gpa [ k ] = ( gpa [ k ] * fontSize ) / 1000;
+ }
+ }
+ return adjustments;
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Map sequence CS, comprising a sequence of UTF-16 encoded Unicode Code Points, to
+ * an output character sequence GS, comprising a sequence of Glyph Indices. N.B. Unlike
+ * mapChar(), this method does not make use of embedded subset encodings.
+ * @param cs a CharSequence containing UTF-16 encoded Unicode characters
+ * @returns a CharSequence containing glyph indices
+ */
+ private GlyphSequence mapCharsToGlyphs ( CharSequence cs ) {
+ IntBuffer cb = IntBuffer.allocate ( cs.length() );
+ IntBuffer gb = IntBuffer.allocate ( cs.length() );
+ int gi, giMissing = findGlyphIndex ( Typeface.NOT_FOUND );
+ for ( int i = 0, n = cs.length(); i < n; i++ ) {
+ int cc = cs.charAt ( i );
+ if ( ( cc >= 0xD800 ) && ( cc < 0xDC00 ) ) {
+ if ( ( i + 1 ) < n ) {
+ int sh = cc;
+ int sl = cs.charAt ( ++i );
+ if ( ( sl >= 0xDC00 ) && ( sl < 0xE000 ) ) {
+ cc = 0x10000 + ( ( sh - 0xD800 ) << 10 ) + ( ( sl - 0xDC00 ) << 0 );
+ } else {
+ throw new IllegalArgumentException
+ ( "ill-formed UTF-16 sequence, "
+ + "contains isolated high surrogate at index " + i );
+ }
+ } else {
+ throw new IllegalArgumentException
+ ( "ill-formed UTF-16 sequence, "
+ + "contains isolated high surrogate at end of sequence" );
+ }
+ } else if ( ( cc >= 0xDC00 ) && ( cc < 0xE000 ) ) {
+ throw new IllegalArgumentException
+ ( "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 );
+ gb.put ( gi );
+ }
+ cb.flip();
+ gb.flip();
+ return new GlyphSequence ( cb, gb, null );
+ }
+
+ /**
+ * Map sequence GS, comprising a sequence of Glyph Indices, to output sequence CS,
+ * comprising a sequence of UTF-16 encoded Unicode Code Points.
+ * @param gs a GlyphSequence containing glyph indices
+ * @returns a CharSequence containing UTF-16 encoded Unicode characters
+ */
+ private CharSequence mapGlyphsToChars ( GlyphSequence gs ) {
+ int ng = gs.getGlyphCount();
+ CharBuffer cb = CharBuffer.allocate ( ng );
+ int ccMissing = Typeface.NOT_FOUND;
+ for ( int i = 0, n = ng; i < n; i++ ) {
+ int gi = gs.getGlyph ( i );
+ int cc = findCharacterFromGlyphIndex ( gi );
+ if ( ( cc == 0 ) || ( cc > 0x10FFFF ) ) {
+ cc = ccMissing;
+ log.warn("Unable to map glyph index " + gi
+ + " to Unicode scalar in font '"
+ + getFullName() + "', substituting missing character '"
+ + (char) cc + "'");
+ }
+ if ( cc > 0x00FFFF ) {
+ int sh, sl;
+ cc -= 0x10000;
+ sh = ( ( cc >> 10 ) & 0x3FF ) + 0xD800;
+ sl = ( ( cc >> 0 ) & 0x3FF ) + 0xDC00;
+ cb.put ( (char) sh );
+ cb.put ( (char) sl );
+ } else {
+ cb.put ( (char) cc );
+ }
+ }
+ cb.flip();
+ return (CharSequence) cb;
+ }
+
}
diff --git a/src/java/org/apache/fop/fonts/MutableFont.java b/src/java/org/apache/fop/fonts/MutableFont.java
index bcbcadbdc..41c552a0b 100644
--- a/src/java/org/apache/fop/fonts/MutableFont.java
+++ b/src/java/org/apache/fop/fonts/MutableFont.java
@@ -133,6 +133,12 @@ public interface MutableFont {
void setKerningEnabled(boolean enabled);
/**
+ * Enables/disabled advanced typographic features.
+ * @param enabled true if advanced typographic features should be enabled if available
+ */
+ void setAdvancedEnabled(boolean enabled);
+
+ /**
* Adds an entry to the kerning table.
* @param key Kerning key
* @param value Kerning value
diff --git a/src/java/org/apache/fop/fonts/Typeface.java b/src/java/org/apache/fop/fonts/Typeface.java
index a0c1d99ec..e933781cd 100644
--- a/src/java/org/apache/fop/fonts/Typeface.java
+++ b/src/java/org/apache/fop/fonts/Typeface.java
@@ -143,6 +143,10 @@ public abstract class Typeface implements FontMetrics {
/** {@inheritDoc} */
public String toString() {
- return getFullName();
+ StringBuffer sbuf = new StringBuffer(super.toString());
+ sbuf.append('{');
+ sbuf.append(getFullName());
+ sbuf.append('}');
+ return sbuf.toString();
}
}
diff --git a/src/java/org/apache/fop/fonts/apps/AbstractFontReader.java b/src/java/org/apache/fop/fonts/apps/AbstractFontReader.java
index 1c973cd51..89c7890af 100644
--- a/src/java/org/apache/fop/fonts/apps/AbstractFontReader.java
+++ b/src/java/org/apache/fop/fonts/apps/AbstractFontReader.java
@@ -67,7 +67,7 @@ public abstract class AbstractFontReader {
List arguments = new java.util.ArrayList();
for (int i = 0; i < args.length; i++) {
if (args[i].startsWith("-")) {
- if ("-d".equals(args[i]) || "-q".equals(args[i])) {
+ if ("-t".equals(args[i]) || "-d".equals(args[i]) || "-q".equals(args[i])) {
options.put(args[i], "");
} else if ((i + 1) < args.length && !args[i + 1].startsWith("-")) {
options.put(args[i], args[i + 1]);
@@ -101,7 +101,9 @@ public abstract class AbstractFontReader {
*/
protected static void determineLogLevel(Map options) {
//Determine log level
- if (options.get("-d") != null) {
+ if (options.get("-t") != null) {
+ setLogLevel("trace");
+ } else if (options.get("-d") != null) {
setLogLevel("debug");
} else if (options.get("-q") != null) {
setLogLevel("error");
diff --git a/src/java/org/apache/fop/fonts/apps/TTFReader.java b/src/java/org/apache/fop/fonts/apps/TTFReader.java
index 6e64f9144..1804a8dd9 100644
--- a/src/java/org/apache/fop/fonts/apps/TTFReader.java
+++ b/src/java/org/apache/fop/fonts/apps/TTFReader.java
@@ -21,6 +21,7 @@ package org.apache.fop.fonts.apps;
import java.io.IOException;
import java.util.Iterator;
+import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -38,6 +39,9 @@ import org.w3c.dom.Element;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
+// CSOFF: InnerAssignmentCheck
+// CSOFF: LineLengthCheck
+
/**
* A tool which reads TTF files and generates
* XML font metrics file for use in FOP.
@@ -61,6 +65,7 @@ public class TTFReader extends AbstractFontReader {
"java " + TTFReader.class.getName() + " [options] fontfile.ttf xmlfile.xml");
System.out.println();
System.out.println("where options can be:");
+ System.out.println("-t Trace mode");
System.out.println("-d Debug mode");
System.out.println("-q Quiet mode");
System.out.println("-enc ansi");
@@ -102,6 +107,8 @@ public class TTFReader extends AbstractFontReader {
* you can also include the fontfile in the fop.jar file when building fop.
* You can use both -ef and -er. The file specified in -ef will be searched first,
* then the -er file.
+ * -nocs
+ * if complex script features are disabled
*/
public static void main(String[] args) {
String embFile = null;
@@ -155,13 +162,19 @@ public class TTFReader extends AbstractFontReader {
className = (String)options.get("-cn");
}
+ boolean useKerning = true;
+ boolean useAdvanced = true;
+ if (options.get("-nocs") != null) {
+ useAdvanced = false;
+ }
+
if (arguments.length != 2 || options.get("-h") != null
|| options.get("-help") != null || options.get("--help") != null) {
displayUsage();
} else {
try {
log.info("Parsing font...");
- TTFFile ttf = app.loadTTF(arguments[0], ttcName);
+ TTFFile ttf = app.loadTTF(arguments[0], ttcName, useKerning, useAdvanced);
if (ttf != null) {
org.w3c.dom.Document doc = app.constructFontXML(ttf,
fontName, className, embResource, embFile, isCid,
@@ -198,11 +211,13 @@ public class TTFReader extends AbstractFontReader {
*
* @param fileName The filename of the TTF file.
* @param fontName The name of the font
+ * @param useKerning true if should load kerning data
+ * @param useAdvanced true if should load advanced typographic table data
* @return The TTF as an object, null if the font is incompatible.
* @throws IOException In case of an I/O problem
*/
- public TTFFile loadTTF(String fileName, String fontName) throws IOException {
- TTFFile ttfFile = new TTFFile();
+ public TTFFile loadTTF(String fileName, String fontName, boolean useKerning, boolean useAdvanced) throws IOException {
+ TTFFile ttfFile = new TTFFile(useKerning, useAdvanced);
log.info("Reading " + fileName + "...");
FontFileReader reader = new FontFileReader(fileName);
@@ -463,7 +478,6 @@ public class TTFReader extends AbstractFontReader {
}
}
-
/**
* Bugzilla 40739, check that attr has a metrics-version attribute
* compatible with ours.
diff --git a/src/java/org/apache/fop/fonts/autodetect/FontInfoFinder.java b/src/java/org/apache/fop/fonts/autodetect/FontInfoFinder.java
index 714af0e57..bf6f493cf 100644
--- a/src/java/org/apache/fop/fonts/autodetect/FontInfoFinder.java
+++ b/src/java/org/apache/fop/fonts/autodetect/FontInfoFinder.java
@@ -148,7 +148,7 @@ public class FontInfoFinder {
subFontName = ((MultiByteFont)customFont).getTTCName();
}
EmbedFontInfo fontInfo = new EmbedFontInfo(null, customFont.isKerningEnabled(),
- fontTripletList, embedUrl, subFontName);
+ customFont.isAdvancedEnabled(), fontTripletList, embedUrl, subFontName);
fontInfo.setPostScriptName(customFont.getFontName());
if (fontCache != null) {
fontCache.addFont(fontInfo);
@@ -168,6 +168,9 @@ public class FontInfoFinder {
public EmbedFontInfo[] find(URL fontURL, FontResolver resolver, FontCache fontCache) {
String embedURL = null;
embedURL = fontURL.toExternalForm();
+ boolean useKerning = true;
+ boolean useAdvanced = ( resolver != null )
+ ? resolver.isComplexScriptFeaturesEnabled() : true;
long fileLastModified = -1;
if (fontCache != null) {
@@ -190,14 +193,14 @@ 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<String> ttcNames = null;
String fontFileURL = fontURL.toExternalForm().trim();
InputStream in = null;
try {
in = FontLoader.openFontUri(resolver, fontFileURL);
- TTFFile ttf = new TTFFile();
+ TTFFile ttf = new TTFFile(false, false);
FontFileReader reader = new FontFileReader(in);
ttcNames = ttf.getTTCnames(reader);
} catch (Exception e) {
@@ -218,7 +221,8 @@ public class FontInfoFinder {
}
try {
TTFFontLoader ttfLoader = new TTFFontLoader(
- fontFileURL, fontName, true, EncodingMode.AUTO, true, resolver);
+ fontFileURL, fontName, true, EncodingMode.AUTO,
+ useKerning, useAdvanced, resolver);
customFont = ttfLoader.getFont();
if (this.eventListener != null) {
customFont.setEventListener(this.eventListener);
diff --git a/src/java/org/apache/fop/fonts/truetype/TTFDirTabEntry.java b/src/java/org/apache/fop/fonts/truetype/TTFDirTabEntry.java
index 405a71395..1f05ebfa1 100644
--- a/src/java/org/apache/fop/fonts/truetype/TTFDirTabEntry.java
+++ b/src/java/org/apache/fop/fonts/truetype/TTFDirTabEntry.java
@@ -26,7 +26,7 @@ import java.io.UnsupportedEncodingException;
/**
* This class represents an entry to a TrueType font's Dir Tab.
*/
-class TTFDirTabEntry {
+public class TTFDirTabEntry {
private byte[] tag = new byte[4];
private int checksum;
@@ -34,7 +34,10 @@ class TTFDirTabEntry {
private long length;
/**
- * Read Dir Tab, return tag name
+ * Read Dir Tab.
+ * @param in font file reader
+ * @return tag name
+ * @throws IOException upon I/O exception
*/
public String read(FontFileReader in) throws IOException {
tag[0] = in.readTTFByte();
@@ -52,6 +55,7 @@ class TTFDirTabEntry {
}
+ @Override
public String toString() {
return "Read dir tab ["
+ tag[0] + " " + tag[1] + " " + tag[2] + " " + tag[3] + "]"
diff --git a/src/java/org/apache/fop/fonts/truetype/TTFFile.java b/src/java/org/apache/fop/fonts/truetype/TTFFile.java
index f4806d95e..33d608540 100644
--- a/src/java/org/apache/fop/fonts/truetype/TTFFile.java
+++ b/src/java/org/apache/fop/fonts/truetype/TTFFile.java
@@ -28,9 +28,15 @@ import java.util.Set;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
-import org.apache.fop.fonts.FontUtil;
import org.apache.xmlgraphics.fonts.Glyphs;
+import org.apache.fop.complexscripts.fonts.AdvancedTypographicTableFormatException;
+import org.apache.fop.complexscripts.fonts.GlyphDefinitionTable;
+import org.apache.fop.complexscripts.fonts.GlyphPositioningTable;
+import org.apache.fop.complexscripts.fonts.GlyphSubstitutionTable;
+import org.apache.fop.complexscripts.fonts.OTFAdvancedTypographicTableReader;
+import org.apache.fop.fonts.FontUtil;
+
/**
* Reads a TrueType file or a TrueType Collection.
* The TrueType spec can be found at the Microsoft.
@@ -43,12 +49,12 @@ public class TTFFile {
static final int MAX_CHAR_CODE = 255;
static final int ENC_BUF_SIZE = 1024;
- /** Set to true to get even more debug output than with level DEBUG */
- public static final boolean TRACE_ENABLED = false;
-
private final String encoding = "WinAnsiEncoding"; // Default encoding
private final short firstChar = 0;
+
+ private boolean useKerning = false;
+
private boolean isEmbeddable = true;
private boolean hasSerifs = true;
/**
@@ -58,7 +64,7 @@ public class TTFFile {
private Map<Integer, Map<Integer, Integer>> kerningTab; // for CIDs
private Map<Integer, Map<Integer, Integer>> ansiKerningTab; // For winAnsiEncoding
private List cmaps;
- private List unicodeMapping;
+ private Set unicodeMappings;
private int upem; // unitsPerEm from "head" table
private int nhmtx; // Number of horizontal metrics
@@ -120,15 +126,29 @@ public class TTFFile {
private boolean isCFF;
+ // advanced typographic table support
+ private boolean useAdvanced = false;
+ private OTFAdvancedTypographicTableReader advancedTableReader;
+
/**
* logging instance
*/
protected Log log = LogFactory.getLog(TTFFile.class);
/**
+ * Constructor
+ * @param useKerning true if kerning data should be loaded
+ * @param useAdvanced true if advanced typographic tables should be loaded
+ */
+ public TTFFile ( boolean useKerning, boolean useAdvanced ) {
+ this.useKerning = useKerning;
+ this.useAdvanced = useAdvanced;
+ }
+
+ /**
* Key-value helper class
*/
- class UnicodeMapping {
+ class UnicodeMapping implements Comparable {
private final int unicodeIndex;
private final int glyphIndex;
@@ -155,15 +175,67 @@ public class TTFFile {
public int getUnicodeIndex() {
return unicodeIndex;
}
+
+
+ /** {@inheritDoc} */
+ public int hashCode() {
+ int hc = unicodeIndex;
+ hc = 19 * hc + ( hc ^ glyphIndex );
+ return hc;
+ }
+
+ /** {@inheritDoc} */
+ public boolean equals ( Object o ) {
+ if ( o instanceof UnicodeMapping ) {
+ UnicodeMapping m = (UnicodeMapping) o;
+ if ( unicodeIndex != m.unicodeIndex ) {
+ return false;
+ } else {
+ return ( glyphIndex == m.glyphIndex );
+ }
+ } else {
+ return false;
+ }
+ }
+
+ /** {@inheritDoc} */
+ public int compareTo ( Object o ) {
+ if ( o instanceof UnicodeMapping ) {
+ UnicodeMapping m = (UnicodeMapping) o;
+ if ( unicodeIndex > m.unicodeIndex ) {
+ return 1;
+ } else if ( unicodeIndex < m.unicodeIndex ) {
+ return -1;
+ } else {
+ return 0;
+ }
+ } else {
+ return -1;
+ }
+ }
+ }
+
+ /**
+ * Obtain directory table entry.
+ * @param name (tag) of entry
+ * @return a directory table entry or null if none found
+ */
+ public TTFDirTabEntry getDirectoryEntry ( String name ) {
+ return (TTFDirTabEntry) dirTabs.get ( name );
}
/**
* Position inputstream to position indicated
* in the dirtab offset + offset
+ * @param in font file reader
+ * @param name (tag) of table
+ * @param offset from start of table
+ * @return true if seek succeeded
+ * @throws IOException if I/O exception occurs during seek
*/
- boolean seekTab(FontFileReader in, String name,
+ public boolean seekTab(FontFileReader in, String name,
long offset) throws IOException {
- TTFDirTabEntry dt = (TTFDirTabEntry)dirTabs.get(name);
+ TTFDirTabEntry dt = getDirectoryEntry ( name );
if (dt == null) {
log.error("Dirtab " + name + " not found.");
return false;
@@ -203,7 +275,7 @@ public class TTFFile {
*/
private boolean readCMAP(FontFileReader in) throws IOException {
- unicodeMapping = new java.util.ArrayList();
+ unicodeMappings = new java.util.TreeSet();
seekTab(in, "cmap", 2);
int numCMap = in.readTTFUShort(); // Number of cmap subtables
@@ -344,7 +416,7 @@ public class TTFFile {
glyphIdx = (in.readTTFUShort() + cmapDeltas[i])
& 0xffff;
- unicodeMapping.add(new UnicodeMapping(glyphIdx, j));
+ unicodeMappings.add(new UnicodeMapping(glyphIdx, j));
mtxTab[glyphIdx].getUnicodeIndex().add(new Integer(j));
if (encodingID == 0 && j >= 0xF020 && j <= 0xF0FF) {
@@ -354,7 +426,7 @@ public class TTFFile {
int mapped = j - 0xF000;
if (!eightBitGlyphs.get(mapped)) {
//Only map if Unicode code point hasn't been mapped before
- unicodeMapping.add(new UnicodeMapping(glyphIdx, mapped));
+ unicodeMappings.add(new UnicodeMapping(glyphIdx, mapped));
mtxTab[glyphIdx].getUnicodeIndex().add(new Integer(mapped));
}
}
@@ -395,7 +467,7 @@ public class TTFFile {
+ mtxTab.length);
}
- unicodeMapping.add(new UnicodeMapping(glyphIdx, j));
+ unicodeMappings.add(new UnicodeMapping(glyphIdx, j));
if (glyphIdx < mtxTab.length) {
mtxTab[glyphIdx].getUnicodeIndex().add(new Integer(j));
} else {
@@ -554,9 +626,27 @@ public class TTFFile {
}
// Create cmaps for bfentries
createCMaps();
- // print_max_min();
- readKerning(in);
+ if ( useKerning ) {
+ readKerning(in);
+ }
+
+ // Read advanced typographic tables.
+ if ( useAdvanced ) {
+ try {
+ OTFAdvancedTypographicTableReader atr
+ = new OTFAdvancedTypographicTableReader ( this, in );
+ atr.readAll();
+ this.advancedTableReader = atr;
+ } catch ( AdvancedTypographicTableFormatException e ) {
+ log.warn (
+ "Encountered format constraint violation in advanced (typographic) table (AT) "
+ + "in font '" + getFullName() + "', ignoring AT data: "
+ + e.getMessage()
+ );
+ }
+ }
+
guessVerticalMetricsFromGlyphBBox();
return true;
}
@@ -565,7 +655,7 @@ public class TTFFile {
cmaps = new java.util.ArrayList();
TTFCmapEntry tce = new TTFCmapEntry();
- Iterator e = unicodeMapping.listIterator();
+ Iterator e = unicodeMappings.iterator();
UnicodeMapping um = (UnicodeMapping)e.next();
UnicodeMapping lastMapping = um;
@@ -927,8 +1017,8 @@ public class TTFFile {
int mtxSize = Math.max(numberOfGlyphs, nhmtx);
mtxTab = new TTFMtxEntry[mtxSize];
- if (TRACE_ENABLED) {
- log.debug("*** Widths array: \n");
+ if (log.isTraceEnabled()) {
+ log.trace("*** Widths array: \n");
}
for (int i = 0; i < mtxSize; i++) {
mtxTab[i] = new TTFMtxEntry();
@@ -937,11 +1027,9 @@ public class TTFFile {
mtxTab[i].setWx(in.readTTFUShort());
mtxTab[i].setLsb(in.readTTFUShort());
- if (TRACE_ENABLED) {
- if (log.isDebugEnabled()) {
- log.debug(" width[" + i + "] = "
- + convertTTFUnit2PDFUnit(mtxTab[i].getWx()) + ";");
- }
+ if (log.isTraceEnabled()) {
+ log.trace(" width[" + i + "] = "
+ + convertTTFUnit2PDFUnit(mtxTab[i].getWx()) + ";");
}
}
@@ -1045,7 +1133,7 @@ public class TTFFile {
*/
private void readOS2(FontFileReader in) throws IOException {
// Check if font is embeddable
- TTFDirTabEntry os2Entry = (TTFDirTabEntry)dirTabs.get("OS/2");
+ TTFDirTabEntry os2Entry = getDirectoryEntry ( "OS/2" );
if (os2Entry != null) {
seekTab(in, "OS/2", 0);
int version = in.readTTFUShort();
@@ -1133,7 +1221,7 @@ public class TTFFile {
* @throws IOException In case of a I/O problem
*/
private void readGlyf(FontFileReader in) throws IOException {
- TTFDirTabEntry dirTab = (TTFDirTabEntry)dirTabs.get("glyf");
+ TTFDirTabEntry dirTab = getDirectoryEntry ( "glyf" );
if (dirTab == null) {
throw new IOException("glyf table not found, cannot continue");
}
@@ -1153,7 +1241,7 @@ public class TTFFile {
}
- long n = ((TTFDirTabEntry)dirTabs.get("glyf")).getOffset();
+ long n = dirTab.getOffset();
for (int i = 0; i < numberOfGlyphs; i++) {
if ((i + 1) >= mtxTab.length
|| mtxTab[i].getOffset() != mtxTab[i + 1].getOffset()) {
@@ -1259,7 +1347,7 @@ public class TTFFile {
* @throws IOException In case of a I/O problem
*/
private boolean readPCLT(FontFileReader in) throws IOException {
- TTFDirTabEntry dirTab = (TTFDirTabEntry)dirTabs.get("PCLT");
+ TTFDirTabEntry dirTab = getDirectoryEntry ( "PCLT" );
if (dirTab != null) {
in.seekSet(dirTab.getOffset() + 4 + 4 + 2);
xHeight = in.readTTFUShort();
@@ -1380,7 +1468,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.");
}
}
@@ -1390,7 +1478,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.");
}
}
@@ -1406,7 +1494,7 @@ public class TTFFile {
// Read kerning
kerningTab = new java.util.HashMap();
ansiKerningTab = new java.util.HashMap();
- TTFDirTabEntry dirTab = (TTFDirTabEntry)dirTabs.get("kern");
+ TTFDirTabEntry dirTab = getDirectoryEntry ( "kern" );
if (dirTab != null) {
seekTab(in, "kern", 2);
for (int n = in.readTTFUShort(); n > 0; n--) {
@@ -1584,11 +1672,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]);
@@ -1596,7 +1682,7 @@ public class TTFFile {
readName(in);
- log.debug(fullName);
+ log.info(fullName);
fontNames.add(fullName);
// Reset names
@@ -1667,9 +1753,8 @@ public class TTFFile {
*
* @param glyphIndex
* @return unicode code point
- * @throws IOException if glyphIndex not found
*/
- private Integer glyphToUnicode(int glyphIndex) throws IOException {
+ private Integer glyphToUnicode(int glyphIndex) {
return (Integer) glyphToUnicodeMap.get(new Integer(glyphIndex));
}
@@ -1678,7 +1763,6 @@ public class TTFFile {
*
* @param unicodeIndex unicode code point
* @return glyph index
- * @throws IOException if unicodeIndex not found
*/
private Integer unicodeToGlyph(int unicodeIndex) throws IOException {
final Integer result
@@ -1691,12 +1775,62 @@ public class TTFFile {
}
/**
+ * Determine if advanced (typographic) table is present.
+ * @return true if advanced (typographic) table is present
+ */
+ public boolean hasAdvancedTable() {
+ if ( advancedTableReader != null ) {
+ return advancedTableReader.hasAdvancedTable();
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Returns the GDEF table or null if none present.
+ * @return the GDEF table
+ */
+ public GlyphDefinitionTable getGDEF() {
+ if ( advancedTableReader != null ) {
+ return advancedTableReader.getGDEF();
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Returns the GSUB table or null if none present.
+ * @return the GSUB table
+ */
+ public GlyphSubstitutionTable getGSUB() {
+ if ( advancedTableReader != null ) {
+ return advancedTableReader.getGSUB();
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Returns the GPOS table or null if none present.
+ * @return the GPOS table
+ */
+ public GlyphPositioningTable getGPOS() {
+ if ( advancedTableReader != null ) {
+ return advancedTableReader.getGPOS();
+ } else {
+ return null;
+ }
+ }
+
+ /**
* Static main method to get info about a TrueType font.
* @param args The command line arguments
*/
public static void main(String[] args) {
try {
- TTFFile ttfFile = new TTFFile();
+ boolean useKerning = true;
+ boolean useAdvanced = true;
+ TTFFile ttfFile = new TTFFile(useKerning, useAdvanced);
FontFileReader reader = new FontFileReader(args[0]);
@@ -1713,4 +1847,4 @@ public class TTFFile {
ioe.printStackTrace(System.err);
}
}
-} \ No newline at end of file
+}
diff --git a/src/java/org/apache/fop/fonts/truetype/TTFFontLoader.java b/src/java/org/apache/fop/fonts/truetype/TTFFontLoader.java
index f9be8e010..54324be52 100644
--- a/src/java/org/apache/fop/fonts/truetype/TTFFontLoader.java
+++ b/src/java/org/apache/fop/fonts/truetype/TTFFontLoader.java
@@ -54,7 +54,7 @@ public class TTFFontLoader extends FontLoader {
* @param resolver the FontResolver for font URI resolution
*/
public TTFFontLoader(String fontFileURI, FontResolver resolver) {
- this(fontFileURI, null, true, EncodingMode.AUTO, true, resolver);
+ this(fontFileURI, null, true, EncodingMode.AUTO, true, true, resolver);
}
/**
@@ -65,12 +65,13 @@ public class TTFFontLoader extends FontLoader {
* @param embedded indicates whether the font is embedded or referenced
* @param encodingMode the requested encoding mode
* @param useKerning true to enable loading kerning info if available, false to disable
+ * @param useAdvanced true to enable loading advanced info if available, false to disable
* @param resolver the FontResolver for font URI resolution
*/
public TTFFontLoader(String fontFileURI, String subFontName,
boolean embedded, EncodingMode encodingMode, boolean useKerning,
- FontResolver resolver) {
- super(fontFileURI, embedded, useKerning, resolver);
+ boolean useAdvanced, FontResolver resolver) {
+ super(fontFileURI, embedded, useKerning, useAdvanced, resolver);
this.subFontName = subFontName;
this.encodingMode = encodingMode;
if (this.encodingMode == EncodingMode.AUTO) {
@@ -93,7 +94,7 @@ public class TTFFontLoader extends FontLoader {
private void read(String ttcFontName) throws IOException {
InputStream in = openFontUri(resolver, this.fontFileURI);
try {
- TTFFile ttf = new TTFFile();
+ TTFFile ttf = new TTFFile(useKerning, useAdvanced);
FontFileReader reader = new FontFileReader(in);
boolean supported = ttf.readFont(reader, ttcFontName);
if (!supported) {
@@ -169,6 +170,9 @@ public class TTFFontLoader extends FontLoader {
if (useKerning) {
copyKerning(ttf, isCid);
}
+ if (useAdvanced) {
+ copyAdvanced(ttf);
+ }
if (this.embedded) {
if (ttf.isEmbeddable()) {
returnFont.setEmbedFileName(this.fontFileURI);
@@ -229,4 +233,17 @@ public class TTFFontLoader extends FontLoader {
returnFont.putKerningEntry(kpx1, h2);
}
}
+
+ /**
+ * Copy advanced typographic information.
+ */
+ private void copyAdvanced ( TTFFile ttf ) {
+ if ( returnFont instanceof MultiByteFont ) {
+ MultiByteFont mbf = (MultiByteFont) returnFont;
+ mbf.setGDEF ( ttf.getGDEF() );
+ mbf.setGSUB ( ttf.getGSUB() );
+ mbf.setGPOS ( ttf.getGPOS() );
+ }
+ }
+
}
diff --git a/src/java/org/apache/fop/fonts/truetype/TTFSubSetFile.java b/src/java/org/apache/fop/fonts/truetype/TTFSubSetFile.java
index 665cf289d..e081734be 100644
--- a/src/java/org/apache/fop/fonts/truetype/TTFSubSetFile.java
+++ b/src/java/org/apache/fop/fonts/truetype/TTFSubSetFile.java
@@ -57,6 +57,22 @@ public class TTFSubSetFile extends TTFFile {
private int locaOffset = 0;
/**
+ * Default Constructor
+ */
+ public TTFSubSetFile() {
+ this(false, false);
+ }
+
+ /**
+ * Constructor
+ * @param useKerning true if kerning data should be loaded
+ * @param useAdvanced true if advanced typographic tables should be loaded
+ */
+ public TTFSubSetFile ( boolean useKerning, boolean useAdvanced ) {
+ super(useKerning, useAdvanced);
+ }
+
+ /**
* Initalize the output array
*/
private void init(int size) {
diff --git a/src/java/org/apache/fop/fonts/type1/Type1FontLoader.java b/src/java/org/apache/fop/fonts/type1/Type1FontLoader.java
index 1e93763e8..924f7b0b8 100644
--- a/src/java/org/apache/fop/fonts/type1/Type1FontLoader.java
+++ b/src/java/org/apache/fop/fonts/type1/Type1FontLoader.java
@@ -52,7 +52,7 @@ public class Type1FontLoader extends FontLoader {
*/
public Type1FontLoader(String fontFileURI, boolean embedded, boolean useKerning,
FontResolver resolver) throws IOException {
- super(fontFileURI, embedded, useKerning, resolver);
+ super(fontFileURI, embedded, useKerning, true, resolver);
}
private String getPFMURI(String pfbURI) {
diff --git a/src/java/org/apache/fop/layoutmgr/AbstractLayoutManager.java b/src/java/org/apache/fop/layoutmgr/AbstractLayoutManager.java
index 8e96f3eb1..dd9c34149 100644
--- a/src/java/org/apache/fop/layoutmgr/AbstractLayoutManager.java
+++ b/src/java/org/apache/fop/layoutmgr/AbstractLayoutManager.java
@@ -448,7 +448,7 @@ public abstract class AbstractLayoutManager extends AbstractBaseLayoutManager
/** {@inheritDoc} */
@Override
public String toString() {
- return (super.toString() + (fobj != null ? "[fobj=" + fobj.toString() + "]" : ""));
+ return (super.toString() + (fobj != null ? "{fobj = " + fobj.toString() + "}" : ""));
}
/** {@inheritDoc} */
diff --git a/src/java/org/apache/fop/layoutmgr/BlockContainerLayoutManager.java b/src/java/org/apache/fop/layoutmgr/BlockContainerLayoutManager.java
index 95b7f8a39..205fac0f2 100644
--- a/src/java/org/apache/fop/layoutmgr/BlockContainerLayoutManager.java
+++ b/src/java/org/apache/fop/layoutmgr/BlockContainerLayoutManager.java
@@ -349,7 +349,12 @@ public class BlockContainerLayoutManager extends BlockStackingLayoutManager
contentRectOffsetX = 0;
contentRectOffsetY = 0;
- contentRectOffsetX += fo.getCommonMarginBlock().startIndent.getValue(this);
+ int level = fo.getBidiLevel();
+ if ( ( level < 0 ) || ( ( level & 1 ) == 0 ) ) {
+ contentRectOffsetX += fo.getCommonMarginBlock().startIndent.getValue(this);
+ } else {
+ contentRectOffsetX += fo.getCommonMarginBlock().endIndent.getValue(this);
+ }
contentRectOffsetY += fo.getCommonBorderPaddingBackground().getBorderBeforeWidth(false);
contentRectOffsetY += fo.getCommonBorderPaddingBackground().getPaddingBefore(false, this);
@@ -834,10 +839,13 @@ public class BlockContainerLayoutManager extends BlockStackingLayoutManager
if (referenceArea == null) {
boolean switchedProgressionDirection = blockProgressionDirectionChanges();
boolean allowBPDUpdate = autoHeight && !switchedProgressionDirection;
+ int level = getBlockContainerFO().getBidiLevel();
viewportBlockArea = new BlockViewport(allowBPDUpdate);
viewportBlockArea.addTrait(Trait.IS_VIEWPORT_AREA, Boolean.TRUE);
-
+ if ( level >= 0 ) {
+ viewportBlockArea.setBidiLevel ( level );
+ }
viewportBlockArea.setIPD(getContentAreaIPD());
if (allowBPDUpdate) {
viewportBlockArea.setBPD(0);
@@ -872,6 +880,9 @@ public class BlockContainerLayoutManager extends BlockStackingLayoutManager
referenceArea = new Block();
referenceArea.addTrait(Trait.IS_REFERENCE_AREA, Boolean.TRUE);
+ if ( level >= 0 ) {
+ referenceArea.setBidiLevel ( level );
+ }
TraitSetter.setProducerID(referenceArea, getBlockContainerFO().getId());
if (abProps.absolutePosition == EN_ABSOLUTE) {
diff --git a/src/java/org/apache/fop/layoutmgr/BlockLayoutManager.java b/src/java/org/apache/fop/layoutmgr/BlockLayoutManager.java
index ed2ea4ee9..9c3639f93 100644
--- a/src/java/org/apache/fop/layoutmgr/BlockLayoutManager.java
+++ b/src/java/org/apache/fop/layoutmgr/BlockLayoutManager.java
@@ -359,6 +359,8 @@ public class BlockLayoutManager extends BlockStackingLayoutManager
curBlockArea.setIPD(super.getContentAreaIPD());
+ curBlockArea.setBidiLevel ( getBlockFO().getBidiLevel() );
+
TraitSetter.addBreaks(curBlockArea,
getBlockFO().getBreakBefore(), getBlockFO().getBreakAfter());
@@ -503,4 +505,3 @@ public class BlockLayoutManager extends BlockStackingLayoutManager
}
}
-
diff --git a/src/java/org/apache/fop/layoutmgr/ExternalDocumentLayoutManager.java b/src/java/org/apache/fop/layoutmgr/ExternalDocumentLayoutManager.java
index c4aa1a39b..a46b74e1a 100644
--- a/src/java/org/apache/fop/layoutmgr/ExternalDocumentLayoutManager.java
+++ b/src/java/org/apache/fop/layoutmgr/ExternalDocumentLayoutManager.java
@@ -51,6 +51,7 @@ import org.apache.fop.datatypes.URISpecification;
import org.apache.fop.fo.Constants;
import org.apache.fop.fo.extensions.ExternalDocument;
import org.apache.fop.layoutmgr.inline.ImageLayout;
+import org.apache.fop.traits.WritingMode;
/**
* LayoutManager for an external-document extension element. This class is instantiated by
@@ -184,12 +185,12 @@ 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);
vp.setContentPosition(imageLayout.getPlacement());
- vp.setOffset(0);
+ vp.setBlockProgressionOffset(0);
//Link them all together...
lineArea.addInlineArea(vp);
@@ -230,8 +231,9 @@ public class ExternalDocumentLayoutManager extends AbstractPageSequenceLayoutMan
referenceRect = new Rectangle(0, 0, imageSize.height, imageSize.width);
}
FODimension reldims = new FODimension(0, 0);
+ // [TBD] BIDI ALERT
CTM pageCTM = CTM.getCTMandRelDims(pageSeq.getReferenceOrientation(),
- Constants.EN_LR_TB, referenceRect, reldims);
+ WritingMode.LR_TB, referenceRect, reldims);
Page page = new Page(referenceRect, pageNumber, pageNumberString, isBlank);
diff --git a/src/java/org/apache/fop/layoutmgr/LayoutContext.java b/src/java/org/apache/fop/layoutmgr/LayoutContext.java
index 18059c5ca..a2512c50b 100644
--- a/src/java/org/apache/fop/layoutmgr/LayoutContext.java
+++ b/src/java/org/apache/fop/layoutmgr/LayoutContext.java
@@ -26,6 +26,7 @@ import org.apache.fop.fo.Constants;
import org.apache.fop.layoutmgr.inline.AlignmentContext;
import org.apache.fop.layoutmgr.inline.HyphContext;
import org.apache.fop.traits.MinOptMax;
+import org.apache.fop.traits.WritingMode;
/**
@@ -92,7 +93,7 @@ public class LayoutContext {
//overlap with refIPD. Need to investigate how best to refactor that.
/** the writing mode established by the nearest ancestor reference area */
- private int writingMode = Constants.EN_LR_TB;
+ private WritingMode writingMode = WritingMode.LR_TB;
/** Current pending space-after or space-end from preceding area */
private SpaceSpecifier trailingSpace;
@@ -564,7 +565,7 @@ public class LayoutContext {
* Get the writing mode of the relevant reference area.
* @return the applicable writing mode
*/
- public int getWritingMode() {
+ public WritingMode getWritingMode() {
return writingMode;
}
@@ -572,7 +573,7 @@ public class LayoutContext {
* Set the writing mode.
* @param writingMode the writing mode
*/
- public void setWritingMode(int writingMode) {
+ public void setWritingMode(WritingMode writingMode) {
this.writingMode = writingMode;
}
diff --git a/src/java/org/apache/fop/layoutmgr/LayoutManagerMapping.java b/src/java/org/apache/fop/layoutmgr/LayoutManagerMapping.java
index 2acf67c1f..76a1cb9e4 100644
--- a/src/java/org/apache/fop/layoutmgr/LayoutManagerMapping.java
+++ b/src/java/org/apache/fop/layoutmgr/LayoutManagerMapping.java
@@ -67,6 +67,7 @@ import org.apache.fop.fo.pagination.SideRegion;
import org.apache.fop.fo.pagination.StaticContent;
import org.apache.fop.fo.pagination.Title;
import org.apache.fop.layoutmgr.inline.BasicLinkLayoutManager;
+import org.apache.fop.layoutmgr.inline.BidiLayoutManager;
import org.apache.fop.layoutmgr.inline.CharacterLayoutManager;
import org.apache.fop.layoutmgr.inline.ContentLayoutManager;
import org.apache.fop.layoutmgr.inline.ExternalGraphicLayoutManager;
@@ -246,28 +247,9 @@ public class LayoutManagerMapping implements LayoutManagerMaker {
public static class BidiOverrideLayoutManagerMaker extends Maker {
/** {@inheritDoc} */
public void make(FONode node, List lms) {
- /* [GA] remove broken code
- if (false) {
- // this is broken; it does nothing
- // it should make something like an InlineStackingLM
- super.make(node, lms);
- } else {
- ArrayList childList = new ArrayList();
- // this is broken; it does nothing
- // it should make something like an InlineStackingLM
- super.make(node, childList);
- for (int count = childList.size() - 1; count >= 0; count--) {
- LayoutManager lm = (LayoutManager) childList.get(count);
- if (lm instanceof InlineLevelLayoutManager) {
- LayoutManager blm = new BidiLayoutManager
- ((BidiOverride) node, (InlineLayoutManager) lm);
- lms.add(blm);
- } else {
- lms.add(lm);
- }
- }
+ if ( node instanceof BidiOverride ) {
+ lms.add(new BidiLayoutManager((BidiOverride) node));
}
- */
}
}
diff --git a/src/java/org/apache/fop/layoutmgr/PageProvider.java b/src/java/org/apache/fop/layoutmgr/PageProvider.java
index 364839f8a..a668b044b 100644
--- a/src/java/org/apache/fop/layoutmgr/PageProvider.java
+++ b/src/java/org/apache/fop/layoutmgr/PageProvider.java
@@ -335,6 +335,7 @@ public class PageProvider implements Constants {
//Set unique key obtained from the AreaTreeHandler
page.getPageViewport().setKey(areaTreeHandler.generatePageViewportKey());
page.getPageViewport().setForeignAttributes(spm.getForeignAttributes());
+ page.getPageViewport().setWritingModeTraits(pageSeq);
cachedPages.add(page);
return page;
}
diff --git a/src/java/org/apache/fop/layoutmgr/PageSequenceLayoutManager.java b/src/java/org/apache/fop/layoutmgr/PageSequenceLayoutManager.java
index 1b2b5d7c5..8ad124c27 100644
--- a/src/java/org/apache/fop/layoutmgr/PageSequenceLayoutManager.java
+++ b/src/java/org/apache/fop/layoutmgr/PageSequenceLayoutManager.java
@@ -25,6 +25,7 @@ import org.apache.commons.logging.LogFactory;
import org.apache.fop.area.AreaTreeHandler;
import org.apache.fop.area.AreaTreeModel;
import org.apache.fop.area.LineArea;
+import org.apache.fop.complexscripts.bidi.BidiResolver;
import org.apache.fop.fo.Constants;
import org.apache.fop.fo.pagination.PageSequence;
import org.apache.fop.fo.pagination.PageSequenceMaster;
@@ -78,8 +79,12 @@ public class PageSequenceLayoutManager extends AbstractPageSequenceLayoutManager
public void activateLayout() {
initialize();
- LineArea title = null;
+ // perform step 5.8 of refinement process (Unicode BIDI Processing)
+ if ( areaTreeHandler.isComplexScriptFeaturesEnabled() ) {
+ BidiResolver.resolveInlineDirectionality(getPageSequence());
+ }
+ LineArea title = null;
if (getPageSequence().getTitleFO() != null) {
try {
ContentLayoutManager clm = getLayoutManagerMaker().
diff --git a/src/java/org/apache/fop/layoutmgr/inline/AbstractGraphicsLayoutManager.java b/src/java/org/apache/fop/layoutmgr/inline/AbstractGraphicsLayoutManager.java
index 8c797a48c..a7bc78be8 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,30 +77,35 @@ 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.addStructureTreeElement(vp, fobj.getStructureTreeElement());
TraitSetter.setProducerID(vp, fobj.getId());
vp.setIPD(imageLayout.getViewportSize().width);
vp.setBPD(imageLayout.getViewportSize().height);
vp.setContentPosition(placement);
vp.setClip(imageLayout.isClipped());
- vp.setOffset(0);
+ 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 8c769924a..aba137115 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}
@@ -98,35 +95,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/AlignmentContext.java b/src/java/org/apache/fop/layoutmgr/inline/AlignmentContext.java
index daf7ce01d..eb199da9d 100644
--- a/src/java/org/apache/fop/layoutmgr/inline/AlignmentContext.java
+++ b/src/java/org/apache/fop/layoutmgr/inline/AlignmentContext.java
@@ -24,6 +24,7 @@ import org.apache.fop.datatypes.LengthBase;
import org.apache.fop.datatypes.SimplePercentBaseContext;
import org.apache.fop.fo.Constants;
import org.apache.fop.fonts.Font;
+import org.apache.fop.traits.WritingMode;
/**
* The alignment context is carried within a LayoutContext and as
@@ -173,7 +174,7 @@ public class AlignmentContext implements Constants {
* @param lineHeight the computed value of the lineHeight property
* @param writingMode the current writing mode
*/
- AlignmentContext(Font font, int lineHeight, int writingMode) {
+ AlignmentContext(Font font, int lineHeight, WritingMode writingMode) {
this.areaHeight = font.getAscender() - font.getDescender();
this.lineHeight = lineHeight;
this.xHeight = font.getXHeight();
@@ -299,6 +300,14 @@ public class AlignmentContext implements Constants {
}
/**
+ * Return the writing mode.
+ * @return the writing mode
+ */
+/* public WritingMode getWritingMode() {
+ return scaledBaselineTable.getWritingMode();
+ }*/
+
+ /**
* Calculates the baseline shift value based on the baseline-shift
* property value.
* @param baselineShift the baseline shift property value
@@ -504,6 +513,10 @@ public class AlignmentContext implements Constants {
&& parentAlignmentContext.usesInitialBaselineTable());
}
+ /* private boolean isHorizontalWritingMode() {
+ return (getWritingMode() == WritingMode.LR_TB || getWritingMode() == WritingMode.RL_TB);
+ }*/
+
/** {@inheritDoc} */
public String toString() {
StringBuffer sb = new StringBuffer(64);
diff --git a/src/java/org/apache/fop/layoutmgr/inline/BasicLinkLayoutManager.java b/src/java/org/apache/fop/layoutmgr/inline/BasicLinkLayoutManager.java
index 1390c04d8..593003d55 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/BidiLayoutManager.java b/src/java/org/apache/fop/layoutmgr/inline/BidiLayoutManager.java
index f342399fc..b6145f025 100644
--- a/src/java/org/apache/fop/layoutmgr/inline/BidiLayoutManager.java
+++ b/src/java/org/apache/fop/layoutmgr/inline/BidiLayoutManager.java
@@ -19,56 +19,19 @@
package org.apache.fop.layoutmgr.inline;
-import java.util.ArrayList;
-import java.util.List;
-
-import org.apache.fop.area.inline.InlineArea;
import org.apache.fop.fo.flow.BidiOverride;
-
/**
- * If this bidi has a different writing mode direction
- * ltr or rtl than its parent writing mode then this
- * reverses the inline areas (at the character level).
+ * Layout manager for fo:bidi-override.
*/
-public class BidiLayoutManager extends LeafNodeLayoutManager {
-
- private List children;
+public class BidiLayoutManager extends InlineLayoutManager {
/**
* Construct bidi layout manager.
- * @param node bidi override FO
- * @param cLM parent layout manager
+ * @param node an BidiOverride FONode
*/
- public BidiLayoutManager(BidiOverride node, InlineLayoutManager cLM) {
+ public BidiLayoutManager(BidiOverride node) {
super(node);
- setParent(cLM);
- children = new ArrayList();
-/*
- for (int count = cLM.size() - 1; count >= 0; count--) {
- InlineArea ia = cLM.get(count);
- if (ia instanceof Word) {
- // reverse word
- Word word = (Word) ia;
- StringBuffer sb = new StringBuffer(word.getWord());
- word.setWord(sb.reverse().toString());
- }
- children.add(ia);
- }
-*/
- }
-
- /** @return number of children */
- public int size() {
- return children.size();
- }
-
- /**
- * @param index of child inline area
- * @return a child inline area
- */
- public InlineArea get(int index) {
- return (InlineArea) children.get(index);
}
}
diff --git a/src/java/org/apache/fop/layoutmgr/inline/CharacterLayoutManager.java b/src/java/org/apache/fop/layoutmgr/inline/CharacterLayoutManager.java
index 4877ff9bd..32d4d1004 100644
--- a/src/java/org/apache/fop/layoutmgr/inline/CharacterLayoutManager.java
+++ b/src/java/org/apache/fop/layoutmgr/inline/CharacterLayoutManager.java
@@ -77,13 +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.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 {
- text.addWord(String.valueOf(ch), 0);
+ int[] levels = ( level >= 0 ) ? new int[] {level} : null;
+ text.addWord(String.valueOf(ch), ipd, null, levels, null, blockProgressionOffset);
}
TraitSetter.setProducerID(text, node.getId());
TraitSetter.addTextDecoration(text, node.getTextDecoration());
@@ -105,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);
@@ -227,4 +231,3 @@ public class CharacterLayoutManager extends LeafNodeLayoutManager {
}
}
-
diff --git a/src/java/org/apache/fop/layoutmgr/inline/InlineLayoutManager.java b/src/java/org/apache/fop/layoutmgr/inline/InlineLayoutManager.java
index ede078066..1eb91e0ab 100644
--- a/src/java/org/apache/fop/layoutmgr/inline/InlineLayoutManager.java
+++ b/src/java/org/apache/fop/layoutmgr/inline/InlineLayoutManager.java
@@ -207,7 +207,7 @@ public class InlineLayoutManager extends InlineStackingLayoutManager {
InlineArea area;
if (isInline) {
area = createInlineParent();
- area.setOffset(0);
+ area.setBlockProgressionOffset(0);
} else {
area = new InlineBlockParent();
}
@@ -482,12 +482,12 @@ public class InlineLayoutManager extends InlineStackingLayoutManager {
|| lastLM instanceof InlineLevelLayoutManager);
parent.setBPD(alignmentContext.getHeight());
if (parent instanceof InlineParent) {
- parent.setOffset(alignmentContext.getOffset());
+ parent.setBlockProgressionOffset(alignmentContext.getOffset());
} else if (parent instanceof InlineBlockParent) {
// All inline elements are positioned by the renderers relative to
// the before edge of their content rectangle
if (borderProps != null) {
- parent.setOffset(borderProps.getPaddingBefore(false, this)
+ parent.setBlockProgressionOffset(borderProps.getPaddingBefore(false, this)
+ borderProps.getBorderBeforeWidth(false));
}
}
diff --git a/src/java/org/apache/fop/layoutmgr/inline/InlineStackingLayoutManager.java b/src/java/org/apache/fop/layoutmgr/inline/InlineStackingLayoutManager.java
index b15f0f8db..4a203d55e 100644
--- a/src/java/org/apache/fop/layoutmgr/inline/InlineStackingLayoutManager.java
+++ b/src/java/org/apache/fop/layoutmgr/inline/InlineStackingLayoutManager.java
@@ -180,6 +180,10 @@ public abstract class InlineStackingLayoutManager extends AbstractLayoutManager
//getLogger().debug("Add leading space: " + iAdjust);
Space ls = new Space();
ls.setIPD(iAdjust);
+ int level = parentArea.getBidiLevel();
+ if ( level >= 0 ) {
+ ls.setBidiLevel ( level );
+ }
parentArea.addChildArea(ls);
}
}
diff --git a/src/java/org/apache/fop/layoutmgr/inline/LeaderLayoutManager.java b/src/java/org/apache/fop/layoutmgr/inline/LeaderLayoutManager.java
index 2a58b2018..b3d3218ab 100644
--- a/src/java/org/apache/fop/layoutmgr/inline/LeaderLayoutManager.java
+++ b/src/java/org/apache/fop/layoutmgr/inline/LeaderLayoutManager.java
@@ -115,7 +115,7 @@ public class LeaderLayoutManager extends LeafNodeLayoutManager {
private InlineArea getLeaderInlineArea(LayoutContext context) {
InlineArea leaderArea = null;
-
+ int level = fobj.getBidiLevel();
if (fobj.getLeaderPattern() == EN_RULE) {
if (fobj.getRuleStyle() != EN_NONE) {
org.apache.fop.area.inline.Leader leader
@@ -125,28 +125,41 @@ public class LeaderLayoutManager extends LeafNodeLayoutManager {
leaderArea = leader;
} else {
leaderArea = new Space();
+ if ( level >= 0 ) {
+ leaderArea.setBidiLevel ( level );
+ }
}
leaderArea.setBPD(fobj.getRuleThickness().getValue(this));
leaderArea.addTrait(Trait.COLOR, fobj.getColor());
+ if ( level >= 0 ) {
+ leaderArea.setBidiLevel ( level );
+ }
} else if (fobj.getLeaderPattern() == EN_SPACE) {
leaderArea = new Space();
leaderArea.setBPD(fobj.getRuleThickness().getValue(this));
+ if ( level >= 0 ) {
+ leaderArea.setBidiLevel ( level );
+ }
} else if (fobj.getLeaderPattern() == EN_DOTS) {
TextArea t = new TextArea();
char dot = '.'; // userAgent.getLeaderDotCharacter();
-
int width = font.getCharWidth(dot);
- t.addWord("" + dot, 0);
+ int[] levels = ( level < 0 ) ? null : new int[] {level};
+ t.addWord("" + dot, width, null, levels, null, 0);
t.setIPD(width);
t.setBPD(width);
t.setBaselineOffset(width);
TraitSetter.addFontTraits(t, font);
t.addTrait(Trait.COLOR, fobj.getColor());
Space spacer = null;
- if (fobj.getLeaderPatternWidth().getValue(this) > width) {
+ int widthLeaderPattern = fobj.getLeaderPatternWidth().getValue(this);
+ if (widthLeaderPattern > width) {
spacer = new Space();
- spacer.setIPD(fobj.getLeaderPatternWidth().getValue(this) - width);
- width = fobj.getLeaderPatternWidth().getValue(this);
+ spacer.setIPD(widthLeaderPattern - width);
+ if ( level >= 0 ) {
+ spacer.setBidiLevel ( level );
+ }
+ width = widthLeaderPattern;
}
FilledArea fa = new FilledArea();
fa.setUnitWidth(width);
@@ -155,7 +168,6 @@ public class LeaderLayoutManager extends LeafNodeLayoutManager {
fa.addChildArea(spacer);
}
fa.setBPD(t.getBPD());
-
leaderArea = fa;
} else if (fobj.getLeaderPattern() == EN_USECONTENT) {
if (fobj.getChildNodes() == null) {
@@ -188,6 +200,9 @@ public class LeaderLayoutManager extends LeafNodeLayoutManager {
if (fobj.getLeaderPatternWidth().getValue(this) > width) {
spacer = new Space();
spacer.setIPD(fobj.getLeaderPatternWidth().getValue(this) - width);
+ if ( level >= 0 ) {
+ spacer.setBidiLevel ( level );
+ }
width = fobj.getLeaderPatternWidth().getValue(this);
}
fa.setUnitWidth(width);
@@ -199,6 +214,7 @@ public class LeaderLayoutManager extends LeafNodeLayoutManager {
//Content collapsed to nothing, so use a space
leaderArea = new Space();
leaderArea.setBPD(fobj.getRuleThickness().getValue(this));
+ leaderArea.setBidiLevel ( fobj.getBidiLevelRecursive() );
}
}
TraitSetter.setProducerID(leaderArea, fobj.getId());
diff --git a/src/java/org/apache/fop/layoutmgr/inline/LeafNodeLayoutManager.java b/src/java/org/apache/fop/layoutmgr/inline/LeafNodeLayoutManager.java
index 5e3464da7..c7205905b 100644
--- a/src/java/org/apache/fop/layoutmgr/inline/LeafNodeLayoutManager.java
+++ b/src/java/org/apache/fop/layoutmgr/inline/LeafNodeLayoutManager.java
@@ -224,7 +224,7 @@ public abstract class LeafNodeLayoutManager extends AbstractLayoutManager
* @param context the layout context used for adding the area
*/
protected void offsetArea(InlineArea area, LayoutContext context) {
- area.setOffset(alignmentContext.getOffset());
+ area.setBlockProgressionOffset(alignmentContext.getOffset());
}
/**
diff --git a/src/java/org/apache/fop/layoutmgr/inline/LineLayoutManager.java b/src/java/org/apache/fop/layoutmgr/inline/LineLayoutManager.java
index db7d1edc8..f57b9367a 100644
--- a/src/java/org/apache/fop/layoutmgr/inline/LineLayoutManager.java
+++ b/src/java/org/apache/fop/layoutmgr/inline/LineLayoutManager.java
@@ -32,6 +32,7 @@ import org.apache.fop.area.Area;
import org.apache.fop.area.LineArea;
import org.apache.fop.area.Trait;
import org.apache.fop.area.inline.InlineArea;
+import org.apache.fop.complexscripts.bidi.BidiResolver;
import org.apache.fop.datatypes.Length;
import org.apache.fop.datatypes.Numeric;
import org.apache.fop.fo.Constants;
@@ -111,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;
@@ -119,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;
@@ -129,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;
@@ -140,6 +143,7 @@ public class LineLayoutManager extends InlineStackingLayoutManager
}
+ private int bidiLevel = -1;
private int textAlignment = EN_JUSTIFY;
private int textAlignmentLast;
private int effectiveAlignment;
@@ -333,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;
@@ -377,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++;
}
@@ -389,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;
@@ -456,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);
@@ -544,6 +574,7 @@ public class LineLayoutManager extends InlineStackingLayoutManager
/** {@inheritDoc} */
@Override
public void initialize() {
+ bidiLevel = fobj.getBidiLevel();
textAlignment = fobj.getTextAlign();
textAlignmentLast = fobj.getTextAlignLast();
textIndent = fobj.getTextIndent();
@@ -1429,8 +1460,12 @@ 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);
lineArea.addTrait(Trait.SPACE_BEFORE, lbp.spaceBefore);
lineArea.addTrait(Trait.SPACE_AFTER, lbp.spaceAfter);
alignmentContext.resizeLine(lbp.lineHeight, lbp.baseline);
@@ -1506,7 +1541,10 @@ public class LineLayoutManager extends InlineStackingLayoutManager
&& (!context.isLastArea() || !isLastPosition)) {
lineArea.setBPD(lineArea.getBPD() + context.getSpaceAfter());
}
- lineArea.finalise();
+ lineArea.finish();
+ if ( lineArea.getBidiLevel() >= 0 ) {
+ BidiResolver.reorder ( lineArea );
+ }
parentLayoutManager.addChildArea(lineArea);
}
@@ -1556,6 +1594,9 @@ public class LineLayoutManager extends InlineStackingLayoutManager
blocklc.setTrailingSpace(new SpaceSpecifier(false));
}
lineArea.updateExtentsFromChildren();
+ if ( lineArea.getBidiLevel() >= 0 ) {
+ BidiResolver.reorder ( lineArea );
+ }
parentLayoutManager.addChildArea(lineArea);
}
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/PageNumberLayoutManager.java b/src/java/org/apache/fop/layoutmgr/inline/PageNumberLayoutManager.java
index 4b7289b37..c3ee5af32 100644
--- a/src/java/org/apache/fop/layoutmgr/inline/PageNumberLayoutManager.java
+++ b/src/java/org/apache/fop/layoutmgr/inline/PageNumberLayoutManager.java
@@ -101,7 +101,7 @@ public class PageNumberLayoutManager extends LeafNodeLayoutManager {
TraitSetter.setProducerID(ta, fobj.getId());
ta.setIPD(baseArea.getIPD());
ta.setBPD(baseArea.getBPD());
- ta.setOffset(baseArea.getOffset());
+ ta.setBlockProgressionOffset(baseArea.getBlockProgressionOffset());
ta.setBaselineOffset(baseArea.getBaselineOffset());
ta.addTrait(Trait.COLOR, fobj.getColor()); //only to initialize the trait map
ta.getTraits().putAll(baseArea.getTraits());
diff --git a/src/java/org/apache/fop/layoutmgr/inline/ScaledBaselineTable.java b/src/java/org/apache/fop/layoutmgr/inline/ScaledBaselineTable.java
index d5d3e5edb..1ccba23d7 100644
--- a/src/java/org/apache/fop/layoutmgr/inline/ScaledBaselineTable.java
+++ b/src/java/org/apache/fop/layoutmgr/inline/ScaledBaselineTable.java
@@ -20,6 +20,7 @@
package org.apache.fop.layoutmgr.inline;
import org.apache.fop.fo.Constants;
+import org.apache.fop.traits.WritingMode;
/**
@@ -43,7 +44,7 @@ final class ScaledBaselineTable {
private final int dominantBaselineIdentifier;
- private final int writingMode;
+ private final WritingMode writingMode;
private final int dominantBaselineOffset;
@@ -66,7 +67,7 @@ final class ScaledBaselineTable {
int depth,
int xHeight,
int dominantBaselineIdentifier,
- int writingMode) {
+ WritingMode writingMode) {
this.altitude = altitude;
this.depth = depth;
this.xHeight = xHeight;
@@ -89,7 +90,7 @@ final class ScaledBaselineTable {
* Return the writing mode for this baseline table.
* @return the writing mode
*/
- int getWritingMode() {
+ WritingMode getWritingMode() {
return this.writingMode;
}
@@ -139,7 +140,7 @@ final class ScaledBaselineTable {
}
private boolean isHorizontalWritingMode() {
- return writingMode == Constants.EN_LR_TB || writingMode == Constants.EN_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 8767fe296..c63c65277 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;
@@ -29,6 +30,8 @@ import org.apache.commons.logging.LogFactory;
import org.apache.fop.area.Trait;
import org.apache.fop.area.inline.TextArea;
+import org.apache.fop.complexscripts.fonts.GlyphPositioningTable;
+import org.apache.fop.complexscripts.util.CharScript;
import org.apache.fop.fo.Constants;
import org.apache.fop.fo.FOText;
import org.apache.fop.fonts.Font;
@@ -71,6 +74,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;
@@ -78,14 +82,17 @@ public class TextLayoutManager extends LeafNodeLayoutManager {
private final boolean isSpace;
private boolean breakOppAfter;
private final Font font;
+ private final int level;
+ private final int[][] gposAdjustments;
- AreaInfo( // CSOK: ParameterNumber
- int startIndex, int breakIndex, int wordSpaceCount, int letterSpaceCount,
- MinOptMax areaIPD, boolean isHyphenated, boolean isSpace, boolean breakOppAfter,
- Font font) {
+ AreaInfo // CSOK: ParameterNumber
+ (int startIndex, int breakIndex, int wordSpaceCount, int letterSpaceCount,
+ MinOptMax areaIPD, boolean isHyphenated, boolean isSpace, boolean breakOppAfter,
+ Font font, int level, int[][] gposAdjustments) {
assert startIndex <= breakIndex;
this.startIndex = startIndex;
this.breakIndex = breakIndex;
+ this.wordCharLength = -1;
this.wordSpaceCount = wordSpaceCount;
this.letterSpaceCount = letterSpaceCount;
this.areaIPD = areaIPD;
@@ -93,10 +100,26 @@ public class TextLayoutManager extends LeafNodeLayoutManager {
this.isSpace = isSpace;
this.breakOppAfter = breakOppAfter;
this.font = font;
+ this.level = level;
+ 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) {
@@ -104,16 +127,16 @@ public class TextLayoutManager extends LeafNodeLayoutManager {
}
public String toString() {
- return "AreaInfo["
- + "letterSpaceCount = " + letterSpaceCount
- + ", wordSpaceCount = " + wordSpaceCount
+ return super.toString() + "{"
+ + "interval = [" + startIndex + "," + breakIndex + "]"
+ + ", isSpace = " + isSpace
+ + ", level = " + level
+ ", areaIPD = " + areaIPD
- + ", startIndex = " + startIndex
- + ", breakIndex = " + breakIndex
+ + ", letterSpaceCount = " + letterSpaceCount
+ + ", wordSpaceCount = " + wordSpaceCount
+ ", isHyphenated = " + isHyphenated
- + ", isSpace = " + isSpace
+ ", font = " + font
- + "]";
+ + "}";
}
}
@@ -149,7 +172,7 @@ public class TextLayoutManager extends LeafNodeLayoutManager {
* be used to influence the start position of the first letter. The entry i+1 defines the
* cursor advancement after the character i. A null entry means no special advancement.
*/
- private final MinOptMax[] letterAdjustArray; //size = textArray.length + 1
+ private final MinOptMax[] letterSpaceAdjustArray; //size = textArray.length + 1
/** Font used for the space between words. */
private Font spaceFont = null;
@@ -192,7 +215,7 @@ public class TextLayoutManager extends LeafNodeLayoutManager {
*/
public TextLayoutManager(FOText node) {
foText = node;
- letterAdjustArray = new MinOptMax[node.length() + 1];
+ letterSpaceAdjustArray = new MinOptMax[node.length() + 1];
areaInfos = new ArrayList();
}
@@ -269,7 +292,9 @@ public class TextLayoutManager extends LeafNodeLayoutManager {
}
if (tbpNext.getLeafPos() != -1) {
areaInfo = (AreaInfo) areaInfos.get(tbpNext.getLeafPos());
- if (lastAreaInfo == null || areaInfo.font != lastAreaInfo.font) {
+ if (lastAreaInfo == null
+ || ( areaInfo.font != lastAreaInfo.font )
+ || ( areaInfo.level != lastAreaInfo.level ) ) {
if (lastAreaInfo != null) {
addAreaInfoAreas(lastAreaInfo, wordSpaceCount,
letterSpaceCount, firstAreaInfoIndex,
@@ -291,6 +316,7 @@ public class TextLayoutManager extends LeafNodeLayoutManager {
addAreaInfoAreas(lastAreaInfo, wordSpaceCount, letterSpaceCount, firstAreaInfoIndex,
lastAreaInfoIndex, realWidth, context);
}
+
}
private void addAreaInfoAreas(AreaInfo areaInfo, int wordSpaceCount, int letterSpaceCount,
@@ -301,7 +327,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 "-";
@@ -311,8 +337,8 @@ public class TextLayoutManager extends LeafNodeLayoutManager {
}
for (int i = areaInfo.startIndex; i < areaInfo.breakIndex; i++) {
- MinOptMax letterAdjustment = letterAdjustArray[i + 1];
- if (letterAdjustment != null && letterAdjustment.isElastic()) {
+ MinOptMax letterSpaceAdjustment = letterSpaceAdjustArray[i + 1];
+ if (letterSpaceAdjustment != null && letterSpaceAdjustment.isElastic()) {
letterSpaceCount++;
}
}
@@ -387,21 +413,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[] letterAdjust;
- private int letterAdjustIndex;
-
- 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 <code>TextAreaBuilder</code> which itself builds an inline word area. This
@@ -432,7 +464,7 @@ public class TextLayoutManager extends LeafNodeLayoutManager {
calcBlockProgressionDimension();
setBlockProgressionDimension();
setBaselineOffset();
- setOffset();
+ setBlockProgressionOffset();
setText();
TraitSetter.addFontTraits(textArea, font);
textArea.addTrait(Trait.COLOR, foText.getColor());
@@ -471,11 +503,11 @@ public class TextLayoutManager extends LeafNodeLayoutManager {
textArea.setBaselineOffset(font.getAscender());
}
- private void setOffset() {
+ private void setBlockProgressionOffset() {
if (blockProgressionDimension == alignmentContext.getHeight()) {
- textArea.setOffset(0);
+ textArea.setBlockProgressionOffset(0);
} else {
- textArea.setOffset(alignmentContext.getOffset());
+ textArea.setBlockProgressionOffset(alignmentContext.getOffset());
}
}
@@ -483,7 +515,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);
@@ -491,15 +523,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;
}
}
}
@@ -509,26 +541,78 @@ 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);
addLetterAdjust(wordAreaInfo);
+ if ( addGlyphPositionAdjustments(wordAreaInfo) ) {
+ gposAdjusted = true;
+ }
}
if (isHyphenated(endIndex)) {
+ // TODO may be problematic in some I18N contexts [GA]
addHyphenationChar();
}
- textArea.addWord(wordChars.toString(), 0, letterAdjust);
+ if ( !gposAdjusted ) {
+ gposAdjustments = null;
+ }
+ textArea.addWord(wordChars.toString(), wordIPD, letterSpaceAdjust,
+ getNonEmptyLevels(), gposAdjustments, blockProgressionOffset);
}
- private void initWord(int charLength) {
- wordChars = new StringBuffer(charLength);
- letterAdjust = new int[charLength];
- letterAdjustIndex = 0;
+ 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;
+ }
+ }
+
+ /**
+ * 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[wordLength];
+ wordLevelsIndex = 0;
+ Arrays.fill ( wordLevels, -1 );
+ gposAdjustments = new int[wordLength][4];
+ gposAdjustmentsIndex = 0;
+ wordIPD = 0;
}
private boolean isHyphenated(int endIndex) {
@@ -537,27 +621,112 @@ 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) {
- for (int i = wordAreaInfo.startIndex; i < wordAreaInfo.breakIndex; i++) {
- wordChars.append(foText.charAt(i));
+ int s = wordAreaInfo.startIndex;
+ int e = wordAreaInfo.breakIndex;
+ if ( foText.hasMapping ( s, e ) ) {
+ wordChars.append ( foText.getMapping ( s, e ) );
+ addWordLevels ( foText.getMappingBidiLevels ( s, e ) );
+ } else {
+ for (int i = s; i < e; i++) {
+ wordChars.append(foText.charAt(i));
+ }
+ addWordLevels ( foText.getBidiLevels ( s, e ) );
+ }
+ 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 ) {
+ 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" );
+ }
}
+ 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 (letterAdjustIndex > 0) {
- MinOptMax adj = letterAdjustArray[i];
- letterAdjust[letterAdjustIndex] = 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) {
- letterAdjust[letterAdjustIndex] += textArea.getTextLetterSpaceAdjust();
+ if ( letterSpaceCount > 0 ) {
+ letterSpaceAdjust [ j ] += taAdjust;
letterSpaceCount--;
}
- letterAdjustIndex++;
}
+ 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;
+ 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;
+ }
+ }
+ }
+ }
+ } else {
+ throw new IllegalStateException
+ ( "gpos adjustments array too short: expect at least "
+ + need + " entries, but has only " + gposAdjustments.length
+ + " entries" );
+ }
+ }
+ gposAdjustmentsIndex += wordLength;
+ return adjusted;
}
/**
@@ -566,13 +735,44 @@ public class TextLayoutManager extends LeafNodeLayoutManager {
* Add the spaces - except zero-width spaces - to the TextArea.
*/
private void addSpaces() {
+ int blockProgressionOffset = 0;
+ // [TBD] need to better handling of spaceIPD assignment, for now,
+ // divide the area info's allocated IPD evenly among the
+ // non-zero-width space characters
+ int numZeroWidthSpaces = 0;
for (int i = areaInfo.startIndex; i < areaInfo.breakIndex; i++) {
char spaceChar = foText.charAt(i);
+ if (CharUtilities.isZeroWidthSpace(spaceChar)) {
+ numZeroWidthSpaces++;
+ }
+ }
+ int numSpaces = areaInfo.breakIndex - areaInfo.startIndex - numZeroWidthSpaces;
+ int spaceIPD = areaInfo.areaIPD.getOpt() / ( ( numSpaces > 0 ) ? numSpaces : 1 );
+ // add space area children, one for each non-zero-width space character
+ for (int i = areaInfo.startIndex; i < areaInfo.breakIndex; i++) {
+ char spaceChar = foText.charAt(i);
+ int level = foText.bidiLevelAt(i);
if (!CharUtilities.isZeroWidthSpace(spaceChar)) {
- textArea.addSpace(spaceChar, 0, CharUtilities.isAdjustableSpace(spaceChar));
+ textArea.addSpace
+ ( spaceChar, spaceIPD,
+ CharUtilities.isAdjustableSpace(spaceChar),
+ blockProgressionOffset, level );
}
}
}
+
+ }
+
+ private void addAreaInfo ( AreaInfo ai ) {
+ addAreaInfo ( areaInfos.size(), ai );
+ }
+
+ private void addAreaInfo ( int index, AreaInfo ai ) {
+ areaInfos.add ( index, ai );
+ }
+
+ private void removeAreaInfo ( int index ) {
+ areaInfos.remove ( index );
}
private AreaInfo getAreaInfo(int index) {
@@ -580,10 +780,10 @@ public class TextLayoutManager extends LeafNodeLayoutManager {
}
private void addToLetterAdjust(int index, int width) {
- if (letterAdjustArray[index] == null) {
- letterAdjustArray[index] = MinOptMax.getInstance(width);
+ if (letterSpaceAdjustArray[index] == null) {
+ letterSpaceAdjustArray[index] = MinOptMax.getInstance(width);
} else {
- letterAdjustArray[index] = letterAdjustArray[index].plus(width);
+ letterSpaceAdjustArray[index] = letterSpaceAdjustArray[index].plus(width);
}
}
@@ -600,6 +800,7 @@ public class TextLayoutManager extends LeafNodeLayoutManager {
/** {@inheritDoc} */
public List getNextKnuthElements(final LayoutContext context, final int alignment) {
+
lineStartBAP = context.getLineStartBorderAndPaddingWidth();
lineEndBAP = context.getLineEndBorderAndPaddingWidth();
alignmentContext = context.getAlignmentContext();
@@ -610,13 +811,19 @@ public class TextLayoutManager extends LeafNodeLayoutManager {
AreaInfo prevAreaInfo = null;
returnList.add(sequence);
+ if (LOG.isDebugEnabled()) {
+ LOG.debug ( "GK: [" + nextStart + "," + foText.length() + "]" );
+ }
LineBreakStatus lineBreakStatus = new LineBreakStatus();
thisStart = nextStart;
boolean inWord = false;
boolean inWhitespace = false;
char ch = 0;
+ int level = -1;
+ int prevLevel = -1;
while (nextStart < foText.length()) {
ch = foText.charAt(nextStart);
+ level = foText.bidiLevelAt(nextStart);
boolean breakOpportunity = false;
byte breakAction = keepTogether
? LineBreakStatus.PROHIBITED_BREAK
@@ -635,17 +842,29 @@ public class TextLayoutManager extends LeafNodeLayoutManager {
default:
TextLayoutManager.LOG.error("Unexpected breakAction: " + breakAction);
}
+ if (LOG.isDebugEnabled()) {
+ LOG.debug ( "GK: {"
+ + " index = " + nextStart
+ + ", char = " + CharUtilities.charToNCRef ( ch )
+ + ", level = " + level
+ + ", levelPrev = " + prevLevel
+ + ", inWord = " + inWord
+ + ", inSpace = " + inWhitespace
+ + "}" );
+ }
if (inWord) {
- if (breakOpportunity
- || TextLayoutManager.isSpace(ch)
- || CharUtilities.isExplicitBreak(ch)) {
+ if ( breakOpportunity
+ || TextLayoutManager.isSpace(ch)
+ || CharUtilities.isExplicitBreak(ch)
+ || ( ( prevLevel != -1 ) && ( level != prevLevel ) ) ) {
// this.foText.charAt(lastIndex) == CharUtilities.SOFT_HYPHEN
prevAreaInfo = processWord(alignment, sequence, prevAreaInfo, ch,
- breakOpportunity, true);
+ breakOpportunity, true, prevLevel);
}
} else if (inWhitespace) {
if (ch != CharUtilities.SPACE || breakOpportunity) {
- prevAreaInfo = processWhitespace(alignment, sequence, breakOpportunity);
+ prevAreaInfo = processWhitespace(alignment, sequence,
+ breakOpportunity, prevLevel);
}
} else {
if (areaInfo != null) {
@@ -665,14 +884,14 @@ public class TextLayoutManager extends LeafNodeLayoutManager {
// preserved space or non-breaking space:
// create the AreaInfo object
areaInfo = new AreaInfo(nextStart, nextStart + 1, 1, 0, wordSpaceIPD, false, true,
- breakOpportunity, spaceFont);
+ breakOpportunity, spaceFont, level, null);
thisStart = nextStart + 1;
} else if (CharUtilities.isFixedWidthSpace(ch) || CharUtilities.isZeroWidthSpace(ch)) {
// create the AreaInfo object
Font font = FontSelector.selectFontForCharacterInText(ch, foText, this);
MinOptMax ipd = MinOptMax.getInstance(font.getCharWidth(ch));
areaInfo = new AreaInfo(nextStart, nextStart + 1, 0, 0, ipd, false, true,
- breakOpportunity, font);
+ breakOpportunity, font, level, null);
thisStart = nextStart + 1;
} else if (CharUtilities.isExplicitBreak(ch)) {
//mandatory break-character: only advance index
@@ -682,14 +901,15 @@ public class TextLayoutManager extends LeafNodeLayoutManager {
inWord = !TextLayoutManager.isSpace(ch) && !CharUtilities.isExplicitBreak(ch);
inWhitespace = ch == CharUtilities.SPACE
&& foText.getWhitespaceTreatment() != Constants.EN_PRESERVE;
+ prevLevel = level;
nextStart++;
}
// Process any last elements
if (inWord) {
- processWord(alignment, sequence, prevAreaInfo, ch, false, false);
+ 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);
@@ -708,6 +928,8 @@ public class TextLayoutManager extends LeafNodeLayoutManager {
} else {
return returnList;
}
+
+
}
private KnuthSequence processLinebreak(List returnList, KnuthSequence sequence) {
@@ -723,20 +945,27 @@ public class TextLayoutManager extends LeafNodeLayoutManager {
private void processLeftoverAreaInfo(int alignment,
KnuthSequence sequence, AreaInfo areaInfo,
boolean breakOpportunityAfter) {
- areaInfos.add(areaInfo);
+ addAreaInfo(areaInfo);
areaInfo.breakOppAfter = breakOpportunityAfter;
addElementsForASpace(sequence, alignment, areaInfo, areaInfos.size() - 1);
}
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 + "]" );
+ }
+
// End of whitespace
// create the AreaInfo object
assert nextStart >= thisStart;
- AreaInfo areaInfo = new AreaInfo(thisStart, nextStart, nextStart - thisStart, 0,
- wordSpaceIPD.mult(nextStart - thisStart), false, true, breakOpportunity, spaceFont);
+ AreaInfo areaInfo = new AreaInfo
+ ( thisStart, nextStart, nextStart - thisStart, 0,
+ wordSpaceIPD.mult(nextStart - thisStart),
+ false, true, breakOpportunity, spaceFont, level, null );
- areaInfos.add(areaInfo);
+ addAreaInfo(areaInfo);
// create the elements
addElementsForASpace(sequence, alignment, areaInfo, areaInfos.size() - 1);
@@ -745,22 +974,136 @@ public class TextLayoutManager extends LeafNodeLayoutManager {
return areaInfo;
}
- private AreaInfo processWord(final int alignment, final KnuthSequence sequence,
- AreaInfo prevAreaInfo, final char ch, final boolean breakOpportunity,
- final boolean checkEndsWithHyphen) {
+ private AreaInfo processWordMapping
+ ( int lastIndex, final Font font, AreaInfo prevAreaInfo, final char breakOpportunityChar,
+ final boolean endsWithHyphen, int level ) {
+ int s = this.thisStart; // start index of word in FOText character buffer
+ int e = lastIndex; // end index of word in FOText character buffer
+ int nLS = 0; // # of letter spaces
+ String script = foText.getScript();
+ String language = foText.getLanguage();
+
+ if (LOG.isDebugEnabled()) {
+ LOG.debug ( "PW: [" + thisStart + "," + lastIndex + "]: {"
+ + " +M"
+ + ", level = " + level
+ + " }" );
+ }
- //Word boundary found, process widths and kerning
- int lastIndex = nextStart;
- while (lastIndex > 0 && foText.charAt(lastIndex - 1) == CharUtilities.SOFT_HYPHEN) {
- lastIndex--;
+ // 1. extract unmapped character sequence
+ CharSequence ics = foText.subSequence ( s, e );
+
+ // 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 = CharScript.scriptTagFromCode ( CharScript.dominantScript ( ics ) );
+ }
+ if ( ( language == null ) || "none".equals(language) ) {
+ language = "dflt";
}
- Font font = FontSelector
- .selectFontForCharactersInText(foText, thisStart, lastIndex, foText, this);
+
+ // 3. perform mapping of chars to glyphs ... to glyphs ... to chars
+ CharSequence mcs = font.performSubstitution ( ics, script, language );
+
+ // 4. compute glyph position adjustments on (substituted) characters
+ int[][] gpa;
+ if ( font.performsPositioning() ) {
+ // handle GPOS adjustments
+ gpa = font.performPositioning ( mcs, script, language );
+ } else if ( font.hasKerning() ) {
+ // handle standard (non-GPOS) kerning adjustments
+ gpa = getKerningAdjustments ( mcs, font );
+ } else {
+ 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++ ) {
+ int c = mcs.charAt ( i );
+ // TODO !BMP
+ int w = font.getCharWidth ( c );
+ if ( w < 0 ) {
+ w = 0;
+ }
+ if ( gpa != null ) {
+ w += gpa [ i ] [ GlyphPositioningTable.Value.IDX_X_ADVANCE ];
+ }
+ ipd = ipd.plus ( w );
+ }
+
+ // [TBD] - handle letter spacing
+
+ return new AreaInfo
+ ( s, e, 0, nLS, ipd, endsWithHyphen, false,
+ breakOpportunityChar != 0, font, level, gpa );
+ }
+
+ /**
+ * Given a mapped character sequence MCS, obtain glyph position adjustments
+ * from the font's kerning data.
+ * @param mcs mapped character sequence
+ * @param font applicable font
+ * @return glyph position adjustments (or null if no kerning)
+ */
+ private int[][] getKerningAdjustments ( CharSequence mcs, final Font font ) {
+ int nc = mcs.length();
+ // extract kerning array
+ int[] ka = new int [ nc ]; // kerning array
+ for ( int i = 0, n = nc, cPrev = -1; i < n; i++ ) {
+ int c = mcs.charAt ( i );
+ // TODO !BMP
+ if ( cPrev >= 0 ) {
+ ka[i] = font.getKernValue ( cPrev, c );
+ }
+ cPrev = c;
+ }
+ // was there a non-zero kerning?
+ boolean hasKerning = false;
+ for ( int i = 0, n = nc; i < n; i++ ) {
+ if ( ka[i] != 0 ) {
+ hasKerning = true;
+ break;
+ }
+ }
+ // if non-zero kerning, then create and return glyph position adjustment array
+ if ( hasKerning ) {
+ int[][] gpa = new int [ nc ] [ 4 ];
+ for ( int i = 0, n = nc; i < n; i++ ) {
+ if ( i > 0 ) {
+ gpa [ i - 1 ] [ GlyphPositioningTable.Value.IDX_X_ADVANCE ] = ka [ i ];
+ }
+ }
+ return gpa;
+ } else {
+ return null;
+ }
+ }
+
+ private AreaInfo processWordNoMapping(int lastIndex, final Font font, AreaInfo prevAreaInfo,
+ final char breakOpportunityChar, final boolean endsWithHyphen, int level) {
boolean kerning = font.hasKerning();
MinOptMax wordIPD = MinOptMax.ZERO;
+
+ if (LOG.isDebugEnabled()) {
+ LOG.debug ( "PW: [" + thisStart + "," + lastIndex + "]: {"
+ + " -M"
+ + ", level = " + level
+ + " }" );
+ }
+
for (int i = thisStart; i < lastIndex; i++) {
char currentChar = foText.charAt(i);
-
+
//character width
int charWidth = font.getCharWidth(currentChar);
wordIPD = wordIPD.plus(charWidth);
@@ -782,14 +1125,12 @@ public class TextLayoutManager extends LeafNodeLayoutManager {
}
}
}
- boolean endsWithHyphen = checkEndsWithHyphen
- && foText.charAt(lastIndex) == CharUtilities.SOFT_HYPHEN;
if (kerning
- && breakOpportunity
- && !TextLayoutManager.isSpace(ch)
+ && ( breakOpportunityChar != 0 )
+ && !TextLayoutManager.isSpace(breakOpportunityChar)
&& lastIndex > 0
&& endsWithHyphen) {
- int kern = font.getKernValue(foText.charAt(lastIndex - 1), ch);
+ int kern = font.getKernValue(foText.charAt(lastIndex - 1), breakOpportunityChar);
if (kern != 0) {
addToLetterAdjust(lastIndex, kern);
//TODO: add kern to wordIPD?
@@ -801,23 +1142,46 @@ public class TextLayoutManager extends LeafNodeLayoutManager {
int letterSpaces = 0;
if (wordLength != 0) {
letterSpaces = wordLength - 1;
- // if there is a break opportunity and the next one
+ // if there is a break opportunity and the next one (break character)
// is not a space, it could be used as a line end;
// add one more letter space, in case other text follows
- if (breakOpportunity && !TextLayoutManager.isSpace(ch)) {
- letterSpaces++;
+ if (( breakOpportunityChar != 0 ) && !TextLayoutManager.isSpace(breakOpportunityChar)) {
+ letterSpaces++;
}
}
assert letterSpaces >= 0;
wordIPD = wordIPD.plus(letterSpaceIPD.mult(letterSpaces));
- // create the AreaInfo object
- AreaInfo areaInfo = new AreaInfo(thisStart, lastIndex, 0,
- letterSpaces, wordIPD,
- endsWithHyphen,
- false, breakOpportunity, font);
+ // create and return the AreaInfo object
+ return new AreaInfo(thisStart, lastIndex, 0,
+ letterSpaces, wordIPD,
+ endsWithHyphen,
+ false, breakOpportunityChar != 0, font, level, null);
+ }
+
+ private AreaInfo processWord(final int alignment, final KnuthSequence sequence,
+ AreaInfo prevAreaInfo, final char ch, final boolean breakOpportunity,
+ final boolean checkEndsWithHyphen, int level) {
+
+ //Word boundary found, process widths and kerning
+ int lastIndex = nextStart;
+ while (lastIndex > 0 && foText.charAt(lastIndex - 1) == CharUtilities.SOFT_HYPHEN) {
+ lastIndex--;
+ }
+ final boolean endsWithHyphen = checkEndsWithHyphen
+ && foText.charAt(lastIndex) == CharUtilities.SOFT_HYPHEN;
+ Font font = FontSelector.selectFontForCharactersInText
+ ( foText, thisStart, lastIndex, foText, this );
+ AreaInfo areaInfo;
+ if ( font.performsSubstitution() || font.performsPositioning() ) {
+ areaInfo = processWordMapping
+ ( lastIndex, font, prevAreaInfo, breakOpportunity ? ch : 0, endsWithHyphen, level );
+ } else {
+ areaInfo = processWordNoMapping
+ ( lastIndex, font, prevAreaInfo, breakOpportunity ? ch : 0, endsWithHyphen, level );
+ }
prevAreaInfo = areaInfo;
- areaInfos.add(areaInfo);
+ addAreaInfo(areaInfo);
tempStart = nextStart;
//add the elements
@@ -902,22 +1266,22 @@ public class TextLayoutManager extends LeafNodeLayoutManager {
newIPD = newIPD.plus(font.getCharWidth(ch));
//if (i > startIndex) {
if (i < stopIndex) {
- MinOptMax letterAdjust = letterAdjustArray[i + 1];
+ MinOptMax letterSpaceAdjust = letterSpaceAdjustArray[i + 1];
if (i == stopIndex - 1 && hyphenFollows) {
//the letter adjust here needs to be handled further down during
//element generation because it depends on hyph/no-hyph condition
- letterAdjust = null;
+ letterSpaceAdjust = null;
}
- if (letterAdjust != null) {
- newIPD = newIPD.plus(letterAdjust);
+ if (letterSpaceAdjust != null) {
+ newIPD = newIPD.plus(letterSpaceAdjust);
}
}
}
// add letter spaces
boolean isWordEnd
- = stopIndex == areaInfo.breakIndex
- && areaInfo.letterSpaceCount < areaInfo.getCharLength();
+ = (stopIndex == areaInfo.breakIndex)
+ && (areaInfo.letterSpaceCount < areaInfo.getWordLength());
int letterSpaceCount = isWordEnd ? stopIndex - startIndex - 1 : stopIndex - startIndex;
assert letterSpaceCount >= 0;
@@ -925,8 +1289,11 @@ public class TextLayoutManager extends LeafNodeLayoutManager {
if (!(nothingChanged && stopIndex == areaInfo.breakIndex && !hyphenFollows)) {
// the new AreaInfo object is not equal to the old one
- changeList.add(new PendingChange(new AreaInfo(startIndex, stopIndex, 0,
- letterSpaceCount, newIPD, hyphenFollows, false, false, font),
+ changeList.add
+ ( new PendingChange
+ ( new AreaInfo(startIndex, stopIndex, 0,
+ letterSpaceCount, newIPD, hyphenFollows,
+ false, false, font, -1, null),
((LeafPosition) pos).getLeafPos() + changeOffset));
nothingChanged = false;
}
@@ -992,9 +1359,9 @@ public class TextLayoutManager extends LeafNodeLayoutManager {
areaInfosAdded++;
oldIndex = currChange.index;
changeIndex = currChange.index + areaInfosAdded - areaInfosRemoved;
- areaInfos.remove(changeIndex);
+ removeAreaInfo(changeIndex);
}
- areaInfos.add(changeIndex, currChange.areaInfo);
+ addAreaInfo(changeIndex, currChange.areaInfo);
}
changeList.clear();
}
@@ -1035,7 +1402,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));
}
@@ -1209,7 +1576,7 @@ public class TextLayoutManager extends LeafNodeLayoutManager {
MinOptMax widthIfNoBreakOccurs = null;
if (areaInfo.breakIndex < foText.length()) {
//Add in kerning in no-break condition
- widthIfNoBreakOccurs = letterAdjustArray[areaInfo.breakIndex];
+ widthIfNoBreakOccurs = letterSpaceAdjustArray[areaInfo.breakIndex];
}
//if (areaInfo.breakIndex)
@@ -1325,4 +1692,14 @@ public class TextLayoutManager extends LeafNodeLayoutManager {
}
+ /** {@inheritDoc} */
+ public String toString() {
+ return super.toString() + "{"
+ + "chars = \'"
+ + CharUtilities.toNCRefs ( foText.getCharSequence().toString() )
+ + "\'"
+ + ", len = " + foText.length()
+ + "}";
+ }
+
}
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/pdf/PDFTextUtil.java b/src/java/org/apache/fop/pdf/PDFTextUtil.java
index 3768bb6ce..ed475fcb8 100644
--- a/src/java/org/apache/fop/pdf/PDFTextUtil.java
+++ b/src/java/org/apache/fop/pdf/PDFTextUtil.java
@@ -81,8 +81,8 @@ public abstract class PDFTextUtil {
sb.append(PDFNumber.doubleOut(lt[5], DEC));
}
- private void writeChar(char ch, StringBuffer sb) {
- if (!useMultiByte) {
+ private static void writeChar(char ch, StringBuffer sb, boolean multibyte) {
+ if (!multibyte) {
if (ch < 32 || ch > 127) {
sb.append("\\").append(Integer.toOctalString(ch));
} else {
@@ -101,6 +101,10 @@ public abstract class PDFTextUtil {
}
}
+ private void writeChar(char ch, StringBuffer sb) {
+ writeChar ( ch, sb, useMultiByte );
+ }
+
private void checkInTextObject() {
if (!inTextObject) {
throw new IllegalStateException("Not in text object");
@@ -245,24 +249,38 @@ public abstract class PDFTextUtil {
bufTJ = new StringBuffer();
}
if (bufTJ.length() == 0) {
- bufTJ.append("[").append(startText);
+ bufTJ.append("[");
+ bufTJ.append(startText);
}
writeChar(codepoint, bufTJ);
}
/**
* Writes a glyph adjust value to the "TJ-Buffer".
+
+ * <p>Assumes the following:</p>
+ * <ol>
+ * <li>if buffer is currently empty, then this is the start of the array object
+ * that encodes the adjustment and character values, and, therfore, a LEFT
+ * SQUARE BRACKET '[' must be prepended; and
+ * </li>
+ * <li>otherwise (the buffer is
+ * not empty), then the last element written to the buffer was a mapped
+ * character, and, therefore, a terminating '&gt;' or ')' followed by a space
+ * must be appended to the buffer prior to appending the adjustment value.
+ * </li>
+ * </ol>
* @param adjust the glyph adjust value in thousands of text unit space.
*/
public void adjustGlyphTJ(double adjust) {
if (bufTJ == null) {
bufTJ = new StringBuffer();
}
- if (bufTJ.length() > 0) {
- bufTJ.append(endText).append(" ");
- }
if (bufTJ.length() == 0) {
bufTJ.append("[");
+ } else {
+ bufTJ.append(endText);
+ bufTJ.append(" ");
}
bufTJ.append(PDFNumber.doubleOut(adjust, DEC - 4));
bufTJ.append(" ");
@@ -285,4 +303,31 @@ public abstract class PDFTextUtil {
return bufTJ != null && bufTJ.length() > 0;
}
+ /**
+ * Writes a "Td" command with specified x and y coordinates.
+ * @param x coordinate
+ * @param y coordinate
+ */
+ public void writeTd ( double x, double y ) {
+ StringBuffer sb = new StringBuffer();
+ sb.append(PDFNumber.doubleOut(x, DEC));
+ sb.append(' ');
+ sb.append(PDFNumber.doubleOut(y, DEC));
+ sb.append ( " Td\n" );
+ write ( sb.toString() );
+ }
+
+ /**
+ * Writes a "Tj" command with specified character code.
+ * @param ch character code to write
+ */
+ public void writeTj ( char ch ) {
+ StringBuffer sb = new StringBuffer();
+ sb.append ( '<' );
+ writeChar ( ch, sb, true );
+ sb.append ( '>' );
+ sb.append ( " Tj\n" );
+ write ( sb.toString() );
+ }
+
}
diff --git a/src/java/org/apache/fop/render/AbstractPathOrientedRenderer.java b/src/java/org/apache/fop/render/AbstractPathOrientedRenderer.java
index 333248f4d..7930e62ab 100644
--- a/src/java/org/apache/fop/render/AbstractPathOrientedRenderer.java
+++ b/src/java/org/apache/fop/render/AbstractPathOrientedRenderer.java
@@ -73,29 +73,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);
}
/**
@@ -114,7 +116,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);
@@ -161,9 +168,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());
}
/**
@@ -179,11 +186,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();
@@ -192,19 +232,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) {
@@ -264,86 +304,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);
@@ -354,30 +407,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);
@@ -387,30 +440,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);
@@ -420,30 +474,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);
@@ -453,7 +508,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();
}
}
@@ -466,16 +521,16 @@ 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) {
float x = currentIPPosition / 1000f;
- float y = (currentBPPosition + area.getOffset()) / 1000f;
+ float y = (currentBPPosition + area.getBlockProgressionOffset()) / 1000f;
float width = area.getIPD() / 1000f;
drawBackAndBorders(area, x, y - borderPaddingBefore
, width + bpwidth
@@ -512,10 +567,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;
@@ -536,14 +597,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);
@@ -694,25 +759,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.getOffset()) / 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 3c07c0006..639f8590b 100644
--- a/src/java/org/apache/fop/render/AbstractRenderer.java
+++ b/src/java/org/apache/fop/render/AbstractRenderer.java
@@ -57,6 +57,7 @@ import org.apache.fop.area.RegionViewport;
import org.apache.fop.area.Span;
import org.apache.fop.area.Trait;
import org.apache.fop.area.inline.Container;
+import org.apache.fop.area.inline.FilledArea;
import org.apache.fop.area.inline.ForeignObject;
import org.apache.fop.area.inline.Image;
import org.apache.fop.area.inline.InlineArea;
@@ -402,22 +403,34 @@ public abstract class AbstractRenderer
* @param mr The main reference area
*/
protected void renderMainReference(MainReference mr) {
- int saveIPPos = currentIPPosition;
-
Span span = null;
List spans = mr.getSpans();
int saveBPPos = currentBPPosition;
int saveSpanBPPos = saveBPPos;
+ int saveIPPos = currentIPPosition;
for (int count = 0; count < spans.size(); count++) {
span = (Span) spans.get(count);
+ int level = span.getBidiLevel();
+ if ( level < 0 ) {
+ level = 0;
+ }
+ if ( ( level & 1 ) == 1 ) {
+ currentIPPosition += span.getIPD();
+ currentIPPosition += mr.getColumnGap();
+ }
for (int c = 0; c < span.getColumnCount(); c++) {
NormalFlow flow = span.getNormalFlow(c);
-
if (flow != null) {
currentBPPosition = saveSpanBPPos;
+ if ( ( level & 1 ) == 1 ) {
+ currentIPPosition -= flow.getIPD();
+ currentIPPosition -= mr.getColumnGap();
+ }
renderFlow(flow);
- currentIPPosition += flow.getIPD();
- currentIPPosition += mr.getColumnGap();
+ if ( ( level & 1 ) == 0 ) {
+ currentIPPosition += flow.getIPD();
+ currentIPPosition += mr.getColumnGap();
+ }
}
}
currentIPPosition = saveIPPos;
@@ -512,17 +525,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
@@ -541,15 +547,19 @@ public abstract class AbstractRenderer
renderBlock((Block) obj);
containingBPPosition = contBP;
containingIPPosition = contIP;
- } else {
+ } else if (obj instanceof LineArea) {
// 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()
- + line.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;
@@ -562,6 +572,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) {
@@ -611,13 +622,40 @@ public abstract class AbstractRenderer
List children = line.getInlineAreas();
int saveBP = currentBPPosition;
currentBPPosition += line.getSpaceBefore();
- for (int count = 0; count < children.size(); count++) {
- InlineArea inline = (InlineArea) children.get(count);
+ 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);
}
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
@@ -678,11 +716,12 @@ public abstract class AbstractRenderer
* @param text the text to render
*/
protected void renderText(TextArea text) {
+ List children = text.getChildAreas();
int saveIP = currentIPPosition;
int saveBP = currentBPPosition;
- Iterator iter = text.getChildAreas().iterator();
- while (iter.hasNext()) {
- renderInlineArea((InlineArea) iter.next());
+ for (int i = 0, l = children.size(); i < l; i++) {
+ InlineArea inline = (InlineArea) children.get(i);
+ renderInlineArea(inline);
}
currentIPPosition = saveIP + text.getAllocIPD();
}
@@ -708,14 +747,39 @@ 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();
- currentBPPosition += ip.getOffset();
- Iterator iter = ip.getChildAreas().iterator();
- while (iter.hasNext()) {
- renderInlineArea((InlineArea) iter.next());
+ // if inline parent is a filled area (generated by Leader), and if
+ // it is right-to-left, then adjust starting ip position in order to
+ // align children to starting (right) edge of filled area
+ int ipAdjust;
+ if ( ( ip instanceof FilledArea ) && ( ( level & 1 ) != 0 ) ) {
+ int ipdChildren = 0;
+ for (int i = 0, l = children.size(); i < l; i++) {
+ InlineArea inline = (InlineArea) children.get(i);
+ ipdChildren += inline.getAllocIPD();
+ }
+ ipAdjust = ip.getAllocIPD() - ipdChildren;
+ } else {
+ ipAdjust = 0;
+ }
+ // perform inline position adjustments
+ if ( ( level == -1 ) || ( ( level & 1 ) == 0 ) ) {
+ currentIPPosition += ip.getBorderAndPaddingWidthStart();
+ } else {
+ currentIPPosition += ip.getBorderAndPaddingWidthEnd();
+ if ( ipAdjust > 0 ) {
+ currentIPPosition += ipAdjust;
+ }
+ }
+ currentBPPosition += ip.getBlockProgressionOffset();
+ // render children inlines
+ for (int i = 0, l = children.size(); i < l; i++) {
+ InlineArea inline = (InlineArea) children.get(i);
+ renderInlineArea(inline);
}
currentIPPosition = saveIP + ip.getAllocIPD();
currentBPPosition = saveBP;
@@ -726,11 +790,16 @@ 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.getOffset();
+ currentBPPosition += ibp.getBlockProgressionOffset();
renderBlock(ibp.getChildArea());
currentBPPosition = saveBP;
}
@@ -742,7 +811,7 @@ public abstract class AbstractRenderer
protected void renderInlineViewport(InlineViewport viewport) {
Area content = viewport.getContent();
int saveBP = currentBPPosition;
- currentBPPosition += viewport.getOffset();
+ currentBPPosition += viewport.getBlockProgressionOffset();
Rectangle2D contpos = viewport.getContentPosition();
if (content instanceof Image) {
renderImage((Image) content, contpos);
diff --git a/src/java/org/apache/fop/render/DefaultFontResolver.java b/src/java/org/apache/fop/render/DefaultFontResolver.java
index a4960c665..0642537c4 100644
--- a/src/java/org/apache/fop/render/DefaultFontResolver.java
+++ b/src/java/org/apache/fop/render/DefaultFontResolver.java
@@ -44,4 +44,9 @@ public class DefaultFontResolver implements FontResolver {
return userAgent.resolveURI(href, userAgent.getFactory().getFontManager().getFontBaseURL());
}
+ /** {@inheritDoc} */
+ public boolean isComplexScriptFeaturesEnabled() {
+ return userAgent.isComplexScriptFeaturesEnabled();
+ }
+
}
diff --git a/src/java/org/apache/fop/render/PrintRenderer.java b/src/java/org/apache/fop/render/PrintRenderer.java
index 4e49adc25..96a62cb11 100644
--- a/src/java/org/apache/fop/render/PrintRenderer.java
+++ b/src/java/org/apache/fop/render/PrintRenderer.java
@@ -91,7 +91,8 @@ public abstract class PrintRenderer extends AbstractRenderer {
FontManager fontManager = userAgent.getFactory().getFontManager();
FontCollection[] fontCollections = new FontCollection[] {
new Base14FontCollection(fontManager.isBase14KerningEnabled()),
- new CustomFontCollection(getFontResolver(), getFontList())
+ new CustomFontCollection(getFontResolver(), getFontList(),
+ userAgent.isComplexScriptFeaturesEnabled())
};
fontManager.setup(getFontInfo(), fontCollections);
}
diff --git a/src/java/org/apache/fop/render/PrintRendererConfigurator.java b/src/java/org/apache/fop/render/PrintRendererConfigurator.java
index 90305ae03..93678b4f8 100644
--- a/src/java/org/apache/fop/render/PrintRendererConfigurator.java
+++ b/src/java/org/apache/fop/render/PrintRendererConfigurator.java
@@ -95,7 +95,9 @@ public class PrintRendererConfigurator extends AbstractRendererConfigurator
FontManager fontManager = factory.getFontManager();
if (fontResolver == null) {
//Ensure that we have minimal font resolution capabilities
- fontResolver = FontManager.createMinimalFontResolver();
+ fontResolver
+ = FontManager.createMinimalFontResolver
+ ( userAgent.isComplexScriptFeaturesEnabled() );
}
boolean strict = factory.validateUserConfigStrictly();
@@ -128,7 +130,8 @@ public class PrintRendererConfigurator extends AbstractRendererConfigurator
FontEventListener listener = new FontEventAdapter(
userAgent.getEventBroadcaster());
List<EmbedFontInfo> fontList = buildFontList(cfg, fontResolver, listener);
- fontCollections.add(new CustomFontCollection(fontResolver, fontList));
+ fontCollections.add(new CustomFontCollection(fontResolver, fontList,
+ userAgent.isComplexScriptFeaturesEnabled()));
}
fontManager.setup(fontInfo,
diff --git a/src/java/org/apache/fop/render/afp/AFPPainter.java b/src/java/org/apache/fop/render/afp/AFPPainter.java
index 28ca6c67f..30e4e7693 100644
--- a/src/java/org/apache/fop/render/afp/AFPPainter.java
+++ b/src/java/org/apache/fop/render/afp/AFPPainter.java
@@ -57,6 +57,7 @@ import org.apache.fop.render.intermediate.BorderPainter;
import org.apache.fop.render.intermediate.IFContext;
import org.apache.fop.render.intermediate.IFException;
import org.apache.fop.render.intermediate.IFState;
+import org.apache.fop.render.intermediate.IFUtil;
import org.apache.fop.traits.BorderProps;
import org.apache.fop.traits.RuleStyle;
import org.apache.fop.util.CharUtilities;
@@ -259,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);
}
@@ -354,8 +355,8 @@ public class AFPPainter extends AbstractIFPainter {
/** {@inheritDoc} */
public void drawText( // CSOK: MethodLength
- int x, int y, final int letterSpacing, final int wordSpacing, final int[] dx,
- final String text) throws IFException {
+ int x, int y, final int letterSpacing, final int wordSpacing,
+ final int[][] dp, final String text) throws IFException {
final int fontSize = this.state.getFontSize();
getPaintingState().setFontSize(fontSize);
@@ -404,6 +405,7 @@ public class AFPPainter extends AbstractIFPainter {
builder.setCodedFont((byte)fontReference);
int l = text.length();
+ int[] dx = IFUtil.convertDPToDX ( dp );
int dxl = (dx != null ? dx.length : 0);
StringBuffer sb = new StringBuffer();
diff --git a/src/java/org/apache/fop/render/bitmap/BitmapRendererConfigurator.java b/src/java/org/apache/fop/render/bitmap/BitmapRendererConfigurator.java
index 2d78b53c6..296d4628a 100644
--- a/src/java/org/apache/fop/render/bitmap/BitmapRendererConfigurator.java
+++ b/src/java/org/apache/fop/render/bitmap/BitmapRendererConfigurator.java
@@ -129,7 +129,8 @@ public class BitmapRendererConfigurator extends Java2DRendererConfigurator
FontEventListener listener = new FontEventAdapter(
userAgent.getEventBroadcaster());
List fontList = buildFontList(cfg, fontResolver, listener);
- fontCollections.add(new ConfiguredFontCollection(fontResolver, fontList));
+ fontCollections.add(new ConfiguredFontCollection(fontResolver, fontList,
+ userAgent.isComplexScriptFeaturesEnabled()));
}
fontManager.setup(fontInfo,
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 00fd74209..06dfbd181 100644
--- a/src/java/org/apache/fop/render/intermediate/IFPainter.java
+++ b/src/java/org/apache/fop/render/intermediate/IFPainter.java
@@ -151,12 +151,14 @@ public interface IFPainter {
* @param y Y-coordinate of the starting point of the text
* @param letterSpacing additional spacing between characters (may be 0)
* @param wordSpacing additional spacing between words (may be 0)
- * @param dx an array of adjustment values for each character in X-direction (may be null)
+ * @param dp an array of 4-tuples, expressing [X,Y] placment
+ * adjustments and [X,Y] advancement adjustments, in that order (may be null); if
+ * not null, then adjustments.length must be the same as text.length()
* @param text the text
* @throws IFException if an error occurs while handling this event
*/
void drawText(int x, int y, int letterSpacing, int wordSpacing,
- int[] dx, String text) throws IFException;
+ int[][] dp, String text) throws IFException;
/**
* Restricts the current clipping region with the given rectangle.
@@ -178,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 36f1fd841..26d1b62ed 100644
--- a/src/java/org/apache/fop/render/intermediate/IFParser.java
+++ b/src/java/org/apache/fop/render/intermediate/IFParser.java
@@ -618,8 +618,14 @@ public class IFParser implements IFConstants {
s = lastAttributes.getValue("word-spacing");
int wordSpacing = (s != null ? Integer.parseInt(s) : 0);
int[] dx = XMLUtil.getAttributeAsIntArray(lastAttributes, "dx");
+ int[][] dp = XMLUtil.getAttributeAsPositionAdjustments(lastAttributes, "dp");
+ // if only DX present, then convert DX to DP; otherwise use only DP,
+ // effectively ignoring DX
+ if ( ( dp == null ) && ( dx != null ) ) {
+ dp = IFUtil.convertDXToDP ( dx );
+ }
establishStructureTreeElement(lastAttributes);
- painter.drawText(x, y, letterSpacing, wordSpacing, dx, content.toString());
+ painter.drawText(x, y, letterSpacing, wordSpacing, dp, content.toString());
resetStructureTreeElement();
}
@@ -679,7 +685,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 4fec6c623..2987a4be8 100644
--- a/src/java/org/apache/fop/render/intermediate/IFRenderer.java
+++ b/src/java/org/apache/fop/render/intermediate/IFRenderer.java
@@ -61,6 +61,7 @@ import org.apache.fop.area.BlockViewport;
import org.apache.fop.area.BookmarkData;
import org.apache.fop.area.CTM;
import org.apache.fop.area.DestinationData;
+import org.apache.fop.area.LineArea;
import org.apache.fop.area.OffDocumentExtensionAttachment;
import org.apache.fop.area.OffDocumentItem;
import org.apache.fop.area.PageSequence;
@@ -488,7 +489,8 @@ public class IFRenderer extends AbstractPathOrientedRenderer {
if (hasDocumentNavigation() && id != null) {
int extraMarginBefore = 5000; // millipoints
int ipp = currentIPPosition;
- int bpp = currentBPPosition + inlineArea.getOffset() - extraMarginBefore;
+ int bpp = currentBPPosition
+ + inlineArea.getBlockProgressionOffset() - extraMarginBefore;
saveAbsolutePosition(id, ipp, bpp);
}
}
@@ -919,7 +921,7 @@ public class IFRenderer extends AbstractPathOrientedRenderer {
AbstractAction action = null;
// make sure the rect is determined *before* calling super!
int ipp = currentIPPosition;
- int bpp = currentBPPosition + ip.getOffset();
+ int bpp = currentBPPosition + ip.getBlockProgressionOffset();
ipRect = new Rectangle(ipp, bpp, ip.getIPD(), ip.getBPD());
AffineTransform transform = graphicContext.getTransform();
ipRect = transform.createTransformedShape(ipRect).getBounds();
@@ -1031,7 +1033,7 @@ public class IFRenderer extends AbstractPathOrientedRenderer {
}
int rx = currentIPPosition + text.getBorderAndPaddingWidthStart();
- int bl = currentBPPosition + text.getOffset() + text.getBaselineOffset();
+ int bl = currentBPPosition + text.getBlockProgressionOffset() + text.getBaselineOffset();
textUtil.flush();
textUtil.setStartPosition(rx, bl);
textUtil.setSpacing(text.getTextLetterSpaceAdjust(), text.getTextWordSpaceAdjust());
@@ -1047,7 +1049,12 @@ public class IFRenderer extends AbstractPathOrientedRenderer {
Font font = getFontFromArea(word.getParentArea());
String s = word.getWord();
- renderText(s, word.getLetterAdjustArray(),
+ int[][] dp = word.getGlyphPositionAdjustments();
+ if ( dp == null ) {
+ dp = IFUtil.convertDXToDP ( word.getLetterAdjustArray() );
+ }
+
+ renderText(s, dp, word.isReversed(),
font, (AbstractTextArea)word.getParentArea());
super.renderWord(word);
@@ -1059,7 +1066,7 @@ public class IFRenderer extends AbstractPathOrientedRenderer {
String s = space.getSpace();
AbstractTextArea textArea = (AbstractTextArea)space.getParentArea();
- renderText(s, null, font, textArea);
+ renderText(s, null, false, font, textArea);
if (textUtil.combined && space.isAdjustable()) {
//Used for justified text, for example
@@ -1072,24 +1079,32 @@ public class IFRenderer extends AbstractPathOrientedRenderer {
super.renderSpace(space);
}
+ private void renderText(String s,
+ int[][] dp, boolean reversed,
+ Font font, AbstractTextArea parentArea) {
+ if ( ( dp == null ) || IFUtil.isDPOnlyDX ( dp ) ) {
+ int[] dx = IFUtil.convertDPToDX ( dp );
+ renderTextWithAdjustments ( s, dx, reversed, font, parentArea );
+ } else {
+ renderTextWithAdjustments ( s, dp, reversed, font, parentArea );
+ }
+ }
+
/**
- * Does low-level rendering of text.
+ * Does low-level rendering of text using DX only position adjustments.
* @param s text to render
- * @param letterAdjust an array of widths for letter adjustment (may be null)
+ * @param dx an array of widths for letter adjustment (may be null)
+ * @param reversed if true then text has been reversed (from logical order)
* @param font to font in use
* @param parentArea the parent text area to retrieve certain traits from
*/
- protected void renderText(String s,
- int[] letterAdjust,
- Font font, AbstractTextArea parentArea) {
+ private void renderTextWithAdjustments(String s,
+ int[] dx, boolean reversed,
+ Font font, AbstractTextArea parentArea) {
int l = s.length();
if (l == 0) {
return;
}
-
- if (letterAdjust != null) {
- textUtil.adjust(letterAdjust[0]);
- }
for (int i = 0; i < l; i++) {
char ch = s.charAt(i);
textUtil.addChar(ch);
@@ -1098,18 +1113,38 @@ public class IFRenderer extends AbstractPathOrientedRenderer {
int tls = (i < l - 1 ? parentArea.getTextLetterSpaceAdjust() : 0);
glyphAdjust += tls;
}
- if (letterAdjust != null && i < l - 1) {
- glyphAdjust += letterAdjust[i + 1];
+ if (dx != null && i < l) {
+ glyphAdjust += dx[i];
}
-
textUtil.adjust(glyphAdjust);
}
}
+ /**
+ * Does low-level rendering of text using generalized position adjustments.
+ * @param s text to render
+ * @param dp an array of 4-tuples, expressing [X,Y] placment
+ * adjustments and [X,Y] advancement adjustments, in that order (may be null)
+ * @param reversed if true then text has been reversed (from logical order)
+ * @param font to font in use
+ * @param parentArea the parent text area to retrieve certain traits from
+ */
+ private void renderTextWithAdjustments(String s,
+ int[][] dp, boolean reversed,
+ Font font, AbstractTextArea parentArea) {
+ assert !textUtil.combined;
+ for ( int i = 0, n = s.length(); i < n; i++ ) {
+ textUtil.addChar ( s.charAt ( i ) );
+ if ( dp != null ) {
+ textUtil.adjust ( dp[i] );
+ }
+ }
+ }
+
private class TextUtil {
private static final int INITIAL_BUFFER_SIZE = 16;
- private int[] dx = new int[INITIAL_BUFFER_SIZE];
- private int lastDXPos = 0;
+ private int[][] dp = new int[INITIAL_BUFFER_SIZE][4];
+ // private int lastDPPos = 0; // TBD - not yet used
private final StringBuffer text = new StringBuffer();
private int startx, starty;
private int tls, tws;
@@ -1119,25 +1154,41 @@ public class IFRenderer extends AbstractPathOrientedRenderer {
text.append(ch);
}
- void adjust(int adjust) {
- if (adjust != 0) {
+ void adjust(int dx) {
+ adjust ( new int[] {
+ dx, // xPlaAdjust
+ 0, // yPlaAdjust
+ dx, // xAdvAdjust
+ 0 // yAdvAdjust
+ } );
+ }
+
+ void adjust(int[] pa) {
+ if ( !IFUtil.isPAIdentity ( pa ) ) {
int idx = text.length();
- if (idx > dx.length - 1) {
- int newSize = Math.max(dx.length, idx + 1) + INITIAL_BUFFER_SIZE;
- int[] newDX = new int[newSize];
- System.arraycopy(dx, 0, newDX, 0, dx.length);
- dx = newDX;
+ if (idx > dp.length - 1) {
+ int newSize = Math.max(dp.length, idx + 1) + INITIAL_BUFFER_SIZE;
+ int[][] newDP = new int[newSize][];
+ // reuse prior PA[0]...PA[dp.length-1]
+ System.arraycopy(dp, 0, newDP, 0, dp.length);
+ // populate new PA[dp.length]...PA[newDP.length-1]
+ for ( int i = dp.length, n = newDP.length; i < n; i++ ) {
+ newDP[i] = new int[4];
+ }
+ dp = newDP;
}
- dx[idx] += adjust;
- lastDXPos = idx;
+ IFUtil.adjustPA ( dp[idx - 1], pa );
+ // lastDPPos = idx;
}
}
void reset() {
if (text.length() > 0) {
text.setLength(0);
- Arrays.fill(dx, 0);
- lastDXPos = 0;
+ for ( int i = 0, n = dp.length; i < n; i++ ) {
+ Arrays.fill(dp[i], 0);
+ }
+ // lastDPPos = 0;
}
}
@@ -1154,16 +1205,12 @@ public class IFRenderer extends AbstractPathOrientedRenderer {
void flush() {
if (text.length() > 0) {
try {
- int[] effDX = null;
- if (lastDXPos > 0) {
- int size = lastDXPos + 1;
- effDX = new int[size];
- System.arraycopy(dx, 0, effDX, 0, size);
- }
if (combined) {
- painter.drawText(startx, starty, 0, 0, effDX, text.toString());
+ painter.drawText(startx, starty, 0, 0,
+ trimAdjustments ( dp, text.length() ), text.toString());
} else {
- painter.drawText(startx, starty, tls, tws, effDX, text.toString());
+ painter.drawText(startx, starty, tls, tws,
+ trimAdjustments ( dp, text.length() ), text.toString());
}
} catch (IFException e) {
handleIFException(e);
@@ -1171,6 +1218,38 @@ public class IFRenderer extends AbstractPathOrientedRenderer {
reset();
}
}
+
+ /**
+ * Trim adjustments array <code>dp</code> to be no greater length than
+ * text length, and where trailing all-zero entries are removed.
+ * @param dp a position adjustments array (or null)
+ * @param textLength the length of the associated text
+ * @return either the original value of <code>dp</code> or a copy
+ * of its first N significant adjustment entries, such that N is
+ * no greater than text length, and the last entry has a non-zero
+ * adjustment.
+ */
+ private int[][] trimAdjustments ( int[][] dp, int textLength ) {
+ if ( dp != null ) {
+ int tl = textLength;
+ int pl = dp.length;
+ int i = ( tl < pl ) ? tl : pl;
+ while ( i > 0 ) {
+ int[] pa = dp [ i - 1 ];
+ if ( !IFUtil.isPAIdentity ( pa ) ) {
+ break;
+ } else {
+ i--;
+ }
+ }
+ if ( i == 0 ) {
+ dp = null;
+ } else if ( i < pl ) {
+ dp = IFUtil.copyDP ( dp, 0, i );
+ }
+ }
+ return dp;
+ }
}
/** {@inheritDoc} */
@@ -1220,7 +1299,7 @@ public class IFRenderer extends AbstractPathOrientedRenderer {
int style = area.getRuleStyle();
int ruleThickness = area.getRuleThickness();
int startx = currentIPPosition + area.getBorderAndPaddingWidthStart();
- int starty = currentBPPosition + area.getOffset() + (ruleThickness / 2);
+ int starty = currentBPPosition + area.getBlockProgressionOffset() + (ruleThickness / 2);
int endx = currentIPPosition
+ area.getBorderAndPaddingWidthStart()
+ area.getIPD();
@@ -1262,10 +1341,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 7b9d95849..f8f286cb3 100644
--- a/src/java/org/apache/fop/render/intermediate/IFSerializer.java
+++ b/src/java/org/apache/fop/render/intermediate/IFSerializer.java
@@ -521,9 +521,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 {
@@ -532,17 +532,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) {
@@ -571,7 +571,7 @@ public class IFSerializer extends AbstractXMLWritingIFDocumentHandler
/** {@inheritDoc} */
public void drawText(int x, int y, int letterSpacing, int wordSpacing,
- int[] dx, String text) throws IFException {
+ int[][] dp, String text) throws IFException {
try {
addID();
AttributesImpl atts = new AttributesImpl();
@@ -583,8 +583,17 @@ public class IFSerializer extends AbstractXMLWritingIFDocumentHandler
if (wordSpacing != 0) {
addAttribute(atts, "word-spacing", Integer.toString(wordSpacing));
}
- if (dx != null) {
- addAttribute(atts, "dx", IFUtil.toString(dx));
+ if (dp != null) {
+ if ( IFUtil.isDPIdentity(dp) ) {
+ // don't add dx or dp attribute
+ } else if ( IFUtil.isDPOnlyDX(dp) ) {
+ // add dx attribute only
+ int[] dx = IFUtil.convertDPToDX(dp);
+ addAttribute(atts, "dx", IFUtil.toString(dx));
+ } else {
+ // add dp attribute only
+ addAttribute(atts, "dp", XMLUtil.encodePositionAdjustments(dp));
+ }
}
addStructureReference(atts);
handler.startElement(EL_TEXT, atts);
diff --git a/src/java/org/apache/fop/render/intermediate/IFUtil.java b/src/java/org/apache/fop/render/intermediate/IFUtil.java
index 1867b0294..86991ecb1 100644
--- a/src/java/org/apache/fop/render/intermediate/IFUtil.java
+++ b/src/java/org/apache/fop/render/intermediate/IFUtil.java
@@ -199,4 +199,182 @@ public final class IFUtil {
return documentHandler.getMimeType();
}
+ /**
+ * Convert the general gpos 'dp' adjustments to the older 'dx' adjustments.
+ * This utility method is used to provide backward compatibility in implementations
+ * of IFPainter that have not yet been upgraded to the general position adjustments format.
+ * @param dp an array of 4-tuples, expressing [X,Y] placment
+ * adjustments and [X,Y] advancement adjustments, in that order (may be null)
+ * @param count if <code>dp</code> is not null, then a count of dp values to convert
+ * @return if <code>dp</code> is not null, then an array of adjustments to the current
+ * x position prior to rendering individual glyphs; otherwise, null
+ */
+ public static int[] convertDPToDX ( int[][] dp, int count ) {
+ int[] dx;
+ if ( dp != null ) {
+ dx = new int [ count ];
+ for ( int i = 0, n = count; i < n; i++ ) {
+ dx [ i ] = dp [ i ] [ 0 ]; // xPlaAdjust[i]
+ }
+ } else {
+ dx = null;
+ }
+ return dx;
+ }
+
+ /**
+ * Convert the general gpos 'dp' adjustments to the older 'dx' adjustments.
+ * This utility method is used to provide backward compatibility in implementations
+ * of IFPainter that have not yet been upgraded to the general position adjustments format.
+ * @param dp an array of 4-tuples, expressing [X,Y] placment
+ * adjustments and [X,Y] advancement adjustments, in that order (may be null)
+ * @return if <code>dp</code> is not null, then an array of adjustments to the current
+ * x position prior to rendering individual glyphs; otherwise, null
+ */
+ public static int[] convertDPToDX ( int[][] dp ) {
+ return convertDPToDX ( dp, ( dp != null ) ? dp.length : 0 );
+ }
+
+ /**
+ * Convert the general gpos 'dp' adjustments to the older 'dx' adjustments.
+ * This utility method is used to provide backward compatibility in implementations
+ * of IFPainter that have not yet been upgraded to the general position adjustments format.
+ * @param dx an array of adjustments to the current x position prior to rendering
+ * individual glyphs or null
+ * @param count if <code>dx</code> is not null, then a count of dx values to convert
+ * @return if <code>dx</code> is not null, then an array of 4-tuples, expressing [X,Y]
+ * placment adjustments and [X,Y] advancement adjustments, in that order; otherwise, null
+ */
+ public static int[][] convertDXToDP ( int[] dx, int count ) {
+ int[][] dp;
+ if ( dx != null ) {
+ dp = new int [ count ] [ 4 ];
+ for ( int i = 0, n = count; i < n; i++ ) {
+ int[] pa = dp [ i ];
+ int d = dx [ i ];
+ pa [ 0 ] = d; // xPlaAdjust[i]
+ pa [ 2 ] = d; // xAdvAdjust[i]
+ }
+ } else {
+ dp = null;
+ }
+ return dp;
+ }
+
+ /**
+ * Convert the general gpos 'dp' adjustments to the older 'dx' adjustments.
+ * This utility method is used to provide backward compatibility in implementations
+ * of IFPainter that have not yet been upgraded to the general position adjustments format.
+ * @param dx an array of adjustments to the current x position prior to rendering
+ * individual glyphs or null
+ * @return if <code>dx</code> is not null, then an array of 4-tuples, expressing [X,Y]
+ * placment adjustments and [X,Y] advancement adjustments, in that order; otherwise, null
+ */
+ public static int[][] convertDXToDP ( int[] dx ) {
+ return convertDXToDP ( dx, ( dx != null ) ? dx.length : 0 );
+ }
+
+ /**
+ * Determine if position adjustment is the identity adjustment, i.e., no non-zero adjustment.
+ * @param pa a 4-tuple, expressing [X,Y] placment and [X,Y] advance adjuustments (may be null)
+ * @return true if <code>dp</code> is null or contains no non-zero adjustment
+ */
+ public static boolean isPAIdentity ( int[] pa ) {
+ if ( pa == null ) {
+ return true;
+ } else {
+ for ( int k = 0; k < 4; k++ ) {
+ if ( pa[k] != 0 ) {
+ return false;
+ }
+ }
+ return true;
+ }
+ }
+
+ /**
+ * Determine if position adjustments is the identity adjustment, i.e., no non-zero adjustment.
+ * @param dp an array of 4-tuples, expressing [X,Y] placment
+ * adjustments and [X,Y] advancement adjustments, in that order (may be null)
+ * @return true if <code>dp</code> is null or contains no non-zero adjustment
+ */
+ public static boolean isDPIdentity ( int[][] dp ) {
+ if ( dp == null ) {
+ return true;
+ } else {
+ for ( int i = 0, n = dp.length; i < n; i++ ) {
+ if ( !isPAIdentity ( dp[i] ) ) {
+ return false;
+ }
+ }
+ return true;
+ }
+ }
+
+ /**
+ * Determine if position adjustments comprises only DX adjustments as encoded by
+ * {@link #convertDPToDX}. Note that if given a set of all all zero position
+ * adjustments, both this method and {@link #isDPIdentity} will return true;
+ * however, this method may return true when {@link #isDPIdentity} returns false.
+ * @param dp an array of 4-tuples, expressing [X,Y] placment
+ * adjustments and [X,Y] advancement adjustments, in that order (may be null)
+ * @return true if <code>dp</code> is not null and contains only xPlaAdjust
+ * and xAdvAdjust values consistent with the output of {@link #convertDPToDX}.
+ */
+ public static boolean isDPOnlyDX ( int[][] dp ) {
+ if ( dp == null ) {
+ return false;
+ } else {
+ for ( int i = 0, n = dp.length; i < n; i++ ) {
+ int[] pa = dp[i];
+ if ( pa[0] != pa[2] ) {
+ return false;
+ }
+ }
+ return true;
+ }
+ }
+
+ /**
+ * Adjust a position adjustments array. If both <code>paDst</code> and <code>paSrc</code> are
+ * non-null, then <code>paSrc[i]</code> is added to <code>paDst[i]</code>.
+ * @param paDst a 4-tuple, expressing [X,Y] placment
+ * and [X,Y] advance adjuustments (may be null)
+ * @param paSrc a 4-tuple, expressing [X,Y] placment
+ * and [X,Y] advance adjuustments (may be null)
+ */
+ public static void adjustPA ( int[] paDst, int[] paSrc ) {
+ if ( ( paDst != null ) && ( paSrc != null ) ) {
+ assert paDst.length == 4; assert paSrc.length == 4;
+ for ( int i = 0; i < 4; i++ ) {
+ paDst[i] += paSrc[i];
+ }
+ }
+ }
+
+ /**
+ * Copy entries from position adjustments.
+ * @param dp an array of 4-tuples, expressing [X,Y] placment
+ * adjustments and [X,Y] advancement adjustments, in that order
+ * @param offset starting offset from which to copy
+ * @param count number of entries to copy
+ * @return a deep copy of the count position adjustment entries start at
+ * offset
+ */
+ public static int[][] copyDP ( int[][] dp, int offset, int count ) {
+ if ( ( dp == null ) || ( offset > dp.length ) || ( ( offset + count ) > dp.length ) ) {
+ throw new IllegalArgumentException();
+ } else {
+ int[][] dpNew = new int [ count ] [ 4 ];
+ for ( int i = 0, n = count; i < n; i++ ) {
+ int[] paDst = dpNew [ i ];
+ int[] paSrc = dp [ i + offset ];
+ for ( int k = 0; k < 4; k++ ) {
+ paDst [ k ] = paSrc [ k ];
+ }
+ }
+ return dpNew;
+ }
+ }
+
}
diff --git a/src/java/org/apache/fop/render/java2d/ConfiguredFontCollection.java b/src/java/org/apache/fop/render/java2d/ConfiguredFontCollection.java
index fb88b8bce..7ac350d5d 100644
--- a/src/java/org/apache/fop/render/java2d/ConfiguredFontCollection.java
+++ b/src/java/org/apache/fop/render/java2d/ConfiguredFontCollection.java
@@ -51,13 +51,14 @@ public class ConfiguredFontCollection implements FontCollection {
* Main constructor
* @param fontResolver a font resolver
* @param customFonts the list of custom fonts
+ * @param useComplexScriptFeatures true if complex script features enabled
*/
public ConfiguredFontCollection(FontResolver fontResolver,
- List/*<EmbedFontInfo>*/ customFonts) {
+ List/*<EmbedFontInfo>*/ customFonts, boolean useComplexScriptFeatures) {
this.fontResolver = fontResolver;
if (this.fontResolver == null) {
//Ensure that we have minimal font resolution capabilities
- this.fontResolver = FontManager.createMinimalFontResolver();
+ this.fontResolver = FontManager.createMinimalFontResolver(useComplexScriptFeatures);
}
this.embedFontInfoList = customFonts;
}
@@ -89,7 +90,8 @@ public class ConfiguredFontCollection implements FontCollection {
} else {
CustomFont fontMetrics = FontLoader.loadFont(
fontFile, null, true, EncodingMode.AUTO,
- configFontInfo.getKerning(), fontResolver);
+ configFontInfo.getKerning(),
+ configFontInfo.getAdvanced(), fontResolver);
font = new CustomFontMetricsMapper(fontMetrics);
}
diff --git a/src/java/org/apache/fop/render/java2d/Java2DPainter.java b/src/java/org/apache/fop/render/java2d/Java2DPainter.java
index 396645768..c7fa1adc5 100644
--- a/src/java/org/apache/fop/render/java2d/Java2DPainter.java
+++ b/src/java/org/apache/fop/render/java2d/Java2DPainter.java
@@ -42,6 +42,7 @@ import org.apache.fop.render.intermediate.IFContext;
import org.apache.fop.render.intermediate.IFException;
import org.apache.fop.render.intermediate.IFPainter;
import org.apache.fop.render.intermediate.IFState;
+import org.apache.fop.render.intermediate.IFUtil;
import org.apache.fop.traits.BorderProps;
import org.apache.fop.traits.RuleStyle;
import org.apache.fop.util.CharUtilities;
@@ -184,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");
@@ -203,7 +204,7 @@ public class Java2DPainter extends AbstractIFPainter {
}
/** {@inheritDoc} */
- public void drawText(int x, int y, int letterSpacing, int wordSpacing, int[] dx, String text)
+ public void drawText(int x, int y, int letterSpacing, int wordSpacing, int[][] dp, String text)
throws IFException {
g2dState.updateColor(state.getTextColor());
FontTriplet triplet = new FontTriplet(
@@ -220,6 +221,7 @@ public class Java2DPainter extends AbstractIFPainter {
Point2D cursor = new Point2D.Float(0, 0);
int l = text.length();
+ int[] dx = IFUtil.convertDPToDX ( dp );
int dxl = (dx != null ? dx.length : 0);
if (dx != null && dxl > 0 && dx[0] != 0) {
diff --git a/src/java/org/apache/fop/render/java2d/Java2DRenderer.java b/src/java/org/apache/fop/render/java2d/Java2DRenderer.java
index 1d1697607..45c91b2ee 100644
--- a/src/java/org/apache/fop/render/java2d/Java2DRenderer.java
+++ b/src/java/org/apache/fop/render/java2d/Java2DRenderer.java
@@ -178,7 +178,8 @@ public abstract class Java2DRenderer extends AbstractPathOrientedRenderer implem
FontCollection[] fontCollections = new FontCollection[] {
new Base14FontCollection(java2DFontMetrics),
new InstalledFontCollection(java2DFontMetrics),
- new ConfiguredFontCollection(getFontResolver(), getFontList())
+ new ConfiguredFontCollection(getFontResolver(), getFontList(),
+ userAgent.isComplexScriptFeaturesEnabled())
};
userAgent.getFactory().getFontManager().setup(
getFontInfo(), fontCollections);
@@ -715,7 +716,7 @@ public abstract class Java2DRenderer extends AbstractPathOrientedRenderer implem
renderInlineAreaBackAndBorders(text);
int rx = currentIPPosition + text.getBorderAndPaddingWidthStart();
- int bl = currentBPPosition + text.getOffset() + text.getBaselineOffset();
+ int bl = currentBPPosition + text.getBlockProgressionOffset() + text.getBaselineOffset();
int saveIP = currentIPPosition;
Font font = getFontFromArea(text);
@@ -827,7 +828,7 @@ public abstract class Java2DRenderer extends AbstractPathOrientedRenderer implem
// TODO Colors do not work on Leaders yet
float startx = (currentIPPosition + area.getBorderAndPaddingWidthStart()) / 1000f;
- float starty = ((currentBPPosition + area.getOffset()) / 1000f);
+ float starty = ((currentBPPosition + area.getBlockProgressionOffset()) / 1000f);
float endx = (currentIPPosition + area.getBorderAndPaddingWidthStart()
+ area.getIPD()) / 1000f;
diff --git a/src/java/org/apache/fop/render/pcl/PCLPainter.java b/src/java/org/apache/fop/render/pcl/PCLPainter.java
index afae8ac27..53e3d77da 100644
--- a/src/java/org/apache/fop/render/pcl/PCLPainter.java
+++ b/src/java/org/apache/fop/render/pcl/PCLPainter.java
@@ -51,6 +51,7 @@ import org.apache.fop.render.intermediate.IFContext;
import org.apache.fop.render.intermediate.IFException;
import org.apache.fop.render.intermediate.IFPainter;
import org.apache.fop.render.intermediate.IFState;
+import org.apache.fop.render.intermediate.IFUtil;
import org.apache.fop.render.java2d.FontMetricsMapper;
import org.apache.fop.render.java2d.Java2DPainter;
import org.apache.fop.traits.BorderProps;
@@ -206,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();
@@ -224,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);
@@ -307,7 +308,7 @@ public class PCLPainter extends AbstractIFPainter implements PCLConstants {
}
/** {@inheritDoc} */
- public void drawText(int x, int y, int letterSpacing, int wordSpacing, int[] dx, String text)
+ public void drawText(int x, int y, int letterSpacing, int wordSpacing, int[][] dp, String text)
throws IFException {
try {
FontTriplet triplet = new FontTriplet(
@@ -319,13 +320,13 @@ public class PCLPainter extends AbstractIFPainter implements PCLConstants {
? false
: HardcodedFonts.setFont(gen, fontKey, state.getFontSize(), text);
if (pclFont) {
- drawTextNative(x, y, letterSpacing, wordSpacing, dx, text, triplet);
+ drawTextNative(x, y, letterSpacing, wordSpacing, dp, text, triplet);
} else {
- drawTextAsBitmap(x, y, letterSpacing, wordSpacing, dx, text, triplet);
+ drawTextAsBitmap(x, y, letterSpacing, wordSpacing, dp, text, triplet);
if (DEBUG) {
state.setTextColor(Color.GRAY);
HardcodedFonts.setFont(gen, "F1", state.getFontSize(), text);
- drawTextNative(x, y, letterSpacing, wordSpacing, dx, text, triplet);
+ drawTextNative(x, y, letterSpacing, wordSpacing, dp, text, triplet);
}
}
} catch (IOException ioe) {
@@ -333,7 +334,7 @@ public class PCLPainter extends AbstractIFPainter implements PCLConstants {
}
}
- private void drawTextNative(int x, int y, int letterSpacing, int wordSpacing, int[] dx,
+ private void drawTextNative(int x, int y, int letterSpacing, int wordSpacing, int[][] dp,
String text, FontTriplet triplet) throws IOException {
Color textColor = state.getTextColor();
if (textColor != null) {
@@ -347,6 +348,7 @@ public class PCLPainter extends AbstractIFPainter implements PCLConstants {
float fontSize = state.getFontSize() / 1000f;
Font font = parent.getFontInfo().getFontInstance(triplet, state.getFontSize());
int l = text.length();
+ int[] dx = IFUtil.convertDPToDX ( dp );
int dxl = (dx != null ? dx.length : 0);
StringBuffer sb = new StringBuffer(Math.max(16, l));
@@ -392,7 +394,7 @@ public class PCLPainter extends AbstractIFPainter implements PCLConstants {
private Rectangle getTextBoundingBox( // CSOK: ParameterNumber
int x, int y,
- int letterSpacing, int wordSpacing, int[] dx,
+ int letterSpacing, int wordSpacing, int[][] dp,
String text,
Font font, FontMetricsMapper metrics) {
int maxAscent = metrics.getMaxAscent(font.getFontSize()) / 1000;
@@ -403,6 +405,7 @@ public class PCLPainter extends AbstractIFPainter implements PCLConstants {
0, maxAscent - descent + 2 * safetyMargin);
int l = text.length();
+ int[] dx = IFUtil.convertDPToDX ( dp );
int dxl = (dx != null ? dx.length : 0);
if (dx != null && dxl > 0 && dx[0] != 0) {
@@ -432,7 +435,7 @@ public class PCLPainter extends AbstractIFPainter implements PCLConstants {
}
private void drawTextAsBitmap(final int x, final int y,
- final int letterSpacing, final int wordSpacing, final int[] dx,
+ final int letterSpacing, final int wordSpacing, final int[][] dp,
final String text, FontTriplet triplet) throws IFException {
//Use Java2D to paint different fonts via bitmap
final Font font = parent.getFontInfo().getFontInstance(triplet, state.getFontSize());
@@ -447,7 +450,7 @@ public class PCLPainter extends AbstractIFPainter implements PCLConstants {
final int baselineOffset = maxAscent + safetyMargin;
final Rectangle boundingBox = getTextBoundingBox(x, y,
- letterSpacing, wordSpacing, dx, text, font, mapper);
+ letterSpacing, wordSpacing, dp, text, font, mapper);
final Dimension dim = boundingBox.getSize();
Graphics2DImagePainter painter = new Graphics2DImagePainter() {
@@ -470,7 +473,7 @@ public class PCLPainter extends AbstractIFPainter implements PCLConstants {
Java2DPainter painter = new Java2DPainter(g2d,
getContext(), parent.getFontInfo(), state);
try {
- painter.drawText(x, y, letterSpacing, wordSpacing, dx, text);
+ painter.drawText(x, y, letterSpacing, wordSpacing, dp, text);
} catch (IFException e) {
//This should never happen with the Java2DPainter
throw new RuntimeException("Unexpected error while painting text", e);
diff --git a/src/java/org/apache/fop/render/pcl/PCLRendererConfigurator.java b/src/java/org/apache/fop/render/pcl/PCLRendererConfigurator.java
index 605220ee2..84608b685 100644
--- a/src/java/org/apache/fop/render/pcl/PCLRendererConfigurator.java
+++ b/src/java/org/apache/fop/render/pcl/PCLRendererConfigurator.java
@@ -119,7 +119,8 @@ public class PCLRendererConfigurator extends PrintRendererConfigurator
FontEventListener listener = new FontEventAdapter(
userAgent.getEventBroadcaster());
List fontList = buildFontList(cfg, fontResolver, listener);
- fontCollections.add(new ConfiguredFontCollection(fontResolver, fontList));
+ fontCollections.add(new ConfiguredFontCollection(fontResolver, fontList,
+ userAgent.isComplexScriptFeaturesEnabled()));
}
fontManager.setup(fontInfo,
diff --git a/src/java/org/apache/fop/render/pdf/PDFPainter.java b/src/java/org/apache/fop/render/pdf/PDFPainter.java
index f2fbfd014..4928e7251 100644
--- a/src/java/org/apache/fop/render/pdf/PDFPainter.java
+++ b/src/java/org/apache/fop/render/pdf/PDFPainter.java
@@ -45,8 +45,10 @@ import org.apache.fop.render.intermediate.AbstractIFPainter;
import org.apache.fop.render.intermediate.IFContext;
import org.apache.fop.render.intermediate.IFException;
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;
@@ -259,12 +261,12 @@ public class PDFPainter 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) {
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);
}
@@ -291,7 +293,7 @@ public class PDFPainter extends AbstractIFPainter {
}
/** {@inheritDoc} */
- public void drawText(int x, int y, int letterSpacing, int wordSpacing, int[] dx,
+ public void drawText(int x, int y, int letterSpacing, int wordSpacing, int[][] dp,
String text)
throws IFException {
if (accessEnabled) {
@@ -309,6 +311,19 @@ public class PDFPainter extends AbstractIFPainter {
FontTriplet triplet = new FontTriplet(
state.getFontFamily(), state.getFontStyle(), state.getFontWeight());
+
+ if ( ( dp == null ) || IFUtil.isDPOnlyDX ( dp ) ) {
+ drawTextWithDX ( x, y, text, triplet, letterSpacing,
+ wordSpacing, IFUtil.convertDPToDX ( dp ) );
+ } else {
+ drawTextWithDP ( x, y, text, triplet, letterSpacing,
+ wordSpacing, dp );
+ }
+ }
+
+ private void drawTextWithDX ( int x, int y, String text, FontTriplet triplet,
+ int letterSpacing, int wordSpacing, int[] dx ) {
+
//TODO Ignored: state.getFontVariant()
//TODO Opportunity for font caching if font state is more heavily used
String fontKey = getFontInfo().getInternalFontKey(triplet);
@@ -375,6 +390,70 @@ public class PDFPainter extends AbstractIFPainter {
textutil.writeTJ();
}
+ private static int[] paZero = new int[4];
+
+ private void drawTextWithDP ( int x, int y, String text, FontTriplet triplet,
+ int letterSpacing, int wordSpacing, int[][] dp ) {
+ assert text != null;
+ assert triplet != null;
+ assert dp != null;
+ String fk = getFontInfo().getInternalFontKey(triplet);
+ Typeface tf = getTypeface(fk);
+ if ( tf.isMultiByte() ) {
+ int fs = state.getFontSize();
+ float fsPoints = fs / 1000f;
+ Font f = getFontInfo().getFontInstance(triplet, fs);
+ // String fn = f.getFontName();
+ PDFTextUtil tu = generator.getTextUtil();
+ double xc = 0f;
+ 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) + maybeWordOffsetX ( wox, ch, null );
+ double ya = 0;
+ double xd = ( xo - xoLast ) / 1000f;
+ double yd = ( yo - yoLast ) / 1000f;
+ tu.writeTd ( xd, yd );
+ tu.writeTj ( f.mapChar ( ch ) );
+ xc += xa + pa[2];
+ yc += ya + pa[3];
+ xoLast = xo;
+ yoLast = yo;
+ }
+ }
+ }
+
+ 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;
+ }
+ }
+ */
+
private char selectAndMapSingleByteFont(SingleByteFont singleByteFont, String fontName,
float fontSize, PDFTextUtil textutil, char ch) {
if (singleByteFont != null && singleByteFont.hasAdditionalEncodings()) {
diff --git a/src/java/org/apache/fop/render/ps/AbstractPSTranscoder.java b/src/java/org/apache/fop/render/ps/AbstractPSTranscoder.java
index 766c939f1..ad5090686 100644
--- a/src/java/org/apache/fop/render/ps/AbstractPSTranscoder.java
+++ b/src/java/org/apache/fop/render/ps/AbstractPSTranscoder.java
@@ -112,8 +112,9 @@ public abstract class AbstractPSTranscoder extends AbstractFOPTranscoder {
graphics = createDocumentGraphics2D();
if (!isTextStroked()) {
try {
+ boolean useComplexScriptFeatures = false; //TODO - FIX ME
this.fontInfo = PDFDocumentGraphics2DConfigurator.createFontInfo(
- getEffectiveConfiguration());
+ getEffectiveConfiguration(), useComplexScriptFeatures);
graphics.setCustomTextHandler(new NativeTextHandler(graphics, fontInfo));
} catch (FOPException fe) {
throw new TranscoderException(fe);
diff --git a/src/java/org/apache/fop/render/ps/NativeTextHandler.java b/src/java/org/apache/fop/render/ps/NativeTextHandler.java
index 7cf59d519..e1171b3e1 100644
--- a/src/java/org/apache/fop/render/ps/NativeTextHandler.java
+++ b/src/java/org/apache/fop/render/ps/NativeTextHandler.java
@@ -73,7 +73,8 @@ public class NativeTextHandler implements PSTextHandler {
private void setupFontInfo() {
//Sets up a FontInfo with default fonts
fontInfo = new FontInfo();
- FontSetup.setup(fontInfo);
+ boolean base14Kerning = false;
+ FontSetup.setup(fontInfo, base14Kerning);
}
/**
diff --git a/src/java/org/apache/fop/render/ps/PSPainter.java b/src/java/org/apache/fop/render/ps/PSPainter.java
index ef9239c2a..370472457 100644
--- a/src/java/org/apache/fop/render/ps/PSPainter.java
+++ b/src/java/org/apache/fop/render/ps/PSPainter.java
@@ -51,6 +51,7 @@ import org.apache.fop.render.intermediate.AbstractIFPainter;
import org.apache.fop.render.intermediate.IFContext;
import org.apache.fop.render.intermediate.IFException;
import org.apache.fop.render.intermediate.IFState;
+import org.apache.fop.render.intermediate.IFUtil;
import org.apache.fop.traits.BorderProps;
import org.apache.fop.traits.RuleStyle;
import org.apache.fop.util.CharUtilities;
@@ -234,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);
@@ -343,9 +344,8 @@ public class PSPainter extends AbstractIFPainter {
/** {@inheritDoc} */
public void drawText(int x, int y, int letterSpacing, int wordSpacing,
- int[] dx, String text) throws IFException {
+ int[][] dp, String text) throws IFException {
try {
- //Note: dy is currently ignored
PSGenerator generator = getGenerator();
generator.useColor(state.getTextColor());
beginTextObject();
@@ -383,8 +383,8 @@ public class PSPainter extends AbstractIFPainter {
int encoding = mapped / 256;
if (currentEncoding != encoding) {
if (i > 0) {
- writeText(text, start, i - start, letterSpacing, wordSpacing, dx,
- font, tf);
+ writeText(text, start, i - start,
+ letterSpacing, wordSpacing, dp, font, tf);
}
if (encoding == 0) {
useFont(fontKey, sizeMillipoints);
@@ -399,7 +399,7 @@ public class PSPainter extends AbstractIFPainter {
//Simple single-font painting
useFont(fontKey, sizeMillipoints);
}
- writeText(text, start, textLen - start, letterSpacing, wordSpacing, dx, font, tf);
+ writeText(text, start, textLen - start, letterSpacing, wordSpacing, dp, font, tf);
} catch (IOException ioe) {
throw new IFException("I/O error in drawText()", ioe);
}
@@ -407,7 +407,7 @@ public class PSPainter extends AbstractIFPainter {
private void writeText( // CSOK: ParameterNumber
String text, int start, int len,
- int letterSpacing, int wordSpacing, int[] dx,
+ int letterSpacing, int wordSpacing, int[][] dp,
Font font, Typeface tf) throws IOException {
PSGenerator generator = getGenerator();
int end = start + len;
@@ -420,6 +420,7 @@ public class PSPainter extends AbstractIFPainter {
int lineStart = 0;
StringBuffer accText = new StringBuffer(initialSize);
StringBuffer sb = new StringBuffer(initialSize);
+ int[] dx = IFUtil.convertDPToDX ( dp );
int dxl = (dx != null ? dx.length : 0);
for (int i = start; i < end; i++) {
char orgChar = text.charAt(i);
diff --git a/src/java/org/apache/fop/render/rtf/RTFHandler.java b/src/java/org/apache/fop/render/rtf/RTFHandler.java
index 22c0c8b76..95c4fec12 100644
--- a/src/java/org/apache/fop/render/rtf/RTFHandler.java
+++ b/src/java/org/apache/fop/render/rtf/RTFHandler.java
@@ -167,7 +167,8 @@ public class RTFHandler extends FOEventHandler {
this.os = os;
bDefer = true;
- FontSetup.setup(fontInfo, null, new DefaultFontResolver(userAgent));
+ boolean base14Kerning = false;
+ FontSetup.setup(fontInfo, null, new DefaultFontResolver(userAgent), base14Kerning);
}
/**
diff --git a/src/java/org/apache/fop/render/xml/XMLRenderer.java b/src/java/org/apache/fop/render/xml/XMLRenderer.java
index 504133c09..4ac650269 100644
--- a/src/java/org/apache/fop/render/xml/XMLRenderer.java
+++ b/src/java/org/apache/fop/render/xml/XMLRenderer.java
@@ -86,6 +86,7 @@ import org.apache.fop.render.Renderer;
import org.apache.fop.render.RendererContext;
import org.apache.fop.render.XMLHandler;
import org.apache.fop.util.ColorUtil;
+import org.apache.fop.util.XMLUtil;
/**
* Renderer that renders areas to XML for debugging purposes.
@@ -157,6 +158,7 @@ public class XMLRenderer extends AbstractXMLRenderer {
protected void addAreaAttributes(Area area) {
addAttribute("ipd", area.getIPD());
addAttribute("bpd", area.getBPD());
+ maybeAddLevelAttribute(area);
if (isDetailedFormat()) {
if (area.getIPD() != 0) {
addAttribute("ipda", area.getAllocIPD());
@@ -708,7 +710,7 @@ public class XMLRenderer extends AbstractXMLRenderer {
atts.clear();
addAreaAttributes(viewport);
addTraitAttributes(viewport);
- addAttribute("offset", viewport.getOffset());
+ addAttribute("offset", viewport.getBlockProgressionOffset());
addAttribute("pos", viewport.getContentPosition());
if (viewport.hasClip()) {
addAttribute("clip", "true");
@@ -770,7 +772,7 @@ public class XMLRenderer extends AbstractXMLRenderer {
atts.clear();
addAreaAttributes(space);
addTraitAttributes(space);
- addAttribute("offset", space.getOffset());
+ addAttribute("offset", space.getBlockProgressionOffset());
startElement("space", atts);
endElement("space");
}
@@ -787,7 +789,7 @@ public class XMLRenderer extends AbstractXMLRenderer {
if (text.getTextLetterSpaceAdjust() != 0) {
addAttribute("tlsadjust", text.getTextLetterSpaceAdjust());
}
- addAttribute("offset", text.getOffset());
+ addAttribute("offset", text.getBlockProgressionOffset());
addAttribute("baseline", text.getBaselineOffset());
addAreaAttributes(text);
addTraitAttributes(text);
@@ -802,7 +804,10 @@ public class XMLRenderer extends AbstractXMLRenderer {
@Override
protected void renderWord(WordArea word) {
atts.clear();
- addAttribute("offset", word.getOffset());
+ int offset = word.getBlockProgressionOffset();
+ if ( offset != 0 ) {
+ addAttribute("offset", offset);
+ }
int[] letterAdjust = word.getLetterAdjustArray();
if (letterAdjust != null) {
StringBuffer sb = new StringBuffer(64);
@@ -818,6 +823,11 @@ public class XMLRenderer extends AbstractXMLRenderer {
addAttribute("letter-adjust", sb.toString());
}
}
+ maybeAddLevelAttribute(word);
+ maybeAddPositionAdjustAttribute(word);
+ if ( word.isReversed() ) {
+ addAttribute("reversed", "true");
+ }
startElement("word", atts);
characters(word.getWord());
endElement("word");
@@ -830,7 +840,11 @@ public class XMLRenderer extends AbstractXMLRenderer {
@Override
protected void renderSpace(SpaceArea space) {
atts.clear();
- addAttribute("offset", space.getOffset());
+ int offset = space.getBlockProgressionOffset();
+ if ( offset != 0 ) {
+ addAttribute("offset", offset);
+ }
+ maybeAddLevelAttribute(space);
if (!space.isAdjustable()) {
addAttribute("adj", "false"); //default is true
}
@@ -848,7 +862,7 @@ public class XMLRenderer extends AbstractXMLRenderer {
atts.clear();
addAreaAttributes(ip);
addTraitAttributes(ip);
- addAttribute("offset", ip.getOffset());
+ addAttribute("offset", ip.getBlockProgressionOffset());
startElement("inlineparent", atts);
super.renderInlineParent(ip);
endElement("inlineparent");
@@ -862,7 +876,7 @@ public class XMLRenderer extends AbstractXMLRenderer {
atts.clear();
addAreaAttributes(ibp);
addTraitAttributes(ibp);
- addAttribute("offset", ibp.getOffset());
+ addAttribute("offset", ibp.getBlockProgressionOffset());
startElement("inlineblockparent", atts);
super.renderInlineBlockParent(ibp);
endElement("inlineblockparent");
@@ -876,7 +890,7 @@ public class XMLRenderer extends AbstractXMLRenderer {
atts.clear();
addAreaAttributes(area);
addTraitAttributes(area);
- addAttribute("offset", area.getOffset());
+ addAttribute("offset", area.getBlockProgressionOffset());
addAttribute("ruleStyle", area.getRuleStyleAsString());
addAttribute("ruleThickness", area.getRuleThickness());
startElement("leader", atts);
@@ -889,5 +903,19 @@ public class XMLRenderer extends AbstractXMLRenderer {
return XML_MIME_TYPE;
}
-}
+ private void maybeAddLevelAttribute ( Area a ) {
+ int level = a.getBidiLevel();
+ if ( level >= 0 ) {
+ addAttribute ( "level", level );
+ }
+ }
+ private void maybeAddPositionAdjustAttribute ( WordArea w ) {
+ int[][] adjustments = w.getGlyphPositionAdjustments();
+ if ( adjustments != null ) {
+ addAttribute ( "position-adjust", XMLUtil.encodePositionAdjustments ( adjustments ) );
+ }
+ }
+
+
+}
diff --git a/src/java/org/apache/fop/svg/PDFDocumentGraphics2D.java b/src/java/org/apache/fop/svg/PDFDocumentGraphics2D.java
index 7ec9d7f2b..e91e2231c 100644
--- a/src/java/org/apache/fop/svg/PDFDocumentGraphics2D.java
+++ b/src/java/org/apache/fop/svg/PDFDocumentGraphics2D.java
@@ -159,7 +159,8 @@ public class PDFDocumentGraphics2D extends PDFGraphics2D {
if (fontInfo == null) {
//Default minimal fonts
FontInfo fontInfo = new FontInfo();
- FontSetup.setup(fontInfo);
+ boolean base14Kerning = false;
+ FontSetup.setup(fontInfo, base14Kerning);
setFontInfo(fontInfo);
}
}
diff --git a/src/java/org/apache/fop/svg/PDFDocumentGraphics2DConfigurator.java b/src/java/org/apache/fop/svg/PDFDocumentGraphics2DConfigurator.java
index 24974b01a..954291a57 100644
--- a/src/java/org/apache/fop/svg/PDFDocumentGraphics2DConfigurator.java
+++ b/src/java/org/apache/fop/svg/PDFDocumentGraphics2DConfigurator.java
@@ -46,9 +46,11 @@ public class PDFDocumentGraphics2DConfigurator {
* Configures a PDFDocumentGraphics2D instance using an Avalon Configuration object.
* @param graphics the PDFDocumentGraphics2D instance
* @param cfg the configuration
+ * @param useComplexScriptFeatures true if complex script features enabled
* @throws ConfigurationException if an error occurs while configuring the object
*/
- public void configure(PDFDocumentGraphics2D graphics, Configuration cfg)
+ public void configure(PDFDocumentGraphics2D graphics, Configuration cfg,
+ boolean useComplexScriptFeatures )
throws ConfigurationException {
PDFDocument pdfDoc = graphics.getPDFDocument();
@@ -58,7 +60,7 @@ public class PDFDocumentGraphics2DConfigurator {
//Fonts
try {
- FontInfo fontInfo = createFontInfo(cfg);
+ FontInfo fontInfo = createFontInfo(cfg, useComplexScriptFeatures);
graphics.setFontInfo(fontInfo);
} catch (FOPException e) {
throw new ConfigurationException("Error while setting up fonts", e);
@@ -68,13 +70,15 @@ public class PDFDocumentGraphics2DConfigurator {
/**
* Creates the {@link FontInfo} instance for the given configuration.
* @param cfg the configuration
+ * @param useComplexScriptFeatures true if complex script features enabled
* @return the font collection
* @throws FOPException if an error occurs while setting up the fonts
*/
- public static FontInfo createFontInfo(Configuration cfg) throws FOPException {
+ public static FontInfo createFontInfo(Configuration cfg, boolean useComplexScriptFeatures)
+ throws FOPException {
FontInfo fontInfo = new FontInfo();
final boolean strict = false;
- FontResolver fontResolver = FontManager.createMinimalFontResolver();
+ FontResolver fontResolver = FontManager.createMinimalFontResolver(useComplexScriptFeatures);
//TODO The following could be optimized by retaining the FontManager somewhere
FontManager fontManager = new FontManager();
if (cfg != null) {
@@ -92,7 +96,8 @@ public class PDFDocumentGraphics2DConfigurator {
= new FontInfoConfigurator(cfg, fontManager, fontResolver, listener, strict);
List/*<EmbedFontInfo>*/ fontInfoList = new java.util.ArrayList/*<EmbedFontInfo>*/();
fontInfoConfigurator.configure(fontInfoList);
- fontCollections.add(new CustomFontCollection(fontResolver, fontInfoList));
+ fontCollections.add(new CustomFontCollection(fontResolver, fontInfoList,
+ fontResolver.isComplexScriptFeaturesEnabled()));
}
fontManager.setup(fontInfo,
(FontCollection[])fontCollections.toArray(
diff --git a/src/java/org/apache/fop/svg/PDFGraphics2D.java b/src/java/org/apache/fop/svg/PDFGraphics2D.java
index 0d25e166c..429a1ea35 100644
--- a/src/java/org/apache/fop/svg/PDFGraphics2D.java
+++ b/src/java/org/apache/fop/svg/PDFGraphics2D.java
@@ -965,7 +965,8 @@ public class PDFGraphics2D extends AbstractGraphics2D implements NativeImageHand
preparePainting();
FontInfo specialFontInfo = new FontInfo();
- FontSetup.setup(specialFontInfo);
+ boolean base14Kerning = false;
+ FontSetup.setup(specialFontInfo, base14Kerning);
PDFResources res = pdfDoc.getFactory().makeResources();
PDFResourceContext context = new PDFResourceContext(res);
diff --git a/src/java/org/apache/fop/svg/PDFTranscoder.java b/src/java/org/apache/fop/svg/PDFTranscoder.java
index bf08b2fcf..8cf396ae5 100644
--- a/src/java/org/apache/fop/svg/PDFTranscoder.java
+++ b/src/java/org/apache/fop/svg/PDFTranscoder.java
@@ -126,7 +126,8 @@ public class PDFTranscoder extends AbstractFOPTranscoder {
if (effCfg != null) {
PDFDocumentGraphics2DConfigurator configurator
= new PDFDocumentGraphics2DConfigurator();
- configurator.configure(graphics, effCfg);
+ boolean useComplexScriptFeatures = false; //TODO - FIX ME
+ configurator.configure(graphics, effCfg, useComplexScriptFeatures);
} else {
graphics.setupDefaultFontInfo();
}
diff --git a/src/java/org/apache/fop/text/linebreak/LineBreakUtils.java b/src/java/org/apache/fop/text/linebreak/LineBreakUtils.java
index 343191eea..d6e19a97a 100644
--- a/src/java/org/apache/fop/text/linebreak/LineBreakUtils.java
+++ b/src/java/org/apache/fop/text/linebreak/LineBreakUtils.java
@@ -100,35 +100,35 @@ public final class LineBreakUtils {
lineBreakProperties[6] = new byte[] { 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 13, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 13, 13, 13, 13, 13, 13, 13, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 2, 2, 2, 2, 19, 0};
lineBreakProperties[7] = new byte[] { 0, 0, 0, 0, 2, 2, 2, 2, 2, 2, 2, 0, 2, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2};
lineBreakProperties[9] = new byte[] { 2, 2, 2, 9, 9, 9, 9, 9, 9, 9, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2};
- lineBreakProperties[10] = new byte[] { 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 2, 2, 2, 2, 2, 2, 2, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2};
+ lineBreakProperties[10] = new byte[] { 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 2, 2, 2, 2, 2, 2, 2, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2};
lineBreakProperties[11] = new byte[] { 2, 2, 2, 2, 2, 2, 2, 2, 0, 19, 4, 0, 0, 0, 0, 0, 0, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 4, 9, 2, 9, 9, 2, 9, 9, 12, 9, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
- lineBreakProperties[12] = new byte[] { 2, 2, 2, 2, 0, 0, 2, 2, 2, 28, 28, 28, 19, 19, 2, 2, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 12, 0, 0, 12, 12, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 0, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 28, 26, 26, 2, 2, 2, 9, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2};
- lineBreakProperties[13] = new byte[] { 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 12, 2, 9, 9, 9, 9, 9, 9, 9, 2, 9, 9, 9, 9, 9, 9, 9, 2, 2, 9, 9, 2, 9, 9, 9, 9, 2, 2, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 2, 2, 2, 2, 2, 2};
+ lineBreakProperties[12] = new byte[] { 2, 2, 2, 2, 0, 0, 2, 2, 2, 28, 28, 28, 19, 19, 2, 2, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 12, 0, 0, 12, 12, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 28, 26, 26, 2, 2, 2, 9, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2};
+ lineBreakProperties[13] = new byte[] { 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 12, 2, 9, 9, 9, 9, 9, 9, 9, 2, 2, 9, 9, 9, 9, 9, 9, 2, 2, 9, 9, 2, 9, 9, 9, 9, 2, 2, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 2, 2, 2, 2, 2, 2};
lineBreakProperties[14] = new byte[] { 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 2, 2, 9, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2};
lineBreakProperties[15] = new byte[] { 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 9, 9, 9, 9, 9, 9, 9, 9, 9, 2, 2, 2, 2, 19, 12, 2, 0, 0, 0, 0, 0};
- lineBreakProperties[16] = new byte[] { 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 9, 9, 9, 9, 2, 9, 9, 9, 9, 9, 9, 9, 9, 9, 2, 9, 9, 9, 2, 9, 9, 9, 9, 9, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+ lineBreakProperties[16] = new byte[] { 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 9, 9, 9, 9, 2, 9, 9, 9, 9, 9, 9, 9, 9, 9, 2, 9, 9, 9, 2, 9, 9, 9, 9, 9, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 9, 9, 9, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
lineBreakProperties[17] = new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
- lineBreakProperties[18] = new byte[] { 9, 9, 9, 9, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 9, 2, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 0, 2, 9, 9, 9, 9, 9, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 9, 9, 4, 4, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 2, 2, 2, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2, 2};
+ lineBreakProperties[18] = new byte[] { 9, 9, 9, 9, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 9, 9, 9, 2, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 2, 9, 9, 9, 9, 9, 9, 9, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 9, 9, 4, 4, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 2, 2, 2, 2, 2, 2, 2, 2, 0, 2, 2, 2, 2, 2, 2, 2};
lineBreakProperties[19] = new byte[] { 0, 9, 9, 9, 0, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 2, 2, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 2, 2, 2, 2, 2, 2, 2, 0, 2, 0, 0, 0, 2, 2, 2, 2, 0, 0, 9, 2, 9, 9, 9, 9, 9, 9, 9, 0, 0, 9, 9, 0, 0, 9, 9, 9, 2, 0, 0, 0, 0, 0, 0, 0, 0, 9, 0, 0, 0, 0, 2, 2, 0, 2, 2, 2, 9, 9, 0, 0, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 2, 2, 28, 28, 2, 2, 2, 2, 2, 28, 2, 29, 0, 0, 0, 0};
lineBreakProperties[20] = new byte[] { 0, 9, 9, 9, 0, 2, 2, 2, 2, 2, 2, 0, 0, 0, 0, 2, 2, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 2, 2, 2, 2, 2, 2, 2, 0, 2, 2, 0, 2, 2, 0, 2, 2, 0, 0, 9, 0, 9, 9, 9, 9, 9, 0, 0, 0, 0, 9, 9, 0, 0, 9, 9, 9, 0, 0, 0, 9, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 0, 2, 0, 0, 0, 0, 0, 0, 0, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 9, 9, 2, 2, 2, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
lineBreakProperties[21] = new byte[] { 0, 9, 9, 9, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 2, 2, 2, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 2, 2, 2, 2, 2, 2, 2, 0, 2, 2, 0, 2, 2, 2, 2, 2, 0, 0, 9, 2, 9, 9, 9, 9, 9, 9, 9, 9, 0, 9, 9, 9, 0, 9, 9, 9, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 9, 9, 0, 0, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 0, 29, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
- lineBreakProperties[22] = new byte[] { 0, 9, 9, 9, 0, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 2, 2, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 2, 2, 2, 2, 2, 2, 2, 0, 2, 2, 0, 2, 2, 2, 2, 2, 0, 0, 9, 2, 9, 9, 9, 9, 9, 9, 9, 0, 0, 9, 9, 0, 0, 9, 9, 9, 0, 0, 0, 0, 0, 0, 0, 0, 9, 9, 0, 0, 0, 0, 2, 2, 0, 2, 2, 2, 9, 9, 0, 0, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+ lineBreakProperties[22] = new byte[] { 0, 9, 9, 9, 0, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 2, 2, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 2, 2, 2, 2, 2, 2, 2, 0, 2, 2, 0, 2, 2, 2, 2, 2, 0, 0, 9, 2, 9, 9, 9, 9, 9, 9, 9, 0, 0, 9, 9, 0, 0, 9, 9, 9, 0, 0, 0, 0, 0, 0, 0, 0, 9, 9, 0, 0, 0, 0, 2, 2, 0, 2, 2, 2, 9, 9, 0, 0, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0};
lineBreakProperties[23] = new byte[] { 0, 0, 9, 2, 0, 2, 2, 2, 2, 2, 2, 0, 0, 0, 2, 2, 2, 0, 2, 2, 2, 2, 0, 0, 0, 2, 2, 0, 2, 0, 2, 2, 0, 0, 0, 2, 2, 0, 0, 0, 2, 2, 2, 0, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 0, 9, 9, 9, 9, 9, 0, 0, 0, 9, 9, 9, 0, 9, 9, 9, 9, 0, 0, 2, 0, 0, 0, 0, 0, 0, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 2, 2, 2, 2, 2, 2, 2, 2, 2, 29, 2, 0, 0, 0, 0, 0};
lineBreakProperties[24] = new byte[] { 0, 9, 9, 9, 0, 2, 2, 2, 2, 2, 2, 2, 2, 0, 2, 2, 2, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 2, 2, 2, 2, 2, 0, 0, 0, 2, 9, 9, 9, 9, 9, 9, 9, 0, 9, 9, 9, 0, 9, 9, 9, 9, 0, 0, 0, 0, 0, 0, 0, 9, 9, 0, 2, 2, 0, 0, 0, 0, 0, 0, 2, 2, 9, 9, 0, 0, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2};
lineBreakProperties[25] = new byte[] { 0, 0, 9, 9, 0, 2, 2, 2, 2, 2, 2, 2, 2, 0, 2, 2, 2, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 2, 2, 2, 2, 2, 0, 0, 9, 2, 9, 9, 9, 9, 9, 9, 9, 0, 9, 9, 9, 0, 9, 9, 9, 9, 0, 0, 0, 0, 0, 0, 0, 9, 9, 0, 0, 0, 0, 0, 0, 0, 2, 0, 2, 2, 9, 9, 0, 0, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 0, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
- lineBreakProperties[26] = new byte[] { 0, 0, 9, 9, 0, 2, 2, 2, 2, 2, 2, 2, 2, 0, 2, 2, 2, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 2, 9, 9, 9, 9, 9, 9, 9, 0, 9, 9, 9, 0, 9, 9, 9, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 9, 9, 0, 0, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 2, 2, 2, 2, 2, 2, 0, 0, 0, 28, 2, 2, 2, 2, 2, 2};
+ lineBreakProperties[26] = new byte[] { 0, 0, 9, 9, 0, 2, 2, 2, 2, 2, 2, 2, 2, 0, 2, 2, 2, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 2, 9, 9, 9, 9, 9, 9, 9, 0, 9, 9, 9, 0, 9, 9, 9, 9, 2, 0, 0, 0, 0, 0, 0, 0, 0, 9, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 9, 9, 0, 0, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 2, 2, 2, 2, 2, 2, 0, 0, 0, 28, 2, 2, 2, 2, 2, 2};
lineBreakProperties[27] = new byte[] { 0, 0, 9, 9, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 2, 0, 0, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 9, 0, 0, 0, 0, 9, 9, 9, 9, 9, 9, 0, 9, 0, 9, 9, 9, 9, 9, 9, 9, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 9, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
lineBreakProperties[28] = new byte[] { 0, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 0, 0, 0, 0, 29, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 2, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 4, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
lineBreakProperties[29] = new byte[] { 0, 31, 31, 0, 31, 0, 0, 31, 31, 0, 31, 0, 0, 31, 0, 0, 0, 0, 0, 0, 31, 31, 31, 31, 0, 31, 31, 31, 31, 31, 31, 31, 0, 31, 31, 31, 0, 31, 0, 31, 0, 0, 31, 31, 0, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 0, 31, 31, 31, 0, 0, 31, 31, 31, 31, 31, 0, 31, 0, 31, 31, 31, 31, 31, 31, 0, 0, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 0, 0, 31, 31, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
lineBreakProperties[30] = new byte[] { 2, 5, 5, 5, 5, 2, 5, 5, 13, 5, 5, 4, 13, 12, 12, 12, 12, 12, 13, 2, 12, 2, 2, 2, 9, 9, 2, 2, 2, 2, 2, 2, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 4, 9, 2, 9, 2, 9, 27, 8, 27, 8, 9, 9, 2, 2, 2, 2, 2, 2, 2, 2, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 0, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 4};
- lineBreakProperties[31] = new byte[] { 9, 9, 9, 9, 9, 4, 9, 9, 2, 2, 2, 2, 0, 0, 0, 0, 9, 9, 9, 9, 9, 9, 9, 9, 0, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 0, 4, 4, 2, 2, 2, 2, 2, 2, 9, 2, 2, 2, 2, 2, 2, 0, 2, 2, 5, 5, 4, 5, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+ lineBreakProperties[31] = new byte[] { 9, 9, 9, 9, 9, 4, 9, 9, 2, 2, 2, 2, 2, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 0, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 0, 4, 4, 2, 2, 2, 2, 2, 2, 9, 2, 2, 2, 2, 2, 2, 0, 2, 2, 5, 5, 4, 5, 2, 2, 2, 2, 2, 13, 13, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
lineBreakProperties[32] = new byte[] { 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 4, 4, 2, 2, 2, 2, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31};
lineBreakProperties[33] = new byte[] { 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 31, 31, 31, 31, 31, 31, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0};
lineBreakProperties[34] = new byte[] { 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22};
lineBreakProperties[35] = new byte[] { 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21};
lineBreakProperties[36] = new byte[] { 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 2, 2, 2, 2, 0, 0, 2, 2, 2, 2, 2, 2, 2, 0, 2, 0, 2, 2, 2, 2, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2};
lineBreakProperties[37] = new byte[] { 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 2, 2, 2, 2, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 2, 2, 2, 2, 0, 0, 2, 2, 2, 2, 2, 2, 2, 0, 2, 0, 2, 2, 2, 2, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2};
- lineBreakProperties[38] = new byte[] { 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 2, 2, 2, 2, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 0, 9, 2, 4, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0};
+ lineBreakProperties[38] = new byte[] { 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 2, 2, 2, 2, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 9, 9, 9, 2, 4, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0};
lineBreakProperties[39] = new byte[] { 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
lineBreakProperties[40] = new byte[] { 4, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2};
lineBreakProperties[45] = new byte[] { 4, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 27, 8, 0, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 4, 4, 4, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
@@ -137,24 +137,24 @@ public final class LineBreakUtils {
lineBreakProperties[48] = new byte[] { 2, 2, 12, 12, 4, 4, 5, 2, 12, 12, 2, 9, 9, 9, 13, 0, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0};
lineBreakProperties[49] = new byte[] { 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 9, 2, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
lineBreakProperties[50] = new byte[] { 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 0, 0, 0, 0, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 0, 0, 0, 0, 2, 0, 0, 0, 12, 12, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 0, 0, 31, 31, 31, 31, 31, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
- lineBreakProperties[51] = new byte[] { 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 0, 0, 0, 0, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 0, 0, 0, 0, 0, 0, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 0, 0, 0, 31, 31, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2};
+ lineBreakProperties[51] = new byte[] { 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 0, 0, 0, 0, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 0, 0, 0, 0, 0, 0, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 31, 0, 0, 0, 31, 31, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2};
lineBreakProperties[52] = new byte[] { 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 9, 9, 9, 9, 9, 0, 0, 2, 2, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 0, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 0, 0, 9};
lineBreakProperties[53] = new byte[] { 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 0, 0, 0, 0, 0, 0, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 0, 0, 0, 0, 0, 0, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
lineBreakProperties[54] = new byte[] { 9, 9, 9, 9, 9, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 0, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 4, 4, 2, 4, 4, 4, 4, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 9, 9, 9, 9, 9, 9, 9, 9, 9, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0};
- lineBreakProperties[55] = new byte[] { 9, 9, 9, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 0, 0, 0, 2, 2, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+ lineBreakProperties[55] = new byte[] { 9, 9, 9, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 0, 0, 0, 2, 2, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2};
lineBreakProperties[56] = new byte[] { 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 0, 0, 0, 4, 4, 4, 4, 4, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 0, 0, 0, 2, 2, 2, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 4, 4};
lineBreakProperties[57] = new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 9, 9, 2, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 2, 2, 2, 2, 9, 2, 2, 2, 2, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
- lineBreakProperties[59] = new byte[] { 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 9, 9};
+ lineBreakProperties[59] = new byte[] { 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 9, 9, 9};
lineBreakProperties[62] = new byte[] { 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 2, 2, 2, 2, 2, 2, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 2, 2, 2, 2, 2, 2, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 0, 2, 0, 2, 0, 2, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0};
lineBreakProperties[63] = new byte[] { 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 2, 2, 2, 2, 2, 2, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 2, 2, 2, 0, 2, 2, 2, 2, 2, 2, 2, 5, 2, 0};
lineBreakProperties[64] = new byte[] { 4, 4, 4, 4, 4, 4, 4, 13, 4, 4, 4, 37, 9, 9, 9, 9, 4, 13, 4, 4, 3, 1, 1, 2, 30, 30, 27, 30, 30, 30, 27, 30, 1, 1, 2, 2, 18, 18, 18, 4, 6, 6, 9, 9, 9, 9, 9, 13, 28, 28, 28, 28, 28, 28, 28, 28, 2, 30, 30, 1, 25, 25, 2, 2, 2, 2, 2, 2, 19, 27, 8, 25, 25, 25, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 4, 2, 4, 4, 4, 4, 2, 4, 4, 4, 35, 2, 2, 2, 2, 0, 0, 0, 0, 0, 9, 9, 9, 9, 9, 9, 2, 2, 0, 0, 1, 2, 2, 2, 2, 2, 2, 2, 2, 27, 8, 1};
- lineBreakProperties[65] = new byte[] { 2, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 27, 8, 0, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 29, 29, 29, 29, 29, 29, 29, 28, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 28, 29, 29, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+ lineBreakProperties[65] = new byte[] { 2, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 27, 8, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 29, 29, 29, 29, 29, 29, 29, 28, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 28, 29, 29, 29, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
lineBreakProperties[66] = new byte[] { 2, 2, 2, 28, 2, 1, 2, 2, 2, 28, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 2, 2, 29, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 2, 2, 2, 2, 2, 1, 2, 2, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2};
lineBreakProperties[67] = new byte[] { 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 2, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2};
lineBreakProperties[68] = new byte[] { 1, 2, 1, 1, 2, 2, 2, 1, 1, 2, 2, 1, 2, 2, 2, 1, 2, 1, 29, 29, 2, 1, 2, 2, 2, 2, 1, 2, 2, 1, 1, 1, 1, 2, 2, 1, 2, 1, 2, 1, 1, 1, 1, 1, 1, 2, 1, 2, 2, 2, 2, 2, 1, 1, 1, 1, 2, 2, 2, 2, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 1, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 2, 2, 1, 1, 1, 1, 2, 2, 1, 1, 2, 2, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2};
lineBreakProperties[69] = new byte[] { 2, 2, 1, 1, 2, 2, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2};
lineBreakProperties[70] = new byte[] { 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 27, 8, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2};
- lineBreakProperties[71] = new byte[] { 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+ lineBreakProperties[71] = new byte[] { 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
lineBreakProperties[72] = new byte[] { 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1};
lineBreakProperties[73] = new byte[] { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2};
}
@@ -163,14 +163,14 @@ public final class LineBreakUtils {
lineBreakProperties[74] = new byte[] { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2};
lineBreakProperties[75] = new byte[] { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 2, 2, 1, 1, 2, 2, 2, 2, 1, 1, 2, 2, 1, 1, 2, 2, 2, 2, 1, 1, 1, 2, 2, 1, 2, 2, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2};
lineBreakProperties[76] = new byte[] { 2, 2, 2, 2, 2, 1, 1, 2, 2, 1, 2, 2, 2, 2, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1, 2, 2, 2, 2, 1, 2, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 2, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 2, 1, 1, 1, 2, 1, 1, 1, 1, 2, 1, 1, 2, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2};
- lineBreakProperties[77] = new byte[] { 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1};
- lineBreakProperties[78] = new byte[] { 0, 2, 2, 2, 2, 0, 2, 2, 2, 2, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 2, 0, 2, 2, 2, 2, 0, 0, 0, 2, 1, 2, 2, 2, 30, 30, 30, 30, 0, 0, 2, 12, 12, 2, 2, 2, 2, 27, 8, 27, 8, 27, 8, 27, 8, 27, 8, 27, 8, 27, 8, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1};
- lineBreakProperties[79] = new byte[] { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 0, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 2, 2, 2, 2, 2, 27, 8, 2, 2, 2, 2, 0, 2, 0, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 27, 8, 27, 8, 27, 8, 27, 8, 27, 8, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2};
+ lineBreakProperties[77] = new byte[] { 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1};
+ lineBreakProperties[78] = new byte[] { 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 30, 30, 30, 30, 2, 2, 2, 12, 12, 2, 2, 2, 2, 27, 8, 27, 8, 27, 8, 27, 8, 27, 8, 27, 8, 27, 8, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1};
+ lineBreakProperties[79] = new byte[] { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 27, 8, 2, 2, 2, 2, 0, 2, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 27, 8, 27, 8, 27, 8, 27, 8, 27, 8, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2};
lineBreakProperties[83] = new byte[] { 2, 2, 2, 27, 8, 27, 8, 27, 8, 27, 8, 27, 8, 27, 8, 27, 8, 27, 8, 27, 8, 27, 8, 27, 8, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 27, 8, 27, 8, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 27, 8, 2, 2};
lineBreakProperties[86] = new byte[] { 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
lineBreakProperties[88] = new byte[] { 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2};
lineBreakProperties[89] = new byte[] { 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 9, 9, 9, 0, 0, 0, 0, 0, 0, 0, 12, 4, 4, 4, 2, 12, 4};
- lineBreakProperties[90] = new byte[] { 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+ lineBreakProperties[90] = new byte[] { 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9};
lineBreakProperties[91] = new byte[] { 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2, 2, 0, 2, 2, 2, 2, 2, 2, 2, 0, 2, 2, 2, 2, 2, 2, 2, 0, 2, 2, 2, 2, 2, 2, 2, 0, 2, 2, 2, 2, 2, 2, 2, 0, 2, 2, 2, 2, 2, 2, 2, 0, 2, 2, 2, 2, 2, 2, 2, 0, 2, 2, 2, 2, 2, 2, 2, 0, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9};
lineBreakProperties[92] = new byte[] { 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 4, 4, 4, 4, 4, 4, 4, 4, 2, 4, 27, 4, 2, 2, 30, 30, 2, 2, 30, 30, 27, 8, 27, 8, 27, 8, 27, 8, 4, 4, 4, 4, 12, 2, 4, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
lineBreakProperties[93] = new byte[] { 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 0, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
@@ -179,21 +179,22 @@ public final class LineBreakUtils {
lineBreakProperties[96] = new byte[] { 17, 8, 8, 17, 17, 25, 17, 17, 27, 8, 27, 8, 27, 8, 27, 8, 27, 8, 17, 17, 27, 8, 27, 8, 27, 8, 27, 8, 25, 27, 8, 8, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 9, 9, 9, 9, 9, 9, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 25, 25, 17, 17, 17, 0, 25, 17, 25, 17, 25, 17, 25, 17, 25, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 25, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17};
lineBreakProperties[97] = new byte[] { 17, 17, 17, 25, 17, 25, 17, 25, 17, 17, 17, 17, 17, 17, 25, 17, 17, 17, 17, 17, 17, 25, 25, 0, 0, 9, 9, 25, 25, 25, 25, 17, 25, 25, 17, 25, 17, 25, 17, 25, 17, 25, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 25, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 25, 17, 25, 17, 25, 17, 17, 17, 17, 17, 17, 25, 17, 17, 17, 17, 17, 17, 25, 25, 17, 17, 17, 17, 25, 25, 25, 25, 17};
lineBreakProperties[98] = new byte[] { 0, 0, 0, 0, 0, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 0, 0, 0, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17};
- lineBreakProperties[99] = new byte[] { 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 0, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 0, 0, 0, 0, 0, 0, 0, 0, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25};
+ lineBreakProperties[99] = new byte[] { 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 0, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 0, 0, 0, 0, 0, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25};
lineBreakProperties[100] = new byte[] { 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 0, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 1, 1, 1, 1, 1, 1, 1, 1, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17};
lineBreakProperties[101] = new byte[] { 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 0};
lineBreakProperties[155] = new byte[] { 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2};
lineBreakProperties[320] = new byte[] { 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 25, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17};
lineBreakProperties[329] = new byte[] { 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 0, 0, 0, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 4, 4};
- lineBreakProperties[332] = new byte[] { 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 4, 12, 4, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 9, 9, 9, 9, 2, 0, 0, 0, 0, 0, 0, 0, 0, 9, 9, 2, 2};
+ lineBreakProperties[332] = new byte[] { 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 4, 12, 4, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 9, 9, 9, 9, 2, 0, 0, 0, 0, 0, 0, 0, 0, 9, 9, 2, 2};
lineBreakProperties[333] = new byte[] { 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 9, 9, 2, 4, 4, 4, 4, 4, 0, 0, 0, 0, 0, 0, 0, 0};
- lineBreakProperties[335] = new byte[] { 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2};
+ lineBreakProperties[335] = new byte[] { 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2};
lineBreakProperties[336] = new byte[] { 2, 2, 9, 2, 2, 2, 9, 2, 2, 2, 2, 9, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 9, 9, 9, 9, 9, 2, 2, 2, 2, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 28, 2, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 5, 5, 12, 12, 0, 0, 0, 0, 0, 0, 0, 0};
lineBreakProperties[337] = new byte[] { 9, 9, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 4, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 0, 0, 0, 0, 0, 0, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 0};
lineBreakProperties[338] = new byte[] { 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 9, 9, 9, 9, 9, 9, 9, 9, 4, 4, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 0, 0, 0};
lineBreakProperties[339] = new byte[] { 9, 9, 9, 9, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 2, 2, 2, 2, 2, 2, 4, 4, 4, 2, 2, 2, 2, 0, 2, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 0, 0, 0, 0, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
lineBreakProperties[340] = new byte[] { 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 9, 2, 2, 2, 2, 2, 2, 2, 2, 9, 9, 0, 0, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 0, 0, 2, 4, 4, 4, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 0, 0, 0, 0};
lineBreakProperties[341] = new byte[] { 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 31, 31, 31, 31, 31, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+ lineBreakProperties[342] = new byte[] { 0, 2, 2, 2, 2, 2, 2, 0, 0, 2, 2, 2, 2, 2, 2, 0, 0, 2, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2, 2, 0, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
lineBreakProperties[343] = new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 9, 9, 9, 9, 9, 9, 9, 9, 4, 9, 9, 0, 0, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 0, 0, 0, 0, 0, 0};
lineBreakProperties[344] = new byte[] { 14, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 14, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 14, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 14, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 14, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15};
lineBreakProperties[345] = new byte[] { 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 14, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 14, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 14, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 14, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 14, 15, 15, 15};
@@ -206,7 +207,7 @@ public final class LineBreakUtils {
lineBreakProperties[432] = new byte[] { 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32};
lineBreakProperties[448] = new byte[] { 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36};
lineBreakProperties[502] = new byte[] { 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 2, 9, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 2, 2, 2, 2, 2, 0, 2, 0, 2, 2, 0, 2, 2, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2};
- lineBreakProperties[503] = new byte[] { 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2};
+ lineBreakProperties[503] = new byte[] { 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2};
lineBreakProperties[506] = new byte[] { 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 27, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2};
lineBreakProperties[507] = new byte[] { 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 28, 2, 0, 0};
lineBreakProperties[508] = new byte[] { 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 19, 8, 8, 19, 19, 12, 12, 27, 8, 18, 0, 0, 0, 0, 0, 0, 9, 9, 9, 9, 9, 9, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 17, 17, 17, 17, 17, 27, 8, 27, 8, 27, 8, 27, 8, 27, 8, 27, 8, 27, 8, 27, 8, 17, 17, 27, 8, 17, 17, 17, 17, 17, 17, 17, 8, 17, 8, 0, 25, 25, 12, 12, 17, 27, 8, 27, 8, 27, 8, 17, 17, 17, 17, 17, 17, 17, 17, 0, 17, 29, 28, 17, 0, 0, 0, 0, 2, 2, 2, 2, 2, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2};
@@ -462,7 +463,6 @@ public final class LineBreakUtils {
lineBreakProperties[330] = lineBreakProperties[2];
lineBreakProperties[331] = lineBreakProperties[2];
lineBreakProperties[334] = lineBreakProperties[2];
- lineBreakProperties[342] = lineBreakProperties[17];
lineBreakProperties[351] = lineBreakProperties[344];
lineBreakProperties[352] = lineBreakProperties[345];
lineBreakProperties[353] = lineBreakProperties[346];
diff --git a/src/java/org/apache/fop/traits/Direction.java b/src/java/org/apache/fop/traits/Direction.java
new file mode 100644
index 000000000..5eb36058e
--- /dev/null
+++ b/src/java/org/apache/fop/traits/Direction.java
@@ -0,0 +1,108 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.traits;
+
+import java.io.ObjectStreamException;
+
+import org.apache.fop.fo.Constants;
+
+/**
+ * Enumeration class for direction traits, namely {inline,block}-progression-direction
+ * and shift-direction.
+ */
+public final class Direction extends TraitEnum {
+
+ private static final long serialVersionUID = 1L;
+
+ private static final String[] DIRECTION_NAMES = new String[]
+ {"lr", "rl", "tb", "bt"};
+
+ private static final int[] DIRECTION_VALUES = new int[]
+ {Constants.EN_LR, Constants.EN_RL, Constants.EN_TB, Constants.EN_BT};
+
+ /** direction: left-to-right */
+ public static final Direction LR = new Direction(0);
+ /** direction: right-to-left */
+ public static final Direction RL = new Direction(1);
+ /** direction: top-to-bottom */
+ public static final Direction TB = new Direction(2);
+ /** direction: bottom-to-top */
+ public static final Direction BT = new Direction(3);
+
+ private static final Direction[] DIRECTIONS = new Direction[] {LR, RL, TB, BT};
+
+ private Direction(int index) {
+ 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
+ * @return the enumeration object
+ */
+ public static Direction valueOf(String name) {
+ for (int i = 0; i < DIRECTIONS.length; i++) {
+ if (DIRECTIONS[i].getName().equalsIgnoreCase(name)) {
+ return DIRECTIONS[i];
+ }
+ }
+ throw new IllegalArgumentException("Illegal direction: " + name);
+ }
+
+ /**
+ * Returns the enumeration/singleton object based on its name.
+ * @param enumValue the enumeration value
+ * @return the enumeration object
+ */
+ public static Direction valueOf(int enumValue) {
+ for (int i = 0; i < DIRECTIONS.length; i++) {
+ if (DIRECTIONS[i].getEnumValue() == enumValue) {
+ return DIRECTIONS[i];
+ }
+ }
+ throw new IllegalArgumentException("Illegal direction: " + enumValue);
+ }
+
+ private Object readResolve() throws ObjectStreamException {
+ return valueOf(getName());
+ }
+
+ /** {@inheritDoc} */
+ public String toString() {
+ return getName();
+ }
+
+}
diff --git a/src/java/org/apache/fop/traits/WritingMode.java b/src/java/org/apache/fop/traits/WritingMode.java
new file mode 100644
index 000000000..5b12bb127
--- /dev/null
+++ b/src/java/org/apache/fop/traits/WritingMode.java
@@ -0,0 +1,166 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.traits;
+
+import java.io.ObjectStreamException;
+
+import org.apache.fop.fo.Constants;
+
+/** Enumeration class for writing mode trait. */
+public final class WritingMode extends TraitEnum {
+
+ private static final long serialVersionUID = 1L;
+
+ private static final String[] WRITING_MODE_NAMES = new String[]
+ {"lr-tb", "rl-tb", "tb-lr", "tb-rl"};
+
+ private static final int[] WRITING_MODE_VALUES = new int[]
+ {Constants.EN_LR_TB, Constants.EN_RL_TB, Constants.EN_TB_LR, Constants.EN_TB_RL};
+
+ /** writing mode: lr-tb */
+ public static final WritingMode LR_TB = new WritingMode(0);
+ /** writing mode: rl-tb */
+ public static final WritingMode RL_TB = new WritingMode(1);
+ /** writing mode: tb-lr */
+ public static final WritingMode TB_LR = new WritingMode(2);
+ /** writing mode: tb-rl */
+ public static final WritingMode TB_RL = new WritingMode(3);
+
+ private static final WritingMode[] WRITING_MODES
+ = new WritingMode[] {LR_TB, RL_TB, TB_LR, TB_RL};
+
+ private WritingMode(int index) {
+ super(WRITING_MODE_NAMES[index], WRITING_MODE_VALUES[index]);
+ }
+
+ /**
+ * Assign writing mode traits from this trait to the specified
+ * writing mode traits setter.
+ * @param wms a writing mode traits setter
+ */
+ public void assignWritingModeTraits ( WritingModeTraitsSetter wms ) {
+ Direction inlineProgressionDirection;
+ Direction blockProgressionDirection;
+ Direction columnProgressionDirection;
+ Direction rowProgressionDirection;
+ Direction shiftDirection;
+ switch ( getEnumValue() ) {
+ default:
+ case Constants.EN_LR_TB:
+ inlineProgressionDirection = Direction.LR;
+ blockProgressionDirection = Direction.TB;
+ columnProgressionDirection = Direction.LR;
+ rowProgressionDirection = Direction.TB;
+ shiftDirection = Direction.BT;
+ break;
+ case Constants.EN_RL_TB:
+ inlineProgressionDirection = Direction.RL;
+ blockProgressionDirection = Direction.TB;
+ columnProgressionDirection = Direction.RL;
+ rowProgressionDirection = Direction.TB;
+ shiftDirection = Direction.BT;
+ break;
+ case Constants.EN_TB_LR:
+ inlineProgressionDirection = Direction.TB;
+ blockProgressionDirection = Direction.LR;
+ columnProgressionDirection = Direction.TB;
+ rowProgressionDirection = Direction.LR;
+ shiftDirection = Direction.RL;
+ break;
+ case Constants.EN_TB_RL:
+ inlineProgressionDirection = Direction.TB;
+ blockProgressionDirection = Direction.RL;
+ columnProgressionDirection = Direction.TB;
+ rowProgressionDirection = Direction.RL;
+ shiftDirection = Direction.LR;
+ break;
+ }
+ wms.setInlineProgressionDirection ( inlineProgressionDirection );
+ wms.setBlockProgressionDirection ( blockProgressionDirection );
+ wms.setColumnProgressionDirection ( columnProgressionDirection );
+ wms.setRowProgressionDirection ( rowProgressionDirection );
+ wms.setShiftDirection ( shiftDirection );
+ 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
+ * @return the enumeration object
+ */
+ public static WritingMode valueOf(String name) {
+ for (int i = 0; i < WRITING_MODES.length; i++) {
+ if (WRITING_MODES[i].getName().equalsIgnoreCase(name)) {
+ return WRITING_MODES[i];
+ }
+ }
+ throw new IllegalArgumentException("Illegal writing mode: " + name);
+ }
+
+ /**
+ * Returns the enumeration/singleton object based on its name.
+ * @param enumValue the enumeration value
+ * @return the enumeration object
+ */
+ public static WritingMode valueOf(int enumValue) {
+ for (int i = 0; i < WRITING_MODES.length; i++) {
+ if (WRITING_MODES[i].getEnumValue() == enumValue) {
+ return WRITING_MODES[i];
+ }
+ }
+ throw new IllegalArgumentException("Illegal writing mode: " + enumValue);
+ }
+
+ private Object readResolve() throws ObjectStreamException {
+ return valueOf(getName());
+ }
+
+ /** {@inheritDoc} */
+ public String toString() {
+ return getName();
+ }
+
+}
diff --git a/src/java/org/apache/fop/traits/WritingModeTraits.java b/src/java/org/apache/fop/traits/WritingModeTraits.java
new file mode 100644
index 000000000..c96cd73c3
--- /dev/null
+++ b/src/java/org/apache/fop/traits/WritingModeTraits.java
@@ -0,0 +1,157 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.traits;
+
+/**
+ * This class provides a reusable implementation of the WritingModeTraitsSetter
+ * interface.
+ */
+public class WritingModeTraits implements WritingModeTraitsSetter {
+
+ private Direction inlineProgressionDirection;
+ private Direction blockProgressionDirection;
+ private Direction columnProgressionDirection;
+ private Direction rowProgressionDirection;
+ private Direction shiftDirection;
+ private WritingMode writingMode;
+
+ /**
+ * Default writing mode traits constructor.
+ */
+ public WritingModeTraits() {
+ this ( WritingMode.LR_TB );
+ }
+
+ /**
+ * Construct writing mode traits using the specified writing mode.
+ * @param writingMode a writing mode traits object
+ */
+ public WritingModeTraits ( WritingMode writingMode ) {
+ assignWritingModeTraits ( writingMode );
+ }
+
+ /**
+ * @return the "inline-progression-direction" trait.
+ */
+ public Direction getInlineProgressionDirection() {
+ return inlineProgressionDirection;
+ }
+
+ /**
+ * @param direction the "inline-progression-direction" trait.
+ */
+ public void setInlineProgressionDirection ( Direction direction ) {
+ this.inlineProgressionDirection = direction;
+ }
+
+ /**
+ * @return the "block-progression-direction" trait.
+ */
+ public Direction getBlockProgressionDirection() {
+ return blockProgressionDirection;
+ }
+
+ /**
+ * @param direction the "block-progression-direction" trait.
+ */
+ public void setBlockProgressionDirection ( Direction direction ) {
+ this.blockProgressionDirection = direction;
+ }
+
+ /**
+ * @return the "column-progression-direction" trait.
+ */
+ public Direction getColumnProgressionDirection() {
+ return columnProgressionDirection;
+ }
+
+ /**
+ * @param direction the "column-progression-direction" trait.
+ */
+ public void setColumnProgressionDirection ( Direction direction ) {
+ this.columnProgressionDirection = direction;
+ }
+
+ /**
+ * @return the "row-progression-direction" trait.
+ */
+ public Direction getRowProgressionDirection() {
+ return rowProgressionDirection;
+ }
+
+ /**
+ * @param direction the "row-progression-direction" trait.
+ */
+ public void setRowProgressionDirection ( Direction direction ) {
+ this.rowProgressionDirection = direction;
+ }
+
+ /**
+ * @return the "shift-direction" trait.
+ */
+ public Direction getShiftDirection() {
+ return shiftDirection;
+ }
+
+ /**
+ * @param direction the "shift-direction" trait.
+ */
+ public void setShiftDirection ( Direction direction ) {
+ this.shiftDirection = direction;
+ }
+
+ /**
+ * @return the "writing-mode" trait.
+ */
+ public WritingMode getWritingMode() {
+ return writingMode;
+ }
+
+ /**
+ * @param writingMode the "writing-mode" trait.
+ */
+ public void setWritingMode ( WritingMode writingMode ) {
+ this.writingMode = writingMode;
+ }
+
+ /**
+ * @param writingMode the "writing-mode" trait.
+ */
+ public void assignWritingModeTraits ( WritingMode writingMode ) {
+ 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/traits/WritingModeTraitsGetter.java b/src/java/org/apache/fop/traits/WritingModeTraitsGetter.java
new file mode 100644
index 000000000..a67e437c9
--- /dev/null
+++ b/src/java/org/apache/fop/traits/WritingModeTraitsGetter.java
@@ -0,0 +1,58 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.traits;
+
+/**
+ * This interface provides read access to FO traits related to writing mode.
+ */
+public interface WritingModeTraitsGetter {
+
+ /**
+ * @return the "inline-progression-direction" trait
+ */
+ Direction getInlineProgressionDirection();
+
+ /**
+ * @return the "block-progression-direction" trait
+ */
+ Direction getBlockProgressionDirection();
+
+ /**
+ * @return the "column-progression-direction" trait
+ */
+ Direction getColumnProgressionDirection();
+
+ /**
+ * @return the "row-progression-direction" trait
+ */
+
+ Direction getRowProgressionDirection();
+
+ /**
+ * @return the "shift-direction" trait
+ */
+ Direction getShiftDirection();
+
+ /**
+ * @return the "writing-mode" trait
+ */
+ WritingMode getWritingMode();
+
+}
diff --git a/src/java/org/apache/fop/traits/WritingModeTraitsSetter.java b/src/java/org/apache/fop/traits/WritingModeTraitsSetter.java
new file mode 100644
index 000000000..1b94e22de
--- /dev/null
+++ b/src/java/org/apache/fop/traits/WritingModeTraitsSetter.java
@@ -0,0 +1,70 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.traits;
+
+/**
+ * This interface provides read and assignment access to FO traits related to writing mode.
+ */
+public interface WritingModeTraitsSetter extends WritingModeTraitsGetter {
+
+ /**
+ * Set value of inline-progression-direction trait.
+ * @param direction the "inline-progression-direction" trait
+ */
+ void setInlineProgressionDirection ( Direction direction );
+
+ /**
+ * Set value of block-progression-direction trait.
+ * @param direction the "block-progression-direction" trait
+ */
+ void setBlockProgressionDirection ( Direction direction );
+
+ /**
+ * Set value of column-progression-direction trait.
+ * @param direction the "column-progression-direction" trait
+ */
+ void setColumnProgressionDirection ( Direction direction );
+
+ /**
+ * Set value of row-progression-direction trait.
+ * @param direction the "row-progression-direction" trait
+ */
+ void setRowProgressionDirection ( Direction direction );
+
+ /**
+ * Set value of shift-direction trait.
+ * @param direction the "shift-direction" trait
+ */
+ void setShiftDirection ( Direction direction );
+
+ /**
+ * Set value of writing-mode trait.
+ * @param writingMode the "writing-mode" trait
+ */
+ void setWritingMode ( WritingMode writingMode );
+
+ /**
+ * Collectivelly assign values to all writing mode traits based upon a specific
+ * writing mode.
+ * @param writingMode the "writing-mode" trait
+ */
+ void assignWritingModeTraits ( WritingMode writingMode );
+
+}
diff --git a/src/java/org/apache/fop/util/CharUtilities.java b/src/java/org/apache/fop/util/CharUtilities.java
index 7786552ff..8b90e3d25 100644
--- a/src/java/org/apache/fop/util/CharUtilities.java
+++ b/src/java/org/apache/fop/util/CharUtilities.java
@@ -74,6 +74,20 @@ public class CharUtilities {
public static final char WORD_JOINER = '\u2060';
/** zero-width joiner */
public static final char ZERO_WIDTH_JOINER = '\u200D';
+ /** left-to-right mark */
+ public static final char LRM = '\u200E';
+ /** right-to-left mark */
+ public static final char RLM = '\u202F';
+ /** left-to-right embedding */
+ public static final char LRE = '\u202A';
+ /** right-to-left embedding */
+ public static final char RLE = '\u202B';
+ /** pop directional formatting */
+ public static final char PDF = '\u202C';
+ /** left-to-right override */
+ public static final char LRO = '\u202D';
+ /** right-to-left override */
+ public static final char RLO = '\u202E';
/** zero-width no-break space (= byte order mark) */
public static final char ZERO_WIDTH_NOBREAK_SPACE = '\uFEFF';
/** soft hyphen */
@@ -86,10 +100,11 @@ 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';
-
/**
* Utility class: Constructor prevents instantiating when subclassed.
*/
@@ -103,7 +118,7 @@ public class CharUtilities {
* @param c character to inspect
* @return the determined character class
*/
- public static int classOf(char c) {
+ public static int classOf ( int c ) {
switch (c) {
case CODE_EOT:
return EOT;
@@ -126,7 +141,7 @@ public class CharUtilities {
* @param c character to inspect
* @return True if the character is a normal space
*/
- public static boolean isBreakableSpace(char c) {
+ public static boolean isBreakableSpace ( int c ) {
return (c == SPACE || isFixedWidthSpace(c));
}
@@ -135,7 +150,7 @@ public class CharUtilities {
* @param c the character to check
* @return true if the character is a zero-width space
*/
- public static boolean isZeroWidthSpace(char c) {
+ public static boolean isZeroWidthSpace ( int c ) {
return c == ZERO_WIDTH_SPACE // 200Bh
|| c == WORD_JOINER // 2060h
|| c == ZERO_WIDTH_NOBREAK_SPACE; // FEFFh (also used as BOM)
@@ -146,7 +161,7 @@ public class CharUtilities {
* @param c the character to check
* @return true if the character has a fixed-width
*/
- public static boolean isFixedWidthSpace(char c) {
+ public static boolean isFixedWidthSpace ( int c ) {
return (c >= '\u2000' && c <= '\u200B')
|| c == '\u3000';
// c == '\u2000' // en quad
@@ -170,7 +185,7 @@ public class CharUtilities {
* @param c character to check
* @return True if the character is a nbsp
*/
- public static boolean isNonBreakableSpace(char c) {
+ public static boolean isNonBreakableSpace ( int c ) {
return
(c == NBSPACE // no-break space
|| c == '\u202F' // narrow no-break space
@@ -185,7 +200,7 @@ public class CharUtilities {
* @param c character to check
* @return True if the character is adjustable
*/
- public static boolean isAdjustableSpace(char c) {
+ public static boolean isAdjustableSpace ( int c ) {
//TODO: are there other kinds of adjustable spaces?
return
(c == '\u0020' // normal space
@@ -197,19 +212,19 @@ public class CharUtilities {
* @param c character to check
* @return True if the character represents any kind of space
*/
- public static boolean isAnySpace(char c) {
+ public static boolean isAnySpace ( int c ) {
return (isBreakableSpace(c) || isNonBreakableSpace(c));
}
/**
* Indicates whether a character is classified as "Alphabetic" by the Unicode standard.
- * @param ch the character
+ * @param c the character
* @return true if the character is "Alphabetic"
*/
- public static boolean isAlphabetic(char ch) {
+ public static boolean isAlphabetic ( int c ) {
//http://www.unicode.org/Public/UNIDATA/UCD.html#Alphabetic
//Generated from: Other_Alphabetic + Lu + Ll + Lt + Lm + Lo + Nl
- int generalCategory = Character.getType(ch);
+ int generalCategory = Character.getType((char)c);
switch (generalCategory) {
case Character.UPPERCASE_LETTER: //Lu
case Character.LOWERCASE_LETTER: //Ll
@@ -227,15 +242,120 @@ public class CharUtilities {
/**
* Indicates whether the given character is an explicit break-character
- * @param ch the character to check
+ * @param c the character to check
* @return true if the character represents an explicit break
*/
- public static boolean isExplicitBreak(char ch) {
- return (ch == LINEFEED_CHAR
- || ch == CARRIAGE_RETURN
- || ch == NEXT_LINE
- || ch == LINE_SEPARATOR
- || ch == PARAGRAPH_SEPARATOR);
+ public static boolean isExplicitBreak ( int c ) {
+ return (c == LINEFEED_CHAR
+ || c == CARRIAGE_RETURN
+ || c == NEXT_LINE
+ || c == LINE_SEPARATOR
+ || c == PARAGRAPH_SEPARATOR);
+ }
+
+ /**
+ * Convert a single unicode scalar value to an XML numeric character
+ * reference. If in the BMP, four digits are used, otherwise 6 digits are used.
+ * @param c a unicode scalar value
+ * @return a string representing a numeric character reference
+ */
+ public static String charToNCRef ( int c ) {
+ StringBuffer sb = new StringBuffer();
+ for ( int i = 0, nDigits = ( c > 0xFFFF ) ? 6 : 4; i < nDigits; i++, c >>= 4 ) {
+ int d = c & 0xF;
+ char hd;
+ if ( d < 10 ) {
+ hd = (char) ( (int) '0' + d );
+ } else {
+ hd = (char) ( (int) 'A' + ( d - 10 ) );
+ }
+ sb.append ( hd );
+ }
+ return "&#x" + sb.reverse() + ";";
+ }
+
+ /**
+ * Convert a string to a sequence of ASCII or XML numeric character references.
+ * @param s a java string (encoded in UTF-16)
+ * @return a string representing a sequence of numeric character reference or
+ * ASCII characters
+ * @author Glenn Adams
+ */
+ public static String toNCRefs ( String s ) {
+ StringBuffer sb = new StringBuffer();
+ if ( s != null ) {
+ for ( int i = 0; i < s.length(); i++ ) {
+ char c = s.charAt(i);
+ if ( ( c >= 32 ) && ( c < 127 ) ) {
+ if ( c == '<' ) {
+ sb.append ( "&lt;" );
+ } else if ( c == '>' ) {
+ sb.append ( "&gt;" );
+ } else if ( c == '&' ) {
+ sb.append ( "&amp;" );
+ } else {
+ sb.append ( c );
+ }
+ } else {
+ sb.append ( charToNCRef ( c ) );
+ }
+ }
+ }
+ return sb.toString();
+ }
+
+ /**
+ * Pad a string S on left out to width W using padding character PAD.
+ * @param s string to pad
+ * @param width width of field to add padding
+ * @param pad character to use for padding
+ * @return padded string
+ * @author Glenn Adams
+ */
+ public static String padLeft ( String s, int width, char pad ) {
+ StringBuffer sb = new StringBuffer();
+ for ( int i = s.length(); i < width; i++ ) {
+ sb.append(pad);
+ }
+ sb.append ( s );
+ return sb.toString();
+ }
+
+ /**
+ * Format character for debugging output, which it is prefixed with "0x", padded left with '0'
+ * and either 4 or 6 hex characters in width according to whether it is in the BMP or not.
+ * @param c character code
+ * @return formatted character string
+ * @author Glenn Adams
+ */
+ public static String format ( int c ) {
+ if ( c < 1114112 ) {
+ return "0x" + padLeft ( Integer.toString ( c, 16 ), ( c < 65536 ) ? 4 : 6, '0' );
+ } else {
+ return "!NOT A CHARACTER!";
+ }
}
-}
+ /**
+ * 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
+ * @author Glenn Adams
+ */
+ 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/src/java/org/apache/fop/util/XMLUtil.java b/src/java/org/apache/fop/util/XMLUtil.java
index d4397c2c7..e91b6eb5d 100644
--- a/src/java/org/apache/fop/util/XMLUtil.java
+++ b/src/java/org/apache/fop/util/XMLUtil.java
@@ -173,4 +173,126 @@ public final class XMLUtil implements XMLConstants {
atts.addAttribute("", localName, localName, XMLUtil.CDATA, value);
}
+ /**
+ * Encode a glyph position adjustments array as a string, where the string value
+ * adheres to the following syntax:
+ *
+ * count ( 'Z' repeat | number )
+ *
+ * where each token is separated by whitespace, except that 'Z' followed by repeat
+ * are considered to be a single token with no intervening whitespace, and where
+ * 'Z' repeat encodes repeated zeroes.
+ * @param dp the adjustments array
+ * @param paCount the number of entries to encode from adjustments array
+ * @return the encoded value
+ */
+ public static String encodePositionAdjustments ( int[][] dp, int paCount ) {
+ assert dp != null;
+ StringBuffer sb = new StringBuffer();
+ int na = paCount;
+ int nz = 0;
+ sb.append ( na );
+ for ( int i = 0; i < na; i++ ) {
+ int[] pa = dp [ i ];
+ for ( int k = 0; k < 4; k++ ) {
+ int a = pa [ k ];
+ if ( a != 0 ) {
+ encodeNextAdjustment ( sb, nz, a ); nz = 0;
+ } else {
+ nz++;
+ }
+ }
+ }
+ encodeNextAdjustment ( sb, nz, 0 );
+ return sb.toString();
+ }
+
+ /**
+ * Encode a glyph position adjustments array as a string, where the string value
+ * adheres to the following syntax:
+ *
+ * count ( 'Z' repeat | number )
+ *
+ * where each token is separated by whitespace, except that 'Z' followed by repeat
+ * are considered to be a single token with no intervening whitespace.
+ * @param dp the adjustments array
+ * @return the encoded value
+ */
+ public static String encodePositionAdjustments ( int[][] dp ) {
+ assert dp != null;
+ return encodePositionAdjustments ( dp, dp.length );
+ }
+
+ private static void encodeNextAdjustment ( StringBuffer sb, int nz, int a ) {
+ encodeZeroes ( sb, nz );
+ encodeAdjustment ( sb, a );
+ }
+
+ private static void encodeZeroes ( StringBuffer sb, int nz ) {
+ if ( nz > 0 ) {
+ sb.append ( ' ' );
+ if ( nz == 1 ) {
+ sb.append ( '0' );
+ } else {
+ sb.append ( 'Z' );
+ sb.append ( nz );
+ }
+ }
+ }
+
+ private static void encodeAdjustment ( StringBuffer sb, int a ) {
+ if ( a != 0 ) {
+ sb.append ( ' ' );
+ sb.append ( a );
+ }
+ }
+
+ /**
+ * Decode a string as a glyph position adjustments array, where the string
+ * shall adhere to the syntax specified by {@link #encodePositionAdjustments}.
+ * @param value the encoded value
+ * @return the position adjustments array
+ */
+ public static int[][] decodePositionAdjustments ( String value ) {
+ int[][] dp = null;
+ if ( value != null ) {
+ String[] sa = value.split ( "\\s" );
+ if ( sa != null ) {
+ if ( sa.length > 0 ) {
+ int na = Integer.parseInt ( sa[0] );
+ dp = new int [ na ] [ 4 ];
+ for ( int i = 1, n = sa.length, k = 0; i < n; i++ ) {
+ String s = sa [ i ];
+ if ( s.charAt(0) == 'Z' ) {
+ int nz = Integer.parseInt ( s.substring ( 1 ) );
+ k += nz;
+ } else {
+ dp [ k / 4 ] [ k % 4 ] = Integer.parseInt ( s );
+ k += 1;
+ }
+ }
+ }
+ }
+ }
+ return dp;
+ }
+
+ /**
+ * Returns an attribute value as a glyph position adjustments array. The string value
+ * is expected to be a non-empty sequence of either Z<repeat> or <number>, where the
+ * former encodes a repeat count (of zeroes) and the latter encodes a integer number,
+ * and where each item is separated by whitespace.
+ * @param attributes the Attributes object
+ * @param name the name of the attribute
+ * @return the position adjustments array
+ */
+ public static int[][] getAttributeAsPositionAdjustments(Attributes attributes, String name) {
+ String s = attributes.getValue(name);
+ if (s == null) {
+ return null;
+ } else {
+ return decodePositionAdjustments(s.trim());
+ }
+ }
+
}