| // © 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 |
| |