C++程序  |  1280行  |  38.05 KB

/*
 **********************************************************************
 *   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[scriptCodeCount] = {
    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 (runStart = 0, 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 >= (UScriptCode) scriptCodeCount) {
        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