sl@0: /* sl@0: * sl@0: * (C) Copyright IBM Corp. 1998-2005 - All Rights Reserved sl@0: * sl@0: * This file is a modification of the ICU file IndicReordering.cpp sl@0: * by Jens Herden and Javier Sola for Khmer language sl@0: * sl@0: */ sl@0: sl@0: #include "LETypes.h" sl@0: #include "KhmerReordering.h" sl@0: #include "LEGlyphStorage.h" sl@0: sl@0: sl@0: U_NAMESPACE_BEGIN sl@0: sl@0: // Characters that get refered to by name... sl@0: enum sl@0: { sl@0: C_SIGN_ZWNJ = 0x200C, sl@0: C_SIGN_ZWJ = 0x200D, sl@0: C_DOTTED_CIRCLE = 0x25CC, sl@0: C_RO = 0x179A, sl@0: C_VOWEL_AA = 0x17B6, sl@0: C_SIGN_NIKAHIT = 0x17C6, sl@0: C_VOWEL_E = 0x17C1, sl@0: C_COENG = 0x17D2 sl@0: }; sl@0: sl@0: sl@0: enum sl@0: { sl@0: // simple classes, they are used in the statetable (in this file) to control the length of a syllable sl@0: // they are also used to know where a character should be placed (location in reference to the base character) sl@0: // and also to know if a character, when independtly displayed, should be displayed with a dotted-circle to sl@0: // indicate error in syllable construction sl@0: _xx = KhmerClassTable::CC_RESERVED, sl@0: _sa = KhmerClassTable::CC_SIGN_ABOVE | KhmerClassTable::CF_DOTTED_CIRCLE | KhmerClassTable::CF_POS_ABOVE, sl@0: _sp = KhmerClassTable::CC_SIGN_AFTER | KhmerClassTable::CF_DOTTED_CIRCLE| KhmerClassTable::CF_POS_AFTER, sl@0: _c1 = KhmerClassTable::CC_CONSONANT | KhmerClassTable::CF_CONSONANT, sl@0: _c2 = KhmerClassTable::CC_CONSONANT2 | KhmerClassTable::CF_CONSONANT, sl@0: _c3 = KhmerClassTable::CC_CONSONANT3 | KhmerClassTable::CF_CONSONANT, sl@0: _rb = KhmerClassTable::CC_ROBAT | KhmerClassTable::CF_POS_ABOVE | KhmerClassTable::CF_DOTTED_CIRCLE, sl@0: _cs = KhmerClassTable::CC_CONSONANT_SHIFTER | KhmerClassTable::CF_DOTTED_CIRCLE | KhmerClassTable::CF_SHIFTER, sl@0: _dl = KhmerClassTable::CC_DEPENDENT_VOWEL | KhmerClassTable::CF_POS_BEFORE | KhmerClassTable::CF_DOTTED_CIRCLE, sl@0: _db = KhmerClassTable::CC_DEPENDENT_VOWEL | KhmerClassTable::CF_POS_BELOW | KhmerClassTable::CF_DOTTED_CIRCLE, sl@0: _da = KhmerClassTable::CC_DEPENDENT_VOWEL | KhmerClassTable::CF_POS_ABOVE | KhmerClassTable::CF_DOTTED_CIRCLE | KhmerClassTable::CF_ABOVE_VOWEL, sl@0: _dr = KhmerClassTable::CC_DEPENDENT_VOWEL | KhmerClassTable::CF_POS_AFTER | KhmerClassTable::CF_DOTTED_CIRCLE, sl@0: _co = KhmerClassTable::CC_COENG | KhmerClassTable::CF_COENG | KhmerClassTable::CF_DOTTED_CIRCLE, sl@0: sl@0: // split vowel sl@0: _va = _da | KhmerClassTable::CF_SPLIT_VOWEL, sl@0: _vr = _dr | KhmerClassTable::CF_SPLIT_VOWEL sl@0: }; sl@0: sl@0: sl@0: // Character class tables sl@0: // _xx character does not combine into syllable, such as numbers, puntuation marks, non-Khmer signs... sl@0: // _sa Sign placed above the base sl@0: // _sp Sign placed after the base sl@0: // _c1 Consonant of type 1 or independent vowel (independent vowels behave as type 1 consonants) sl@0: // _c2 Consonant of type 2 (only RO) sl@0: // _c3 Consonant of type 3 sl@0: // _rb Khmer sign robat u17CC. combining mark for subscript consonants sl@0: // _cd Consonant-shifter sl@0: // _dl Dependent vowel placed before the base (left of the base) sl@0: // _db Dependent vowel placed below the base sl@0: // _da Dependent vowel placed above the base sl@0: // _dr Dependent vowel placed behind the base (right of the base) sl@0: // _co Khmer combining mark COENG u17D2, combines with the consonant or independent vowel following sl@0: // it to create a subscript consonant or independent vowel sl@0: // _va Khmer split vowel in wich the first part is before the base and the second one above the base sl@0: // _vr Khmer split vowel in wich the first part is before the base and the second one behind (right of) the base sl@0: sl@0: static const KhmerClassTable::CharClass khmerCharClasses[] = sl@0: { sl@0: _c1, _c1, _c1, _c3, _c1, _c1, _c1, _c1, _c3, _c1, _c1, _c1, _c1, _c3, _c1, _c1, // 1780 - 178F sl@0: _c1, _c1, _c1, _c1, _c3, _c1, _c1, _c1, _c1, _c3, _c2, _c1, _c1, _c1, _c3, _c3, // 1790 - 179F sl@0: _c1, _c3, _c1, _c1, _c1, _c1, _c1, _c1, _c1, _c1, _c1, _c1, _c1, _c1, _c1, _c1, // 17A0 - 17AF sl@0: _c1, _c1, _c1, _c1, _dr, _dr, _dr, _da, _da, _da, _da, _db, _db, _db, _va, _vr, // 17B0 - 17BF sl@0: _vr, _dl, _dl, _dl, _vr, _vr, _sa, _sp, _sp, _cs, _cs, _sa, _rb, _sa, _sa, _sa, // 17C0 - 17CF sl@0: _sa, _sa, _co, _sa, _xx, _xx, _xx, _xx, _xx, _xx, _xx, _xx, _xx, _sa, _xx, _xx, // 17D0 - 17DF sl@0: }; sl@0: sl@0: sl@0: // sl@0: // Khmer Class Tables sl@0: // sl@0: sl@0: // sl@0: // The range of characters defined in the above table is defined here. FOr Khmer 1780 to 17DF sl@0: // Even if the Khmer range is bigger, all other characters are not combinable, and therefore treated sl@0: // as _xx sl@0: static const KhmerClassTable khmerClassTable = {0x1780, 0x17df, khmerCharClasses}; sl@0: sl@0: sl@0: // Below we define how a character in the input string is either in the khmerCharClasses table sl@0: // (in which case we get its type back), a ZWJ or ZWNJ (two characters that may appear sl@0: // within the syllable, but are not in the table) we also get their type back, or an unknown object sl@0: // in which case we get _xx (CC_RESERVED) back sl@0: KhmerClassTable::CharClass KhmerClassTable::getCharClass(LEUnicode ch) const sl@0: { sl@0: sl@0: if (ch == C_SIGN_ZWJ) { sl@0: return CC_ZERO_WIDTH_J_MARK; sl@0: } sl@0: sl@0: if (ch == C_SIGN_ZWNJ) { sl@0: return CC_ZERO_WIDTH_NJ_MARK; sl@0: } sl@0: sl@0: if (ch < firstChar || ch > lastChar) { sl@0: return CC_RESERVED; sl@0: } sl@0: sl@0: return classTable[ch - firstChar]; sl@0: } sl@0: sl@0: const KhmerClassTable *KhmerClassTable::getKhmerClassTable() sl@0: { sl@0: return &khmerClassTable; sl@0: } sl@0: sl@0: sl@0: sl@0: class ReorderingOutput : public UMemory { sl@0: private: sl@0: le_int32 fOutIndex; sl@0: LEUnicode *fOutChars; sl@0: sl@0: LEGlyphStorage &fGlyphStorage; sl@0: sl@0: sl@0: public: sl@0: ReorderingOutput(LEUnicode *outChars, LEGlyphStorage &glyphStorage) sl@0: : fOutIndex(0), fOutChars(outChars), fGlyphStorage(glyphStorage) sl@0: { sl@0: // nothing else to do... sl@0: } sl@0: sl@0: ~ReorderingOutput() sl@0: { sl@0: // nothing to do here... sl@0: } sl@0: sl@0: void writeChar(LEUnicode ch, le_uint32 charIndex, const LETag *charTags) sl@0: { sl@0: LEErrorCode success = LE_NO_ERROR; sl@0: sl@0: fOutChars[fOutIndex] = ch; sl@0: sl@0: fGlyphStorage.setCharIndex(fOutIndex, charIndex, success); sl@0: fGlyphStorage.setAuxData(fOutIndex, (void *) charTags, success); sl@0: sl@0: fOutIndex += 1; sl@0: } sl@0: sl@0: le_int32 getOutputIndex() sl@0: { sl@0: return fOutIndex; sl@0: } sl@0: }; sl@0: sl@0: sl@0: static const LETag emptyTag = 0x00000000; // '' sl@0: //TODO remove unused flags sl@0: //static const LETag nuktFeatureTag = LE_NUKT_FEATURE_TAG; sl@0: //static const LETag akhnFeatureTag = LE_AKHN_FEATURE_TAG; sl@0: //static const LETag rphfFeatureTag = LE_RPHF_FEATURE_TAG; sl@0: static const LETag blwfFeatureTag = LE_BLWF_FEATURE_TAG; sl@0: //static const LETag halfFeatureTag = LE_HALF_FEATURE_TAG; sl@0: static const LETag pstfFeatureTag = LE_PSTF_FEATURE_TAG; sl@0: //static const LETag vatuFeatureTag = LE_VATU_FEATURE_TAG; sl@0: static const LETag presFeatureTag = LE_PRES_FEATURE_TAG; sl@0: static const LETag blwsFeatureTag = LE_BLWS_FEATURE_TAG; sl@0: static const LETag abvsFeatureTag = LE_ABVS_FEATURE_TAG; sl@0: static const LETag pstsFeatureTag = LE_PSTS_FEATURE_TAG; sl@0: //static const LETag halnFeatureTag = LE_HALN_FEATURE_TAG; sl@0: sl@0: static const LETag blwmFeatureTag = LE_BLWM_FEATURE_TAG; sl@0: static const LETag abvmFeatureTag = LE_ABVM_FEATURE_TAG; sl@0: static const LETag distFeatureTag = LE_DIST_FEATURE_TAG; sl@0: sl@0: static const LETag prefFeatureTag = LE_PREF_FEATURE_TAG; sl@0: static const LETag abvfFeatureTag = LE_ABVF_FEATURE_TAG; sl@0: static const LETag cligFeatureTag = LE_CLIG_FEATURE_TAG; sl@0: static const LETag mkmkFeatureTag = LE_MKMK_FEATURE_TAG; sl@0: sl@0: // These are in the order in which the features need to be applied sl@0: // for correct processing sl@0: static const LETag featureOrder[] = sl@0: { sl@0: // Shaping features sl@0: prefFeatureTag, blwfFeatureTag, abvfFeatureTag, pstfFeatureTag, sl@0: presFeatureTag, blwsFeatureTag, abvsFeatureTag, pstsFeatureTag, sl@0: cligFeatureTag, sl@0: sl@0: // Positioning features sl@0: distFeatureTag, blwmFeatureTag, abvmFeatureTag, mkmkFeatureTag, sl@0: emptyTag sl@0: }; sl@0: sl@0: static const LETag tagPref[] = sl@0: { sl@0: prefFeatureTag, presFeatureTag, sl@0: cligFeatureTag, sl@0: sl@0: // Positioning features sl@0: distFeatureTag, sl@0: emptyTag sl@0: }; sl@0: sl@0: static const LETag tagAbvf[] = sl@0: { sl@0: abvfFeatureTag, abvsFeatureTag, sl@0: cligFeatureTag, sl@0: sl@0: // Positioning features sl@0: distFeatureTag, abvmFeatureTag, mkmkFeatureTag, sl@0: emptyTag sl@0: }; sl@0: sl@0: static const LETag tagPstf[] = sl@0: { sl@0: blwfFeatureTag, blwsFeatureTag, sl@0: prefFeatureTag, presFeatureTag, sl@0: sl@0: pstfFeatureTag, pstsFeatureTag, sl@0: cligFeatureTag, sl@0: sl@0: // Positioning features sl@0: distFeatureTag, blwmFeatureTag, sl@0: emptyTag sl@0: }; sl@0: sl@0: static const LETag tagBlwf[] = sl@0: { sl@0: blwfFeatureTag, blwsFeatureTag, sl@0: cligFeatureTag, sl@0: sl@0: // Positioning features sl@0: distFeatureTag, blwmFeatureTag, mkmkFeatureTag, sl@0: emptyTag sl@0: }; sl@0: sl@0: sl@0: // TODO do we need all of them? sl@0: static const LETag tagDefault[] = sl@0: { sl@0: // Shaping feature sl@0: prefFeatureTag, blwfFeatureTag, /*abvfFeatureTag,*/ /*pstfFeatureTag, */ sl@0: presFeatureTag, blwsFeatureTag, /*abvsFeatureTag,*/ /*pstsFeatureTag,*/ sl@0: cligFeatureTag, sl@0: sl@0: // Positioning features sl@0: distFeatureTag, abvmFeatureTag, blwmFeatureTag, mkmkFeatureTag, sl@0: emptyTag sl@0: }; sl@0: sl@0: sl@0: sl@0: // The stateTable is used to calculate the end (the length) of a well sl@0: // formed Khmer Syllable. sl@0: // sl@0: // Each horizontal line is ordered exactly the same way as the values in KhmerClassTable sl@0: // CharClassValues in KhmerReordering.h This coincidence of values allows the sl@0: // follow up of the table. sl@0: // sl@0: // Each line corresponds to a state, which does not necessarily need to be a type sl@0: // of component... for example, state 2 is a base, with is always a first character sl@0: // in the syllable, but the state could be produced a consonant of any type when sl@0: // it is the first character that is analysed (in ground state). sl@0: // sl@0: // Differentiating 3 types of consonants is necessary in order to sl@0: // forbid the use of certain combinations, such as having a second sl@0: // coeng after a coeng RO, sl@0: // The inexistent possibility of having a type 3 after another type 3 is permitted, sl@0: // eliminating it would very much complicate the table, and it does not create typing sl@0: // problems, as the case above. sl@0: // sl@0: // The table is quite complex, in order to limit the number of coeng consonants sl@0: // to 2 (by means of the table). sl@0: // sl@0: // There a peculiarity, as far as Unicode is concerned: sl@0: // - The consonant-shifter is considered in two possible different sl@0: // locations, the one considered in Unicode 3.0 and the one considered in sl@0: // Unicode 4.0. (there is a backwards compatibility problem in this standard). sl@0: sl@0: sl@0: // xx independent character, such as a number, punctuation sign or non-khmer char sl@0: // sl@0: // c1 Khmer consonant of type 1 or an independent vowel sl@0: // that is, a letter in which the subscript for is only under the sl@0: // base, not taking any space to the right or to the left sl@0: // sl@0: // c2 Khmer consonant of type 2, the coeng form takes space under sl@0: // and to the left of the base (only RO is of this type) sl@0: // sl@0: // c3 Khmer consonant of type 3. Its subscript form takes space under sl@0: // and to the right of the base. sl@0: // sl@0: // cs Khmer consonant shifter sl@0: // sl@0: // rb Khmer robat sl@0: // sl@0: // co coeng character (u17D2) sl@0: // sl@0: // dv dependent vowel (including split vowels, they are treated in the same way). sl@0: // even if dv is not defined above, the component that is really tested for is sl@0: // KhmerClassTable::CC_DEPENDENT_VOWEL, which is common to all dependent vowels sl@0: // sl@0: // zwj Zero Width joiner sl@0: // sl@0: // zwnj Zero width non joiner sl@0: // sl@0: // sa above sign sl@0: // sl@0: // sp post sign sl@0: // sl@0: // there are lines with equal content but for an easier understanding sl@0: // (and maybe change in the future) we did not join them sl@0: // sl@0: static const le_int8 khmerStateTable[][KhmerClassTable::CC_COUNT] = sl@0: { sl@0: sl@0: // xx c1 c2 c3 zwnj cs rb co dv sa sp zwj sl@0: { 1, 2, 2, 2, 1, 1, 1, 6, 1, 1, 1, 2}, // 0 - ground state sl@0: {-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, // 1 - exit state (or sign to the right of the syllable) sl@0: {-1, -1, -1, -1, 3, 4, 5, 6, 16, 17, 1, -1}, // 2 - Base consonant sl@0: {-1, -1, -1, -1, -1, 4, -1, -1, 16, -1, -1, -1}, // 3 - First ZWNJ before a register shifter sl@0: // It can only be followed by a shifter or a vowel sl@0: {-1, -1, -1, -1, 15, -1, -1, 6, 16, 17, 1, 14}, // 4 - First register shifter sl@0: {-1, -1, -1, -1, -1, -1, -1, -1, 20, -1, 1, -1}, // 5 - Robat sl@0: {-1, 7, 8, 9, -1, -1, -1, -1, -1, -1, -1, -1}, // 6 - First Coeng sl@0: {-1, -1, -1, -1, 12, 13, -1, 10, 16, 17, 1, 14}, // 7 - First consonant of type 1 after coeng sl@0: {-1, -1, -1, -1, 12, 13, -1, -1, 16, 17, 1, 14}, // 8 - First consonant of type 2 after coeng sl@0: {-1, -1, -1, -1, 12, 13, -1, 10, 16, 17, 1, 14}, // 9 - First consonant or type 3 after ceong sl@0: {-1, 11, 11, 11, -1, -1, -1, -1, -1, -1, -1, -1}, // 10 - Second Coeng (no register shifter before) sl@0: {-1, -1, -1, -1, 15, -1, -1, -1, 16, 17, 1, 14}, // 11 - Second coeng consonant (or ind. vowel) no register shifter before sl@0: {-1, -1, 1, -1, -1, 13, -1, -1, 16, -1, -1, -1}, // 12 - Second ZWNJ before a register shifter sl@0: {-1, -1, -1, -1, 15, -1, -1, -1, 16, 17, 1, 14}, // 13 - Second register shifter sl@0: {-1, -1, -1, -1, -1, -1, -1, -1, 16, -1, -1, -1}, // 14 - ZWJ before vowel sl@0: {-1, -1, -1, -1, -1, -1, -1, -1, 16, -1, -1, -1}, // 15 - ZWNJ before vowel sl@0: {-1, -1, -1, -1, -1, -1, -1, -1, -1, 17, 1, 18}, // 16 - dependent vowel sl@0: {-1, -1, 1, -1, -1, -1, -1, -1, -1, -1, 1, 18}, // 17 - sign above sl@0: {-1, -1, -1, -1, -1, -1, -1, 19, -1, -1, -1, -1}, // 18 - ZWJ after vowel sl@0: {-1, 1, -1, 1, -1, -1, -1, -1, -1, -1, -1, -1}, // 19 - Third coeng sl@0: {-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 1, -1}, // 20 - dependent vowel after a Robat sl@0: sl@0: }; sl@0: sl@0: sl@0: const LETag *KhmerReordering::getFeatureOrder() sl@0: { sl@0: return featureOrder; sl@0: } sl@0: sl@0: sl@0: // Given an input string of characters and a location in which to start looking sl@0: // calculate, using the state table, which one is the last character of the syllable sl@0: // that starts in the starting position. sl@0: le_int32 KhmerReordering::findSyllable(const KhmerClassTable *classTable, const LEUnicode *chars, le_int32 prev, le_int32 charCount) sl@0: { sl@0: le_int32 cursor = prev; sl@0: le_int8 state = 0; sl@0: sl@0: while (cursor < charCount) { sl@0: KhmerClassTable::CharClass charClass = (classTable->getCharClass(chars[cursor]) & KhmerClassTable::CF_CLASS_MASK); sl@0: sl@0: state = khmerStateTable[state][charClass]; sl@0: sl@0: if (state < 0) { sl@0: break; sl@0: } sl@0: sl@0: cursor += 1; sl@0: } sl@0: sl@0: return cursor; sl@0: } sl@0: sl@0: sl@0: // This is the real reordering function as applied to the Khmer language sl@0: sl@0: le_int32 KhmerReordering::reorder(const LEUnicode *chars, le_int32 charCount, le_int32 /*scriptCode*/, sl@0: LEUnicode *outChars, LEGlyphStorage &glyphStorage) sl@0: { sl@0: const KhmerClassTable *classTable = KhmerClassTable::getKhmerClassTable(); sl@0: sl@0: ReorderingOutput output(outChars, glyphStorage); sl@0: KhmerClassTable::CharClass charClass; sl@0: le_int32 i, prev = 0, coengRo; sl@0: sl@0: sl@0: // This loop only exits when we reach the end of a run, which may contain sl@0: // several syllables. sl@0: while (prev < charCount) { sl@0: le_int32 syllable = findSyllable(classTable, chars, prev, charCount); sl@0: sl@0: // write a pre vowel or the pre part of a split vowel first sl@0: // and look out for coeng + ro. RO is the only vowel of type 2, and sl@0: // therefore the only one that requires saving space before the base. sl@0: coengRo = -1; // There is no Coeng Ro, if found this value will change sl@0: for (i = prev; i < syllable; i += 1) { sl@0: charClass = classTable->getCharClass(chars[i]); sl@0: sl@0: // if a split vowel, write the pre part. In Khmer the pre part sl@0: // is the same for all split vowels, same glyph as pre vowel C_VOWEL_E sl@0: if (charClass & KhmerClassTable::CF_SPLIT_VOWEL) { sl@0: output.writeChar(C_VOWEL_E, i, &tagPref[0]); sl@0: break; // there can be only one vowel sl@0: } sl@0: sl@0: // if a vowel with pos before write it out sl@0: if (charClass & KhmerClassTable::CF_POS_BEFORE) { sl@0: output.writeChar(chars[i], i, &tagPref[0]); sl@0: break; // there can be only one vowel sl@0: } sl@0: sl@0: // look for coeng + ro and remember position sl@0: // works because coeng + ro is always in front of a vowel (if there is a vowel) sl@0: // and because CC_CONSONANT2 is enough to identify it, as it is the only consonant sl@0: // with this flag sl@0: if ( (charClass & KhmerClassTable::CF_COENG) && (i + 1 < syllable) && sl@0: ( (classTable->getCharClass(chars[i + 1]) & KhmerClassTable::CF_CLASS_MASK) == KhmerClassTable::CC_CONSONANT2) ) sl@0: { sl@0: coengRo = i; sl@0: } sl@0: } sl@0: sl@0: // write coeng + ro if found sl@0: if (coengRo > -1) { sl@0: output.writeChar(C_COENG, coengRo, &tagPref[0]); sl@0: output.writeChar(C_RO, coengRo + 1, &tagPref[0]); sl@0: } sl@0: sl@0: // shall we add a dotted circle? sl@0: // If in the position in which the base should be (first char in the string) there is sl@0: // a character that has the Dotted circle flag (a character that cannot be a base) sl@0: // then write a dotted circle sl@0: if (classTable->getCharClass(chars[prev]) & KhmerClassTable::CF_DOTTED_CIRCLE) { sl@0: output.writeChar(C_DOTTED_CIRCLE, prev, &tagDefault[0]); sl@0: } sl@0: sl@0: // copy what is left to the output, skipping before vowels and coeng Ro if they are present sl@0: for (i = prev; i < syllable; i += 1) { sl@0: charClass = classTable->getCharClass(chars[i]); sl@0: sl@0: // skip a before vowel, it was already processed sl@0: if (charClass & KhmerClassTable::CF_POS_BEFORE) { sl@0: continue; sl@0: } sl@0: sl@0: // skip coeng + ro, it was already processed sl@0: if (i == coengRo) { sl@0: i += 1; sl@0: continue; sl@0: } sl@0: sl@0: switch (charClass & KhmerClassTable::CF_POS_MASK) { sl@0: case KhmerClassTable::CF_POS_ABOVE : sl@0: output.writeChar(chars[i], i, &tagAbvf[0]); sl@0: break; sl@0: sl@0: case KhmerClassTable::CF_POS_AFTER : sl@0: output.writeChar(chars[i], i, &tagPstf[0]); sl@0: break; sl@0: sl@0: case KhmerClassTable::CF_POS_BELOW : sl@0: output.writeChar(chars[i], i, &tagBlwf[0]); sl@0: break; sl@0: sl@0: default: sl@0: // assign the correct flags to a coeng consonant sl@0: // Consonants of type 3 are taged as Post forms and those type 1 as below forms sl@0: if ( (charClass & KhmerClassTable::CF_COENG) && i + 1 < syllable ) { sl@0: if ( (classTable->getCharClass(chars[i + 1]) & KhmerClassTable::CF_CLASS_MASK) sl@0: == KhmerClassTable::CC_CONSONANT3) { sl@0: output.writeChar(chars[i], i, &tagPstf[0]); sl@0: i += 1; sl@0: output.writeChar(chars[i], i, &tagPstf[0]); sl@0: } sl@0: else { sl@0: output.writeChar(chars[i], i, &tagBlwf[0]); sl@0: i += 1; sl@0: output.writeChar(chars[i], i, &tagBlwf[0]); sl@0: } sl@0: break; sl@0: } sl@0: // if a shifter is followed by an above vowel change the shifter to below form, sl@0: // an above vowel can have two possible positions i + 1 or i + 3 sl@0: // (position i+1 corresponds to unicode 3, position i+3 to Unicode 4) sl@0: // and there is an extra rule for C_VOWEL_AA + C_SIGN_NIKAHIT also for two sl@0: // different positions, right after the shifter or after a vowel (Unicode 4) sl@0: if ( (charClass & KhmerClassTable::CF_SHIFTER) && (i + 1 < syllable) ) { sl@0: if ((classTable->getCharClass(chars[i + 1]) & KhmerClassTable::CF_ABOVE_VOWEL) sl@0: || (i + 2 < syllable sl@0: && ( (classTable->getCharClass(chars[i + 1]) & KhmerClassTable::CF_CLASS_MASK) == C_VOWEL_AA) sl@0: && ( (classTable->getCharClass(chars[i + 2]) & KhmerClassTable::CF_CLASS_MASK) == C_SIGN_NIKAHIT)) sl@0: || (i + 3 < syllable && (classTable->getCharClass(chars[i + 3]) & KhmerClassTable::CF_ABOVE_VOWEL)) sl@0: || (i + 4 < syllable sl@0: && ( (classTable->getCharClass(chars[i + 3]) & KhmerClassTable::CF_CLASS_MASK) == C_VOWEL_AA) sl@0: && ( (classTable->getCharClass(chars[i + 4]) & KhmerClassTable::CF_CLASS_MASK) == C_SIGN_NIKAHIT) ) ) sl@0: { sl@0: output.writeChar(chars[i], i, &tagBlwf[0]); sl@0: break; sl@0: } sl@0: sl@0: } sl@0: // default - any other characters sl@0: output.writeChar(chars[i], i, &tagDefault[0]); sl@0: break; sl@0: } // switch sl@0: } // for sl@0: sl@0: prev = syllable; // move the pointer to the start of next syllable sl@0: } sl@0: sl@0: return output.getOutputIndex(); sl@0: } sl@0: sl@0: sl@0: U_NAMESPACE_END