float Font::width(const TextRun& run, HashSet<const SimpleFontData*>* fallbackFonts, GlyphOverflow* glyphOverflow) const { CodePath codePathToUse = codePath(run); if (codePathToUse != Complex) { // The complex path is more restrictive about returning fallback fonts than the simple path, so we need an explicit test to make their behaviors match. if (!canReturnFallbackFontsForComplexText()) fallbackFonts = 0; // The simple path can optimize the case where glyph overflow is not observable. if (codePathToUse != SimpleWithGlyphOverflow && (glyphOverflow && !glyphOverflow->computeBounds)) glyphOverflow = 0; } bool hasKerningOrLigatures = typesettingFeatures() & (Kerning | Ligatures); bool hasWordSpacingOrLetterSpacing = wordSpacing() | letterSpacing(); float* cacheEntry = m_fontFallbackList->widthCache().add(run, std::numeric_limits<float>::quiet_NaN(), hasKerningOrLigatures, hasWordSpacingOrLetterSpacing, glyphOverflow); if (cacheEntry && !std::isnan(*cacheEntry)) return *cacheEntry; float result; if (codePathToUse == Complex) result = floatWidthForComplexText(run, fallbackFonts, glyphOverflow); else result = floatWidthForSimpleText(run, fallbackFonts, glyphOverflow); if (cacheEntry && (!fallbackFonts || fallbackFonts->isEmpty())) *cacheEntry = result; return result; }
float Font::floatWidthForComplexText(const TextRun& run, HashSet<const SimpleFontData*>* /* fallbackFonts */, GlyphOverflow* /* glyphOverflow */) const { TextRunWalker walker(run, 0, this); walker.setWordSpacingAdjustment(wordSpacing()); walker.setLetterSpacingAdjustment(letterSpacing()); return walker.widthOfFullRun(); }
float Font::floatWidthForComplexText(const TextRun& run, HashSet<const SimpleFontData*>* /* fallbackFonts */, GlyphOverflow* /* glyphOverflow */) const { ComplexTextController controller(run, 0, 0, this); controller.setWordSpacingAdjustment(wordSpacing()); controller.setLetterSpacingAdjustment(letterSpacing()); controller.setPadding(run.expansion()); return controller.widthOfFullRun(); }
void Font::drawComplexText(GraphicsContext* gc, const TextRun& run, const FloatPoint& point, int from, int to) const { if (!run.length()) return; SkCanvas* canvas = gc->platformContext()->canvas(); TextDrawingModeFlags textMode = gc->platformContext()->getTextDrawingMode(); bool fill = textMode & TextModeFill; bool stroke = (textMode & TextModeStroke) && gc->platformContext()->getStrokeStyle() != NoStroke && gc->platformContext()->getStrokeThickness() > 0; if (!fill && !stroke) return; SkPaint strokePaint, fillPaint; if (fill) { gc->platformContext()->setupPaintForFilling(&fillPaint); setupForTextPainting(&fillPaint, gc->fillColor().rgb()); } if (stroke) { gc->platformContext()->setupPaintForStroking(&strokePaint, 0, 0); setupForTextPainting(&strokePaint, gc->strokeColor().rgb()); } ComplexTextController controller(run, point.x(), point.y(), this); controller.setWordSpacingAdjustment(wordSpacing()); controller.setLetterSpacingAdjustment(letterSpacing()); controller.setPadding(run.expansion()); if (run.rtl()) { // FIXME: this causes us to shape the text twice -- once to compute the width and then again // below when actually rendering. Change ComplexTextController to match platform/mac and // platform/chromium/win by having it store the shaped runs, so we can reuse the results. controller.reset(point.x() + controller.widthOfFullRun()); // We need to set the padding again because ComplexTextController layout consumed the value. // Fixing the above problem would help here too. controller.setPadding(run.expansion()); } while (controller.nextScriptRun()) { if (fill) { controller.fontPlatformDataForScriptRun()->setupPaint(&fillPaint); adjustTextRenderMode(&fillPaint, gc->platformContext()); canvas->drawPosText(controller.glyphs(), controller.length() << 1, controller.positions(), fillPaint); } if (stroke) { controller.fontPlatformDataForScriptRun()->setupPaint(&strokePaint); adjustTextRenderMode(&strokePaint, gc->platformContext()); canvas->drawPosText(controller.glyphs(), controller.length() << 1, controller.positions(), strokePaint); } } }
// Return the code point index for the given |x| offset into the text run. int Font::offsetForPositionForComplexText(const TextRun& run, float xFloat, bool includePartialGlyphs) const { // FIXME: This truncation is not a problem for HTML, but only affects SVG, which passes floating-point numbers // to Font::offsetForPosition(). Bug http://webkit.org/b/40673 tracks fixing this problem. int targetX = static_cast<int>(xFloat); // (Mac code ignores includePartialGlyphs, and they don't know what it's // supposed to do, so we just ignore it as well.) ComplexTextController controller(run, 0, 0, this); controller.setWordSpacingAdjustment(wordSpacing()); controller.setLetterSpacingAdjustment(letterSpacing()); controller.setPadding(run.expansion()); if (run.rtl()) { // See FIXME in drawComplexText. controller.reset(controller.widthOfFullRun()); controller.setPadding(run.expansion()); } unsigned basePosition = 0; int x = controller.offsetX(); while (controller.nextScriptRun()) { int nextX = controller.offsetX(); if (std::min(x, nextX) <= targetX && targetX <= std::max(x, nextX)) { // The x value in question is within this script run. const int glyphIndex = glyphIndexForXPositionInScriptRun(controller, targetX); // Now that we have a glyph index, we have to turn that into a // code-point index. Because of ligatures, several code-points may // have gone into a single glyph. We iterate over the clusters log // and find the first code-point which contributed to the glyph. // Some shapers (i.e. Khmer) will produce cluster logs which report // that /no/ code points contributed to certain glyphs. Because of // this, we take any code point which contributed to the glyph in // question, or any subsequent glyph. If we run off the end, then // we take the last code point. const unsigned short* log = controller.logClusters(); for (unsigned j = 0; j < controller.numCodePoints(); ++j) { if (log[j] >= glyphIndex) return basePosition + j; } return basePosition + controller.numCodePoints() - 1; } basePosition += controller.numCodePoints(); } return basePosition; }
// Return the rectangle for selecting the given range of code-points in the TextRun. FloatRect Font::selectionRectForComplexText(const TextRun& run, const FloatPoint& point, int height, int from, int to) const { int fromX = -1, toX = -1; ComplexTextController controller(run, 0, 0, this); controller.setWordSpacingAdjustment(wordSpacing()); controller.setLetterSpacingAdjustment(letterSpacing()); controller.setPadding(run.expansion()); if (run.rtl()) { // See FIXME in drawComplexText. controller.reset(controller.widthOfFullRun()); controller.setPadding(run.expansion()); } // Iterate through the script runs in logical order, searching for the run covering the positions of interest. while (controller.nextScriptRun() && (fromX == -1 || toX == -1)) { if (fromX == -1 && from >= 0 && static_cast<unsigned>(from) < controller.numCodePoints()) { // |from| is within this script run. So we index the clusters log to // find which glyph this code-point contributed to and find its x // position. int glyph = controller.logClusters()[from]; fromX = controller.positions()[glyph].x(); if (controller.rtl()) fromX += truncateFixedPointToInteger(controller.advances()[glyph]); } else from -= controller.numCodePoints(); if (toX == -1 && to >= 0 && static_cast<unsigned>(to) < controller.numCodePoints()) { int glyph = controller.logClusters()[to]; toX = controller.positions()[glyph].x(); if (controller.rtl()) toX += truncateFixedPointToInteger(controller.advances()[glyph]); } else to -= controller.numCodePoints(); } // The position in question might be just after the text. if (fromX == -1) fromX = controller.offsetX(); if (toX == -1) toX = controller.offsetX(); ASSERT(fromX != -1 && toX != -1); if (fromX < toX) return FloatRect(point.x() + fromX, point.y(), toX - fromX, height); return FloatRect(point.x() + toX, point.y(), fromX - toX, height); }
void Font::drawComplexText(GraphicsContext* gc, const TextRun& run, const FloatPoint& point, int from, int to) const { if (!run.length()) return; SkCanvas* canvas = gc->platformContext()->canvas(); int textMode = gc->platformContext()->getTextDrawingMode(); bool fill = textMode & cTextFill; bool stroke = (textMode & cTextStroke) && gc->platformContext()->getStrokeStyle() != NoStroke && gc->platformContext()->getStrokeThickness() > 0; if (!fill && !stroke) return; SkPaint strokePaint, fillPaint; if (fill) { gc->platformContext()->setupPaintForFilling(&fillPaint); setupForTextPainting(&fillPaint, gc->fillColor().rgb()); } if (stroke) { gc->platformContext()->setupPaintForStroking(&strokePaint, 0, 0); setupForTextPainting(&strokePaint, gc->strokeColor().rgb()); } TextRunWalker walker(run, point.x(), this); walker.setWordSpacingAdjustment(wordSpacing()); walker.setLetterSpacingAdjustment(letterSpacing()); walker.setPadding(run.padding()); while (walker.nextScriptRun()) { if (fill) { walker.fontPlatformDataForScriptRun()->setupPaint(&fillPaint); adjustTextRenderMode(&fillPaint, gc->platformContext()); canvas->drawPosTextH(walker.glyphs(), walker.length() << 1, walker.xPositions(), point.y(), fillPaint); } if (stroke) { walker.fontPlatformDataForScriptRun()->setupPaint(&strokePaint); adjustTextRenderMode(&strokePaint, gc->platformContext()); canvas->drawPosTextH(walker.glyphs(), walker.length() << 1, walker.xPositions(), point.y(), strokePaint); } } }
Font::CodePath Font::codePath(const TextRun& run) const { if (s_codePath != Auto) return s_codePath; #if ENABLE(SVG_FONTS) if (run.renderingContext()) return Simple; #endif #if PLATFORM(QT) && !HAVE(QRAWFONT) if (run.expansion() || run.rtl() || isSmallCaps() || wordSpacing() || letterSpacing()) return Complex; #endif if (m_fontDescription.featureSettings() && m_fontDescription.featureSettings()->size() > 0) return Complex; CodePath result = Simple; // Start from 0 since drawing and highlighting also measure the characters before run->from // FIXME: Should use a UnicodeSet in ports where ICU is used. Note that we // can't simply use UnicodeCharacter Property/class because some characters // are not 'combining', but still need to go to the complex path. // Alternatively, we may as well consider binary search over a sorted // list of ranges. for (int i = 0; i < run.length(); i++) { const UChar c = run[i]; if (c < 0x2E5) // U+02E5 through U+02E9 (Modifier Letters : Tone letters) continue; if (c <= 0x2E9) return Complex; if (c < 0x300) // U+0300 through U+036F Combining diacritical marks continue; if (c <= 0x36F) return Complex; if (c < 0x0591 || c == 0x05BE) // U+0591 through U+05CF excluding U+05BE Hebrew combining marks, Hebrew punctuation Paseq, Sof Pasuq and Nun Hafukha continue; if (c <= 0x05CF) return Complex; // U+0600 through U+109F Arabic, Syriac, Thaana, NKo, Samaritan, Mandaic, // Devanagari, Bengali, Gurmukhi, Gujarati, Oriya, Tamil, Telugu, Kannada, // Malayalam, Sinhala, Thai, Lao, Tibetan, Myanmar if (c < 0x0600) continue; if (c <= 0x109F) return Complex; // U+1100 through U+11FF Hangul Jamo (only Ancient Korean should be left here if you precompose; // Modern Korean will be precomposed as a result of step A) if (c < 0x1100) continue; if (c <= 0x11FF) return Complex; if (c < 0x135D) // U+135D through U+135F Ethiopic combining marks continue; if (c <= 0x135F) return Complex; if (c < 0x1700) // U+1780 through U+18AF Tagalog, Hanunoo, Buhid, Taghanwa,Khmer, Mongolian continue; if (c <= 0x18AF) return Complex; if (c < 0x1900) // U+1900 through U+194F Limbu (Unicode 4.0) continue; if (c <= 0x194F) return Complex; if (c < 0x1980) // U+1980 through U+19DF New Tai Lue continue; if (c <= 0x19DF) return Complex; if (c < 0x1A00) // U+1A00 through U+1CFF Buginese, Tai Tham, Balinese, Batak, Lepcha, Vedic continue; if (c <= 0x1CFF) return Complex; if (c < 0x1DC0) // U+1DC0 through U+1DFF Comining diacritical mark supplement continue; if (c <= 0x1DFF) return Complex; // U+1E00 through U+2000 characters with diacritics and stacked diacritics if (c <= 0x2000) { result = SimpleWithGlyphOverflow; continue; } if (c < 0x20D0) // U+20D0 through U+20FF Combining marks for symbols continue; if (c <= 0x20FF) return Complex; if (c < 0x2CEF) // U+2CEF through U+2CF1 Combining marks for Coptic continue; if (c <= 0x2CF1) return Complex; if (c < 0x302A) // U+302A through U+302F Ideographic and Hangul Tone marks continue; if (c <= 0x302F) return Complex; if (c < 0xA67C) // U+A67C through U+A67D Combining marks for old Cyrillic continue; if (c <= 0xA67D) return Complex; if (c < 0xA6F0) // U+A6F0 through U+A6F1 Combining mark for Bamum continue; if (c <= 0xA6F1) return Complex; // U+A800 through U+ABFF Nagri, Phags-pa, Saurashtra, Devanagari Extended, // Hangul Jamo Ext. A, Javanese, Myanmar Extended A, Tai Viet, Meetei Mayek, if (c < 0xA800) continue; if (c <= 0xABFF) return Complex; if (c < 0xD7B0) // U+D7B0 through U+D7FF Hangul Jamo Ext. B continue; if (c <= 0xD7FF) return Complex; if (c <= 0xDBFF) { // High surrogate if (i == run.length() - 1) continue; UChar next = run[++i]; if (!U16_IS_TRAIL(next)) continue; UChar32 supplementaryCharacter = U16_GET_SUPPLEMENTARY(c, next); if (supplementaryCharacter < 0x1F1E6) // U+1F1E6 through U+1F1FF Regional Indicator Symbols continue; if (supplementaryCharacter <= 0x1F1FF) return Complex; if (supplementaryCharacter < 0xE0100) // U+E0100 through U+E01EF Unicode variation selectors. continue; if (supplementaryCharacter <= 0xE01EF) return Complex; // FIXME: Check for Brahmi (U+11000 block), Kaithi (U+11080 block) and other complex scripts // in plane 1 or higher. continue; } if (c < 0xFE00) // U+FE00 through U+FE0F Unicode variation selectors continue; if (c <= 0xFE0F) return Complex; if (c < 0xFE20) // U+FE20 through U+FE2F Combining half marks continue; if (c <= 0xFE2F) return Complex; } if (run.length() > 1 && typesettingFeatures()) return Complex; return result; }
Font::CodePath Font::codePath(const TextRun& run) const { if (s_codePath != Auto) return s_codePath; #if PLATFORM(QT) if (run.expansion() || run.rtl() || isSmallCaps() || wordSpacing() || letterSpacing()) return Complex; #endif CodePath result = Simple; // Start from 0 since drawing and highlighting also measure the characters before run->from for (int i = 0; i < run.length(); i++) { const UChar c = run[i]; if (c < 0x300) // U+0300 through U+036F Combining diacritical marks continue; if (c <= 0x36F) return Complex; if (c < 0x0591 || c == 0x05BE) // U+0591 through U+05CF excluding U+05BE Hebrew combining marks, Hebrew punctuation Paseq, Sof Pasuq and Nun Hafukha continue; if (c <= 0x05CF) return Complex; if (c < 0x0600) // U+0600 through U+1059 Arabic, Syriac, Thaana, Devanagari, Bengali, Gurmukhi, Gujarati, Oriya, Tamil, Telugu, Kannada, Malayalam, Sinhala, Thai, Lao, Tibetan, Myanmar continue; if (c <= 0x1059) return Complex; if (c < 0x1100) // U+1100 through U+11FF Hangul Jamo (only Ancient Korean should be left here if you precompose; Modern Korean will be precomposed as a result of step A) continue; if (c <= 0x11FF) return Complex; if (c < 0x1780) // U+1780 through U+18AF Khmer, Mongolian continue; if (c <= 0x18AF) return Complex; if (c < 0x1900) // U+1900 through U+194F Limbu (Unicode 4.0) continue; if (c <= 0x194F) return Complex; if (c < 0x1E00) // U+1E00 through U+2000 characters with diacritics and stacked diacritics continue; if (c <= 0x2000) { result = SimpleWithGlyphOverflow; continue; } if (c < 0x20D0) // U+20D0 through U+20FF Combining marks for symbols continue; if (c <= 0x20FF) return Complex; if (c < 0xFE20) // U+FE20 through U+FE2F Combining half marks continue; if (c <= 0xFE2F) return Complex; } #if ENABLE(SAMSUNG_WEBKIT_PERFORMANCE_PATCH) // SAMSUNG CHANGE : Webkit Performance Patch Merge + r94303 if (run.length() > 1 && typesettingFeatures()) #else if (typesettingFeatures()) // SAMSUNG CHANGE : Webkit Performance Patch Merge - #endif return Complex; return result; }
// Return the rectangle for selecting the given range of code-points in the TextRun. FloatRect Font::selectionRectForComplexText(const TextRun& run, const FloatPoint& point, int height, int from, int to) const { int fromX = -1, toX = -1, fromAdvance = -1, toAdvance = -1; TextRunWalker walker(run, 0, this); walker.setWordSpacingAdjustment(wordSpacing()); walker.setLetterSpacingAdjustment(letterSpacing()); // Base will point to the x offset for the current script run. Note that, in // the LTR case, width will be 0. int base = walker.rtl() ? walker.widthOfFullRun() : 0; const int leftEdge = base; // We want to enumerate the script runs in code point order in the following // code. This call also resets |walker|. walker.setBackwardsIteration(false); while (walker.nextScriptRun() && (fromX == -1 || toX == -1)) { // TextRunWalker will helpfully accululate the x offsets for different // script runs for us. For this code, however, we always want the x offsets // to start from zero so we call this before each script run. walker.setXOffsetToZero(); if (walker.rtl()) base -= walker.width(); if (fromX == -1 && from < walker.numCodePoints()) { // |from| is within this script run. So we index the clusters log to // find which glyph this code-point contributed to and find its x // position. int glyph = walker.logClusters()[from]; fromX = base + walker.xPositions()[glyph]; fromAdvance = walker.advances()[glyph]; } else from -= walker.numCodePoints(); if (toX == -1 && to < walker.numCodePoints()) { int glyph = walker.logClusters()[to]; toX = base + walker.xPositions()[glyph]; toAdvance = walker.advances()[glyph]; } else to -= walker.numCodePoints(); if (!walker.rtl()) base += walker.width(); } // The position in question might be just after the text. const int rightEdge = base; if (fromX == -1 && !from) fromX = leftEdge; else if (walker.rtl()) fromX += truncateFixedPointToInteger(fromAdvance); if (toX == -1 && !to) toX = rightEdge; ASSERT(fromX != -1 && toX != -1); if (fromX < toX) return FloatRect(point.x() + fromX, point.y(), toX - fromX, height); return FloatRect(point.x() + toX, point.y(), fromX - toX, height); }
// Return the code point index for the given |x| offset into the text run. int Font::offsetForPositionForComplexText(const TextRun& run, float xFloat, bool includePartialGlyphs) const { // FIXME: This truncation is not a problem for HTML, but only affects SVG, which passes floating-point numbers // to Font::offsetForPosition(). Bug http://webkit.org/b/40673 tracks fixing this problem. int x = static_cast<int>(xFloat); // (Mac code ignores includePartialGlyphs, and they don't know what it's // supposed to do, so we just ignore it as well.) TextRunWalker walker(run, 0, this); walker.setWordSpacingAdjustment(wordSpacing()); walker.setLetterSpacingAdjustment(letterSpacing()); // If this is RTL text, the first glyph from the left is actually the last // code point. So we need to know how many code points there are total in // order to subtract. This is different from the length of the TextRun // because UTF-16 surrogate pairs are a single code point, but 32-bits long. // In LTR we leave this as 0 so that we get the correct value for // |basePosition|, below. unsigned totalCodePoints = 0; if (walker.rtl()) { ssize_t offset = 0; while (offset < run.length()) { utf16_to_code_point(run.characters(), run.length(), &offset); totalCodePoints++; } } unsigned basePosition = totalCodePoints; // For RTL: // code-point order: abcd efg hijkl // on screen: lkjih gfe dcba // ^ ^ // | | // basePosition--| | // totalCodePoints----| // Since basePosition is currently the total number of code-points, the // first thing we do is decrement it so that it's pointing to the start of // the current script-run. // // For LTR, basePosition is zero so it already points to the start of the // first script run. while (walker.nextScriptRun()) { if (walker.rtl()) basePosition -= walker.numCodePoints(); if (x >= 0 && x < walker.width()) { // The x value in question is within this script run. We consider // each glyph in presentation order and stop when we find the one // covering this position. const int glyphIndex = glyphIndexForXPositionInScriptRun(walker, x); // Now that we have a glyph index, we have to turn that into a // code-point index. Because of ligatures, several code-points may // have gone into a single glyph. We iterate over the clusters log // and find the first code-point which contributed to the glyph. // Some shapers (i.e. Khmer) will produce cluster logs which report // that /no/ code points contributed to certain glyphs. Because of // this, we take any code point which contributed to the glyph in // question, or any subsequent glyph. If we run off the end, then // we take the last code point. const unsigned short* log = walker.logClusters(); for (unsigned j = 0; j < walker.numCodePoints(); ++j) { if (log[j] >= glyphIndex) return basePosition + j; } return basePosition + walker.numCodePoints() - 1; } x -= walker.width(); if (!walker.rtl()) basePosition += walker.numCodePoints(); } return basePosition; }