git-svn-id: https://svn.apache.org/repos/asf/xmlgraphics/fop/trunk@1846994 13f79535-47bb-0310-9956-ffa450edef68tags/fop-2_4
if (ma != null) { | if (ma != null) { | ||||
for (int i = 0, n = ps.getPosition(); i < n; i++) { | for (int i = 0, n = ps.getPosition(); i < n; i++) { | ||||
int gi = ps.getGlyph(-(i + 1)); | int gi = ps.getGlyph(-(i + 1)); | ||||
if (ps.isMark(gi)) { | |||||
int unprocessedGlyph = ps.getUnprocessedGlyph(-(i + 1)); | |||||
if (ps.isMark(gi) && ps.isMark(unprocessedGlyph)) { | |||||
continue; | continue; | ||||
} else { | } else { | ||||
Anchor a = getBaseAnchor(gi, ma.getMarkClass()); | Anchor a = getBaseAnchor(gi, ma.getMarkClass()); | ||||
v.adjust(0, 0, -ps.getWidth(giMark), 0); | v.adjust(0, 0, -ps.getWidth(giMark), 0); | ||||
} | } | ||||
// end experimental fix for END OF AYAH in Lateef/Scheherazade | // end experimental fix for END OF AYAH in Lateef/Scheherazade | ||||
if (OTFScript.KHMER.equals(ps.script)) { | |||||
v.adjust(-ps.getWidth(gi), -v.yPlacement, 0, 0); | |||||
} | |||||
if (ps.adjust(v)) { | if (ps.adjust(v)) { | ||||
ps.setAdjusted(true); | ps.setAdjusted(true); | ||||
} | } | ||||
int mxc = getMaxComponentCount(); | int mxc = getMaxComponentCount(); | ||||
if (ma != null) { | if (ma != null) { | ||||
for (int i = 0, n = ps.getPosition(); i < n; i++) { | for (int i = 0, n = ps.getPosition(); i < n; i++) { | ||||
int gi = ps.getGlyph(-(i + 1)); | |||||
if (ps.isMark(gi)) { | |||||
int glyphIndex = ps.getUnprocessedGlyph(-(i + 1)); | |||||
if (ps.isMark(glyphIndex)) { | |||||
continue; | continue; | ||||
} else { | } else { | ||||
Anchor a = getLigatureAnchor(gi, mxc, i, ma.getMarkClass()); | |||||
if (a != null) { | |||||
if (ps.adjust(a.getAlignmentAdjustment(ma))) { | |||||
Anchor anchor = getLigatureAnchor(glyphIndex, mxc, i, ma.getMarkClass()); | |||||
if (anchor != null) { | |||||
if (ps.adjust(anchor.getAlignmentAdjustment(ma))) { | |||||
ps.setAdjusted(true); | ps.setAdjusted(true); | ||||
} | } | ||||
} | } | ||||
MarkAnchor ma = getMark1Anchor(ciMark1, giMark1); | MarkAnchor ma = getMark1Anchor(ciMark1, giMark1); | ||||
if (ma != null) { | if (ma != null) { | ||||
if (ps.hasPrev()) { | if (ps.hasPrev()) { | ||||
Anchor a = getMark2Anchor(ps.getGlyph(-1), ma.getMarkClass()); | |||||
if (a != null) { | |||||
if (ps.adjust(a.getAlignmentAdjustment(ma))) { | |||||
Anchor anchor = getMark2Anchor(ps.getUnprocessedGlyph(-1), ma.getMarkClass()); | |||||
if (anchor != null) { | |||||
if (ps.adjust(anchor.getAlignmentAdjustment(ma))) { | |||||
ps.setAdjusted(true); | ps.setAdjusted(true); | ||||
} | } | ||||
} | } |
} | } | ||||
} | } | ||||
public int getUnprocessedGlyph(int offset) throws IndexOutOfBoundsException { | |||||
int i = index + offset; | |||||
if ((i >= 0) && (i < indexLast)) { | |||||
return igs.getUnprocessedGlyph(i); | |||||
} else { | |||||
throw new IndexOutOfBoundsException("Attempting to process glyph at index " + i); | |||||
} | |||||
} | |||||
/** | /** | ||||
* Obtain glyph at current position. | * Obtain glyph at current position. | ||||
* @return glyph at current position | * @return glyph at current position |
import org.apache.fop.complexscripts.util.CharAssociation; | import org.apache.fop.complexscripts.util.CharAssociation; | ||||
import org.apache.fop.complexscripts.util.GlyphSequence; | import org.apache.fop.complexscripts.util.GlyphSequence; | ||||
import org.apache.fop.complexscripts.util.GlyphTester; | import org.apache.fop.complexscripts.util.GlyphTester; | ||||
import org.apache.fop.fonts.MultiByteFont; | |||||
// CSOFF: LineLengthCheck | // CSOFF: LineLengthCheck | ||||
return ogs; | return ogs; | ||||
} | } | ||||
public CharSequence preProcess(CharSequence charSequence, String script, MultiByteFont font, List associations) { | |||||
ScriptProcessor scriptProcessor = ScriptProcessor.getInstance(script, processors); | |||||
return scriptProcessor.preProcess(charSequence, font, associations); | |||||
} | |||||
/** | /** | ||||
* Map a lookup type name to its constant (integer) value. | * Map a lookup type name to its constant (integer) value. | ||||
* @param name lookup type name | * @param name lookup type name |
case CharScript.SCRIPT_TAMIL: | case CharScript.SCRIPT_TAMIL: | ||||
case CharScript.SCRIPT_TAMIL_2: | case CharScript.SCRIPT_TAMIL_2: | ||||
return new TamilScriptProcessor(script); | return new TamilScriptProcessor(script); | ||||
case CharScript.SCRIPT_KHMER: | |||||
return new KhmerScriptProcessor(script); | |||||
// [TBD] implement other script processors | // [TBD] implement other script processors | ||||
default: | default: | ||||
return new IndicScriptProcessor(script); | return new IndicScriptProcessor(script); | ||||
"rkrf", | "rkrf", | ||||
"rphf", | "rphf", | ||||
"vatu", | "vatu", | ||||
"ccmp" | |||||
}; | }; | ||||
static { | static { | ||||
basicShapingFeatures = new HashSet<String>(); | basicShapingFeatures = new HashSet<String>(); | ||||
"haln", | "haln", | ||||
"pres", | "pres", | ||||
"psts", | "psts", | ||||
"clig" | |||||
}; | }; | ||||
static { | static { | ||||
presentationFeatures = new HashSet<String>(); | presentationFeatures = new HashSet<String>(); |
/* | |||||
* Licensed to the Apache Software Foundation (ASF) under one or more | |||||
* contributor license agreements. See the NOTICE file distributed with | |||||
* this work for additional information regarding copyright ownership. | |||||
* The ASF licenses this file to You under the Apache License, Version 2.0 | |||||
* (the "License"); you may not use this file except in compliance with | |||||
* the License. You may obtain a copy of the License at | |||||
* | |||||
* http://www.apache.org/licenses/LICENSE-2.0 | |||||
* | |||||
* Unless required by applicable law or agreed to in writing, software | |||||
* distributed under the License is distributed on an "AS IS" BASIS, | |||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||||
* See the License for the specific language governing permissions and | |||||
* limitations under the License. | |||||
*/ | |||||
/* $Id$ */ | |||||
package org.apache.fop.complexscripts.scripts; | |||||
/** | |||||
* Integrating existing rendering of Android for Khmer Unicode to iText | |||||
* The class from the rendering of Mobile Project, Android from Nokor Group (AKA: Nokor-IT) | |||||
* The understanding also taking from the Khmum Browser that would lead to build this helper | |||||
* (Comment above by Pongsametrey S. <metrey@osify.com>) | |||||
* Thanks for Nokor Group & Mr. Pengleng HUOT | |||||
* | |||||
* author sok.pongsametrey | |||||
* @version 1.0 | |||||
*/ | |||||
/** | |||||
* UnicodeRender Class. | |||||
* author huot.pengleng | |||||
* | |||||
* simple classes, they are used in the state table (in this file) to control the length of a syllable | |||||
* they are also used to know where a character should be placed (location in reference to the base character) | |||||
* and also to know if a character, when independently displayed, should be displayed with a dotted-circle to | |||||
* indicate error in syllable construction | |||||
* Character class tables | |||||
* xx character does not combine into syllable, such as numbers, puntuation marks, non-Khmer signs... | |||||
* sa Sign placed above the base | |||||
* sp Sign placed after the base | |||||
* c1 Consonant of type 1 or independent vowel (independent vowels behave as type 1 consonants) | |||||
* c2 Consonant of type 2 (only RO) | |||||
* c3 Consonant of type 3 | |||||
* rb Khmer sign robat u17CC. combining mark for subscript consonants | |||||
* cd Consonant-shifter | |||||
* dl Dependent vowel placed before the base (left of the base) | |||||
* db Dependent vowel placed below the base | |||||
* da Dependent vowel placed above the base | |||||
* dr Dependent vowel placed behind the base (right of the base) | |||||
* co Khmer combining mark COENG u17D2, combines with the consonant or independent vowel following | |||||
* it to create a subscript consonant or independent vowel | |||||
* va Khmer split vowel in wich the first part is before the base and the second one above the base | |||||
* vr Khmer split vowel in wich the first part is before the base and the second one behind (right of) the base | |||||
* | |||||
*/ | |||||
public class KhmerRenderer { | |||||
private static final int XX = 0; | |||||
private static final int CC_COENG = 7; // Subscript consonant combining character | |||||
private static final int CC_CONSONANT = 1; // Consonant of type 1 or independent vowel | |||||
private static final int CC_CONSONANT_SHIFTER = 5; | |||||
private static final int CC_CONSONANT2 = 2; // Consonant of type 2 | |||||
private static final int CC_CONSONANT3 = 3; // Consonant of type 3 | |||||
private static final int CC_DEPENDENT_VOWEL = 8; | |||||
private static final int CC_ROBAT = 6; // Khmer special diacritic accent -treated differently in state table | |||||
private static final int CC_SIGN_ABOVE = 9; | |||||
private static final int CC_SIGN_AFTER = 10; | |||||
private static final int CF_ABOVE_VOWEL = 536870912; // flag to speed up comparing | |||||
private static final int CF_CLASS_MASK = 65535; | |||||
private static final int CF_COENG = 134217728; // flag to speed up comparing | |||||
private static final int CF_CONSONANT = 16777216; // flag to speed up comparing | |||||
private static final int CF_DOTTED_CIRCLE = 67108864; | |||||
// add a dotted circle if a character with this flag is the first in a syllable | |||||
private static final int CF_POS_ABOVE = 131072; | |||||
private static final int CF_POS_AFTER = 65536; | |||||
private static final int CF_POS_BEFORE = 524288; | |||||
private static final int CF_POS_BELOW = 262144; | |||||
private static final int CF_SHIFTER = 268435456; // flag to speed up comparing | |||||
private static final int CF_SPLIT_VOWEL = 33554432; | |||||
private static final int C1 = CC_CONSONANT + CF_CONSONANT; | |||||
private static final int C2 = CC_CONSONANT2 + CF_CONSONANT; | |||||
private static final int C3 = CC_CONSONANT3 + CF_CONSONANT; | |||||
private static final int CO = CC_COENG + CF_COENG + CF_DOTTED_CIRCLE; | |||||
private static final int CS = CC_CONSONANT_SHIFTER + CF_DOTTED_CIRCLE + CF_SHIFTER; | |||||
private static final int DA = CC_DEPENDENT_VOWEL + CF_POS_ABOVE + CF_DOTTED_CIRCLE + CF_ABOVE_VOWEL; | |||||
private static final int DB = CC_DEPENDENT_VOWEL + CF_POS_BELOW + CF_DOTTED_CIRCLE; | |||||
private static final int DL = CC_DEPENDENT_VOWEL + CF_POS_BEFORE + CF_DOTTED_CIRCLE; | |||||
private static final int DR = CC_DEPENDENT_VOWEL + CF_POS_AFTER + CF_DOTTED_CIRCLE; | |||||
private static final int RB = CC_ROBAT + CF_POS_ABOVE + CF_DOTTED_CIRCLE; | |||||
private static final int SA = CC_SIGN_ABOVE + CF_DOTTED_CIRCLE + CF_POS_ABOVE; | |||||
private static final int SP = CC_SIGN_AFTER + CF_DOTTED_CIRCLE + CF_POS_AFTER; | |||||
private static final int VA = DA + CF_SPLIT_VOWEL; | |||||
private static final int VR = DR + CF_SPLIT_VOWEL; | |||||
// flag for a split vowel -> the first part is added in front of the syllable | |||||
private static final char BA = '\u1794'; | |||||
private static final char COENG = '\u17D2'; | |||||
private static final String CONYO = Character.toString('\u17D2').concat(Character.toString('\u1789')); | |||||
private static final String CORO = Character.toString('\u17D2').concat(Character.toString('\u179A')); | |||||
private int[] khmerCharClasses = new int[] { | |||||
C1, C1, C1, C3, C1, C1, C1, C1, C3, C1, C1, C1, C1, C3, C1, C1, C1, C1, C1, C1, C3, | |||||
C1, C1, C1, C1, C3, C2, C1, C1, C1, C3, C3, C1, C3, C1, C1, C1, C1, C1, C1, C1, C1, | |||||
C1, C1, C1, C1, C1, C1, C1, C1, C1, C1, DR, DR, DR, DA, DA, DA, DA, DB, DB, DB, VA, | |||||
VR, VR, DL, DL, DL, VR, VR, SA, SP, SP, CS, CS, SA, RB, SA, SA, SA, SA, SA, CO, SA, | |||||
XX, XX, XX, XX, XX, XX, XX, XX, XX, SA, XX, XX | |||||
}; | |||||
private short[][] khmerStateTable = new short[][] { | |||||
{ | |||||
1, 2, 2, 2, 1, 1, 1, 6, 1, 1, 1, 2 | |||||
}, { | |||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 | |||||
}, { | |||||
-1, -1, -1, -1, 3, 4, 5, 6, 16, 17, 1, -1 | |||||
}, { | |||||
-1, -1, -1, -1, -1, 4, -1, -1, 16, -1, -1, -1 | |||||
}, { | |||||
-1, -1, -1, -1, 15, -1, -1, 6, 16, 17, 1, 14 | |||||
}, { | |||||
-1, -1, -1, -1, -1, -1, -1, -1, 20, -1, 1, -1 | |||||
}, { | |||||
-1, 7, 8, 9, -1, -1, -1, -1, -1, -1, -1, -1 | |||||
}, { | |||||
-1, -1, -1, -1, 12, 13, -1, 10, 16, 17, 1, 14 | |||||
}, { | |||||
-1, -1, -1, -1, 12, 13, -1, -1, 16, 17, 1, 14 | |||||
}, { | |||||
-1, -1, -1, -1, 12, 13, -1, 10, 16, 17, 1, 14 | |||||
}, { | |||||
-1, 11, 11, 11, -1, -1, -1, -1, -1, -1, -1, -1 | |||||
}, { | |||||
-1, -1, -1, -1, 15, -1, -1, -1, 16, 17, 1, 14 | |||||
}, { | |||||
-1, -1, -1, -1, -1, 13, -1, -1, 16, -1, -1, -1 | |||||
}, { | |||||
-1, -1, -1, -1, 15, -1, -1, -1, 16, 17, 1, 14 | |||||
}, { | |||||
-1, -1, -1, -1, -1, -1, -1, -1, 16, -1, -1, -1 | |||||
}, { | |||||
-1, -1, -1, -1, -1, -1, -1, -1, 16, -1, -1, -1 | |||||
}, { | |||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, 17, 1, 18 | |||||
}, { | |||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 1, 18 | |||||
}, { | |||||
-1, -1, -1, -1, -1, -1, -1, 19, -1, -1, -1, -1 | |||||
}, { | |||||
-1, 1, -1, 1, -1, -1, -1, -1, -1, -1, -1, -1 | |||||
}, { | |||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 1, -1 | |||||
} | |||||
}; | |||||
private static final char MARK = '\u17EA'; | |||||
private static final char NYO = '\u1789'; | |||||
private static final char SA_C = '\u179F'; | |||||
private static final char SRAAA = '\u17B6'; | |||||
private static final char SRAAU = '\u17C5'; | |||||
private static final char SRAE = '\u17C1'; | |||||
private static final char SRAIE = '\u17C0'; | |||||
private static final char SRAII = '\u17B8'; | |||||
private static final char SRAOE = '\u17BE'; | |||||
private static final char SRAOO = '\u17C4'; | |||||
private static final char SRAU = '\u17BB'; | |||||
private static final char SRAYA = '\u17BF'; | |||||
private static final char TRIISAP = '\u17CA'; | |||||
private static final char YO = '\u1799'; | |||||
private char strEcombining(final char chrInput) { | |||||
char retChar = ' '; | |||||
if (chrInput == SRAOE) { | |||||
retChar = SRAII; | |||||
} else if (chrInput == SRAYA) { | |||||
retChar = SRAYA; | |||||
} else if (chrInput == SRAIE) { | |||||
retChar = SRAIE; | |||||
} else if (chrInput == SRAOO) { | |||||
retChar = SRAAA; | |||||
} else if (chrInput == SRAAU) { | |||||
retChar = SRAAU; | |||||
} | |||||
return retChar; | |||||
} | |||||
// Gets the charactor class. | |||||
private int getCharClass(final char uniChar) { | |||||
int retValue = 0; | |||||
int ch; | |||||
ch = uniChar; | |||||
if (ch > 255) { | |||||
if (ch >= '\u1780') { | |||||
ch -= '\u1780'; | |||||
if (ch < khmerCharClasses.length) { | |||||
retValue = khmerCharClasses[ch]; | |||||
} | |||||
} | |||||
} | |||||
return retValue; | |||||
} | |||||
/** | |||||
* Re-order Khmer unicode for display with Khmer.ttf file on Android. | |||||
* @param strInput Khmer unicode string. | |||||
* @return String after render. | |||||
*/ | |||||
public String render(final String strInput) { | |||||
//Given an input String of unicode cluster to reorder. | |||||
//The return is the visual based cluster (legacy style) String. | |||||
int cursor = 0; | |||||
short state = 0; | |||||
int charCount = strInput.length(); | |||||
StringBuilder result = new StringBuilder(); | |||||
while (cursor < charCount) { | |||||
String reserved = ""; | |||||
String signAbove = ""; | |||||
String signAfter = ""; | |||||
String base = ""; | |||||
String robat = ""; | |||||
String shifter = ""; | |||||
String vowelBefore = ""; | |||||
String vowelBelow = ""; | |||||
String vowelAbove = ""; | |||||
String vowelAfter = ""; | |||||
boolean coeng = false; | |||||
String cluster; | |||||
String coeng1 = ""; | |||||
String coeng2 = ""; | |||||
boolean shifterAfterCoeng = false; | |||||
while (cursor < charCount) { | |||||
char curChar = strInput.charAt(cursor); | |||||
int kChar = getCharClass(curChar); | |||||
int charClass = kChar & CF_CLASS_MASK; | |||||
try { | |||||
state = khmerStateTable[state][charClass]; | |||||
} catch (Exception ex) { | |||||
state = -1; | |||||
} | |||||
if (state < 0) { | |||||
break; | |||||
} | |||||
//collect variable for cluster here | |||||
if (kChar == XX) { | |||||
reserved = Character.toString(curChar); | |||||
} else if (kChar == SA) { //Sign placed above the base | |||||
signAbove = Character.toString(curChar); | |||||
} else if (kChar == SP) { //Sign placed after the base | |||||
signAfter = Character.toString(curChar); | |||||
} else if (kChar == C1 || kChar == C2 || kChar == C3) { //Consonant | |||||
if (coeng) { | |||||
if ("".equalsIgnoreCase(coeng1)) { | |||||
coeng1 = Character.toString(COENG).concat(Character.toString(curChar)); | |||||
} else { | |||||
coeng2 = Character.toString(COENG).concat(Character.toString(curChar)); | |||||
} | |||||
coeng = false; | |||||
} else { | |||||
base = Character.toString(curChar); | |||||
} | |||||
} else if (kChar == RB) { //Khmer sign robat u17CC | |||||
robat = Character.toString(curChar); | |||||
} else if (kChar == CS) { //Consonant-shifter | |||||
if (!"".equalsIgnoreCase(coeng1)) { | |||||
shifterAfterCoeng = true; | |||||
} | |||||
shifter = Character.toString(curChar); | |||||
} else if (kChar == DL) { //Dependent vowel placed before the base | |||||
vowelBefore = Character.toString(curChar); | |||||
} else if (kChar == DB) { //Dependent vowel placed below the base | |||||
vowelBelow = Character.toString(curChar); | |||||
} else if (kChar == DA) { //Dependent vowel placed above the base | |||||
vowelAbove = Character.toString(curChar); | |||||
} else if (kChar == DR) { //Dependent vowel placed behind the base | |||||
vowelAfter = Character.toString(curChar); | |||||
} else if (kChar == CO) { //Khmer combining mark COENG | |||||
coeng = true; | |||||
} else if (kChar == VA) { //Khmer split vowel, see da | |||||
vowelBefore = Character.toString(SRAE); | |||||
vowelAbove = Character.toString(strEcombining(curChar)); | |||||
} else if (kChar == VR) { //Khmer split vowel, see dr | |||||
vowelBefore = Character.toString(SRAE); | |||||
vowelAfter = Character.toString(strEcombining(curChar)); | |||||
} | |||||
cursor += 1; | |||||
} | |||||
// end of while (a cluster has found) | |||||
// logic when cluster has coeng | |||||
// should coeng be located on left side | |||||
String coengBefore = ""; | |||||
if (CORO.equalsIgnoreCase(coeng1)) { | |||||
coengBefore = coeng1; | |||||
coeng1 = ""; | |||||
} else if (CORO.equalsIgnoreCase(coeng2)) { | |||||
coengBefore = coeng2; | |||||
coeng2 = ""; | |||||
} | |||||
//logic of shifter with base character | |||||
if (!"".equalsIgnoreCase(base) && !"".equalsIgnoreCase(shifter)) { | |||||
if (!"".equalsIgnoreCase(vowelAbove)) { | |||||
shifter = ""; | |||||
vowelBelow = Character.toString(SRAU); | |||||
} | |||||
} | |||||
// uncomplete coeng | |||||
if (coeng && "".equalsIgnoreCase(coeng1)) { | |||||
coeng1 = Character.toString(COENG); | |||||
} else if (coeng && "".equalsIgnoreCase(coeng2)) { | |||||
coeng2 = Character.toString(MARK).concat(Character.toString(COENG)); | |||||
} | |||||
//place of shifter | |||||
String shifter1 = ""; | |||||
String shifter2 = ""; | |||||
if (shifterAfterCoeng) { | |||||
shifter2 = shifter; | |||||
} else { | |||||
shifter1 = shifter; | |||||
} | |||||
boolean specialCaseBA = false; | |||||
String strMARKSRAAA = Character.toString(MARK).concat(Character.toString(SRAAA)); | |||||
String strMARKSRAAU = Character.toString(MARK).concat(Character.toString(SRAAU)); | |||||
if (Character.toString(BA).equalsIgnoreCase(base) | |||||
&& (Character.toString(SRAAA).equalsIgnoreCase(vowelAfter) | |||||
|| Character.toString(SRAAU).equalsIgnoreCase(vowelAfter) | |||||
|| strMARKSRAAA.equalsIgnoreCase(vowelAfter) || strMARKSRAAU.equalsIgnoreCase(vowelAfter))) { | |||||
specialCaseBA = true; | |||||
if (!"".equalsIgnoreCase(coeng1)) { | |||||
String coeng1Complete = coeng1.substring(0, coeng1.length() - 1); | |||||
if (Character.toString(BA).equalsIgnoreCase(coeng1Complete) | |||||
|| Character.toString(YO).equalsIgnoreCase(coeng1Complete) | |||||
|| Character.toString(SA_C).equalsIgnoreCase(coeng1Complete)) { | |||||
specialCaseBA = false; | |||||
} | |||||
} | |||||
} | |||||
// cluster formation | |||||
if (specialCaseBA) { | |||||
cluster = vowelBefore + coengBefore + base + vowelAfter + robat + shifter1 + coeng1 + coeng2 | |||||
+ shifter2 + vowelBelow + vowelAbove + signAbove + signAfter; | |||||
} else { | |||||
cluster = vowelBefore + coengBefore + base + robat + shifter1 + coeng1 + coeng2 + shifter2 | |||||
+ vowelBelow + vowelAbove + vowelAfter + signAbove + signAfter; | |||||
} | |||||
result.append(cluster + reserved); | |||||
state = 0; | |||||
//end of while | |||||
} | |||||
return result.toString(); | |||||
} | |||||
} |
/* | |||||
* Licensed to the Apache Software Foundation (ASF) under one or more | |||||
* contributor license agreements. See the NOTICE file distributed with | |||||
* this work for additional information regarding copyright ownership. | |||||
* The ASF licenses this file to You under the Apache License, Version 2.0 | |||||
* (the "License"); you may not use this file except in compliance with | |||||
* the License. You may obtain a copy of the License at | |||||
* | |||||
* http://www.apache.org/licenses/LICENSE-2.0 | |||||
* | |||||
* Unless required by applicable law or agreed to in writing, software | |||||
* distributed under the License is distributed on an "AS IS" BASIS, | |||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||||
* See the License for the specific language governing permissions and | |||||
* limitations under the License. | |||||
*/ | |||||
/* $Id$ */ | |||||
package org.apache.fop.complexscripts.scripts; | |||||
import java.util.List; | |||||
import org.apache.fop.complexscripts.fonts.GlyphDefinitionTable; | |||||
import org.apache.fop.complexscripts.fonts.GlyphTable; | |||||
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; | |||||
import org.apache.fop.fonts.MultiByteFont; | |||||
/** | |||||
* <p>The <code>KhmerScriptProcessor</code> class implements a script processor for | |||||
* performing glyph substitution and positioning operations on content associated with the Khmer script.</p> | |||||
*/ | |||||
public class KhmerScriptProcessor extends IndicScriptProcessor { | |||||
private GlyphSequence unprocessedGS; | |||||
private List associations; | |||||
private int[] chars; | |||||
KhmerScriptProcessor(String script) { | |||||
super(script); | |||||
} | |||||
protected Class<? extends IndicScriptProcessor.DefaultSyllabizer> getSyllabizerClass() { | |||||
return KhmerSyllabizer.class; | |||||
} | |||||
private static class KhmerSyllabizer extends DefaultSyllabizer { | |||||
KhmerSyllabizer(String script, String language) { | |||||
super(script, language); | |||||
} | |||||
} | |||||
@Override | |||||
public GlyphSequence reorderCombiningMarks(GlyphDefinitionTable gdef, GlyphSequence glyphSequence, | |||||
int[] unscaledWidths, int[][] glyphPositionAdjustments, String script, | |||||
String language) { | |||||
return glyphSequence; | |||||
} | |||||
public CharSequence preProcess(CharSequence charSequence, MultiByteFont font, List associations) { | |||||
unprocessedGS = font.charSequenceToGlyphSequence(charSequence, associations); | |||||
return new KhmerRenderer().render(charSequence.toString()); | |||||
} | |||||
public boolean position(GlyphSequence glyphSequence, String script, String language, int fontSize, | |||||
GlyphTable.UseSpec[] useSpecs, int[] widths, int[][] adjustments, | |||||
ScriptContextTester scriptContextTester) { | |||||
glyphSequence.setUnprocessedGS(unprocessedGS); | |||||
return super.position(glyphSequence, script, language, fontSize, useSpecs, widths, adjustments, | |||||
scriptContextTester); | |||||
} | |||||
public GlyphSequence substitute(GlyphSequence glyphSequence, String script, String language, | |||||
GlyphTable.UseSpec[] useSpecs, ScriptContextTester scriptContextTester) { | |||||
glyphSequence = super.substitute(glyphSequence, script, language, useSpecs, scriptContextTester); | |||||
associations = glyphSequence.getAssociations(); | |||||
chars = glyphSequence.getCharacters().array(); | |||||
return glyphSequence; | |||||
} | |||||
private ScriptContextTester scriptContextTester = new ScriptContextTester() { | |||||
private GlyphContextTester tester = new GlyphContextTester() { | |||||
public boolean test(String script, String language, String feature, GlyphSequence glyphSequence, int index, | |||||
int flags) { | |||||
CharAssociation charAssociation = (CharAssociation) associations.get(index); | |||||
char vowelSignU = '\u17BB'; | |||||
for (int i = charAssociation.getStart(); i < charAssociation.getEnd(); i++) { | |||||
if (chars[i] == vowelSignU) { | |||||
return false; | |||||
} | |||||
} | |||||
return true; | |||||
} | |||||
}; | |||||
public GlyphContextTester getTester(String feature) { | |||||
return tester; | |||||
} | |||||
}; | |||||
public ScriptContextTester getPositioningContextTester() { | |||||
return scriptContextTester; | |||||
} | |||||
} |
import org.apache.fop.complexscripts.util.CharScript; | import org.apache.fop.complexscripts.util.CharScript; | ||||
import org.apache.fop.complexscripts.util.GlyphSequence; | import org.apache.fop.complexscripts.util.GlyphSequence; | ||||
import org.apache.fop.complexscripts.util.ScriptContextTester; | import org.apache.fop.complexscripts.util.ScriptContextTester; | ||||
import org.apache.fop.fonts.MultiByteFont; | |||||
// CSOFF: LineLengthCheck | // CSOFF: LineLengthCheck | ||||
} | } | ||||
public CharSequence preProcess(CharSequence charSequence, MultiByteFont font, List associations) { | |||||
return charSequence; | |||||
} | |||||
} | } |
case SCRIPT_TAMIL_2: | case SCRIPT_TAMIL_2: | ||||
case SCRIPT_TELUGU: | case SCRIPT_TELUGU: | ||||
case SCRIPT_TELUGU_2: | case SCRIPT_TELUGU_2: | ||||
case SCRIPT_KHMER: | |||||
return true; | return true; | ||||
default: | default: | ||||
return false; | return false; |
/** predications flag */ | /** predications flag */ | ||||
private boolean predications; | private boolean predications; | ||||
protected GlyphSequence unprocessedGS; | |||||
/** | /** | ||||
* Instantiate a glyph sequence, reusing (i.e., not copying) the referenced | * Instantiate a glyph sequence, reusing (i.e., not copying) the referenced | ||||
* character and glyph buffers and associations. If characters is null, then | * character and glyph buffers and associations. If characters is null, then | ||||
this.glyphs = glyphs; | this.glyphs = glyphs; | ||||
this.associations = associations; | this.associations = associations; | ||||
this.predications = predications; | this.predications = predications; | ||||
unprocessedGS = this; | |||||
} | } | ||||
/** | /** | ||||
*/ | */ | ||||
public GlyphSequence(GlyphSequence gs) { | public GlyphSequence(GlyphSequence gs) { | ||||
this (gs.characters.duplicate(), copyBuffer(gs.glyphs), copyAssociations(gs.associations), gs.predications); | this (gs.characters.duplicate(), copyBuffer(gs.glyphs), copyAssociations(gs.associations), gs.predications); | ||||
this.unprocessedGS = gs.unprocessedGS; | |||||
} | } | ||||
/** | /** | ||||
return glyphs.get(index); | return glyphs.get(index); | ||||
} | } | ||||
public int getUnprocessedGlyph(int index) throws IndexOutOfBoundsException { | |||||
return unprocessedGS.getGlyph(index); | |||||
} | |||||
public void setUnprocessedGS(GlyphSequence glyphSequence) { | |||||
unprocessedGS = glyphSequence; | |||||
} | |||||
/** | /** | ||||
* Set glyph id at specified index. | * Set glyph id at specified index. | ||||
* @param index to set glyph | * @param index to set glyph |
} | } | ||||
/** {@inheritDoc} */ | /** {@inheritDoc} */ | ||||
public CharSequence performSubstitution(CharSequence cs, String script, String language, List associations, | |||||
boolean retainControls) { | |||||
public CharSequence performSubstitution(CharSequence charSequence, String script, String language, | |||||
List associations, boolean retainControls) { | |||||
if (gsub != null) { | if (gsub != null) { | ||||
CharSequence ncs = normalize(cs, associations); | |||||
GlyphSequence igs = mapCharsToGlyphs(ncs, associations); | |||||
GlyphSequence ogs = gsub.substitute(igs, script, language); | |||||
charSequence = gsub.preProcess(charSequence, script, this, associations); | |||||
GlyphSequence glyphSequence = charSequenceToGlyphSequence(charSequence, associations); | |||||
GlyphSequence glyphSequenceSubstituted = gsub.substitute(glyphSequence, script, language); | |||||
if (associations != null) { | if (associations != null) { | ||||
associations.clear(); | associations.clear(); | ||||
associations.addAll(ogs.getAssociations()); | |||||
associations.addAll(glyphSequenceSubstituted.getAssociations()); | |||||
} | } | ||||
if (!retainControls) { | if (!retainControls) { | ||||
ogs = elideControls(ogs); | |||||
glyphSequenceSubstituted = elideControls(glyphSequenceSubstituted); | |||||
} | } | ||||
// ocs may not contains all the characters that were in cs. | |||||
// may not contains all the characters that were in charSequence. | |||||
// see: #createPrivateUseMapping(int gi) | // see: #createPrivateUseMapping(int gi) | ||||
CharSequence ocs = mapGlyphsToChars(ogs); | |||||
return ocs; | |||||
return mapGlyphsToChars(glyphSequenceSubstituted); | |||||
} else { | } else { | ||||
return cs; | |||||
return charSequence; | |||||
} | } | ||||
} | } | ||||
public GlyphSequence charSequenceToGlyphSequence(CharSequence charSequence, List associations) { | |||||
CharSequence normalizedCharSequence = normalize(charSequence, associations); | |||||
return mapCharsToGlyphs(normalizedCharSequence, associations); | |||||
} | |||||
/** {@inheritDoc} */ | /** {@inheritDoc} */ | ||||
public CharSequence reorderCombiningMarks( | public CharSequence reorderCombiningMarks( | ||||
CharSequence cs, int[][] gpa, String script, String language, List associations) { | CharSequence cs, int[][] gpa, String script, String language, List associations) { |
/* | |||||
* Licensed to the Apache Software Foundation (ASF) under one or more | |||||
* contributor license agreements. See the NOTICE file distributed with | |||||
* this work for additional information regarding copyright ownership. | |||||
* The ASF licenses this file to You under the Apache License, Version 2.0 | |||||
* (the "License"); you may not use this file except in compliance with | |||||
* the License. You may obtain a copy of the License at | |||||
* | |||||
* http://www.apache.org/licenses/LICENSE-2.0 | |||||
* | |||||
* Unless required by applicable law or agreed to in writing, software | |||||
* distributed under the License is distributed on an "AS IS" BASIS, | |||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||||
* See the License for the specific language governing permissions and | |||||
* limitations under the License. | |||||
*/ | |||||
/* $Id$ */ | |||||
package org.apache.fop.complexscripts.scripts; | |||||
import java.util.Arrays; | |||||
import java.util.Collections; | |||||
import java.util.HashMap; | |||||
import java.util.List; | |||||
import java.util.Map; | |||||
import org.junit.Assert; | |||||
import org.junit.Test; | |||||
import static org.junit.Assert.assertEquals; | |||||
import org.apache.fop.complexscripts.fonts.GlyphCoverageTable; | |||||
import org.apache.fop.complexscripts.fonts.GlyphPositioningTable; | |||||
import org.apache.fop.complexscripts.fonts.GlyphSubtable; | |||||
import org.apache.fop.complexscripts.fonts.GlyphTable; | |||||
import org.apache.fop.complexscripts.fonts.OTFLanguage; | |||||
import org.apache.fop.complexscripts.fonts.OTFScript; | |||||
import org.apache.fop.complexscripts.util.CharScript; | |||||
import org.apache.fop.complexscripts.util.GlyphSequence; | |||||
import org.apache.fop.fonts.MultiByteFont; | |||||
public class KhmerTestCase { | |||||
@Test | |||||
public void testProcessor() { | |||||
String in = "\u179b\u17c1\u1781\u179a\u17c0\u1784\u17b7\u179c\u17d2\u1780\u1780\u1799\u1794\u17d2\u178f\u179a"; | |||||
String out = | |||||
"\u17c1\u179b\u1781\u17c1\u179a\u17c0\u1784\u17b7\u179c\u17d2\u1780\u1780\u1799\u1794\u17d2\u178f\u179a"; | |||||
assertEquals( | |||||
new KhmerScriptProcessor(OTFScript.KHMER).preProcess(in, new MultiByteFont(null, null), null), out); | |||||
} | |||||
@Test | |||||
public void testPositioning() { | |||||
GlyphSubtable subtable5 = GlyphPositioningTable.createSubtable(5, "lu1", 0, 0, 1, | |||||
GlyphCoverageTable.createCoverageTable(Collections.singletonList(0)), | |||||
Arrays.asList( | |||||
GlyphCoverageTable.createCoverageTable(Collections.singletonList(0)), | |||||
0, | |||||
1, | |||||
new GlyphPositioningTable.MarkAnchor[] { | |||||
new GlyphPositioningTable.MarkAnchor(0, new GlyphPositioningTable.Anchor(0, 0)) | |||||
}, | |||||
new GlyphPositioningTable.Anchor[][][] { | |||||
new GlyphPositioningTable.Anchor[][] { | |||||
new GlyphPositioningTable.Anchor[] { | |||||
new GlyphPositioningTable.Anchor(12, 0) | |||||
} | |||||
} | |||||
} | |||||
)); | |||||
Map<GlyphTable.LookupSpec, List> lookups = new HashMap<GlyphTable.LookupSpec, List>(); | |||||
lookups.put(new GlyphTable.LookupSpec(OTFScript.KHMER, OTFLanguage.DEFAULT, "abvm"), | |||||
Collections.singletonList("lu1")); | |||||
Map<String, ScriptProcessor> processors = new HashMap<String, ScriptProcessor>(); | |||||
processors.put(OTFScript.KHMER, new KhmerScriptProcessor(OTFScript.KHMER)); | |||||
GlyphPositioningTable gpt = | |||||
new GlyphPositioningTable(null, lookups, Collections.singletonList(subtable5), processors); | |||||
ScriptProcessor scriptProcessor = ScriptProcessor.getInstance(OTFScript.KHMER, processors); | |||||
MultiByteFont multiByteFont = new MultiByteFont(null, null); | |||||
GlyphSequence glyphSequence = multiByteFont.charSequenceToGlyphSequence("test", null); | |||||
scriptProcessor.preProcess("test", multiByteFont, null); | |||||
scriptProcessor.substitute( | |||||
glyphSequence, OTFScript.KHMER, OTFLanguage.DEFAULT, new GlyphTable.UseSpec[0], null); | |||||
int[][] adjustments = new int[4][1]; | |||||
gpt.position(glyphSequence, OTFScript.KHMER, OTFLanguage.DEFAULT, 0, null, adjustments); | |||||
Assert.assertArrayEquals(adjustments[1], new int[]{12}); | |||||
} | |||||
@Test | |||||
public void testMakeProcessor() { | |||||
Assert.assertTrue(IndicScriptProcessor.makeProcessor(OTFScript.KHMER) instanceof KhmerScriptProcessor); | |||||
Assert.assertTrue(CharScript.isIndicScript(OTFScript.KHMER)); | |||||
} | |||||
@Test | |||||
public void testKhmerRenderer() { | |||||
KhmerRenderer khmerRenderer = new KhmerRenderer(); | |||||
StringBuilder stringBuilder = new StringBuilder(); | |||||
int khmerStart = 6016; | |||||
for (int i = khmerStart; i < khmerStart + 128; i++) { | |||||
stringBuilder.append((char)i); | |||||
} | |||||
String allKhmerChars = stringBuilder.toString(); | |||||
String expected = khmerRenderer.render(allKhmerChars); | |||||
assertEquals(expected.length(), 133); | |||||
StringBuilder diff = new StringBuilder(); | |||||
for (int i = 0; i < allKhmerChars.length(); i++) { | |||||
if (allKhmerChars.charAt(i) != expected.charAt(i)) { | |||||
diff.append(expected.charAt(i)); | |||||
} | |||||
} | |||||
assertEquals(diff.length(), 66); | |||||
assertEquals(diff.charAt(0), (char) 6081); | |||||
} | |||||
} |