From: Glenn Adams Date: Sun, 3 Aug 2014 04:40:34 +0000 (+0000) Subject: FOP-2391: preliminary (but incomplete) support for complex script text nodes in svg... X-Git-Tag: fop-2_0~86 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=7320ab1c5284251ba57cd5e835c3944a6e1cebbd;p=xmlgraphics-fop.git FOP-2391: preliminary (but incomplete) support for complex script text nodes in svg foreign object git-svn-id: https://svn.apache.org/repos/asf/xmlgraphics/fop/trunk@1615385 13f79535-47bb-0310-9956-ffa450edef68 --- diff --git a/findbugs-exclude.xml b/findbugs-exclude.xml index c3a1b76fb..5cc531a6b 100644 --- a/findbugs-exclude.xml +++ b/findbugs-exclude.xml @@ -141,6 +141,16 @@ + + + + + + + + + + @@ -5463,7 +5473,7 @@ - + diff --git a/lib/batik-all-trunk.jar b/lib/batik-all-trunk.jar index e458266b9..347757f32 100644 Binary files a/lib/batik-all-trunk.jar and b/lib/batik-all-trunk.jar differ diff --git a/src/java/org/apache/fop/complexscripts/fonts/GlyphProcessingState.java b/src/java/org/apache/fop/complexscripts/fonts/GlyphProcessingState.java index 4f6e4181c..82a188abc 100644 --- a/src/java/org/apache/fop/complexscripts/fonts/GlyphProcessingState.java +++ b/src/java/org/apache/fop/complexscripts/fonts/GlyphProcessingState.java @@ -23,6 +23,7 @@ import java.nio.IntBuffer; import java.util.ArrayList; import java.util.List; +import org.apache.fop.complexscripts.util.CharAssociation; import org.apache.fop.complexscripts.util.GlyphContextTester; import org.apache.fop.complexscripts.util.GlyphSequence; import org.apache.fop.complexscripts.util.GlyphTester; @@ -417,7 +418,7 @@ public class GlyphProcessingState { * @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 { + public CharAssociation getAssociation(int offset) throws IndexOutOfBoundsException { int i = index + offset; if ((i >= 0) && (i < indexLast)) { return igs.getAssociation(i); @@ -431,7 +432,7 @@ public class GlyphProcessingState { * @return character association of glyph at current position * @throws IndexOutOfBoundsException if no glyph available */ - public GlyphSequence.CharAssociation getAssociation() throws IndexOutOfBoundsException { + public CharAssociation getAssociation() throws IndexOutOfBoundsException { return getAssociation(0); } @@ -722,7 +723,7 @@ public class GlyphProcessingState { * @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) + public CharAssociation[] getAssociations(int offset, int count, boolean reverseOrder, GlyphTester ignoreTester, CharAssociation[] associations, int[] counts) throws IndexOutOfBoundsException { if (count < 0) { count = getGlyphsAvailable(offset, reverseOrder, ignoreTester) [ 0 ]; @@ -736,7 +737,7 @@ public class GlyphProcessingState { throw new IndexOutOfBoundsException("will attempt index at " + (start - count)); } if (associations == null) { - associations = new GlyphSequence.CharAssociation [ count ]; + associations = new 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 + ")"); } @@ -747,7 +748,7 @@ public class GlyphProcessingState { } } - private GlyphSequence.CharAssociation[] getAssociationsForward(int start, int count, GlyphTester ignoreTester, GlyphSequence.CharAssociation[] associations, int[] counts) + private CharAssociation[] getAssociationsForward(int start, int count, GlyphTester ignoreTester, CharAssociation[] associations, int[] counts) throws IndexOutOfBoundsException { int counted = 0; int ignored = 0; @@ -775,7 +776,7 @@ public class GlyphProcessingState { return associations; } - private GlyphSequence.CharAssociation[] getAssociationsReverse(int start, int count, GlyphTester ignoreTester, GlyphSequence.CharAssociation[] associations, int[] counts) + private CharAssociation[] getAssociationsReverse(int start, int count, GlyphTester ignoreTester, CharAssociation[] associations, int[] counts) throws IndexOutOfBoundsException { int counted = 0; int ignored = 0; @@ -813,7 +814,7 @@ public class GlyphProcessingState { * @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 { + public CharAssociation[] getAssociations(int offset, int count) throws IndexOutOfBoundsException { return getAssociations(offset, count, offset < 0, ignoreDefault, null, null); } @@ -833,7 +834,7 @@ public class GlyphProcessingState { * @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) + public CharAssociation[] getIgnoredAssociations(int offset, int count, boolean reverseOrder, GlyphTester ignoreTester, CharAssociation[] associations, int[] counts) throws IndexOutOfBoundsException { return getAssociations(offset, count, reverseOrder, new NotGlyphTester(ignoreTester), associations, counts); } @@ -848,7 +849,7 @@ public class GlyphProcessingState { * @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 { + public CharAssociation[] getIgnoredAssociations(int offset, int count) throws IndexOutOfBoundsException { return getIgnoredAssociations(offset, count, offset < 0, ignoreDefault, null, null); } diff --git a/src/java/org/apache/fop/complexscripts/fonts/GlyphSubstitutionState.java b/src/java/org/apache/fop/complexscripts/fonts/GlyphSubstitutionState.java index 108f26c82..da40bbd87 100644 --- a/src/java/org/apache/fop/complexscripts/fonts/GlyphSubstitutionState.java +++ b/src/java/org/apache/fop/complexscripts/fonts/GlyphSubstitutionState.java @@ -23,6 +23,7 @@ import java.nio.IntBuffer; import java.util.ArrayList; import java.util.List; +import org.apache.fop.complexscripts.util.CharAssociation; import org.apache.fop.complexscripts.util.GlyphSequence; import org.apache.fop.complexscripts.util.ScriptContextTester; @@ -128,7 +129,7 @@ public class GlyphSubstitutionState extends GlyphProcessingState { * @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) { + public void putGlyph(int glyph, CharAssociation a, Object predication) { if (!ogb.hasRemaining()) { ogb = growBuffer(ogb); } @@ -145,7 +146,7 @@ public class GlyphSubstitutionState extends GlyphProcessingState { * @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) { + public void putGlyphs(int[] glyphs, CharAssociation[] associations, Object predication) { assert glyphs != null; assert associations != null; assert associations.length >= glyphs.length; diff --git a/src/java/org/apache/fop/complexscripts/fonts/GlyphSubstitutionTable.java b/src/java/org/apache/fop/complexscripts/fonts/GlyphSubstitutionTable.java index da708bf45..7bb5a23e1 100644 --- a/src/java/org/apache/fop/complexscripts/fonts/GlyphSubstitutionTable.java +++ b/src/java/org/apache/fop/complexscripts/fonts/GlyphSubstitutionTable.java @@ -28,6 +28,7 @@ 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.CharAssociation; import org.apache.fop.complexscripts.util.GlyphSequence; import org.apache.fop.complexscripts.util.GlyphTester; @@ -382,7 +383,7 @@ public class GlyphSubstitutionTable extends GlyphTable { } else { int[] ga = getGlyphsForCoverageIndex(ci, gi); if (ga != null) { - ss.putGlyphs(ga, GlyphSequence.CharAssociation.replicate(ss.getAssociation(), ga.length), Boolean.TRUE); + ss.putGlyphs(ga, CharAssociation.replicate(ss.getAssociation(), ga.length), Boolean.TRUE); ss.consume(1); } return true; @@ -581,9 +582,9 @@ public class GlyphSubstitutionTable extends GlyphTable { nga = counts[0]; ngi = counts[1]; // fetch associations of matched component glyphs - GlyphSequence.CharAssociation[] laa = ss.getAssociations(0, nga); + CharAssociation[] laa = ss.getAssociations(0, nga); // output ligature glyph and its association - ss.putGlyph(go, GlyphSequence.CharAssociation.join(laa), Boolean.TRUE); + ss.putGlyph(go, 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); diff --git a/src/java/org/apache/fop/complexscripts/fonts/Substitutable.java b/src/java/org/apache/fop/complexscripts/fonts/Substitutable.java index 984af8dea..ef349240a 100644 --- a/src/java/org/apache/fop/complexscripts/fonts/Substitutable.java +++ b/src/java/org/apache/fop/complexscripts/fonts/Substitutable.java @@ -19,6 +19,8 @@ package org.apache.fop.complexscripts.fonts; +import java.util.List; + // CSOFF: LineLengthCheck /** @@ -43,10 +45,11 @@ public interface Substitutable { * @param cs character sequence to map to output font encoding character sequence * @param script a script identifier * @param language a language identifier + * @param associations optional list to receive list of character associations * @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); + CharSequence performSubstitution(CharSequence cs, String script, String language, List associations); /** * Reorder combining marks in character sequence so that they precede (within the sequence) the base @@ -57,8 +60,9 @@ public interface Substitutable { * @param gpa associated glyph position adjustments (also reordered) * @param script a script identifier * @param language a language identifier + * @param associations optional list of associations to be reordered * @return output sequence containing reordered "font characters" */ - CharSequence reorderCombiningMarks(CharSequence cs, int[][] gpa, String script, String language); + CharSequence reorderCombiningMarks(CharSequence cs, int[][] gpa, String script, String language, List associations); } diff --git a/src/java/org/apache/fop/complexscripts/scripts/ArabicScriptProcessor.java b/src/java/org/apache/fop/complexscripts/scripts/ArabicScriptProcessor.java index 5e68c8763..2753ca945 100644 --- a/src/java/org/apache/fop/complexscripts/scripts/ArabicScriptProcessor.java +++ b/src/java/org/apache/fop/complexscripts/scripts/ArabicScriptProcessor.java @@ -29,6 +29,7 @@ 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.CharAssociation; import org.apache.fop.complexscripts.util.GlyphContextTester; import org.apache.fop.complexscripts.util.GlyphSequence; import org.apache.fop.complexscripts.util.ScriptContextTester; @@ -147,7 +148,7 @@ public class ArabicScriptProcessor extends DefaultScriptProcessor { } private static boolean inFinalContext(String script, String language, String feature, GlyphSequence gs, int index, int flags) { - GlyphSequence.CharAssociation a = gs.getAssociation(index); + CharAssociation a = gs.getAssociation(index); int[] ca = gs.getCharacterArray(false); int nc = gs.getCharacterCount(); if (nc == 0) { @@ -168,7 +169,7 @@ public class ArabicScriptProcessor extends DefaultScriptProcessor { } private static boolean inInitialContext(String script, String language, String feature, GlyphSequence gs, int index, int flags) { - GlyphSequence.CharAssociation a = gs.getAssociation(index); + CharAssociation a = gs.getAssociation(index); int[] ca = gs.getCharacterArray(false); int nc = gs.getCharacterCount(); if (nc == 0) { @@ -187,7 +188,7 @@ public class ArabicScriptProcessor extends DefaultScriptProcessor { } private static boolean inIsolateContext(String script, String language, String feature, GlyphSequence gs, int index, int flags) { - GlyphSequence.CharAssociation a = gs.getAssociation(index); + CharAssociation a = gs.getAssociation(index); int nc = gs.getCharacterCount(); if (nc == 0) { return false; @@ -199,7 +200,7 @@ public class ArabicScriptProcessor extends DefaultScriptProcessor { } private static boolean inLigatureContext(String script, String language, String feature, GlyphSequence gs, int index, int flags) { - GlyphSequence.CharAssociation a = gs.getAssociation(index); + CharAssociation a = gs.getAssociation(index); int[] ca = gs.getCharacterArray(false); int nc = gs.getCharacterCount(); if (nc == 0) { @@ -218,7 +219,7 @@ public class ArabicScriptProcessor extends DefaultScriptProcessor { } private static boolean inMedialContext(String script, String language, String feature, GlyphSequence gs, int index, int flags) { - GlyphSequence.CharAssociation a = gs.getAssociation(index); + CharAssociation a = gs.getAssociation(index); int[] ca = gs.getCharacterArray(false); int nc = gs.getCharacterCount(); if (nc == 0) { diff --git a/src/java/org/apache/fop/complexscripts/scripts/DefaultScriptProcessor.java b/src/java/org/apache/fop/complexscripts/scripts/DefaultScriptProcessor.java index 7aa532838..ced4d4041 100644 --- a/src/java/org/apache/fop/complexscripts/scripts/DefaultScriptProcessor.java +++ b/src/java/org/apache/fop/complexscripts/scripts/DefaultScriptProcessor.java @@ -20,6 +20,7 @@ package org.apache.fop.complexscripts.scripts; import org.apache.fop.complexscripts.fonts.GlyphDefinitionTable; +import org.apache.fop.complexscripts.util.CharAssociation; import org.apache.fop.complexscripts.util.GlyphSequence; import org.apache.fop.complexscripts.util.ScriptContextTester; @@ -92,18 +93,18 @@ public class DefaultScriptProcessor extends ScriptProcessor { } // 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); + 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 ]; + CharAssociation[] naa = new CharAssociation [ ng ]; int k = 0; - GlyphSequence.CharAssociation ba = null; + 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 ]; + CharAssociation ca = aa [ i ]; if (gdef.isGlyphClass(gid, GlyphDefinitionTable.GLYPH_CLASS_MARK)) { nga [ k ] = gid; naa [ k ] = ca; diff --git a/src/java/org/apache/fop/complexscripts/scripts/DevanagariScriptProcessor.java b/src/java/org/apache/fop/complexscripts/scripts/DevanagariScriptProcessor.java index e3843f444..8d312b846 100644 --- a/src/java/org/apache/fop/complexscripts/scripts/DevanagariScriptProcessor.java +++ b/src/java/org/apache/fop/complexscripts/scripts/DevanagariScriptProcessor.java @@ -22,6 +22,7 @@ package org.apache.fop.complexscripts.scripts; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.apache.fop.complexscripts.util.CharAssociation; import org.apache.fop.complexscripts.util.GlyphSequence; // CSOFF: LineLengthCheck @@ -82,7 +83,7 @@ public class DevanagariScriptProcessor extends IndicScriptProcessor { } private static boolean containsPreBaseMatra(GlyphSequence gs, int k) { - GlyphSequence.CharAssociation a = gs.getAssociation(k); + 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 ])) { @@ -93,7 +94,7 @@ public class DevanagariScriptProcessor extends IndicScriptProcessor { } private static boolean containsConsonant(GlyphSequence gs, int k) { - GlyphSequence.CharAssociation a = gs.getAssociation(k); + 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 ])) { @@ -159,7 +160,7 @@ public class DevanagariScriptProcessor extends IndicScriptProcessor { } private static boolean containsMatra(GlyphSequence gs, int k) { - GlyphSequence.CharAssociation a = gs.getAssociation(k); + 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 ])) { @@ -170,7 +171,7 @@ public class DevanagariScriptProcessor extends IndicScriptProcessor { } private static boolean containsOtherMark(GlyphSequence gs, int k) { - GlyphSequence.CharAssociation a = gs.getAssociation(k); + 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 ])) { diff --git a/src/java/org/apache/fop/complexscripts/scripts/GujaratiScriptProcessor.java b/src/java/org/apache/fop/complexscripts/scripts/GujaratiScriptProcessor.java index e4519623e..1f96455bc 100644 --- a/src/java/org/apache/fop/complexscripts/scripts/GujaratiScriptProcessor.java +++ b/src/java/org/apache/fop/complexscripts/scripts/GujaratiScriptProcessor.java @@ -22,6 +22,7 @@ package org.apache.fop.complexscripts.scripts; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.apache.fop.complexscripts.util.CharAssociation; import org.apache.fop.complexscripts.util.GlyphSequence; // CSOFF: LineLengthCheck @@ -82,7 +83,7 @@ public class GujaratiScriptProcessor extends IndicScriptProcessor { } private static boolean containsPreBaseMatra(GlyphSequence gs, int k) { - GlyphSequence.CharAssociation a = gs.getAssociation(k); + 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 ])) { @@ -93,7 +94,7 @@ public class GujaratiScriptProcessor extends IndicScriptProcessor { } private static boolean containsConsonant(GlyphSequence gs, int k) { - GlyphSequence.CharAssociation a = gs.getAssociation(k); + 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 ])) { @@ -159,7 +160,7 @@ public class GujaratiScriptProcessor extends IndicScriptProcessor { } private static boolean containsMatra(GlyphSequence gs, int k) { - GlyphSequence.CharAssociation a = gs.getAssociation(k); + 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 ])) { @@ -170,7 +171,7 @@ public class GujaratiScriptProcessor extends IndicScriptProcessor { } private static boolean containsOtherMark(GlyphSequence gs, int k) { - GlyphSequence.CharAssociation a = gs.getAssociation(k); + 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 ])) { diff --git a/src/java/org/apache/fop/complexscripts/scripts/GurmukhiScriptProcessor.java b/src/java/org/apache/fop/complexscripts/scripts/GurmukhiScriptProcessor.java index 9c4d49f59..94f5893a9 100644 --- a/src/java/org/apache/fop/complexscripts/scripts/GurmukhiScriptProcessor.java +++ b/src/java/org/apache/fop/complexscripts/scripts/GurmukhiScriptProcessor.java @@ -23,6 +23,7 @@ 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.CharAssociation; import org.apache.fop.complexscripts.util.GlyphSequence; // CSOFF: LineLengthCheck @@ -83,7 +84,7 @@ public class GurmukhiScriptProcessor extends IndicScriptProcessor { } private static boolean containsPreBaseMatra(GlyphSequence gs, int k) { - GlyphSequence.CharAssociation a = gs.getAssociation(k); + 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 ])) { @@ -94,7 +95,7 @@ public class GurmukhiScriptProcessor extends IndicScriptProcessor { } private static boolean containsConsonant(GlyphSequence gs, int k) { - GlyphSequence.CharAssociation a = gs.getAssociation(k); + 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 ])) { @@ -160,7 +161,7 @@ public class GurmukhiScriptProcessor extends IndicScriptProcessor { } private static boolean containsMatra(GlyphSequence gs, int k) { - GlyphSequence.CharAssociation a = gs.getAssociation(k); + 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 ])) { @@ -171,7 +172,7 @@ public class GurmukhiScriptProcessor extends IndicScriptProcessor { } private static boolean containsOtherMark(GlyphSequence gs, int k) { - GlyphSequence.CharAssociation a = gs.getAssociation(k); + 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 ])) { diff --git a/src/java/org/apache/fop/complexscripts/scripts/IndicScriptProcessor.java b/src/java/org/apache/fop/complexscripts/scripts/IndicScriptProcessor.java index fcabad396..a341ef123 100644 --- a/src/java/org/apache/fop/complexscripts/scripts/IndicScriptProcessor.java +++ b/src/java/org/apache/fop/complexscripts/scripts/IndicScriptProcessor.java @@ -31,6 +31,7 @@ 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.CharAssociation; import org.apache.fop.complexscripts.util.CharScript; import org.apache.fop.complexscripts.util.GlyphContextTester; import org.apache.fop.complexscripts.util.GlyphSequence; @@ -488,21 +489,21 @@ public class IndicScriptProcessor extends DefaultScriptProcessor { protected GlyphSequence[] segmentize(GlyphSequence gs, Segment[] sa) { int ng = gs.getGlyphCount(); int[] ga = gs.getGlyphArray(false); - GlyphSequence.CharAssociation[] aa = gs.getAssociations(0, -1); + CharAssociation[] aa = gs.getAssociations(0, -1); Vector nsv = new Vector(); for (int i = 0, ns = sa.length; i < ns; i++) { Segment s = sa [ i ]; Vector ngv = new Vector(ng); - Vector nav = new Vector(ng); + Vector nav = new Vector(ng); for (int j = 0; j < ng; j++) { - GlyphSequence.CharAssociation ca = aa [ j ]; + 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)); + nsv.add(new GlyphSequence(gs, null, toIntArray(ngv), null, null, nav.toArray(new CharAssociation [ nav.size() ]), null)); } } if (nsv.size() > 0) { diff --git a/src/java/org/apache/fop/complexscripts/util/CharAssociation.java b/src/java/org/apache/fop/complexscripts/util/CharAssociation.java new file mode 100644 index 000000000..23aebbb9a --- /dev/null +++ b/src/java/org/apache/fop/complexscripts/util/CharAssociation.java @@ -0,0 +1,490 @@ +/* + * 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.HashMap; +import java.util.Map; + +/** + * A structure class encapsulating an interval of characters expressed as an offset and count of + * Unicode scalar values (in an IntBuffer). A CharAssociation is used to maintain a + * backpointer from a glyph to one or more character intervals from which the glyph was derived. + * + * Each glyph in a glyph sequence is associated with a single CharAssociation instance. + * + * A CharAssociation instance is additionally (and optionally) used to record + * predication information about the glyph, such as whether the glyph was produced by the + * application of a specific substitution table or whether its position was adjusted by a specific + * poisitioning table. + * + *

This work was originally authored by Glenn Adams (gadams@apache.org).

+ */ +public class CharAssociation implements Cloneable { + + // instance state + private final int offset; + private final int count; + private final int[] subIntervals; + private Map predications; + + // class state + private static volatile Map predicationMergers; + + interface PredicationMerger { + Object merge(String key, Object v1, Object v2); + } + + /** + * Instantiate a character association. + * @param offset into array of 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 . + * @param key predication key + * @param value predication value + */ + public void setPredication(String key, Object value) { + if (predications == null) { + predications = new HashMap(); + } + if (predications != null) { + predications.put(key, value); + } + } + + /** + * Get predication KEY. + * @param key predication key + * @return predication KEY at OFFSET or null if none exists + */ + public Object getPredication(String key) { + if (predications != null) { + return predications.get(key); + } else { + return null; + } + } + + /** + * Merge predication . + * @param key predication key + * @param value predication value + */ + public void mergePredication(String key, Object value) { + if (predications == null) { + predications = new HashMap(); + } + if (predications != null) { + if (predications.containsKey(key)) { + Object v1 = predications.get(key); + Object v2 = value; + predications.put(key, mergePredicationValues(key, v1, v2)); + } else { + predications.put(key, value); + } + } + } + + /** + * Merge predication values V1 and V2 on KEY. Uses registered PredicationMerger + * if one exists, otherwise uses V2 if non-null, otherwise uses V1. + * @param key predication key + * @param v1 first (original) predication value + * @param v2 second (to be merged) predication value + * @return merged value + */ + public static Object mergePredicationValues(String key, Object v1, Object v2) { + PredicationMerger pm = getPredicationMerger(key); + if (pm != null) { + return pm.merge(key, v1, v2); + } else if (v2 != null) { + return v2; + } else { + return v1; + } + } + + /** + * Merge predications from another CA. + * @param ca from which to merge + */ + public void mergePredications(CharAssociation ca) { + if (ca.predications != null) { + for (Map.Entry e : ca.predications.entrySet()) { + mergePredication(e.getKey(), e.getValue()); + } + } + } + + /** {@inheritDoc} */ + public Object clone() { + try { + CharAssociation ca = (CharAssociation) super.clone(); + if (predications != null) { + ca.predications = new HashMap(predications); + } + return ca; + } catch (CloneNotSupportedException e) { + return null; + } + } + + /** + * Register predication merger PM for KEY. + * @param key for predication merger + * @param pm predication merger + */ + public static void setPredicationMerger(String key, PredicationMerger pm) { + if (predicationMergers == null) { + predicationMergers = new HashMap(); + } + if (predicationMergers != null) { + predicationMergers.put(key, pm); + } + } + + /** + * Obtain predication merger for KEY. + * @param key for predication merger + * @return predication merger or null if none exists + */ + public static PredicationMerger getPredicationMerger(String key) { + if (predicationMergers != null) { + return predicationMergers.get(key); + } else { + return null; + } + } + + /** + * Replicate association to form repeat new associations. + * @param a association to replicate + * @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[] SORT_INCREMENTS_16 + = { 1391376, 463792, 198768, 86961, 33936, 13776, 4592, 1968, 861, 336, 112, 48, 21, 7, 3, 1 }; + + private static final int[] SORT_INCREMENTS_03 + = { 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) ? SORT_INCREMENTS_03 : SORT_INCREMENTS_16; + 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; + int n; + int nm; + int is; + int 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; + } + + @Override + public String toString() { + StringBuffer sb = new StringBuffer(); + sb.append('['); + sb.append(offset); + sb.append(','); + sb.append(count); + sb.append(']'); + return sb.toString(); + } + +} diff --git a/src/java/org/apache/fop/complexscripts/util/GlyphSequence.java b/src/java/org/apache/fop/complexscripts/util/GlyphSequence.java index e59dc9b32..8db7f109e 100644 --- a/src/java/org/apache/fop/complexscripts/util/GlyphSequence.java +++ b/src/java/org/apache/fop/complexscripts/util/GlyphSequence.java @@ -22,9 +22,7 @@ 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; // CSOFF: LineLengthCheck @@ -527,8 +525,8 @@ public class GlyphSequence implements Cloneable { 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 ]; + CharAssociation[] aa = gs.getAssociations(0, ng); + CharAssociation[] naa = new CharAssociation [ ng ]; if (source < target) { int t = 0; for (int s = 0, e = source; s < e; s++, t++) { @@ -614,462 +612,4 @@ public class GlyphSequence implements Cloneable { } } - /** - * A structure class encapsulating an interval of characters - * expressed as an offset and count of Unicode scalar values (in - * an IntBuffer). A CharAssociation is used to - * maintain a backpointer from a glyph to one or more character - * intervals from which the glyph was derived. - * - * Each glyph in a glyph sequence is associated with a single - * CharAssociation instance. - * - * A CharAssociation instance is additionally (and - * optionally) used to record predication information about the - * glyph, such as whether the glyph was produced by the - * application of a specific substitution table or whether its - * position was adjusted by a specific poisitioning table. - */ - public static class CharAssociation implements Cloneable { - - // instance state - private final int offset; - private final int count; - private final int[] subIntervals; - private Map predications; - - // class state - private static volatile Map predicationMergers; - - interface PredicationMerger { - Object merge(String key, Object v1, Object v2); - } - - /** - * Instantiate a character association. - * @param offset into array of 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 . - * @param key predication key - * @param value predication value - */ - public void setPredication(String key, Object value) { - if (predications == null) { - predications = new HashMap(); - } - if (predications != null) { - predications.put(key, value); - } - } - - /** - * Get predication KEY. - * @param key predication key - * @return predication KEY at OFFSET or null if none exists - */ - public Object getPredication(String key) { - if (predications != null) { - return predications.get(key); - } else { - return null; - } - } - - /** - * Merge predication . - * @param key predication key - * @param value predication value - */ - public void mergePredication(String key, Object value) { - if (predications == null) { - predications = new HashMap(); - } - if (predications != null) { - if (predications.containsKey(key)) { - Object v1 = predications.get(key); - Object v2 = value; - predications.put(key, mergePredicationValues(key, v1, v2)); - } else { - predications.put(key, value); - } - } - } - - /** - * Merge predication values V1 and V2 on KEY. Uses registered PredicationMerger - * if one exists, otherwise uses V2 if non-null, otherwise uses V1. - * @param key predication key - * @param v1 first (original) predication value - * @param v2 second (to be merged) predication value - * @return merged value - */ - public static Object mergePredicationValues(String key, Object v1, Object v2) { - PredicationMerger pm = getPredicationMerger(key); - if (pm != null) { - return pm.merge(key, v1, v2); - } else if (v2 != null) { - return v2; - } else { - return v1; - } - } - - /** - * Merge predications from another CA. - * @param ca from which to merge - */ - public void mergePredications(CharAssociation ca) { - if (ca.predications != null) { - for (Map.Entry e : ca.predications.entrySet()) { - mergePredication(e.getKey(), e.getValue()); - } - } - } - - /** {@inheritDoc} */ - public Object clone() { - try { - CharAssociation ca = (CharAssociation) super.clone(); - if (predications != null) { - ca.predications = new HashMap(predications); - } - return ca; - } catch (CloneNotSupportedException e) { - return null; - } - } - - /** - * Register predication merger PM for KEY. - * @param key for predication merger - * @param pm predication merger - */ - public static void setPredicationMerger(String key, PredicationMerger pm) { - if (predicationMergers == null) { - predicationMergers = new HashMap(); - } - if (predicationMergers != null) { - predicationMergers.put(key, pm); - } - } - - /** - * Obtain predication merger for KEY. - * @param key for predication merger - * @return predication merger or null if none exists - */ - public static PredicationMerger getPredicationMerger(String key) { - if (predicationMergers != null) { - return predicationMergers.get(key); - } else { - return null; - } - } - - /** - * Replicate association to form repeat new associations. - * @param a association to replicate - * @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[] SORT_INCREMENTS_16 - = { 1391376, 463792, 198768, 86961, 33936, 13776, 4592, 1968, 861, 336, 112, 48, 21, 7, 3, 1 }; - - private static final int[] SORT_INCREMENTS_03 - = { 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) ? SORT_INCREMENTS_03 : SORT_INCREMENTS_16; - 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; - int n; - int nm; - int is; - int 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/fonts/Font.java b/src/java/org/apache/fop/fonts/Font.java index 3006d0873..296d2ef8e 100644 --- a/src/java/org/apache/fop/fonts/Font.java +++ b/src/java/org/apache/fop/fonts/Font.java @@ -20,6 +20,7 @@ package org.apache.fop.fonts; import java.util.Collections; +import java.util.List; import java.util.Map; import org.apache.commons.logging.Log; @@ -402,20 +403,22 @@ public class Font implements Substitutable, Positionable { } /** {@inheritDoc} */ - public CharSequence performSubstitution(CharSequence cs, String script, String language) { + public CharSequence performSubstitution(CharSequence cs, + String script, String language, List associations) { if (metric instanceof Substitutable) { Substitutable s = (Substitutable) metric; - return s.performSubstitution(cs, script, language); + return s.performSubstitution(cs, script, language, associations); } else { throw new UnsupportedOperationException(); } } /** {@inheritDoc} */ - public CharSequence reorderCombiningMarks(CharSequence cs, int[][] gpa, String script, String language) { + public CharSequence reorderCombiningMarks(CharSequence cs, int[][] gpa, + String script, String language, List associations) { if (metric instanceof Substitutable) { Substitutable s = (Substitutable) metric; - return s.reorderCombiningMarks(cs, gpa, script, language); + return s.reorderCombiningMarks(cs, gpa, script, language, associations); } else { throw new UnsupportedOperationException(); } diff --git a/src/java/org/apache/fop/fonts/GlyphMapping.java b/src/java/org/apache/fop/fonts/GlyphMapping.java index a8a82085e..69943242d 100644 --- a/src/java/org/apache/fop/fonts/GlyphMapping.java +++ b/src/java/org/apache/fop/fonts/GlyphMapping.java @@ -19,6 +19,9 @@ package org.apache.fop.fonts; +import java.util.Collections; +import java.util.List; + import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -47,18 +50,19 @@ public class GlyphMapping { public final Font font; public final int level; public final int[][] gposAdjustments; - public final String mapping; + public String mapping; + public List associations; public GlyphMapping(int startIndex, int endIndex, int wordSpaceCount, int letterSpaceCount, MinOptMax areaIPD, boolean isHyphenated, boolean isSpace, boolean breakOppAfter, Font font, int level, int[][] gposAdjustments) { this(startIndex, endIndex, wordSpaceCount, letterSpaceCount, areaIPD, isHyphenated, - isSpace, breakOppAfter, font, level, gposAdjustments, null); + isSpace, breakOppAfter, font, level, gposAdjustments, null, null); } public GlyphMapping(int startIndex, int endIndex, int wordSpaceCount, int letterSpaceCount, MinOptMax areaIPD, boolean isHyphenated, boolean isSpace, boolean breakOppAfter, - Font font, int level, int[][] gposAdjustments, String mapping) { + Font font, int level, int[][] gposAdjustments, String mapping, List associations) { assert startIndex <= endIndex; this.startIndex = startIndex; this.endIndex = endIndex; @@ -73,15 +77,17 @@ public class GlyphMapping { this.level = level; this.gposAdjustments = gposAdjustments; this.mapping = mapping; + this.associations = associations; } public static GlyphMapping doGlyphMapping(TextFragment text, int startIndex, int endIndex, Font font, MinOptMax letterSpaceIPD, MinOptMax[] letterSpaceAdjustArray, - char precedingChar, char breakOpportunityChar, final boolean endsWithHyphen, int level) { + char precedingChar, char breakOpportunityChar, final boolean endsWithHyphen, int level, + boolean retainAssociations) { GlyphMapping mapping; if (font.performsSubstitution() || font.performsPositioning()) { mapping = processWordMapping(text, startIndex, endIndex, font, - breakOpportunityChar, endsWithHyphen, level); + breakOpportunityChar, endsWithHyphen, level, retainAssociations); } else { mapping = processWordNoMapping(text, startIndex, endIndex, font, letterSpaceIPD, letterSpaceAdjustArray, precedingChar, breakOpportunityChar, endsWithHyphen, @@ -92,7 +98,7 @@ public class GlyphMapping { private static GlyphMapping processWordMapping(TextFragment text, int startIndex, int endIndex, final Font font, final char breakOpportunityChar, - final boolean endsWithHyphen, int level) { + final boolean endsWithHyphen, int level, boolean retainAssociations) { int e = endIndex; // end index of word in FOText character buffer int nLS = 0; // # of letter spaces String script = text.getScript(); @@ -105,11 +111,11 @@ public class GlyphMapping { + " }"); } - // 1. extract unmapped character sequence + // 1. extract unmapped character sequence. CharSequence ics = text.subSequence(startIndex, e); // 2. if script is not specified (by FO property) or it is specified as 'auto', - // then compute dominant script + // then compute dominant script. if ((script == null) || "auto".equals(script)) { script = CharScript.scriptTagFromCode(CharScript.dominantScript(ics)); } @@ -117,10 +123,12 @@ public class GlyphMapping { language = "dflt"; } - // 3. perform mapping of chars to glyphs ... to glyphs ... to chars - CharSequence mcs = font.performSubstitution(ics, script, language); + // 3. perform mapping of chars to glyphs ... to glyphs ... to chars, retaining + // associations if requested. + List associations = retainAssociations ? new java.util.ArrayList() : null; + CharSequence mcs = font.performSubstitution(ics, script, language, associations); - // 4. compute glyph position adjustments on (substituted) characters + // 4. compute glyph position adjustments on (substituted) characters. int[][] gpa; if (font.performsPositioning()) { // handle GPOS adjustments @@ -133,10 +141,10 @@ public class GlyphMapping { } // 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); + // base to which they are applied; N.B. position adjustments (gpa) are reordered in place. + mcs = font.reorderCombiningMarks(mcs, gpa, script, language, associations); - // 6. compute word ipd based on final position adjustments + // 6. 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); @@ -155,7 +163,7 @@ public class GlyphMapping { return new GlyphMapping(startIndex, e, 0, nLS, ipd, endsWithHyphen, false, breakOpportunityChar != 0, font, level, gpa, - CharUtilities.isSameSequence(mcs, ics) ? null : mcs.toString()); + CharUtilities.isSameSequence(mcs, ics) ? null : mcs.toString(), associations); } /** @@ -311,6 +319,27 @@ public class GlyphMapping { areaIPD = areaIPD.plus(idp); } + public void reverse() { + if (mapping.length() > 0) { + mapping = new StringBuffer(mapping).reverse().toString(); + } + if (associations != null) { + Collections.reverse(associations); + } + if (gposAdjustments != null) { + reverse(gposAdjustments); + } + } + + 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; + } + } + public String toString() { return super.toString() + "{" + "interval = [" + startIndex + "," + endIndex + "]" diff --git a/src/java/org/apache/fop/fonts/LazyFont.java b/src/java/org/apache/fop/fonts/LazyFont.java index 1877a895e..a392ae6f4 100644 --- a/src/java/org/apache/fop/fonts/LazyFont.java +++ b/src/java/org/apache/fop/fonts/LazyFont.java @@ -22,6 +22,7 @@ import java.awt.Rectangle; import java.io.IOException; import java.io.InputStream; import java.net.URI; +import java.util.List; import java.util.Map; import java.util.Set; @@ -402,10 +403,10 @@ public class LazyFont extends Typeface implements FontDescriptor, Substitutable, /** * {@inheritDoc} */ - public CharSequence performSubstitution(CharSequence cs, String script, String language) { + public CharSequence performSubstitution(CharSequence cs, String script, String language, List associations) { load(true); if (realFontDescriptor instanceof Substitutable) { - return ((Substitutable)realFontDescriptor).performSubstitution(cs, script, language); + return ((Substitutable)realFontDescriptor).performSubstitution(cs, script, language, associations); } else { return cs; } @@ -415,13 +416,13 @@ public class LazyFont extends Typeface implements FontDescriptor, Substitutable, * {@inheritDoc} */ public CharSequence reorderCombiningMarks( - CharSequence cs, int[][] gpa, String script, String language) { + CharSequence cs, int[][] gpa, String script, String language, List associations) { if (!isMetricsLoaded) { load(true); } if (realFontDescriptor instanceof Substitutable) { return ((Substitutable)realFontDescriptor) - .reorderCombiningMarks(cs, gpa, script, language); + .reorderCombiningMarks(cs, gpa, script, language, associations); } else { return cs; } diff --git a/src/java/org/apache/fop/fonts/MultiByteFont.java b/src/java/org/apache/fop/fonts/MultiByteFont.java index 84dc429dc..718a14fb2 100644 --- a/src/java/org/apache/fop/fonts/MultiByteFont.java +++ b/src/java/org/apache/fop/fonts/MultiByteFont.java @@ -25,6 +25,7 @@ import java.nio.CharBuffer; import java.nio.IntBuffer; import java.util.BitSet; import java.util.LinkedHashMap; +import java.util.List; import java.util.Map; import org.apache.commons.logging.Log; @@ -487,10 +488,14 @@ public class MultiByteFont extends CIDFont implements Substitutable, Positionabl } /** {@inheritDoc} */ - public CharSequence performSubstitution(CharSequence cs, String script, String language) { + public CharSequence performSubstitution(CharSequence cs, String script, String language, List associations) { if (gsub != null) { - GlyphSequence igs = mapCharsToGlyphs(cs); + GlyphSequence igs = mapCharsToGlyphs(cs, associations); GlyphSequence ogs = gsub.substitute(igs, script, language); + if (associations != null) { + associations.clear(); + associations.addAll(ogs.getAssociations()); + } CharSequence ocs = mapGlyphsToChars(ogs); return ocs; } else { @@ -500,10 +505,14 @@ public class MultiByteFont extends CIDFont implements Substitutable, Positionabl /** {@inheritDoc} */ public CharSequence reorderCombiningMarks( - CharSequence cs, int[][] gpa, String script, String language) { + CharSequence cs, int[][] gpa, String script, String language, List associations) { if (gdef != null) { - GlyphSequence igs = mapCharsToGlyphs(cs); + GlyphSequence igs = mapCharsToGlyphs(cs, associations); GlyphSequence ogs = gdef.reorderCombiningMarks(igs, gpa, script, language); + if (associations != null) { + associations.clear(); + associations.addAll(ogs.getAssociations()); + } CharSequence ocs = mapGlyphsToChars(ogs); return ocs; } else { @@ -520,7 +529,7 @@ public class MultiByteFont extends CIDFont implements Substitutable, Positionabl public int[][] performPositioning(CharSequence cs, String script, String language, int fontSize) { if (gpos != null) { - GlyphSequence gs = mapCharsToGlyphs(cs); + GlyphSequence gs = mapCharsToGlyphs(cs, null); int[][] adjustments = new int [ gs.getGlyphCount() ] [ 4 ]; if (gpos.position(gs, script, language, fontSize, this.width, adjustments)) { return scaleAdjustments(adjustments, fontSize); @@ -559,7 +568,7 @@ public class MultiByteFont extends CIDFont implements Substitutable, Positionabl * @param cs a CharSequence containing UTF-16 encoded Unicode characters * @returns a CharSequence containing glyph indices */ - private GlyphSequence mapCharsToGlyphs(CharSequence cs) { + private GlyphSequence mapCharsToGlyphs(CharSequence cs, List associations) { IntBuffer cb = IntBuffer.allocate(cs.length()); IntBuffer gb = IntBuffer.allocate(cs.length()); int gi; @@ -598,7 +607,12 @@ public class MultiByteFont extends CIDFont implements Substitutable, Positionabl } cb.flip(); gb.flip(); - return new GlyphSequence(cb, gb, null); + if ((associations != null) && (associations.size() == cs.length())) { + associations = new java.util.ArrayList(associations); + } else { + associations = null; + } + return new GlyphSequence(cb, gb, associations); } /** diff --git a/src/java/org/apache/fop/layoutmgr/inline/TextLayoutManager.java b/src/java/org/apache/fop/layoutmgr/inline/TextLayoutManager.java index 9d12348ad..24c1fcbbb 100644 --- a/src/java/org/apache/fop/layoutmgr/inline/TextLayoutManager.java +++ b/src/java/org/apache/fop/layoutmgr/inline/TextLayoutManager.java @@ -933,7 +933,8 @@ public class TextLayoutManager extends LeafNodeLayoutManager { char precedingChar = prevMapping != null && !prevMapping.isSpace && prevMapping.endIndex > 0 ? foText.charAt(prevMapping.endIndex - 1) : 0; GlyphMapping mapping = GlyphMapping.doGlyphMapping(foText, thisStart, lastIndex, font, - letterSpaceIPD, letterSpaceAdjustArray, precedingChar, breakOpportunityChar, endsWithHyphen, level); + letterSpaceIPD, letterSpaceAdjustArray, precedingChar, breakOpportunityChar, + endsWithHyphen, level, false); prevMapping = mapping; addGlyphMapping(mapping); tempStart = nextStart; diff --git a/src/java/org/apache/fop/render/java2d/CustomFontMetricsMapper.java b/src/java/org/apache/fop/render/java2d/CustomFontMetricsMapper.java index 5e9372a75..8a5f4b117 100644 --- a/src/java/org/apache/fop/render/java2d/CustomFontMetricsMapper.java +++ b/src/java/org/apache/fop/render/java2d/CustomFontMetricsMapper.java @@ -24,6 +24,7 @@ import java.awt.FontFormatException; import java.awt.Rectangle; import java.io.IOException; import java.io.InputStream; +import java.util.List; import java.util.Map; import java.util.Set; @@ -261,9 +262,9 @@ public class CustomFontMetricsMapper extends Typeface implements FontMetricsMapp /** * {@inheritDoc} */ - public CharSequence performSubstitution(CharSequence cs, String script, String language) { + public CharSequence performSubstitution(CharSequence cs, String script, String language, List associations) { if (typeface instanceof Substitutable) { - return ((Substitutable) typeface).performSubstitution(cs, script, language); + return ((Substitutable) typeface).performSubstitution(cs, script, language, associations); } else { return cs; } @@ -272,9 +273,10 @@ public class CustomFontMetricsMapper extends Typeface implements FontMetricsMapp /** * {@inheritDoc} */ - public CharSequence reorderCombiningMarks(CharSequence cs, int[][] gpa, String script, String language) { + public CharSequence reorderCombiningMarks(CharSequence cs, int[][] gpa, + String script, String language, List associations) { if (typeface instanceof Substitutable) { - return ((Substitutable) typeface).reorderCombiningMarks(cs, gpa, script, language); + return ((Substitutable) typeface).reorderCombiningMarks(cs, gpa, script, language, associations); } else { return cs; } diff --git a/src/java/org/apache/fop/svg/NativeTextPainter.java b/src/java/org/apache/fop/svg/NativeTextPainter.java index 200f6558b..b5c7d7882 100644 --- a/src/java/org/apache/fop/svg/NativeTextPainter.java +++ b/src/java/org/apache/fop/svg/NativeTextPainter.java @@ -23,6 +23,7 @@ import java.awt.BasicStroke; import java.awt.Color; import java.awt.Graphics2D; import java.awt.Shape; +import java.awt.font.FontRenderContext; import java.awt.geom.AffineTransform; import java.awt.geom.Ellipse2D; import java.awt.geom.GeneralPath; @@ -38,6 +39,8 @@ import org.apache.batik.bridge.SVGGVTFont; import org.apache.batik.gvt.font.FontFamilyResolver; import org.apache.batik.gvt.font.GVTGlyphVector; import org.apache.batik.gvt.renderer.StrokingTextPainter; +import org.apache.batik.gvt.text.GlyphLayout; +import org.apache.batik.gvt.text.TextLayoutFactory; import org.apache.batik.gvt.text.TextPaintInfo; import org.apache.batik.gvt.text.TextSpanLayout; @@ -45,6 +48,7 @@ import org.apache.fop.fonts.Font; import org.apache.fop.fonts.FontInfo; import org.apache.fop.svg.font.FOPFontFamilyResolverImpl; import org.apache.fop.svg.font.FOPGVTFont; +import org.apache.fop.svg.text.ComplexGlyphLayout; import org.apache.fop.util.CharUtilities; /** @@ -273,4 +277,21 @@ public abstract class NativeTextPainter extends StrokingTextPainter { return this.fontFamilyResolver; } + private static final TextLayoutFactory COMPLEX_SCRIPT_TEXT_LAYOUT_FACTORY = + new TextLayoutFactory() { + public TextSpanLayout createTextLayout(AttributedCharacterIterator aci, + int [] charMap, Point2D offset, FontRenderContext frc) { + if (ComplexGlyphLayout.mayRequireComplexLayout(aci)) { + return new ComplexGlyphLayout(aci, charMap, offset, frc); + } else { + return new GlyphLayout(aci, charMap, offset, frc); + } + } + }; + + @Override + protected TextLayoutFactory getTextLayoutFactory() { + return COMPLEX_SCRIPT_TEXT_LAYOUT_FACTORY; + } + } diff --git a/src/java/org/apache/fop/svg/PDFTextPainter.java b/src/java/org/apache/fop/svg/PDFTextPainter.java index c5fa9f04e..c3ff744d5 100644 --- a/src/java/org/apache/fop/svg/PDFTextPainter.java +++ b/src/java/org/apache/fop/svg/PDFTextPainter.java @@ -26,7 +26,10 @@ import java.awt.Shape; import java.awt.Stroke; import java.awt.geom.AffineTransform; import java.awt.geom.Point2D; +import java.text.AttributedCharacterIterator; +import java.util.List; +import org.apache.batik.gvt.TextNode; import org.apache.batik.gvt.text.TextPaintInfo; import org.apache.fop.fonts.FontInfo; @@ -182,4 +185,12 @@ class PDFTextPainter extends NativeTextPainter { textUtil.writeTJMappedChar(glyph); } + @Override + public List computeTextRuns(TextNode node, + AttributedCharacterIterator nodeACI, + AttributedCharacterIterator [] chunkACIs) { + // skip Batik's bidi reordering and use identity character index maps + return super.computeTextRuns(node, nodeACI, chunkACIs, (int[][]) null); + } + } diff --git a/src/java/org/apache/fop/svg/font/ComplexGlyphVector.java b/src/java/org/apache/fop/svg/font/ComplexGlyphVector.java new file mode 100644 index 000000000..55f2eb3f2 --- /dev/null +++ b/src/java/org/apache/fop/svg/font/ComplexGlyphVector.java @@ -0,0 +1,56 @@ +/* + * 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.svg.font; + +import java.awt.font.FontRenderContext; +import java.text.AttributedCharacterIterator; +import java.text.CharacterIterator; + +import org.apache.batik.gvt.text.GVTAttributedCharacterIterator; + +import org.apache.fop.fonts.GlyphMapping; + +class ComplexGlyphVector extends FOPGVTGlyphVector { + + public static final AttributedCharacterIterator.Attribute WRITING_MODE + = GVTAttributedCharacterIterator.TextAttribute.WRITING_MODE; + + public static final Integer WRITING_MODE_RTL + = GVTAttributedCharacterIterator.TextAttribute.WRITING_MODE_RTL; + + ComplexGlyphVector(FOPGVTFont font, final CharacterIterator iter, FontRenderContext frc) { + super(font, iter, frc); + } + + public void performDefaultLayout() { + super.performDefaultLayout(); + } + + protected void maybeReverse(GlyphMapping mapping) { + if (charIter instanceof AttributedCharacterIterator) { + AttributedCharacterIterator aci = (AttributedCharacterIterator) charIter; + aci.first(); + if (aci.getAttribute(WRITING_MODE) == WRITING_MODE_RTL) { + mapping.reverse(); + } + } + } + +} diff --git a/src/java/org/apache/fop/svg/font/FOPGVTFont.java b/src/java/org/apache/fop/svg/font/FOPGVTFont.java index c55e2fa8b..76f77d367 100644 --- a/src/java/org/apache/fop/svg/font/FOPGVTFont.java +++ b/src/java/org/apache/fop/svg/font/FOPGVTFont.java @@ -20,6 +20,7 @@ package org.apache.fop.svg.font; import java.awt.font.FontRenderContext; +import java.text.AttributedString; import java.text.CharacterIterator; import java.text.StringCharacterIterator; @@ -27,6 +28,7 @@ import org.apache.batik.gvt.font.GVTFont; import org.apache.batik.gvt.font.GVTFontFamily; import org.apache.batik.gvt.font.GVTGlyphVector; import org.apache.batik.gvt.font.GVTLineMetrics; +import org.apache.batik.gvt.text.GVTAttributedCharacterIterator; import org.apache.fop.fonts.Font; import org.apache.fop.fonts.FontMetrics; @@ -83,8 +85,11 @@ public class FOPGVTFont implements GVTFont { } public GVTGlyphVector createGlyphVector(FontRenderContext frc, CharacterIterator ci) { - // TODO Batik does manual glyph shaping for Arabic. Replace with complex scripts implementation - return new FOPGVTGlyphVector(this, ci, frc); + if (!font.performsSubstitution() && !font.performsPositioning()) { + return new FOPGVTGlyphVector(this, ci, frc); + } else { + return new ComplexGlyphVector(this, ci, frc); + } } public GVTGlyphVector createGlyphVector(FontRenderContext frc, @@ -93,11 +98,26 @@ public class FOPGVTFont implements GVTFont { throw new UnsupportedOperationException("Not implemented"); } - public GVTGlyphVector createGlyphVector(FontRenderContext frc, String str) { - StringCharacterIterator sci = new StringCharacterIterator(str); + public GVTGlyphVector createGlyphVector(FontRenderContext frc, String text) { + StringCharacterIterator sci = new StringCharacterIterator(text); return createGlyphVector(frc, sci); } + public GVTGlyphVector createGlyphVector(FontRenderContext frc, String text, String script, String language) { + if ((script != null) || (language != null)) { + AttributedString as = new AttributedString(text); + if (script != null) { + as.addAttribute(GVTAttributedCharacterIterator.TextAttribute.SCRIPT, script); + } + if (language != null) { + as.addAttribute(GVTAttributedCharacterIterator.TextAttribute.LANGUAGE, language); + } + return createGlyphVector(frc, as.getIterator()); + } else { + return createGlyphVector(frc, text); + } + } + public FOPGVTFont deriveFont(float size) { throw new UnsupportedOperationException("Not implemented"); } diff --git a/src/java/org/apache/fop/svg/font/FOPGVTGlyphVector.java b/src/java/org/apache/fop/svg/font/FOPGVTGlyphVector.java index 3567bb508..0abffc52c 100644 --- a/src/java/org/apache/fop/svg/font/FOPGVTGlyphVector.java +++ b/src/java/org/apache/fop/svg/font/FOPGVTGlyphVector.java @@ -33,11 +33,13 @@ import java.text.AttributedCharacterIterator; import java.text.CharacterIterator; import java.text.StringCharacterIterator; import java.util.Arrays; +import java.util.List; import org.apache.batik.gvt.font.GVTFont; import org.apache.batik.gvt.font.GVTGlyphMetrics; import org.apache.batik.gvt.font.GVTGlyphVector; import org.apache.batik.gvt.font.GVTLineMetrics; +import org.apache.batik.gvt.text.GVTAttributedCharacterIterator; import org.apache.fop.fonts.Font; import org.apache.fop.fonts.FontMetrics; @@ -47,7 +49,7 @@ import org.apache.fop.traits.MinOptMax; class FOPGVTGlyphVector implements GVTGlyphVector { - private final CharacterIterator charIter; + protected final CharacterIterator charIter; private final FOPGVTFont font; @@ -57,9 +59,11 @@ class FOPGVTGlyphVector implements GVTGlyphVector { private final FontRenderContext frc; - private int[] glyphs; + protected int[] glyphs; - private float[] positions; + protected List associations; + + protected float[] positions; private Rectangle2D[] boundingBoxes; @@ -86,20 +90,37 @@ class FOPGVTGlyphVector implements GVTGlyphVector { MinOptMax letterSpaceIPD = MinOptMax.ZERO; MinOptMax[] letterSpaceAdjustments = new MinOptMax[charIter.getEndIndex() - charIter.getBeginIndex()]; GlyphMapping mapping = GlyphMapping.doGlyphMapping(text, charIter.getBeginIndex(), charIter.getEndIndex(), - f, letterSpaceIPD, letterSpaceAdjustments, '\0', '\0', false, 0 /* TODO */); - glyphs = buildGlyphs(f, mapping.mapping != null ? new StringCharacterIterator(mapping.mapping) : charIter); - buildGlyphPositions(mapping, letterSpaceAdjustments); + f, letterSpaceIPD, letterSpaceAdjustments, '\0', '\0', false, 0, true); + maybeReverse(mapping); + CharacterIterator glyphAsCharIter = + mapping.mapping != null ? new StringCharacterIterator(mapping.mapping) : charIter; + this.glyphs = buildGlyphs(f, glyphAsCharIter); + this.associations = mapping.associations; + this.positions = buildGlyphPositions(glyphAsCharIter, mapping.gposAdjustments, letterSpaceAdjustments); this.glyphVisibilities = new boolean[this.glyphs.length]; Arrays.fill(glyphVisibilities, true); this.glyphTransforms = new AffineTransform[this.glyphs.length]; } + protected void maybeReverse(GlyphMapping mapping) { + } + private static class SVGTextFragment implements TextFragment { private final CharacterIterator charIter; + private String script; + + private String language; + SVGTextFragment(CharacterIterator charIter) { this.charIter = charIter; + if (charIter instanceof AttributedCharacterIterator) { + AttributedCharacterIterator aci = (AttributedCharacterIterator) charIter; + aci.first(); + this.script = (String) aci.getAttribute(GVTAttributedCharacterIterator.TextAttribute.SCRIPT); + this.language = (String) aci.getAttribute(GVTAttributedCharacterIterator.TextAttribute.LANGUAGE); + } } public CharSequence subSequence(int startIndex, int endIndex) { @@ -111,11 +132,19 @@ class FOPGVTGlyphVector implements GVTGlyphVector { } public String getScript() { - return "DFLT"; // TODO pass on script value from SVG + if (script != null) { + return script; + } else { + return "auto"; + } } public String getLanguage() { - return "dflt"; // TODO pass on language value from SVG + if (language != null) { + return language; + } else { + return "none"; + } } public char charAt(int index) { @@ -123,42 +152,69 @@ class FOPGVTGlyphVector implements GVTGlyphVector { } } - private int[] buildGlyphs(Font font, final CharacterIterator charIter) { - int[] glyphs = new int[charIter.getEndIndex() - charIter.getBeginIndex()]; + private int[] buildGlyphs(Font font, final CharacterIterator glyphAsCharIter) { + int[] glyphs = new int[glyphAsCharIter.getEndIndex() - glyphAsCharIter.getBeginIndex()]; int index = 0; - for (char c = charIter.first(); c != CharacterIterator.DONE; c = charIter.next()) { + for (char c = glyphAsCharIter.first(); c != CharacterIterator.DONE; c = glyphAsCharIter.next()) { glyphs[index] = font.mapChar(c); index++; } return glyphs; } - private void buildGlyphPositions(GlyphMapping ai, MinOptMax[] letterSpaceAdjustments) { - positions = new float[2 * glyphs.length + 2]; - if (ai.gposAdjustments != null) { - assert ai.gposAdjustments.length == glyphs.length; - for (int glyphIndex = 0; glyphIndex < glyphs.length; glyphIndex++) { - int n = 2 * glyphIndex; - if (ai.gposAdjustments[glyphIndex] != null) { - for (int p = 0; p < 4; p++) { - positions[n + p] += ai.gposAdjustments[glyphIndex][p] / 1000f; - } - } - positions[n + 2] += positions[n] + getGlyphWidth(glyphIndex); + private static final int[] PA_ZERO = new int[4]; + + /** + * Build glyph position array. + * @param glyphAsCharIter iterator for mapped glyphs as char codes (not glyph codes) + * @param dp optionally null glyph position adjustments array + * @param lsa optionally null letter space adjustments array + * @return array of floats that denote [X,Y] position pairs for each glyph including + * including an implied subsequent glyph; i.e., returned array contains one more pair + * than the numbers of glyphs, where the position denoted by this last pair represents + * the position after the last glyph has incurred advancement + */ + private float[] buildGlyphPositions(final CharacterIterator glyphAsCharIter, int[][] dp, MinOptMax[] lsa) { + int numGlyphs = glyphAsCharIter.getEndIndex() - glyphAsCharIter.getBeginIndex(); + float[] positions = new float[2 * (numGlyphs + 1)]; + float xc = 0f; + float yc = 0f; + if (dp != null) { + for (int i = 0; i < numGlyphs + 1; ++i) { + int[] pa = ((i >= dp.length) || (dp[i] == null)) ? PA_ZERO : dp[i]; + float xo = xc + ((float) pa[0]) / 1000f; + float yo = yc - ((float) pa[1]) / 1000f; + float xa = getGlyphWidth(i) + ((float) pa[2]) / 1000f; + float ya = ((float) pa[3]) / 1000f; + int k = 2 * i; + positions[k + 0] = xo; + positions[k + 1] = yo; + xc += xa; + yc += ya; } - } else { - for (int i = 0, n = 2; i < glyphs.length; i++, n += 2) { - int kern = i < glyphs.length - 1 && letterSpaceAdjustments[i + 1] != null - ? letterSpaceAdjustments[i + 1].getOpt() - : 0; - positions[n] = positions[n - 2] + getGlyphWidth(i) + kern / 1000f; - positions[n + 1] = 0; + } else if (lsa != null) { + for (int i = 0; i < numGlyphs + 1; ++i) { + MinOptMax sa = (((i + 1) >= lsa.length) || (lsa[i + 1] == null)) ? MinOptMax.ZERO : lsa[i + 1]; + float xo = xc; + float yo = yc; + float xa = getGlyphWidth(i) + sa.getOpt() / 1000f; + float ya = 0; + int k = 2 * i; + positions[k + 0] = xo; + positions[k + 1] = yo; + xc += xa; + yc += ya; } } + return positions; } private float getGlyphWidth(int index) { - return fontMetrics.getWidth(glyphs[index], fontSize) / 1000000f; + if (index < glyphs.length) { + return fontMetrics.getWidth(glyphs[index], fontSize) / 1000000f; + } else { + return 0f; + } } public GVTFont getFont() { diff --git a/src/java/org/apache/fop/svg/text/ComplexGlyphLayout.java b/src/java/org/apache/fop/svg/text/ComplexGlyphLayout.java new file mode 100644 index 000000000..06a7188ff --- /dev/null +++ b/src/java/org/apache/fop/svg/text/ComplexGlyphLayout.java @@ -0,0 +1,78 @@ +/* + * 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.svg.text; + +import java.awt.font.FontRenderContext; +import java.awt.geom.Point2D; +import java.text.AttributedCharacterIterator; + +import org.apache.batik.gvt.font.GVTFont; +import org.apache.batik.gvt.font.GVTGlyphVector; +import org.apache.batik.gvt.text.GVTAttributedCharacterIterator; +import org.apache.batik.gvt.text.GlyphLayout; + +import org.apache.fop.fonts.Font; +import org.apache.fop.svg.font.FOPGVTFont; + +public class ComplexGlyphLayout extends GlyphLayout { + + public ComplexGlyphLayout(AttributedCharacterIterator aci, + int [] charMap, Point2D offset, FontRenderContext frc) { + super(aci, charMap, offset, frc); + } + + @Override + protected void doExplicitGlyphLayout() { + GVTGlyphVector gv = this.gv; + gv.performDefaultLayout(); + int ng = gv.getNumGlyphs(); + if (ng > 0) { + this.advance = gv.getGlyphPosition(ng); + } else { + this.advance = new Point2D.Float(0, 0); + } + this.layoutApplied = true; + } + + public static final boolean mayRequireComplexLayout(AttributedCharacterIterator aci) { + boolean rv = false; + GVTAttributedCharacterIterator.TextAttribute attrFont = GVTAttributedCharacterIterator.TextAttribute.GVT_FONT; + int indexSave = aci.getIndex(); + aci.first(); + do { + GVTFont gvtFont = (GVTFont) aci.getAttribute(attrFont); + if (gvtFont == null) { + continue; + } else { + if (gvtFont instanceof FOPGVTFont) { + Font f = ((FOPGVTFont) gvtFont).getFont(); + if (f.performsSubstitution() || f.performsPositioning()) { + rv = true; + break; + } + } + aci.setIndex(aci.getRunLimit(attrFont)); + } + } while (aci.next() != AttributedCharacterIterator.DONE); + aci.setIndex(indexSave); + return rv; + } + +} diff --git a/test/java/org/apache/fop/svg/font/GlyphLayoutTestCase.java b/test/java/org/apache/fop/svg/font/GlyphLayoutTestCase.java index 5c1fb2c86..04af70544 100644 --- a/test/java/org/apache/fop/svg/font/GlyphLayoutTestCase.java +++ b/test/java/org/apache/fop/svg/font/GlyphLayoutTestCase.java @@ -50,7 +50,7 @@ public class GlyphLayoutTestCase extends FOPGVTGlyphVectorTest { private void testGlyphLayout(boolean useAdvanced) { FOPGVTFont font = loadFont(useAdvanced); - glyphVector = (FOPGVTGlyphVector) font.createGlyphVector(null, "L\u201DP,V.F,A\u2019LT."); + glyphVector = (FOPGVTGlyphVector) font.createGlyphVector(null, "L\u201DP,V.F,A\u2019LT.", "DFLT", "dflt"); glyphVector.performDefaultLayout(); // Values in font units (unitsPerEm = 2048), glyph width - kern int[] widths = {