git-svn-id: https://svn.apache.org/repos/asf/xmlgraphics/fop/trunk@1615385 13f79535-47bb-0310-9956-ffa450edef68tags/fop-2_0
@@ -141,6 +141,16 @@ | |||
</And> | |||
</Or> | |||
</Match> | |||
<Match> | |||
<Bug pattern="UWF_FIELD_NOT_INITIALIZED_IN_CONSTRUCTOR"/> | |||
<And> | |||
<Class name="org.apache.fop.svg.font.FOPGVTGlyphVector"/> | |||
<Or> | |||
<Field name="glyphTransforms"/> | |||
<Field name="glyphVisibilities"/> | |||
</Or> | |||
</And> | |||
</Match> | |||
<Match> | |||
<Bug pattern="DM_DEFAULT_ENCODING"/> | |||
<Or> | |||
@@ -5463,7 +5473,7 @@ | |||
<Bug pattern="EI_EXPOSE_REP"/> | |||
</Match> | |||
<Match> | |||
<Class name="org.apache.fop.complexscripts.util.GlyphSequence$CharAssociation"/> | |||
<Class name="org.apache.fop.complexscripts.util.CharAssociation"/> | |||
<Method name="getSubIntervals"/> | |||
<Bug pattern="EI_EXPOSE_REP"/> | |||
</Match> |
@@ -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); | |||
} | |||
@@ -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; |
@@ -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); |
@@ -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); | |||
} |
@@ -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) { |
@@ -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; |
@@ -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 ])) { |
@@ -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 ])) { |
@@ -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 ])) { |
@@ -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<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); | |||
Vector<CharAssociation> nav = new Vector<CharAssociation>(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) { |
@@ -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 <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. | |||
* | |||
* <p>This work was originally authored by Glenn Adams (gadams@apache.org).</p> | |||
*/ | |||
public 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[] 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(); | |||
} | |||
} |
@@ -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 <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[] 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; | |||
} | |||
} | |||
} |
@@ -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(); | |||
} |
@@ -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 + "]" |
@@ -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; | |||
} |
@@ -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); | |||
} | |||
/** |
@@ -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; |
@@ -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; | |||
} |
@@ -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; | |||
} | |||
} |
@@ -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); | |||
} | |||
} |
@@ -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(); | |||
} | |||
} | |||
} | |||
} |
@@ -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"); | |||
} |
@@ -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() { |
@@ -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; | |||
} | |||
} |
@@ -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 = { |