diff options
Diffstat (limited to 'WebCore/platform/graphics/android/FontAndroid.cpp')
| -rw-r--r-- | WebCore/platform/graphics/android/FontAndroid.cpp | 586 |
1 files changed, 586 insertions, 0 deletions
diff --git a/WebCore/platform/graphics/android/FontAndroid.cpp b/WebCore/platform/graphics/android/FontAndroid.cpp index 53d874a..98a571b 100644 --- a/WebCore/platform/graphics/android/FontAndroid.cpp +++ b/WebCore/platform/graphics/android/FontAndroid.cpp @@ -41,6 +41,14 @@ #include "SkTypeface.h" #include "SkUtils.h" +#ifdef SUPPORT_COMPLEX_SCRIPTS +#include "HarfbuzzSkia.h" +#include <unicode/normlzr.h> +#include <unicode/uchar.h> +#include <wtf/OwnArrayPtr.h> +#include <wtf/OwnPtr.h> +#endif + using namespace android; namespace WebCore { @@ -175,6 +183,8 @@ void Font::drawGlyphs(GraphicsContext* gc, const SimpleFontData* font, } } +#ifndef SUPPORT_COMPLEX_SCRIPTS + FloatRect Font::selectionRectForComplexText(const TextRun& run, const IntPoint& point, int h, int, int) const { @@ -248,4 +258,580 @@ int Font::offsetForPositionForComplexText(const TextRun& run, int x, return count; } +#else + +// TODO Should we remove the multilayer support? +// If yes. remove isCanvasMultiLayered() and adjustTextRenderMode(). +static bool isCanvasMultiLayered(SkCanvas* canvas) +{ + SkCanvas::LayerIter layerIterator(canvas, false); + layerIterator.next(); + return !layerIterator.done(); +} + +static void adjustTextRenderMode(SkPaint* paint, bool isCanvasMultiLayered) +{ + // Our layers only have a single alpha channel. This means that subpixel + // rendered text cannot be compositied correctly when the layer is + // collapsed. Therefore, subpixel text is disabled when we are drawing + // onto a layer. + if (isCanvasMultiLayered) + paint->setLCDRenderText(false); +} + +// Harfbuzz uses 26.6 fixed point values for pixel offsets. However, we don't +// handle subpixel positioning so this function is used to truncate Harfbuzz +// values to a number of pixels. +static int truncateFixedPointToInteger(HB_Fixed value) +{ + return value >> 6; +} + +// TextRunWalker walks a TextRun and presents each script run in sequence. A +// TextRun is a sequence of code-points with the same embedding level (i.e. they +// are all left-to-right or right-to-left). A script run is a subsequence where +// all the characters have the same script (e.g. Arabic, Thai etc). Shaping is +// only ever done with script runs since the shapers only know how to deal with +// a single script. +// +// After creating it, the script runs are either iterated backwards or forwards. +// It defaults to backwards for RTL and forwards otherwise (which matches the +// presentation order), however you can set it with |setBackwardsIteration|. +// +// Once you have setup the object, call |nextScriptRun| to get the first script +// run. This will return false when the iteration is complete. At any time you +// can call |reset| to start over again. +class TextRunWalker { +public: + TextRunWalker(const TextRun& run, unsigned startingX, const Font* font) + : m_font(font) + , m_startingX(startingX) + , m_run(getTextRun(run)) + , m_iterateBackwards(m_run.rtl()) + { + // Do not use |run| inside this constructor. Use |m_run| instead. + + memset(&m_item, 0, sizeof(m_item)); + // We cannot know, ahead of time, how many glyphs a given script run + // will produce. We take a guess that script runs will not produce more + // than twice as many glyphs as there are code points and fallback if + // we find that we are wrong. + m_maxGlyphs = m_run.length() * 2; + createGlyphArrays(); + + m_item.log_clusters = new unsigned short[m_run.length()]; + + m_item.face = 0; + m_item.font = allocHarfbuzzFont(); + + m_item.string = m_run.characters(); + m_item.stringLength = m_run.length(); + m_item.item.bidiLevel = m_run.rtl(); + + reset(); + } + + ~TextRunWalker() + { + fastFree(m_item.font); + deleteGlyphArrays(); + delete[] m_item.log_clusters; + } + + void reset() + { + if (m_iterateBackwards) + m_indexOfNextScriptRun = m_run.length() - 1; + else + m_indexOfNextScriptRun = 0; + m_offsetX = m_startingX; + } + + // Set the x offset for the next script run. This affects the values in + // |xPositions| + void setXOffsetToZero() + { + m_offsetX = 0; + } + + bool rtl() const + { + return m_run.rtl(); + } + + void setBackwardsIteration(bool isBackwards) + { + m_iterateBackwards = isBackwards; + reset(); + } + + // Advance to the next script run, returning false when the end of the + // TextRun has been reached. + bool nextScriptRun() + { + if (m_iterateBackwards) { + // In right-to-left mode we need to render the shaped glyph backwards and + // also render the script runs themselves backwards. So given a TextRun: + // AAAAAAACTTTTTTT (A = Arabic, C = Common, T = Thai) + // we render: + // TTTTTTCAAAAAAA + // (and the glyphs in each A, C and T section are backwards too) + if (!hb_utf16_script_run_prev(&m_numCodePoints, &m_item.item, + m_run.characters(), m_run.length(), &m_indexOfNextScriptRun)) + return false; + } else { + if (!hb_utf16_script_run_next(&m_numCodePoints, &m_item.item, + m_run.characters(), m_run.length(), &m_indexOfNextScriptRun)) + return false; + } + + setupFontForScriptRun(); + + if (!shapeGlyphs()) + return false; + setGlyphXPositions(rtl()); + return true; + } + + const uint16_t* glyphs() const + { + return m_glyphs16; + } + + // Return the length of the array returned by |glyphs| + unsigned length() const + { + return m_item.num_glyphs; + } + + // Return the x offset for each of the glyphs. Note that this is translated + // by the current x offset and that the x offset is updated for each script + // run. + const SkScalar* xPositions() const + { + return m_xPositions; + } + + // Get the advances (widths) for each glyph. + const HB_Fixed* advances() const + { + return m_item.advances; + } + + // Return the width (in px) of the current script run. + unsigned width() const + { + return m_pixelWidth; + } + + // Return the cluster log for the current script run. For example: + // script run: f i a n c é (fi gets ligatured) + // log clutrs: 0 0 1 2 3 4 + // So, for each input code point, the log tells you which output glyph was + // generated for it. + const unsigned short* logClusters() const + { + return m_item.log_clusters; + } + + // return the number of code points in the current script run + unsigned numCodePoints() const + { + return m_numCodePoints; + } + + const FontPlatformData* fontPlatformDataForScriptRun() + { + return reinterpret_cast<FontPlatformData*>(m_item.font->userData); + } + + float widthOfFullRun() + { + float widthSum = 0; + while (nextScriptRun()) + widthSum += width(); + + return widthSum; + } + +private: + const TextRun& getTextRun(const TextRun& originalRun) + { + // Normalize the text run in two ways: + // 1) Convert the |originalRun| to NFC normalized form if combining diacritical marks + // (U+0300..) are used in the run. This conversion is necessary since most OpenType + // fonts (e.g., Arial) don't have substitution rules for the diacritical marks in + // their GSUB tables. + // + // Note that we don't use the icu::Normalizer::isNormalized(UNORM_NFC) API here since + // the API returns FALSE (= not normalized) for complex runs that don't require NFC + // normalization (e.g., Arabic text). Unless the run contains the diacritical marks, + // Harfbuzz will do the same thing for us using the GSUB table. + // 2) Convert spacing characters into plain spaces, as some fonts will provide glyphs + // for characters like '\n' otherwise. + for (int16_t i = 0; i < originalRun.length(); ++i) { + UChar ch = originalRun[i]; + UBlockCode block = ::ublock_getCode(ch); + if (block == UBLOCK_COMBINING_DIACRITICAL_MARKS || + (Font::treatAsSpace(ch) && ch != ' ')) { + return getNormalizedTextRun(originalRun); + } + } + return originalRun; + } + + const TextRun& getNormalizedTextRun(const TextRun& originalRun) + { + icu::UnicodeString normalizedString; + UErrorCode error = U_ZERO_ERROR; + icu::Normalizer::normalize(icu::UnicodeString(originalRun.characters(), + originalRun.length()), UNORM_NFC, 0 /* no options */, + normalizedString, error); + if (U_FAILURE(error)) + return originalRun; + + m_normalizedBuffer.set(new UChar[normalizedString.length() + 1]); + normalizedString.extract(m_normalizedBuffer.get(), + normalizedString.length() + 1, error); + ASSERT(U_SUCCESS(error)); + + for (int32_t i = 0; i < normalizedString.length(); ++i) { + if (Font::treatAsSpace(m_normalizedBuffer[i])) + m_normalizedBuffer[i] = ' '; + } + + m_normalizedRun.set(new TextRun(originalRun)); + m_normalizedRun->setText(m_normalizedBuffer.get(), + normalizedString.length()); + return *m_normalizedRun; + } + + void setupFontForScriptRun() + { + const FontData* fontData = m_font->fontDataAt(0); + if (!fontData->containsCharacters(m_item.string + m_item.item.pos, + m_item.item.length)) + fontData = m_font->fontDataForCharacters( + m_item.string + m_item.item.pos, m_item.item.length); + const FontPlatformData& platformData = + fontData->fontDataForCharacter(' ')->platformData(); + m_item.face = platformData.harfbuzzFace(); + void* opaquePlatformData = const_cast<FontPlatformData*>(&platformData); + m_item.font->userData = opaquePlatformData; + } + + HB_FontRec* allocHarfbuzzFont() + { + HB_FontRec* font = + reinterpret_cast<HB_FontRec*>(fastMalloc(sizeof(HB_FontRec))); + memset(font, 0, sizeof(HB_FontRec)); + font->klass = &harfbuzzSkiaClass; + font->userData = 0; + // The values which harfbuzzSkiaClass returns are already scaled to + // pixel units, so we just set all these to one to disable further + // scaling. + font->x_ppem = 1; + font->y_ppem = 1; + font->x_scale = 1; + font->y_scale = 1; + + return font; + } + + void deleteGlyphArrays() + { + delete[] m_item.glyphs; + delete[] m_item.attributes; + delete[] m_item.advances; + delete[] m_item.offsets; + delete[] m_glyphs16; + delete[] m_xPositions; + } + + bool createGlyphArrays() + { + m_item.glyphs = new HB_Glyph[m_maxGlyphs]; + m_item.attributes = new HB_GlyphAttributes[m_maxGlyphs]; + m_item.advances = new HB_Fixed[m_maxGlyphs]; + m_item.offsets = new HB_FixedPoint[m_maxGlyphs]; + // HB_FixedPoint is a struct, so we must use memset to clear it. + memset(m_item.offsets, 0, m_maxGlyphs * sizeof(HB_FixedPoint)); + m_glyphs16 = new uint16_t[m_maxGlyphs]; + m_xPositions = new SkScalar[m_maxGlyphs]; + + return m_item.glyphs + && m_item.attributes + && m_item.advances + && m_item.offsets + && m_glyphs16 + && m_xPositions; + } + + bool expandGlyphArrays() + { + deleteGlyphArrays(); + m_maxGlyphs <<= 1; + return createGlyphArrays(); + } + + bool shapeGlyphs() + { + for (;;) { + m_item.num_glyphs = m_maxGlyphs; + HB_ShapeItem(&m_item); + if (m_item.num_glyphs < m_maxGlyphs) + break; + + // We overflowed our arrays. Resize and retry. + if (!expandGlyphArrays()) + return false; + } + + return true; + } + + void setGlyphXPositions(bool isRTL) + { + int position = 0; + for (unsigned iter = 0; iter < m_item.num_glyphs; ++iter) { + // Glyphs are stored in logical order, but for layout purposes we + // always go left to right. + int i = isRTL ? m_item.num_glyphs - iter - 1 : iter; + + m_glyphs16[i] = m_item.glyphs[i]; + int offsetX = truncateFixedPointToInteger(m_item.offsets[i].x); + m_xPositions[i] = SkIntToScalar(m_offsetX + position + offsetX); + + int advance = truncateFixedPointToInteger(m_item.advances[i]); + position += advance; + } + m_pixelWidth = position; + m_offsetX += m_pixelWidth; + } + + const Font* const m_font; + HB_ShaperItem m_item; + uint16_t* m_glyphs16; // A vector of 16-bit glyph ids. + SkScalar* m_xPositions; // A vector of x positions for each glyph. + ssize_t m_indexOfNextScriptRun; // Indexes the script run in |m_run|. + const unsigned m_startingX; // Offset in pixels of the first script run. + unsigned m_offsetX; // Offset in pixels to the start of the next script run. + unsigned m_pixelWidth; // Width (in px) of the current script run. + unsigned m_numCodePoints; // Code points in current script run. + unsigned m_maxGlyphs; // Current size of all the Harfbuzz arrays. + + OwnPtr<TextRun> m_normalizedRun; + OwnArrayPtr<UChar> m_normalizedBuffer; // A buffer for normalized run. + const TextRun& m_run; + bool m_iterateBackwards; +}; + + +FloatRect Font::selectionRectForComplexText(const TextRun& run, + const IntPoint& point, int height, int from, int to) const +{ + + int fromX = -1, toX = -1, fromAdvance = -1, toAdvance = -1; + TextRunWalker walker(run, 0, this); + + // 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 accumulate 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(); + + int numCodePoints = static_cast<int>(walker.numCodePoints()); + if (fromX == -1 && from < 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 -= numCodePoints; + + if (toX == -1 && to < numCodePoints) { + int glyph = walker.logClusters()[to]; + toX = base + walker.xPositions()[glyph]; + toAdvance = walker.advances()[glyph]; + } else + to -= 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); +} + +void Font::drawComplexText(GraphicsContext* gc, TextRun const& run, + FloatPoint const& point, int, int) const +{ + if (!run.length()) + return; + + int mode = gc->textDrawingMode(); + bool fill = mode & cTextFill; + bool stroke = mode & cTextStroke; + if (!fill && !stroke) + return; + + SkPaint fillPaint, strokePaint; + if (fill) + setupFill(&fillPaint, gc, primaryFont()); + if (stroke) + setupStroke(&strokePaint, gc, primaryFont()); + + SkCanvas* canvas = gc->platformContext()->mCanvas; + bool haveMultipleLayers = isCanvasMultiLayered(canvas); + TextRunWalker walker(run, point.x(), this); + while (walker.nextScriptRun()) { + if (fill) { + walker.fontPlatformDataForScriptRun()->setupPaint(&fillPaint); + adjustTextRenderMode(&fillPaint, haveMultipleLayers); + canvas->drawPosTextH(walker.glyphs(), walker.length() << 1, + walker.xPositions(), point.y(), fillPaint); + } + if (stroke) { + walker.fontPlatformDataForScriptRun()->setupPaint(&strokePaint); + adjustTextRenderMode(&strokePaint, haveMultipleLayers); + canvas->drawPosTextH(walker.glyphs(), walker.length() << 1, + walker.xPositions(), point.y(), strokePaint); + } + } +} + +float Font::floatWidthForComplexText(const TextRun& run, + HashSet<const SimpleFontData*>*, GlyphOverflow*) const +{ + TextRunWalker walker(run, 0, this); + return walker.widthOfFullRun(); +} + +static int glyphIndexForXPositionInScriptRun(const TextRunWalker& walker, int x) +{ + const HB_Fixed* advances = walker.advances(); + int glyphIndex; + if (walker.rtl()) { + for (glyphIndex = walker.length() - 1; glyphIndex >= 0; --glyphIndex) { + if (x < truncateFixedPointToInteger(advances[glyphIndex])) + break; + x -= truncateFixedPointToInteger(advances[glyphIndex]); + } + } else { + for (glyphIndex = 0; glyphIndex < static_cast<int>(walker.length()); + ++glyphIndex) { + if (x < truncateFixedPointToInteger(advances[glyphIndex])) + break; + x -= truncateFixedPointToInteger(advances[glyphIndex]); + } + } + + return glyphIndex; +} + +int Font::offsetForPositionForComplexText(const TextRun& run, int x, + bool includePartialGlyphs) const +{ + // (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); + + // 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 < static_cast<int>(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; +} +#endif + } |
