/* ******************************************************************************* * * Copyright (C) 1999-2001, International Business Machines * Corporation and others. All Rights Reserved. * ******************************************************************************* * file name: Paragraph.cpp * * created on: 09/06/2000 * created by: Eric R. Mader */ #include "unicode/loengine.h" #include "RenderingFontInstance.h" #include "unicode/utypes.h" #include "unicode/unicode.h" #include "unicode/uchriter.h" #include "unicode/brkiter.h" #include "unicode/locid.h" #include "unicode/ubidi.h" #include "paragraph.h" #include "scrptrun.h" #include "UnicodeReader.h" #include "FontMap.h" #define MARGIN 10 Paragraph::Paragraph(void *surface, RunParams params[], int32_t count, UBiDi *bidi) : fBidi(bidi), fRunCount(count), fRunInfo(NULL), fCharCount(0), fText(NULL), fGlyphCount(0), fGlyphs(NULL), fCharIndices(NULL), fGlyphIndices(NULL), fDX(NULL), fBreakArray(NULL), fBreakCount(0), fLineHeight(-1), fAscent(-1) { int32_t i; fWidth = fHeight = 0; fRunInfo = new RunInfo[count + 1]; // Set charBase and rightToLeft for // each run and count the total characters for (i = 0; i < count; i += 1) { fRunInfo[i].charBase = fCharCount; fRunInfo[i].rightToLeft = params[i].rightToLeft; fCharCount += params[i].count; } // Set charBase and rightToLeft for the // fake run at the end. fRunInfo[count].charBase = fCharCount; fRunInfo[count].rightToLeft = false; fBreakArray = new int32_t[fCharCount + 1]; fText = new LEUnicode[fCharCount]; // Copy the text runs into a single array for (i = 0; i < count; i += 1) { int32_t charBase = fRunInfo[i].charBase; int32_t charCount = fRunInfo[i + 1].charBase - charBase; LE_ARRAY_COPY(&fText[charBase], params[i].text, charCount); } Locale thai("th"); UCharCharacterIterator *iter = new UCharCharacterIterator(fText, fCharCount); UErrorCode status = U_ZERO_ERROR; Locale dummyLocale; fBrkiter = BreakIterator::createLineInstance(thai, status); fBrkiter->adoptText(iter); ICULayoutEngine **engines = new ICULayoutEngine *[count]; int32_t maxAscent = -1, maxDescent = -1, maxLeading = -1; float x = 0, y = 0; // Layout each run, set glyphBase and glyphCount // and count the total number of glyphs for (i = 0; i < count; i += 1) { int32_t charBase = fRunInfo[i].charBase; int32_t charCount = fRunInfo[i + 1].charBase - charBase; int32_t glyphCount = 0; int32_t runAscent = 0, runDescent = 0, runLeading = 0; UErrorCode success = U_ZERO_ERROR; fRunInfo[i].fontInstance = params[i].fontInstance; fRunInfo[i].fontInstance->setFont(surface); runAscent = fRunInfo[i].fontInstance->getAscent(); runDescent = fRunInfo[i].fontInstance->getDescent(); runLeading = fRunInfo[i].fontInstance->getLeading(); if (runAscent > maxAscent) { maxAscent = runAscent; } if (runDescent > maxDescent) { maxDescent = runDescent; } if (runLeading > maxLeading) { maxLeading = runLeading; } engines[i] = ICULayoutEngine::createInstance(fRunInfo[i].fontInstance, params[i].scriptCode, dummyLocale, success); glyphCount = engines[i]->layoutChars(fText, charBase, charBase + charCount, fCharCount, fRunInfo[i].rightToLeft, x, y, success); engines[i]->getGlyphPosition(glyphCount, x, y, success); fRunInfo[i].glyphBase = fGlyphCount; fGlyphCount += glyphCount; } fLineHeight = maxAscent + maxDescent + maxLeading; fAscent = maxAscent; // Set glyphBase for the fake run at the end fRunInfo[count].glyphBase = fGlyphCount; fGlyphs = new LEGlyphID[fGlyphCount]; fCharIndices = new int32_t[fGlyphCount]; fGlyphIndices = new int32_t[fCharCount + 1]; fDX = new int32_t[fGlyphCount]; fDY = new int32_t[fGlyphCount]; float *positions = new float[fGlyphCount * 2 + 2]; // Build the glyph, charIndices and positions arrays for (i = 0; i < count; i += 1) { ICULayoutEngine *engine = engines[i]; int32_t charBase = fRunInfo[i].charBase; int32_t glyphBase = fRunInfo[i].glyphBase; UErrorCode success = U_ZERO_ERROR; engine->getGlyphs(&fGlyphs[glyphBase], success); engine->getCharIndices(&fCharIndices[glyphBase], charBase, success); engine->getGlyphPositions(&positions[glyphBase * 2], success); } // Filter deleted glyphs, compute logical advances // and set the char to glyph map for (i = 0; i < fGlyphCount; i += 1) { // Filter deleted glyphs if (fGlyphs[i] == 0xFFFE || fGlyphs[i] == 0xFFFF) { fGlyphs[i] = 0x0001; } // compute the logical advance fDX[i] = (int32_t) (positions[i * 2 + 2] - positions[i * 2]); // save the Y offset fDY[i] = (int32_t) positions[i * 2 + 1]; // set char to glyph map fGlyphIndices[fCharIndices[i]] = i; } if (fRunInfo[count - 1].rightToLeft) { fGlyphIndices[fCharCount] = fRunInfo[count - 1].glyphBase - 1; } else { fGlyphIndices[fCharCount] = fGlyphCount; } delete[] positions; // Get rid of the LayoutEngine's: for (i = 0; i < count; i += 1) { delete engines[i]; } delete[] engines; } Paragraph::~Paragraph() { delete[] fDY; delete[] fDX; delete[] fGlyphIndices; delete[] fCharIndices; delete[] fGlyphs; delete fBrkiter; delete fText; delete[] fBreakArray; delete[] fRunInfo; ubidi_close(fBidi); } int32_t Paragraph::getLineHeight() { return fLineHeight; } int32_t Paragraph::getLineCount() { return fBreakCount; } int32_t Paragraph::getAscent() { return fAscent; } int32_t Paragraph::previousBreak(int32_t charIndex) { LEUnicode ch = fText[charIndex]; // skip over any whitespace or control // characters, because they can hang in // the margin. while (charIndex < fCharCount && (Unicode::isWhitespace(ch) || Unicode::isControl(ch))) { ch = fText[++charIndex]; } // 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 fBrkiter->preceding(charIndex + 1); } void Paragraph::breakLines(int32_t width, int32_t height) { int32_t lineWidth = width - (2 * MARGIN); int32_t thisWidth = 0; int32_t thisBreak = -1; int32_t prevWidth = fWidth; fWidth = width; fHeight = height; // don't re-break if the width hasn't changed if (width == prevWidth) { return; } fBreakArray[0] = 0; fBreakCount = 1; for (int32_t run = 0; run < fRunCount; run += 1) { int32_t glyph = fRunInfo[run].glyphBase; int32_t stop = fRunInfo[run + 1].glyphBase; int32_t dir = 1; if (fRunInfo[run].rightToLeft) { glyph = stop - 1; stop = fRunInfo[run].glyphBase - 1; dir = -1; } while (glyph != stop) { // Find the first glyph that doesn't fit on the line while (thisWidth + fDX[glyph] <= lineWidth) { thisWidth += fDX[glyph]; glyph += dir; if (glyph == stop) { break; } } // Check to see if we fell off the // end of the run if (glyph == stop) { break; } // Find a place before here to break, thisBreak = previousBreak(fCharIndices[glyph]); // If there wasn't one, force one if (thisBreak <= fBreakArray[fBreakCount - 1]) { thisBreak = fCharIndices[glyph]; } // Save the break location. fBreakArray[fBreakCount++] = thisBreak; // Reset the accumulated width thisWidth = 0; // Map the character back to a glyph glyph = fGlyphIndices[thisBreak]; // Check to see if the new glyph is off // the end of the run. if (glyph == stop) { break; } // If the glyph's not in the run we stopped in, we // have to re-synch to the new run if (glyph < fRunInfo[run].glyphBase || glyph >= fRunInfo[run + 1].glyphBase) { run = getGlyphRun(glyph, 0, 1); if (fRunInfo[run].rightToLeft) { stop = fRunInfo[run].glyphBase - 1; dir = -1; } else { stop = fRunInfo[run + 1].glyphBase; dir = 1; } } } } // Make sure the last break is after the last character if (fBreakArray[--fBreakCount] != fCharCount) { fBreakArray[++fBreakCount] = fCharCount; } return; } int32_t Paragraph::getGlyphRun(int32_t glyph, int32_t startingRun, int32_t direction) { int32_t limit; if (direction < 0) { limit = -1; } else { limit = fRunCount; } for (int32_t run = startingRun; run != limit; run += direction) { if (glyph >= fRunInfo[run].glyphBase && glyph < fRunInfo[run + 1].glyphBase) { return run; } } return limit; } int32_t Paragraph::getCharRun(int32_t ch, int32_t startingRun, int32_t direction) { int32_t limit; if (direction < 0) { limit = -1; } else { limit = fRunCount; } for (int32_t run = startingRun; run != limit; run += direction) { if (ch >= fRunInfo[run].charBase && ch < fRunInfo[run + 1].charBase) { return run; } } return limit; } int32_t Paragraph::getRunWidth(int32_t startGlyph, int32_t endGlyph) { int32_t width = 0; for (int32_t glyph = startGlyph; glyph <= endGlyph; glyph += 1) { width += fDX[glyph]; } return width; } int32_t Paragraph::drawRun(void *surface, const RenderingFontInstance *fontInstance, int32_t firstChar, int32_t lastChar, int32_t x, int32_t y) { int32_t firstGlyph = fGlyphIndices[firstChar]; int32_t lastGlyph = fGlyphIndices[lastChar]; for (int32_t ch = firstChar; ch <= lastChar; ch += 1) { int32_t glyph = fGlyphIndices[ch]; if (glyph < firstGlyph) { firstGlyph = glyph; } if (glyph > lastGlyph) { lastGlyph = glyph; } } int32_t dyStart = firstGlyph, dyEnd = dyStart; fontInstance->setFont(surface); while (dyEnd <= lastGlyph) { while (dyEnd <= lastGlyph && fDY[dyStart] == fDY[dyEnd]) { dyEnd += 1; } fontInstance->drawGlyphs(surface, &fGlyphs[dyStart], dyEnd - dyStart, &fDX[dyStart], x, y + fDY[dyStart], fWidth, fHeight); dyStart = dyEnd; } return getRunWidth(firstGlyph, lastGlyph); } void Paragraph::draw(void *surface, int32_t firstLine, int32_t lastLine) { int32_t line, x, y; int32_t prevRun = 0; UErrorCode bidiStatus = U_ZERO_ERROR; UBiDi *lBidi = ubidi_openSized(fCharCount, 0, &bidiStatus); y = fAscent; for (line = firstLine; line <= lastLine; line += 1) { int32_t firstChar = fBreakArray[line]; int32_t lastChar = fBreakArray[line + 1] - 1; int32_t dirCount, dirRun; x = MARGIN; ubidi_setLine(fBidi, firstChar, lastChar + 1, lBidi, &bidiStatus); dirCount = ubidi_countRuns(lBidi, &bidiStatus); for (dirRun = 0; dirRun < dirCount; dirRun += 1) { int32_t relStart = 0, runLength = 0; UBiDiDirection runDirection = ubidi_getVisualRun(lBidi, dirRun, &relStart, &runLength); int32_t runStart = relStart + firstChar; int32_t runEnd = runStart + runLength - 1; int32_t firstRun = getCharRun(runStart, prevRun, 1); int32_t lastRun = getCharRun(runEnd, firstRun, 1); for (int32_t run = firstRun; run <= lastRun; run += 1) { const RenderingFontInstance *fontInstance = fRunInfo[run].fontInstance; int32_t nextBase; if (run == lastRun) { nextBase = runEnd + 1; } else { nextBase = fRunInfo[run + 1].charBase; } x += drawRun(surface, fontInstance, runStart, nextBase - 1, x, y); runStart = nextBase; } prevRun = lastRun; } y += fLineHeight; } ubidi_close(lBidi); } Paragraph *Paragraph::paragraphFactory(const char *fileName, FontMap *fontMap, GUISupport *guiSupport, void *surface) { RunParams params[64]; int32_t paramCount = 0; int32_t charCount = 0; int32_t dirCount = 0; int32_t dirRun = 0; RFIErrorCode fontStatus = RFI_NO_ERROR; UErrorCode bidiStatus = U_ZERO_ERROR; const UChar *text = UnicodeReader::readFile(fileName, guiSupport, charCount); ScriptRun scriptRun(text, charCount); if (text == NULL) { return NULL; } UBiDi *pBidi = ubidi_openSized(charCount, 0, &bidiStatus); ubidi_setPara(pBidi, text, charCount, UBIDI_DEFAULT_LTR, NULL, &bidiStatus); dirCount = ubidi_countRuns(pBidi, &bidiStatus); for (dirRun = 0; dirRun < dirCount; dirRun += 1) { int32_t runStart = 0, runLength = 0; UBiDiDirection runDirection = ubidi_getVisualRun(pBidi, dirRun, &runStart, &runLength); scriptRun.reset(runStart, runLength); while (scriptRun.next()) { int32_t start = scriptRun.getScriptStart(); int32_t end = scriptRun.getScriptEnd(); UScriptCode code = scriptRun.getScriptCode(); params[paramCount].text = &((UChar *) text)[start]; params[paramCount].count = end - start; params[paramCount].scriptCode = (UScriptCode) code; params[paramCount].rightToLeft = runDirection == UBIDI_RTL; params[paramCount].fontInstance = fontMap->getScriptFont(code, fontStatus); if (params[paramCount].fontInstance == NULL) { ubidi_close(pBidi); return 0; } paramCount += 1; } } return new Paragraph(surface, params, paramCount, pBidi); }