
com.adobe.fontengine.inlineformatting.infontformatting.ThaiFormatter Maven / Gradle / Ivy
/*
*
* File: ThaiFormatter.java
*
*
* ADOBE CONFIDENTIAL
* ___________________
*
* Copyright 2004-2005 Adobe Systems Incorporated
* All Rights Reserved.
*
* NOTICE: All information contained herein is, and remains the property of
* Adobe Systems Incorporated and its suppliers, if any. The intellectual
* and technical concepts contained herein are proprietary to Adobe Systems
* Incorporated and its suppliers and may be covered by U.S. and Foreign
* Patents, patents in process, and are protected by trade secret or
* copyright law. Dissemination of this information or reproduction of this
* material is strictly forbidden unless prior written permission is obtained
* from Adobe Systems Incorporated.
*
*/
package com.adobe.fontengine.inlineformatting.infontformatting;
import com.adobe.fontengine.font.FontLoadingException;
import com.adobe.fontengine.font.InvalidFontException;
import com.adobe.fontengine.font.UnsupportedFontException;
import com.adobe.fontengine.font.opentype.OpenTypeFont;
import com.adobe.fontengine.inlineformatting.AttributedRun;
import com.adobe.fontengine.inlineformatting.ElementAttribute;
final class ThaiFormatter extends GenericFormatter {
//------------------------------------------------------------- firstPass ---
/** Expand occurrences of SARA AM into NIKHAHIT and SARA AA.
*/
protected int removeSaraAm (AttributedRun run, int start, int limit) {
// The only difficulty is that we want to place the NIKHAHIT in
// front of any tone mark that may precede the SARA AM.
// We have to do that because most fonts treat the NIKAHIT
// and tone mark as "marks above" without further distinction,
// and the NIKHAHIT of a SARA AM must appear below those tone
// marks.
int cur = start;
while (cur < limit) {
int usv = run.elementAt (cur);
if (usv == 0x0E33 /* U+0E33 THAI CHARACTER SARA AM */) {
int prev = cur;
while ( start <= prev - 1
&& run.getElementStyle (prev-1, ElementAttribute.isGlyph) != Boolean.TRUE
&& 0x0E48 <= run.elementAt (prev-1) /* U+0E48 THAI CHARACTER MAI EK */
&& run.elementAt (prev-1) <= 0xE4B /* U+0E4B THAI CHARACTER MAI CHATTAWA */) {
prev--; }
if (prev == cur) {
// common case without tone marks
run.replace (cur, new int[] {0x0E4D, 0x0E32}); }
else {
int [] positions = new int [cur - prev + 1];
for (int i = 0; i < positions.length; i++) {
positions [i] = prev + i; }
int [] elementIds = new int [cur - prev + 2];
elementIds [0] = 0x0E4D;
for (int i = 1; i < elementIds.length - 1; i++) {
elementIds [i] = run.elementAt (prev + i - 1); }
elementIds [elementIds.length - 1] = 0x0E32;
run.replace (positions, elementIds); }
cur++;
limit++; }
cur++; }
return limit;
}
public int firstPass (AttributedRun run, int start, int limit) {
limit = removeSaraAm (run, start, limit);
return super.firstPass (run, start, limit);
}
//-------------------------------------------------------------- formatTT ---
//
// Up to and including Windows XP SP2, the fonts provided with Windows
// for Thai do not use OpenType layout. Instead, the layout is achieved
// by having extra glyphs in the font, mapped from the code points
// in the range U+F700 - U+F71F, and it is up to the layout engine to
// fetch the appropriate glyphs. This method replaces the characters in
// a run with characters in the PUA range as appropriate.
//
// The conditions under which to use these extra glyphs have been determined
// by looking at the position of the their ink relative to their origin. There
// is unfortunately not specification to build from. In particular, the use
// of the glyphs mapped from U+F71B, U+F71C, U+F71D remains a mystery.
protected void shapeTT (AttributedRun run, int start, int limit) {
// The shaper is essentially a finite state machine, driven by the
// input characters. Actually, we only need to distinguish a few
// character classes:
final int OT = 0; // other
final int C0 = 1; // consonant, no ascender, no descender
final int CA = 2; // consonant, ascender
final int CD = 3; // consonant, descender
final int CR = 4; // consonant, removable piece below
final int BASE = 5;
final int VA = 6; // vowel above
final int VB = 7; // vowel below
final int DA = 8; // tone or diacritic above
final int[] charClass = {
/* 0 1 2 3 4 5 6 7 8 9 a b c d e f */
/* E0x */ OT, C0, C0, C0, C0, C0, C0, C0, C0, C0, C0, C0, C0, CR, CD, CD,
/* E1x */ CR, C0, C0, C0, C0, C0, C0, C0, C0, C0, C0, CA, C0, CA, C0, CA,
/* E2x */ C0, C0, C0, C0, CD, C0, CD, C0, C0, C0, C0, C0, CA, C0, C0, OT,
/* E3x */ OT, VA, OT, OT, VA, VA, VA, VA, VB, VB, VB, OT, OT, OT, OT, OT,
/* E4x */ OT, OT, OT, OT, OT, OT, OT, VA, DA, DA, DA, DA, DA, DA, DA, OT};
// The vowels above mapped from the U+0Exx code points have their ink
// positionned for the case where they sit on top of a consonant without
// an ascender.
//
// The diacritics above mapped from the U+0Exx code points have their ink
// positionned for the case where they sit on top of a vowel above, on
// a consonant without an ascender.
//
// The diacritics below mapped from the U+0Exx code points have their ing
// positionned for the case where they sit below a consonant without a
// descended.
//
// The consonants with a removable piece below mapped from the U+0Exx
// code point have that piece below
//
// The operations of the finite state machine are to select
// alternate glyphs, via alternate entries in the cmap, in the appriate
// conditions.
// This operation moves vowels and diacritics above towards the left, when
// they sit on a consonant with ascender. (Of course, this moves nothing,
// but rather selects an alternate glyph where the ink is more to the left)
final int[] shiftLeft = {
/* E3x */ 0x0E30, 0xf710, 0x0E32, 0x0E33, 0xf701, 0xf702, 0xf703, 0xf704,
0x0E38, 0x0E39, 0x0E3A, 0x0E3B, 0x0E3C, 0x0E3D, 0x0E3E, 0x0E3F,
/* E4x */ 0x0E40, 0x0E41, 0x0E42, 0x0E43, 0x0E44, 0x0E45, 0x0E46, 0xf712,
0xf713, 0xf714, 0xf715, 0xf716, 0xf717, 0xf711, 0x0E4E, 0x0E4F };
// This operation moves diacritics above down, when there is no vowel on
// a ascender-less consonant; and it moves diacritics below down, when
// they are on a consonant with descender.
final int[] shiftDown = {
/* E3x */ 0x0E30, 0x0E31, 0x0E32, 0x0E33, 0x0E34, 0x0E35, 0x0E36, 0x0E37,
0xf718, 0xf719, 0xf71a, 0x0E3B, 0x0E3C, 0x0E3D, 0x0E3E, 0x0E3F,
/* E4x */ 0xE040, 0xE041, 0xE042, 0xE043, 0xE044, 0xE045, 0xE046, 0xE047,
0xf70a, 0xf70b, 0xf70c, 0xf70d, 0xf70e, 0x0E4D, 0x0E4E, 0x0E4F };
// This operation moves diacritics above down and left, when there is no
// vowel on a consonant with an ascender
final int[] shiftLeftAndDown = {
/* E3x */ 0x0E30, 0x0E31, 0x0E32, 0x0E33, 0x0E34, 0x0E35, 0x0E36, 0x0E37,
0x0E38, 0x0E39, 0x0E3A, 0x0E3B, 0x0E3C, 0x0E3D, 0x0E3E, 0x0E3F,
/* E4x */ 0x0E40, 0x0E41, 0x0E42, 0x0E43, 0x0E44, 0x0E45, 0x0E46, 0x0E47,
0xf705, 0xf706, 0xf707, 0xf708, 0xf709, 0x0E4D, 0x0E4E, 0x0E4F };
// This operation removes the piece below a consonant, when there is
// a diacritic below.
final int[] removePieceUnder = {
/* E0x */ 0x0E00, 0x0E01, 0x0E02, 0x0E03, 0x0E04, 0x0E05, 0x0E06, 0x0E07,
0x0E08, 0x0E09, 0x0E0A, 0x0E0B, 0x0E0C, 0xf70f, 0x0E0E, 0x0E0F,
/* E1x */ 0xf700, 0x0E11, 0x0E12, 0x0E13, 0x0E14, 0x0E15, 0x0E16, 0x0E17,
0x0E18, 0x0E19, 0x0E1A, 0x0E1B, 0x0E1C, 0x0E1D, 0x0E1E, 0x0E1F,
/* E2x */ 0x0E20, 0x0E21, 0x0E22, 0x0E23, 0x0E24, 0x0E25, 0x0E26, 0x0E27,
0x0E28, 0x0E29, 0x0E2A, 0x0E2B, 0x0E2C, 0x0E2D, 0x0E2E, 0x0E2F };
// It is the responsibility of the code below to use the appropriate
// indexes in the arrays above.
int baseIndex = Integer.MIN_VALUE;
int baseType = OT;
int baseUsv = -1;
boolean hasVowelAbove = false;
while (start < limit) {
int usv = run.elementAt (start);
if (usv < 0x0E00 || 0x0E4F < usv) {
baseIndex = start;
baseType = OT;
baseUsv = usv;
hasVowelAbove = false; }
else {
int thisCharClass = charClass [usv - 0x0E00];
if (thisCharClass <= BASE) {
baseIndex = start;
baseType = thisCharClass;
baseUsv = usv;
hasVowelAbove = false; }
else if (thisCharClass == VB) {
if (baseType == CR) {
run.replace (baseIndex, removePieceUnder [baseUsv - 0x0E00]);
baseType = C0; }
else if (baseType == CD) {
run.replace (start, shiftDown [usv - 0x0E30]); }}
else if (thisCharClass == VA) {
if (baseType == CA) {
run.replace (start, shiftLeft [usv - 0x0E30]); }
hasVowelAbove = true; }
else if (thisCharClass == DA) {
if (baseType == CA) {
if (hasVowelAbove) {
run.replace (start, shiftLeft [usv - 0x0E30]); }
else {
run.replace (start, shiftLeftAndDown [usv - 0x0E30]); }}
else {
if (! hasVowelAbove) {
run.replace (start, shiftDown [usv - 0x0E30]); }}}}
start++; }
}
protected boolean canFormatTT() {
return true;
}
protected int formatTT (OpenTypeFont otFont, AttributedRun run, int start, int limit, boolean shouldKern)
throws InvalidFontException, UnsupportedFontException, FontLoadingException {
shapeTT (run, start, limit);
return super.formatTT (otFont, run, start, limit, shouldKern);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy