// © 2016 and later: Unicode, Inc. and others. // License & terms of use: http://www.unicode.org/copyright.html /* ********************************************************************** * Copyright (C) 2002-2014, International Business Machines * Corporation and others. All Rights Reserved. ********************************************************************** */ /* * paragraphLayout doesn't make much sense without * BreakIterator... */ #include "layout/LETypes.h" #include "layout/LEScripts.h" #include "layout/LELanguages.h" #include "layout/LayoutEngine.h" #include "layout/LEFontInstance.h" #include "unicode/ubidi.h" #include "unicode/uchriter.h" #include "unicode/brkiter.h" #if ! UCONFIG_NO_BREAK_ITERATION #include "LXUtilities.h" #include "usc_impl.h" /* this is currently private! */ #include "cstring.h" /* this too! */ #include "layout/ParagraphLayout.h" U_NAMESPACE_BEGIN #define ARRAY_SIZE(array) (sizeof array / sizeof array[0]) /* Leave this copyright notice here! It needs to go somewhere in this library. */ static const char copyright[] = U_COPYRIGHT_STRING; class StyleRuns { public: StyleRuns(const RunArray *styleRunArrays[], le_int32 styleCount); ~StyleRuns(); le_int32 getRuns(le_int32 runLimits[], le_int32 styleIndices[]); private: le_int32 fStyleCount; le_int32 fRunCount; le_int32 *fRunLimits; le_int32 *fStyleIndices; }; StyleRuns::StyleRuns(const RunArray *styleRunArrays[], le_int32 styleCount) : fStyleCount(styleCount), fRunCount(0), fRunLimits(NULL), fStyleIndices(NULL) { le_int32 maxRunCount = 0; le_int32 style, run, runStyle; le_int32 *currentRun = LE_NEW_ARRAY(le_int32, styleCount); for (int i = 0; i < styleCount; i += 1) { maxRunCount += styleRunArrays[i]->getCount(); } maxRunCount -= styleCount - 1; fRunLimits = LE_NEW_ARRAY(le_int32, maxRunCount); fStyleIndices = LE_NEW_ARRAY(le_int32, maxRunCount * styleCount); for (style = 0; style < styleCount; style += 1) { currentRun[style] = 0; } run = 0; runStyle = 0; /* * Since the last run limit for each style run must be * the same, all the styles will hit the last limit at * the same time, so we know when we're done when the first * style hits the last limit. */ while (currentRun[0] < styleRunArrays[0]->getCount()) { fRunLimits[run] = 0x7FFFFFFF; // find the minimum run limit for all the styles for (style = 0; style < styleCount; style += 1) { if (styleRunArrays[style]->getLimit(currentRun[style]) < fRunLimits[run]) { fRunLimits[run] = styleRunArrays[style]->getLimit(currentRun[style]); } } // advance all styles whose current run is at this limit to the next run for (style = 0; style < styleCount; style += 1) { fStyleIndices[runStyle++] = currentRun[style]; if (styleRunArrays[style]->getLimit(currentRun[style]) == fRunLimits[run]) { currentRun[style] += 1; } } run += 1; } fRunCount = run; LE_DELETE_ARRAY(currentRun); } StyleRuns::~StyleRuns() { fRunCount = 0; LE_DELETE_ARRAY(fStyleIndices); fStyleIndices = NULL; LE_DELETE_ARRAY(fRunLimits); fRunLimits = NULL; } le_int32 StyleRuns::getRuns(le_int32 runLimits[], le_int32 styleIndices[]) { if (runLimits != NULL) { LE_ARRAY_COPY(runLimits, fRunLimits, fRunCount); } if (styleIndices != NULL) { LE_ARRAY_COPY(styleIndices, fStyleIndices, fRunCount * fStyleCount); } return fRunCount; } /* * NOTE: This table only has "TRUE" values for * those scripts which the LayoutEngine can currently * process, rather for all scripts which require * complex processing for correct rendering. */ static const le_bool complexTable[] = { FALSE , /* Zyyy */ FALSE, /* Qaai */ TRUE, /* Arab */ FALSE, /* Armn */ TRUE, /* Beng */ FALSE, /* Bopo */ FALSE, /* Cher */ FALSE, /* Copt=Qaac */ FALSE, /* Cyrl */ FALSE, /* Dsrt */ TRUE, /* Deva */ FALSE, /* Ethi */ FALSE, /* Geor */ FALSE, /* Goth */ FALSE, /* Grek */ TRUE, /* Gujr */ TRUE, /* Guru */ FALSE, /* Hani */ FALSE, /* Hang */ TRUE, /* Hebr */ FALSE, /* Hira */ TRUE, /* Knda */ FALSE, /* Kana */ FALSE, /* Khmr */ FALSE, /* Laoo */ FALSE, /* Latn */ TRUE, /* Mlym */ FALSE, /* Mong */ FALSE, /* Mymr */ FALSE, /* Ogam */ FALSE, /* Ital */ TRUE, /* Orya */ FALSE, /* Runr */ FALSE, /* Sinh */ FALSE, /* Syrc */ TRUE, /* Taml */ TRUE, /* Telu */ FALSE, /* Thaa */ TRUE, /* Thai */ FALSE, /* Tibt */ FALSE, /* Cans */ FALSE, /* Yiii */ FALSE, /* Tglg */ FALSE, /* Hano */ FALSE, /* Buhd */ FALSE, /* Tagb */ FALSE, /* Brai */ FALSE, /* Cprt */ FALSE, /* Limb */ FALSE, /* Linb */ FALSE, /* Osma */ FALSE, /* Shaw */ FALSE, /* Tale */ FALSE, /* Ugar */ FALSE, /* Hrkt */ FALSE, /* Bugi */ FALSE, /* Glag */ FALSE, /* Khar */ FALSE, /* Sylo */ FALSE, /* Talu */ FALSE, /* Tfng */ FALSE, /* Xpeo */ FALSE, /* Bali */ FALSE, /* Batk */ FALSE, /* Blis */ FALSE, /* Brah */ FALSE, /* Cham */ FALSE, /* Cirt */ FALSE, /* Cyrs */ FALSE, /* Egyd */ FALSE, /* Egyh */ FALSE, /* Egyp */ FALSE, /* Geok */ FALSE, /* Hans */ FALSE, /* Hant */ FALSE, /* Hmng */ FALSE, /* Hung */ FALSE, /* Inds */ FALSE, /* Java */ FALSE, /* Kali */ FALSE, /* Latf */ FALSE, /* Latg */ FALSE, /* Lepc */ FALSE, /* Lina */ FALSE, /* Mand */ FALSE, /* Maya */ FALSE, /* Mero */ FALSE, /* Nkoo */ FALSE, /* Orkh */ FALSE, /* Perm */ FALSE, /* Phag */ FALSE, /* Phnx */ FALSE, /* Plrd */ FALSE, /* Roro */ FALSE, /* Sara */ FALSE, /* Syre */ FALSE, /* Syrj */ FALSE, /* Syrn */ FALSE, /* Teng */ FALSE, /* Taii */ FALSE, /* Visp */ FALSE, /* Xsux */ FALSE, /* Zxxx */ FALSE, /* Zzzz */ FALSE, /* Cari */ FALSE, /* Jpan */ FALSE, /* Lana */ FALSE, /* Lyci */ FALSE, /* Lydi */ FALSE, /* Olck */ FALSE, /* Rjng */ FALSE, /* Saur */ FALSE, /* Sgnw */ FALSE, /* Sund */ FALSE, /* Moon */ FALSE, /* Mtei */ FALSE, /* Armi */ FALSE, /* Avst */ FALSE, /* Cakm */ FALSE, /* Kore */ FALSE, /* Kthi */ FALSE, /* Mani */ FALSE, /* Phli */ FALSE, /* Phlp */ FALSE, /* Phlv */ FALSE, /* Prti */ FALSE, /* Samr */ FALSE, /* Tavt */ FALSE, /* Zmth */ FALSE, /* Zsym */ FALSE, /* Bamu */ FALSE, /* Lisu */ FALSE, /* Nkgb */ FALSE /* Sarb */ }; const char ParagraphLayout::fgClassID = 0; static void fillMissingCharToGlyphMapValues(le_int32 *charToGlyphMap, le_int32 charCount) { le_int32 lastValidGlyph = -1; le_int32 ch; for (ch = 0; ch <= charCount; ch += 1) { if (charToGlyphMap[ch] == -1) { charToGlyphMap[ch] = lastValidGlyph; } else { lastValidGlyph = charToGlyphMap[ch]; } } } /* * How to deal with composite fonts: * * Don't store the client's FontRuns; we'll need to compute sub-font FontRuns using Doug's * LEFontInstance method. Do that by intersecting the client's FontRuns with fScriptRuns. Use * that to compute fFontRuns, and then intersect fFontRuns, fScriptRuns and fLevelRuns. Doing * it in this order means we do a two-way intersection and a three-way intersection. * * An optimization would be to only do this if there's at least one composite font... * * Other notes: * * * Return the sub-fonts as the run fonts... could keep the mapping back to the client's FontRuns * but that probably makes it more complicated of everyone... * * * Take the LineInfo and LineRun types from Paragraph and use them here, incorporate them into the API. * * * Might want to change the name of the StyleRun type, and make a new one that holds fonts, scripts and levels? * */ ParagraphLayout::ParagraphLayout(const LEUnicode chars[], le_int32 count, const FontRuns *fontRuns, const ValueRuns *levelRuns, const ValueRuns *scriptRuns, const LocaleRuns *localeRuns, UBiDiLevel paragraphLevel, le_bool vertical, LEErrorCode &status) : fChars(chars), fCharCount(count), fFontRuns(NULL), fLevelRuns(levelRuns), fScriptRuns(scriptRuns), fLocaleRuns(localeRuns), fVertical(vertical), fClientLevels(TRUE), fClientScripts(TRUE), fClientLocales(TRUE), fEmbeddingLevels(NULL), fAscent(0), fDescent(0), fLeading(0), fGlyphToCharMap(NULL), fCharToMinGlyphMap(NULL), fCharToMaxGlyphMap(NULL), fGlyphWidths(NULL), fGlyphCount(0), fParaBidi(NULL), fLineBidi(NULL), fStyleRunLimits(NULL), fStyleIndices(NULL), fStyleRunCount(0), fBreakIterator(NULL), fLineStart(-1), fLineEnd(0), /*fVisualRuns(NULL), fStyleRunInfo(NULL), fVisualRunCount(-1), fFirstVisualRun(-1), fLastVisualRun(-1),*/ fVisualRunLastX(0), fVisualRunLastY(0) { if (LE_FAILURE(status)) { fCharCount = -1; return; } (void)copyright; // Suppress unused variable warning. (void)fVertical; // Suppress warning for unused field fVertical. // FIXME: should check the limit arrays for consistency... computeLevels(paragraphLevel); if (scriptRuns == NULL) { computeScripts(); } if (localeRuns == NULL) { computeLocales(); } computeSubFonts(fontRuns, status); if (LE_FAILURE(status)) { //other stuff? fCharCount = -1; return; } // now intersect the font, direction and script runs... const RunArray *styleRunArrays[] = {fFontRuns, fLevelRuns, fScriptRuns, fLocaleRuns}; le_int32 styleCount = sizeof styleRunArrays / sizeof styleRunArrays[0]; StyleRuns styleRuns(styleRunArrays, styleCount); LEErrorCode layoutStatus = LE_NO_ERROR; fStyleRunCount = styleRuns.getRuns(NULL, NULL); fStyleRunLimits = LE_NEW_ARRAY(le_int32, fStyleRunCount); fStyleIndices = LE_NEW_ARRAY(le_int32, fStyleRunCount * styleCount); if ((fStyleRunLimits == NULL) || (fStyleIndices == NULL)) { status = LE_MEMORY_ALLOCATION_ERROR; return; } styleRuns.getRuns(fStyleRunLimits, fStyleIndices); // now build a LayoutEngine for each style run... le_int32 *styleIndices = fStyleIndices; le_int32 run, runStart; fStyleRunInfo = LE_NEW_ARRAY(StyleRunInfo, fStyleRunCount); if (fStyleRunInfo == NULL) { status = LE_MEMORY_ALLOCATION_ERROR; return; } else { // initialize for (run = 0; run < fStyleRunCount; run += 1) { fStyleRunInfo[run].font = NULL; fStyleRunInfo[run].runBase = 0; fStyleRunInfo[run].runLimit = 0; fStyleRunInfo[run].script = (UScriptCode)0; fStyleRunInfo[run].locale = NULL; fStyleRunInfo[run].level = 0; fStyleRunInfo[run].glyphBase = 0; fStyleRunInfo[run].engine = NULL; fStyleRunInfo[run].glyphCount = 0; fStyleRunInfo[run].glyphs = NULL; fStyleRunInfo[run].positions = NULL; } } fGlyphCount = 0; for (runStart = 0, run = 0; run < fStyleRunCount; run += 1) { fStyleRunInfo[run].font = fFontRuns->getFont(styleIndices[0]); fStyleRunInfo[run].runBase = runStart; fStyleRunInfo[run].runLimit = fStyleRunLimits[run]; fStyleRunInfo[run].script = (UScriptCode) fScriptRuns->getValue(styleIndices[2]); fStyleRunInfo[run].locale = fLocaleRuns->getLocale(styleIndices[3]); fStyleRunInfo[run].level = (UBiDiLevel) fLevelRuns->getValue(styleIndices[1]); fStyleRunInfo[run].glyphBase = fGlyphCount; fStyleRunInfo[run].engine = LayoutEngine::layoutEngineFactory(fStyleRunInfo[run].font, fStyleRunInfo[run].script, getLanguageCode(fStyleRunInfo[run].locale), layoutStatus); if (LE_FAILURE(layoutStatus)) { status = layoutStatus; return; } fStyleRunInfo[run].glyphCount = fStyleRunInfo[run].engine->layoutChars(fChars, runStart, fStyleRunLimits[run] - runStart, fCharCount, fStyleRunInfo[run].level & 1, 0, 0, layoutStatus); if (LE_FAILURE(layoutStatus)) { status = layoutStatus; return; } runStart = fStyleRunLimits[run]; styleIndices += styleCount; fGlyphCount += fStyleRunInfo[run].glyphCount; } // Make big arrays for the glyph widths, glyph-to-char and char-to-glyph maps, // in logical order. (Both maps need an extra entry for the end of the text.) // // For each layout get the positions and convert them into glyph widths, in // logical order. Get the glyph-to-char mapping, offset by starting index in the // character array. Swap the glyph width and glyph-to-char arrays into logical order. // Finally, fill in the char-to-glyph mappings. fGlyphWidths = LE_NEW_ARRAY(float, fGlyphCount); fGlyphToCharMap = LE_NEW_ARRAY(le_int32, fGlyphCount + 1); fCharToMinGlyphMap = LE_NEW_ARRAY(le_int32, fCharCount + 1); fCharToMaxGlyphMap = LE_NEW_ARRAY(le_int32, fCharCount + 1); if ((fGlyphWidths == NULL) || (fGlyphToCharMap == NULL) || (fCharToMinGlyphMap == NULL) || (fCharToMaxGlyphMap == NULL)) { status = LE_MEMORY_ALLOCATION_ERROR; return; } le_int32 glyph; for (runStart = 0, run = 0; run < fStyleRunCount; run += 1) { LayoutEngine *engine = fStyleRunInfo[run].engine; le_int32 glyphCount = fStyleRunInfo[run].glyphCount; le_int32 glyphBase = fStyleRunInfo[run].glyphBase; fStyleRunInfo[run].glyphs = LE_NEW_ARRAY(LEGlyphID, glyphCount); fStyleRunInfo[run].positions = LE_NEW_ARRAY(float, glyphCount * 2 + 2); if ((fStyleRunInfo[run].glyphs == NULL) || (fStyleRunInfo[run].positions == NULL)) { status = LE_MEMORY_ALLOCATION_ERROR; return; } engine->getGlyphs(fStyleRunInfo[run].glyphs, layoutStatus); if (LE_FAILURE(layoutStatus)) { status = layoutStatus; return; } engine->getGlyphPositions(fStyleRunInfo[run].positions, layoutStatus); if (LE_FAILURE(layoutStatus)) { status = layoutStatus; return; } engine->getCharIndices(&fGlyphToCharMap[glyphBase], runStart, layoutStatus); if (LE_FAILURE(layoutStatus)) { status = layoutStatus; return; } for (glyph = 0; glyph < glyphCount; glyph += 1) { fGlyphWidths[glyphBase + glyph] = fStyleRunInfo[run].positions[glyph * 2 + 2] - fStyleRunInfo[run].positions[glyph * 2]; } if ((fStyleRunInfo[run].level & 1) != 0) { LXUtilities::reverse(&fGlyphWidths[glyphBase], glyphCount); LXUtilities::reverse(&fGlyphToCharMap[glyphBase], glyphCount); } runStart = fStyleRunLimits[run]; delete engine; fStyleRunInfo[run].engine = NULL; } fGlyphToCharMap[fGlyphCount] = fCharCount; // Initialize the char-to-glyph maps to -1 so that we can later figure out // whether any of the entries in the map aren't filled in below. le_int32 chIndex; for (chIndex = 0; chIndex <= fCharCount; chIndex += 1) { fCharToMinGlyphMap[chIndex] = -1; fCharToMaxGlyphMap[chIndex] = -1; } for (glyph = fGlyphCount - 1; glyph >= 0; glyph -= 1) { le_int32 ch = fGlyphToCharMap[glyph]; fCharToMinGlyphMap[ch] = glyph; } fCharToMinGlyphMap[fCharCount] = fGlyphCount; for (glyph = 0; glyph < fGlyphCount; glyph += 1) { le_int32 ch = fGlyphToCharMap[glyph]; fCharToMaxGlyphMap[ch] = glyph; } fCharToMaxGlyphMap[fCharCount] = fGlyphCount; // Now fill in the missing values in the char-to-glyph maps. fillMissingCharToGlyphMapValues(fCharToMinGlyphMap, fCharCount); fillMissingCharToGlyphMapValues(fCharToMaxGlyphMap, fCharCount); } ParagraphLayout::~ParagraphLayout() { delete (FontRuns *) fFontRuns; if (! fClientLevels) { delete (ValueRuns *) fLevelRuns; fLevelRuns = NULL; fClientLevels = TRUE; } if (! fClientScripts) { delete (ValueRuns *) fScriptRuns; fScriptRuns = NULL; fClientScripts = TRUE; } if (! fClientLocales) { delete (LocaleRuns *) fLocaleRuns; fLocaleRuns = NULL; fClientLocales = TRUE; } if (fEmbeddingLevels != NULL) { LE_DELETE_ARRAY(fEmbeddingLevels); fEmbeddingLevels = NULL; } if (fGlyphToCharMap != NULL) { LE_DELETE_ARRAY(fGlyphToCharMap); fGlyphToCharMap = NULL; } if (fCharToMinGlyphMap != NULL) { LE_DELETE_ARRAY(fCharToMinGlyphMap); fCharToMinGlyphMap = NULL; } if (fCharToMaxGlyphMap != NULL) { LE_DELETE_ARRAY(fCharToMaxGlyphMap); fCharToMaxGlyphMap = NULL; } if (fGlyphWidths != NULL) { LE_DELETE_ARRAY(fGlyphWidths); fGlyphWidths = NULL; } if (fParaBidi != NULL) { ubidi_close(fParaBidi); fParaBidi = NULL; } if (fLineBidi != NULL) { ubidi_close(fLineBidi); fLineBidi = NULL; } if (fStyleRunCount > 0) { le_int32 run; LE_DELETE_ARRAY(fStyleRunLimits); LE_DELETE_ARRAY(fStyleIndices); for (run = 0; run < fStyleRunCount; run += 1) { LE_DELETE_ARRAY(fStyleRunInfo[run].glyphs); LE_DELETE_ARRAY(fStyleRunInfo[run].positions); fStyleRunInfo[run].glyphs = NULL; fStyleRunInfo[run].positions = NULL; } LE_DELETE_ARRAY(fStyleRunInfo); fStyleRunLimits = NULL; fStyleIndices = NULL; fStyleRunInfo = NULL; fStyleRunCount = 0; } if (fBreakIterator != NULL) { delete fBreakIterator; fBreakIterator = NULL; } } le_bool ParagraphLayout::isComplex(const LEUnicode chars[], le_int32 count) { UErrorCode scriptStatus = U_ZERO_ERROR; UScriptCode scriptCode = USCRIPT_INVALID_CODE; UScriptRun *sr = uscript_openRun(chars, count, &scriptStatus); le_bool result = FALSE; while (uscript_nextRun(sr, NULL, NULL, &scriptCode)) { if (isComplex(scriptCode)) { result = TRUE; break; } } uscript_closeRun(sr); return result; } le_int32 ParagraphLayout::getAscent() const { if (fAscent <= 0 && fCharCount > 0) { ((ParagraphLayout *) this)->computeMetrics(); } return fAscent; } le_int32 ParagraphLayout::getDescent() const { if (fAscent <= 0 && fCharCount > 0) { ((ParagraphLayout *) this)->computeMetrics(); } return fDescent; } le_int32 ParagraphLayout::getLeading() const { if (fAscent <= 0 && fCharCount > 0) { ((ParagraphLayout *) this)->computeMetrics(); } return fLeading; } le_bool ParagraphLayout::isDone() const { return fLineEnd >= fCharCount; } ParagraphLayout::Line *ParagraphLayout::nextLine(float width) { if (isDone()) { return NULL; } fLineStart = fLineEnd; if (width > 0) { le_int32 glyph = fCharToMinGlyphMap[fLineStart]; float widthSoFar = 0; while (glyph < fGlyphCount && widthSoFar + fGlyphWidths[glyph] <= width) { widthSoFar += fGlyphWidths[glyph++]; } // If no glyphs fit on the line, force one to fit. // // (There shouldn't be any zero width glyphs at the // start of a line unless the paragraph consists of // only zero width glyphs, because otherwise the zero // width glyphs will have been included on the end of // the previous line...) if (widthSoFar == 0 && glyph < fGlyphCount) { glyph += 1; } fLineEnd = previousBreak(fGlyphToCharMap[glyph]); // If this break is at or before the last one, // find a glyph, starting at the one which didn't // fit, that produces a break after the last one. while (fLineEnd <= fLineStart) { fLineEnd = fGlyphToCharMap[glyph++]; } } else { fLineEnd = fCharCount; } return computeVisualRuns(); } void ParagraphLayout::computeLevels(UBiDiLevel paragraphLevel) { UErrorCode bidiStatus = U_ZERO_ERROR; if (fLevelRuns != NULL) { le_int32 ch; le_int32 run; fEmbeddingLevels = LE_NEW_ARRAY(UBiDiLevel, fCharCount); for (ch = 0, run = 0; run < fLevelRuns->getCount(); run += 1) { UBiDiLevel runLevel = (UBiDiLevel) fLevelRuns->getValue(run) | UBIDI_LEVEL_OVERRIDE; le_int32 runLimit = fLevelRuns->getLimit(run); while (ch < runLimit) { fEmbeddingLevels[ch++] = runLevel; } } } fParaBidi = ubidi_openSized(fCharCount, 0, &bidiStatus); ubidi_setPara(fParaBidi, fChars, fCharCount, paragraphLevel, fEmbeddingLevels, &bidiStatus); if (fLevelRuns == NULL) { le_int32 levelRunCount = ubidi_countRuns(fParaBidi, &bidiStatus); ValueRuns *levelRuns = new ValueRuns(levelRunCount); le_int32 logicalStart = 0; le_int32 run; le_int32 limit; UBiDiLevel level; for (run = 0; run < levelRunCount; run += 1) { ubidi_getLogicalRun(fParaBidi, logicalStart, &limit, &level); levelRuns->add(level, limit); logicalStart = limit; } fLevelRuns = levelRuns; fClientLevels = FALSE; } } void ParagraphLayout::computeScripts() { UErrorCode scriptStatus = U_ZERO_ERROR; UScriptRun *sr = uscript_openRun(fChars, fCharCount, &scriptStatus); ValueRuns *scriptRuns = new ValueRuns(0); le_int32 limit; UScriptCode script; while (uscript_nextRun(sr, NULL, &limit, &script)) { scriptRuns->add(script, limit); } uscript_closeRun(sr); fScriptRuns = scriptRuns; fClientScripts = FALSE; } void ParagraphLayout::computeLocales() { LocaleRuns *localeRuns = new LocaleRuns(0); const Locale *defaultLocale = &Locale::getDefault(); localeRuns->add(defaultLocale, fCharCount); fLocaleRuns = localeRuns; fClientLocales = FALSE; } void ParagraphLayout::computeSubFonts(const FontRuns *fontRuns, LEErrorCode &status) { if (LE_FAILURE(status)) { return; } const RunArray *styleRunArrays[] = {fontRuns, fScriptRuns}; le_int32 styleCount = sizeof styleRunArrays / sizeof styleRunArrays[0]; StyleRuns styleRuns(styleRunArrays, styleCount); le_int32 styleRunCount = styleRuns.getRuns(NULL, NULL); le_int32 *styleRunLimits = LE_NEW_ARRAY(le_int32, styleRunCount); le_int32 *styleIndices = LE_NEW_ARRAY(le_int32, styleRunCount * styleCount); FontRuns *subFontRuns = new FontRuns(0); le_int32 run, offset, *si; styleRuns.getRuns(styleRunLimits, styleIndices); si = styleIndices; offset = 0; for (run = 0; run < styleRunCount; run += 1) { const LEFontInstance *runFont = fontRuns->getFont(si[0]); le_int32 script = fScriptRuns->getValue(si[1]); while (offset < styleRunLimits[run]) { const LEFontInstance *subFont = runFont->getSubFont(fChars, &offset, styleRunLimits[run], script, status); if (LE_FAILURE(status)) { delete subFontRuns; goto cleanUp; } subFontRuns->add(subFont, offset); } si += styleCount; } fFontRuns = subFontRuns; cleanUp: LE_DELETE_ARRAY(styleIndices); LE_DELETE_ARRAY(styleRunLimits); } void ParagraphLayout::computeMetrics() { le_int32 i, count = fFontRuns->getCount(); le_int32 maxDL = 0; for (i = 0; i < count; i += 1) { const LEFontInstance *font = fFontRuns->getFont(i); le_int32 ascent = font->getAscent(); le_int32 descent = font->getDescent(); le_int32 leading = font->getLeading(); le_int32 dl = descent + leading; if (ascent > fAscent) { fAscent = ascent; } if (descent > fDescent) { fDescent = descent; } if (leading > fLeading) { fLeading = leading; } if (dl > maxDL) { maxDL = dl; } } fLeading = maxDL - fDescent; } #if 1 struct LanguageMap { const char *localeCode; le_int32 languageCode; }; static const LanguageMap languageMap[] = { {"afr", afkLanguageCode}, // Afrikaans {"ara", araLanguageCode}, // Arabic {"asm", asmLanguageCode}, // Assamese {"bel", belLanguageCode}, // Belarussian {"ben", benLanguageCode}, // Bengali {"bod", tibLanguageCode}, // Tibetan {"bul", bgrLanguageCode}, // Bulgarian {"cat", catLanguageCode}, // Catalan {"ces", csyLanguageCode}, // Czech {"che", cheLanguageCode}, // Chechen {"cop", copLanguageCode}, // Coptic {"cym", welLanguageCode}, // Welsh {"dan", danLanguageCode}, // Danish {"deu", deuLanguageCode}, // German {"dzo", dznLanguageCode}, // Dzongkha {"ell", ellLanguageCode}, // Greek {"eng", engLanguageCode}, // English {"est", etiLanguageCode}, // Estonian {"eus", euqLanguageCode}, // Basque {"fas", farLanguageCode}, // Farsi {"fin", finLanguageCode}, // Finnish {"fra", fraLanguageCode}, // French {"gle", gaeLanguageCode}, // Irish Gaelic {"guj", gujLanguageCode}, // Gujarati {"hau", hauLanguageCode}, // Hausa {"heb", iwrLanguageCode}, // Hebrew {"hin", hinLanguageCode}, // Hindi {"hrv", hrvLanguageCode}, // Croatian {"hun", hunLanguageCode}, // Hungarian {"hye", hyeLanguageCode}, // Armenian {"ind", indLanguageCode}, // Indonesian {"ita", itaLanguageCode}, // Italian {"jpn", janLanguageCode}, // Japanese {"kan", kanLanguageCode}, // Kannada {"kas", kshLanguageCode}, // Kashmiri {"khm", khmLanguageCode}, // Khmer {"kok", kokLanguageCode}, // Konkani {"kor", korLanguageCode}, // Korean // {"mal_XXX", malLanguageCode}, // Malayalam - Traditional {"mal", mlrLanguageCode}, // Malayalam - Reformed {"mar", marLanguageCode}, // Marathi {"mlt", mtsLanguageCode}, // Maltese {"mni", mniLanguageCode}, // Manipuri {"mon", mngLanguageCode}, // Mongolian {"nep", nepLanguageCode}, // Nepali {"ori", oriLanguageCode}, // Oriya {"pol", plkLanguageCode}, // Polish {"por", ptgLanguageCode}, // Portuguese {"pus", pasLanguageCode}, // Pashto {"ron", romLanguageCode}, // Romanian {"rus", rusLanguageCode}, // Russian {"san", sanLanguageCode}, // Sanskrit {"sin", snhLanguageCode}, // Sinhalese {"slk", skyLanguageCode}, // Slovak {"snd", sndLanguageCode}, // Sindhi {"slv", slvLanguageCode}, // Slovenian {"spa", espLanguageCode}, // Spanish {"sqi", sqiLanguageCode}, // Albanian {"srp", srbLanguageCode}, // Serbian {"swe", sveLanguageCode}, // Swedish {"syr", syrLanguageCode}, // Syriac {"tam", tamLanguageCode}, // Tamil {"tel", telLanguageCode}, // Telugu {"tha", thaLanguageCode}, // Thai {"tur", trkLanguageCode}, // Turkish {"urd", urdLanguageCode}, // Urdu {"yid", jiiLanguageCode}, // Yiddish // {"zhp", zhpLanguageCode}, // Chinese - Phonetic {"zho", zhsLanguageCode}, // Chinese {"zho_CHN", zhsLanguageCode}, // Chinese - China {"zho_HKG", zhsLanguageCode}, // Chinese - Hong Kong {"zho_MAC", zhtLanguageCode}, // Chinese - Macao {"zho_SGP", zhsLanguageCode}, // Chinese - Singapore {"zho_TWN", zhtLanguageCode} // Chinese - Taiwan }; static const le_int32 languageMapCount = ARRAY_SIZE(languageMap); le_int32 ParagraphLayout::getLanguageCode(const Locale *locale) { char code[8] = {0, 0, 0, 0, 0, 0, 0, 0}; const char *language = locale->getISO3Language(); const char *country = locale->getISO3Country(); uprv_strcat(code, language); if ((uprv_strcmp(language, "zho") == 0) && country != NULL) { uprv_strcat(code, "_"); uprv_strcat(code, country); } for (le_int32 i = 0; i < languageMapCount; i += 1) { if (uprv_strcmp(code, languageMap[i].localeCode) == 0) { return languageMap[i].languageCode; } } return nullLanguageCode; } #else // TODO - dummy implementation for right now... le_int32 ParagraphLayout::getLanguageCode(const Locale *locale) { return nullLanguageCode; } #endif le_bool ParagraphLayout::isComplex(UScriptCode script) { if (script < 0 || script >= ARRAY_SIZE(complexTable)) { return FALSE; } return complexTable[script]; } le_int32 ParagraphLayout::previousBreak(le_int32 charIndex) { // skip over any whitespace or control characters, // because they can hang in the margin. while (charIndex < fCharCount && (u_isWhitespace(fChars[charIndex]) || u_iscntrl(fChars[charIndex]))) { charIndex += 1; } // Create the BreakIterator if we don't already have one if (fBreakIterator == NULL) { Locale thai("th"); UCharCharacterIterator *iter = new UCharCharacterIterator(fChars, fCharCount); UErrorCode status = U_ZERO_ERROR; fBreakIterator = BreakIterator::createLineInstance(thai, status); fBreakIterator->adoptText(iter); } // return the break location that's at or before // the character we stopped on. Note: if we're // on a break, the "+ 1" will cause preceding to // back up to it. return fBreakIterator->preceding(charIndex + 1); } ParagraphLayout::Line *ParagraphLayout::computeVisualRuns() { UErrorCode bidiStatus = U_ZERO_ERROR; le_int32 dirRunCount, visualRun; fVisualRunLastX = 0; fVisualRunLastY = 0; fFirstVisualRun = getCharRun(fLineStart); fLastVisualRun = getCharRun(fLineEnd - 1); if (fLineBidi == NULL) { fLineBidi = ubidi_openSized(fCharCount, 0, &bidiStatus); } ubidi_setLine(fParaBidi, fLineStart, fLineEnd, fLineBidi, &bidiStatus); dirRunCount = ubidi_countRuns(fLineBidi, &bidiStatus); Line *line = new Line(); for (visualRun = 0; visualRun < dirRunCount; visualRun += 1) { le_int32 relStart, run, runLength; UBiDiDirection runDirection = ubidi_getVisualRun(fLineBidi, visualRun, &relStart, &runLength); le_int32 runStart = fLineStart + relStart; le_int32 runEnd = runStart + runLength - 1; le_int32 firstRun = getCharRun(runStart); le_int32 lastRun = getCharRun(runEnd); le_int32 startRun = (runDirection == UBIDI_LTR)? firstRun : lastRun; le_int32 stopRun = (runDirection == UBIDI_LTR)? lastRun + 1 : firstRun - 1; le_int32 dir = (runDirection == UBIDI_LTR)? 1 : -1; for (run = startRun; run != stopRun; run += dir) { le_int32 firstChar = (run == firstRun)? runStart : fStyleRunInfo[run].runBase; le_int32 lastChar = (run == lastRun)? runEnd : fStyleRunInfo[run].runLimit - 1; appendRun(line, run, firstChar, lastChar); } } return line; } void ParagraphLayout::appendRun(ParagraphLayout::Line *line, le_int32 run, le_int32 firstChar, le_int32 lastChar) { le_int32 glyphBase = fStyleRunInfo[run].glyphBase; le_int32 inGlyph, outGlyph; // Get the glyph indices for all the characters between firstChar and lastChar, // make the minimum one be leftGlyph and the maximum one be rightGlyph. // (need to do this to handle local reorderings like Indic left matras) le_int32 leftGlyph = fGlyphCount; le_int32 rightGlyph = -1; le_int32 ch; for (ch = firstChar; ch <= lastChar; ch += 1) { le_int32 minGlyph = fCharToMinGlyphMap[ch]; le_int32 maxGlyph = fCharToMaxGlyphMap[ch]; if (minGlyph < leftGlyph) { leftGlyph = minGlyph; } if (maxGlyph > rightGlyph) { rightGlyph = maxGlyph; } } if ((fStyleRunInfo[run].level & 1) != 0) { le_int32 swap = rightGlyph; le_int32 last = glyphBase + fStyleRunInfo[run].glyphCount - 1; // Here, we want to remove the glyphBase bias... rightGlyph = last - leftGlyph; leftGlyph = last - swap; } else { rightGlyph -= glyphBase; leftGlyph -= glyphBase; } // Set the position bias for the glyphs. If we're at the start of // a line, we want the first glyph to be at x = 0, even if it comes // from the middle of a layout. If we've got a right-to-left run, we // want the left-most glyph to start at the final x position of the // previous run, even though this glyph may be in the middle of the // run. fVisualRunLastX -= fStyleRunInfo[run].positions[leftGlyph * 2]; // Make rightGlyph be the glyph just to the right of // the run's glyphs rightGlyph += 1; UBiDiDirection direction = ((fStyleRunInfo[run].level & 1) == 0)? UBIDI_LTR : UBIDI_RTL; le_int32 glyphCount = rightGlyph - leftGlyph; LEGlyphID *glyphs = LE_NEW_ARRAY(LEGlyphID, glyphCount); float *positions = LE_NEW_ARRAY(float, glyphCount * 2 + 2); le_int32 *glyphToCharMap = LE_NEW_ARRAY(le_int32, glyphCount); LE_ARRAY_COPY(glyphs, &fStyleRunInfo[run].glyphs[leftGlyph], glyphCount); for (outGlyph = 0, inGlyph = leftGlyph * 2; inGlyph <= rightGlyph * 2; inGlyph += 2, outGlyph += 2) { positions[outGlyph] = fStyleRunInfo[run].positions[inGlyph] + fVisualRunLastX; positions[outGlyph + 1] = fStyleRunInfo[run].positions[inGlyph + 1] + fVisualRunLastY; } // Save the ending position of this run // to use for the start of the next run fVisualRunLastX = positions[outGlyph - 2]; fVisualRunLastY = positions[outGlyph - 1]; if ((fStyleRunInfo[run].level & 1) == 0) { for (outGlyph = 0, inGlyph = leftGlyph; inGlyph < rightGlyph; inGlyph += 1, outGlyph += 1) { glyphToCharMap[outGlyph] = fGlyphToCharMap[glyphBase + inGlyph]; } } else { // Because fGlyphToCharMap is stored in logical order to facilitate line breaking, // we need to map the physical glyph indices to logical indices while we copy the // character indices. le_int32 base = glyphBase + fStyleRunInfo[run].glyphCount - 1; for (outGlyph = 0, inGlyph = leftGlyph; inGlyph < rightGlyph; inGlyph += 1, outGlyph += 1) { glyphToCharMap[outGlyph] = fGlyphToCharMap[base - inGlyph]; } } line->append(fStyleRunInfo[run].font, direction, glyphCount, glyphs, positions, glyphToCharMap); } le_int32 ParagraphLayout::getCharRun(le_int32 charIndex) { if (charIndex < 0 || charIndex > fCharCount) { return -1; } le_int32 run; // NOTE: as long as fStyleRunLimits is well-formed // the above range check guarantees that we'll never // fall off the end of the array. run = 0; while (charIndex >= fStyleRunLimits[run]) { run += 1; } return run; } const char ParagraphLayout::Line::fgClassID = 0; #define INITIAL_RUN_CAPACITY 4 #define RUN_CAPACITY_GROW_LIMIT 16 ParagraphLayout::Line::~Line() { le_int32 i; for (i = 0; i < fRunCount; i += 1) { delete fRuns[i]; } LE_DELETE_ARRAY(fRuns); } le_int32 ParagraphLayout::Line::getAscent() const { if (fAscent <= 0) { ((ParagraphLayout::Line *)this)->computeMetrics(); } return fAscent; } le_int32 ParagraphLayout::Line::getDescent() const { if (fAscent <= 0) { ((ParagraphLayout::Line *)this)->computeMetrics(); } return fDescent; } le_int32 ParagraphLayout::Line::getLeading() const { if (fAscent <= 0) { ((ParagraphLayout::Line *)this)->computeMetrics(); } return fLeading; } le_int32 ParagraphLayout::Line::getWidth() const { const VisualRun *lastRun = getVisualRun(fRunCount - 1); if (lastRun == NULL) { return 0; } le_int32 glyphCount = lastRun->getGlyphCount(); const float *positions = lastRun->getPositions(); return (le_int32) positions[glyphCount * 2]; } const ParagraphLayout::VisualRun *ParagraphLayout::Line::getVisualRun(le_int32 runIndex) const { if (runIndex < 0 || runIndex >= fRunCount) { return NULL; } return fRuns[runIndex]; } void ParagraphLayout::Line::append(const LEFontInstance *font, UBiDiDirection direction, le_int32 glyphCount, const LEGlyphID glyphs[], const float positions[], const le_int32 glyphToCharMap[]) { if (fRunCount >= fRunCapacity) { if (fRunCapacity == 0) { fRunCapacity = INITIAL_RUN_CAPACITY; fRuns = LE_NEW_ARRAY(ParagraphLayout::VisualRun *, fRunCapacity); } else { fRunCapacity += (fRunCapacity < RUN_CAPACITY_GROW_LIMIT? fRunCapacity : RUN_CAPACITY_GROW_LIMIT); fRuns = (ParagraphLayout::VisualRun **) LE_GROW_ARRAY(fRuns, fRunCapacity); } } fRuns[fRunCount++] = new ParagraphLayout::VisualRun(font, direction, glyphCount, glyphs, positions, glyphToCharMap); } void ParagraphLayout::Line::computeMetrics() { le_int32 maxDL = 0; for (le_int32 i = 0; i < fRunCount; i += 1) { le_int32 ascent = fRuns[i]->getAscent(); le_int32 descent = fRuns[i]->getDescent(); le_int32 leading = fRuns[i]->getLeading(); le_int32 dl = descent + leading; if (ascent > fAscent) { fAscent = ascent; } if (descent > fDescent) { fDescent = descent; } if (leading > fLeading) { fLeading = leading; } if (dl > maxDL) { maxDL = dl; } } fLeading = maxDL - fDescent; } const char ParagraphLayout::VisualRun::fgClassID = 0; ParagraphLayout::VisualRun::~VisualRun() { LE_DELETE_ARRAY(fGlyphToCharMap); LE_DELETE_ARRAY(fPositions); LE_DELETE_ARRAY(fGlyphs); } U_NAMESPACE_END #endif