#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 {
}
}
+#ifndef SUPPORT_COMPLEX_SCRIPTS
+
FloatRect Font::selectionRectForComplexText(const TextRun& run,
const IntPoint& point, int h, int, int) const
{
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
+
}
--- /dev/null
+/*
+ * Copyright 2010, The Android Open Source Project
+ * Copyright 2010, Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "config.h"
+
+#include "FontPlatformData.h"
+#include "wtf/OwnArrayPtr.h"
+
+#include "SkFontHost.h"
+#include "SkPaint.h"
+#include "SkPath.h"
+#include "SkPoint.h"
+#include "SkRect.h"
+
+extern "C" {
+#include "harfbuzz-shaper.h"
+}
+
+// This file implements the callbacks which Harfbuzz requires by using Skia
+// calls. See the Harfbuzz source for references about what these callbacks do.
+
+namespace WebCore {
+
+static HB_Fixed SkiaScalarToHarfbuzzFixed(SkScalar value)
+{
+ // HB_Fixed is a 26.6 fixed point format.
+ return value * 64;
+}
+
+static HB_Bool stringToGlyphs(HB_Font hbFont, const HB_UChar16* characters, hb_uint32 length, HB_Glyph* glyphs, hb_uint32* glyphsSize, HB_Bool isRTL)
+{
+ FontPlatformData* font = reinterpret_cast<FontPlatformData*>(hbFont->userData);
+ SkPaint paint;
+
+ font->setupPaint(&paint);
+ paint.setTextEncoding(SkPaint::kUTF16_TextEncoding);
+ int numGlyphs = paint.textToGlyphs(characters, length * sizeof(uint16_t), reinterpret_cast<uint16_t*>(glyphs));
+
+ // HB_Glyph is 32-bit, but Skia outputs only 16-bit numbers. So our
+ // |glyphs| array needs to be converted.
+ for (int i = numGlyphs - 1; i >= 0; --i) {
+ uint16_t value;
+ // We use a memcpy to avoid breaking strict aliasing rules.
+ memcpy(&value, reinterpret_cast<char*>(glyphs) + sizeof(uint16_t) * i, sizeof(value));
+ glyphs[i] = value;
+ }
+
+ *glyphsSize = numGlyphs;
+ return 1;
+}
+
+static void glyphsToAdvances(HB_Font hbFont, const HB_Glyph* glyphs, hb_uint32 numGlyphs, HB_Fixed* advances, int flags)
+{
+ FontPlatformData* font = reinterpret_cast<FontPlatformData*>(hbFont->userData);
+ SkPaint paint;
+
+ font->setupPaint(&paint);
+ paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
+
+ OwnArrayPtr<uint16_t> glyphs16(new uint16_t[numGlyphs]);
+ if (!glyphs16.get())
+ return;
+ for (unsigned i = 0; i < numGlyphs; ++i)
+ glyphs16[i] = glyphs[i];
+ paint.getTextWidths(glyphs16.get(), numGlyphs * sizeof(uint16_t), reinterpret_cast<SkScalar*>(advances));
+
+ // The |advances| values which Skia outputs are SkScalars, which are floats
+ // in Chromium. However, Harfbuzz wants them in 26.6 fixed point format.
+ // These two formats are both 32-bits long.
+ for (unsigned i = 0; i < numGlyphs; ++i) {
+ float value;
+ // We use a memcpy to avoid breaking strict aliasing rules.
+ memcpy(&value, reinterpret_cast<char*>(advances) + sizeof(float) * i, sizeof(value));
+ advances[i] = SkiaScalarToHarfbuzzFixed(value);
+ }
+}
+
+static HB_Bool canRender(HB_Font hbFont, const HB_UChar16* characters, hb_uint32 length)
+{
+ FontPlatformData* font = reinterpret_cast<FontPlatformData*>(hbFont->userData);
+ SkPaint paint;
+
+ font->setupPaint(&paint);
+ paint.setTextEncoding(SkPaint::kUTF16_TextEncoding);
+
+ OwnArrayPtr<uint16_t> glyphs16(new uint16_t[length]);
+ glyphs16.get();
+ int numGlyphs = paint.textToGlyphs(characters, length * sizeof(uint16_t), glyphs16.get());
+
+ for (int i = 0; i < numGlyphs; ++i) {
+ if (!glyphs16[i])
+ return false;
+ }
+
+ return true;
+}
+
+static HB_Error getOutlinePoint(HB_Font hbFont, HB_Glyph glyph, int flags, hb_uint32 point, HB_Fixed* xPos, HB_Fixed* yPos, hb_uint32* resultingNumPoints)
+{
+ FontPlatformData* font = reinterpret_cast<FontPlatformData*>(hbFont->userData);
+ SkPaint paint;
+
+ if (flags & HB_ShaperFlag_UseDesignMetrics)
+ return HB_Err_Invalid_Argument; // This is requesting pre-hinted positions. We can't support this.
+
+ font->setupPaint(&paint);
+ paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
+ uint16_t glyph16 = glyph;
+ SkPath path;
+ paint.getTextPath(&glyph16, sizeof(glyph16), 0, 0, &path);
+ uint32_t numPoints = path.getPoints(NULL, 0);
+ if (point >= numPoints)
+ return HB_Err_Invalid_SubTable;
+ SkPoint* points = reinterpret_cast<SkPoint*>(fastMalloc(sizeof(SkPoint) * (point + 1)));
+ if (!points)
+ return HB_Err_Invalid_SubTable;
+ // Skia does let us get a single point from the path.
+ path.getPoints(points, point + 1);
+ *xPos = SkiaScalarToHarfbuzzFixed(points[point].fX);
+ *yPos = SkiaScalarToHarfbuzzFixed(points[point].fY);
+ *resultingNumPoints = numPoints;
+ fastFree(points);
+
+ return HB_Err_Ok;
+}
+
+static void getGlyphMetrics(HB_Font hbFont, HB_Glyph glyph, HB_GlyphMetrics* metrics)
+{
+ FontPlatformData* font = reinterpret_cast<FontPlatformData*>(hbFont->userData);
+ SkPaint paint;
+
+ font->setupPaint(&paint);
+ paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
+ uint16_t glyph16 = glyph;
+ SkScalar width;
+ SkRect bounds;
+ paint.getTextWidths(&glyph16, sizeof(glyph16), &width, &bounds);
+
+ metrics->x = SkiaScalarToHarfbuzzFixed(bounds.fLeft);
+ metrics->y = SkiaScalarToHarfbuzzFixed(bounds.fTop);
+ metrics->width = SkiaScalarToHarfbuzzFixed(bounds.width());
+ metrics->height = SkiaScalarToHarfbuzzFixed(bounds.height());
+
+ metrics->xOffset = SkiaScalarToHarfbuzzFixed(width);
+ // We can't actually get the |y| correct because Skia doesn't export
+ // the vertical advance. However, nor we do ever render vertical text at
+ // the moment so it's unimportant.
+ metrics->yOffset = 0;
+}
+
+static HB_Fixed getFontMetric(HB_Font hbFont, HB_FontMetric metric)
+{
+ FontPlatformData* font = reinterpret_cast<FontPlatformData*>(hbFont->userData);
+ SkPaint paint;
+
+ font->setupPaint(&paint);
+ SkPaint::FontMetrics skiaMetrics;
+ paint.getFontMetrics(&skiaMetrics);
+
+ switch (metric) {
+ case HB_FontAscent:
+ return SkiaScalarToHarfbuzzFixed(-skiaMetrics.fAscent);
+ // We don't support getting the rest of the metrics and Harfbuzz doesn't seem to need them.
+ default:
+ return 0;
+ }
+}
+
+HB_FontClass harfbuzzSkiaClass = {
+ stringToGlyphs,
+ glyphsToAdvances,
+ canRender,
+ getOutlinePoint,
+ getGlyphMetrics,
+ getFontMetric,
+};
+
+HB_Error harfbuzzSkiaGetTable(void* voidface, const HB_Tag tag, HB_Byte* buffer, HB_UInt* len)
+{
+ FontPlatformData* font = reinterpret_cast<FontPlatformData*>(voidface);
+
+ const size_t tableSize = SkFontHost::GetTableSize(font->uniqueID(), tag);
+ if (!tableSize)
+ return HB_Err_Invalid_Argument;
+ // If Harfbuzz specified a NULL buffer then it's asking for the size of the table.
+ if (!buffer) {
+ *len = tableSize;
+ return HB_Err_Ok;
+ }
+
+ if (*len < tableSize)
+ return HB_Err_Invalid_Argument;
+ SkFontHost::GetTableData(font->uniqueID(), tag, 0, tableSize, buffer);
+ return HB_Err_Ok;
+}
+
+} // namespace WebCore