OSDN Git Service

Support complex scripts by calling Harfbuzz APIs.
authorclaireho <chinglanho@gmail.com>
Tue, 15 Jun 2010 20:14:09 +0000 (13:14 -0700)
committerclaireho <chinglanho@gmail.com>
Fri, 18 Jun 2010 21:17:20 +0000 (14:17 -0700)
Ported the Chrome complex script suppport to Android.
The Harfbuzz libary is built optionally. If the Harfbuzz is not
included, we swicth back to default webkit complex script logic -
it does not work correctly, but at least webkit won't be crash.

Change-Id: I53cd81c82cc4c6a550bcc0f4fe5870f641a881ac

Android.mk
WebCore/Android.mk
WebCore/platform/graphics/android/FontAndroid.cpp
WebCore/platform/graphics/android/FontPlatformData.h
WebCore/platform/graphics/android/FontPlatformDataAndroid.cpp
WebCore/platform/graphics/android/HarfbuzzSkia.cpp [new file with mode: 0644]
WebCore/platform/graphics/android/HarfbuzzSkia.h [new file with mode: 0644]

index fa50cfe..fc8fe2b 100644 (file)
@@ -298,6 +298,15 @@ LOCAL_SHARED_LIBRARIES += libdl libstlport
 include external/stlport/libstlport.mk
 endif
 
+# We need Harfbuzz library to support complex scripts(Arabic, Thai, Hindi...).
+ifeq ($(SUPPORT_COMPLEX_SCRIPTS),true)
+LOCAL_C_INCLUDES := $(LOCAL_C_INCLUDES) \
+       external/harfbuzz/src \
+       external/harfbuzz/contrib
+LOCAL_SHARED_LIBRARIES += libharfbuzz
+LOCAL_CFLAGS += -DSUPPORT_COMPLEX_SCRIPTS=1
+endif
+
 # Build the list of static libraries
 LOCAL_STATIC_LIBRARIES := libxml2 libxslt
 ifeq ($(JAVASCRIPT_ENGINE),v8)
index 3cc87db..4b283dc 100644 (file)
@@ -1018,3 +1018,9 @@ LOCAL_SRC_FILES := $(LOCAL_SRC_FILES) \
        xml/XSLImportRule.cpp \
        loader/CachedXSLStyleSheet.cpp \
        dom/TransformSourceLibxslt.cpp
+
+# For complex scripts(Arabic, Thai, Hindi...).
+ifeq ($(SUPPORT_COMPLEX_SCRIPTS),true)
+LOCAL_SRC_FILES := $(LOCAL_SRC_FILES) \
+       platform/graphics/android/HarfbuzzSkia.cpp
+endif
index 53d874a..98a571b 100644 (file)
 #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
+
 }
index bb6b1c3..19f83f8 100644 (file)
@@ -39,6 +39,8 @@
 class SkPaint;
 class SkTypeface;
 
+struct HB_FaceRec_;
+
 namespace WebCore {
 
 class FontPlatformData {
@@ -65,6 +67,13 @@ public:
     bool operator==(const FontPlatformData& a) const;
 
     void     setupPaint(SkPaint*) const;
+
+    // -------------------------------------------------------------------------
+    // Return Skia's unique id for this font. This encodes both the style and
+    // the font's file name so refers to a single face.
+    // -------------------------------------------------------------------------
+    uint32_t uniqueID() const;
+
     float size() const { return mTextSize; }
     unsigned hash() const;
 
@@ -72,17 +81,39 @@ public:
     String description() const { return ""; }
 #endif
 
+    HB_FaceRec_* harfbuzzFace() const;
+
 private:
+    class RefCountedHarfbuzzFace : public RefCounted<RefCountedHarfbuzzFace> {
+    public:
+        static PassRefPtr<RefCountedHarfbuzzFace> create(HB_FaceRec_* harfbuzzFace)
+        {
+            return adoptRef(new RefCountedHarfbuzzFace(harfbuzzFace));
+        }
+
+        ~RefCountedHarfbuzzFace();
+
+        HB_FaceRec_* face() const { return m_harfbuzzFace; }
+
+    private:
+        RefCountedHarfbuzzFace(HB_FaceRec_* harfbuzzFace) : m_harfbuzzFace(harfbuzzFace)
+        {
+        }
+
+        HB_FaceRec_* m_harfbuzzFace;
+    };
+
     SkTypeface* mTypeface;
     float       mTextSize;
     bool        mFakeBold;
     bool        mFakeItalic;
+    mutable RefPtr<RefCountedHarfbuzzFace> m_harfbuzzFace;
 
     static SkTypeface* hashTableDeletedFontValue() {
         return reinterpret_cast<SkTypeface*>(-1);
     }
 };
-    
+
 } /* namespace */
 
 #endif
index 640940f..974b828 100644 (file)
@@ -30,6 +30,9 @@
 #include "config.h"
 #include "FontPlatformData.h"
 
+#ifdef SUPPORT_COMPLEX_SCRIPTS
+#include "HarfbuzzSkia.h"
+#endif
 #include "SkPaint.h"
 #include "SkTypeface.h"
 
@@ -63,6 +66,13 @@ static void dec_count() { --gCount; }
 
 namespace WebCore {
 
+FontPlatformData::RefCountedHarfbuzzFace::~RefCountedHarfbuzzFace()
+{
+#ifdef SUPPORT_COMPLEX_SCRIPTS
+    HB_FreeFace(m_harfbuzzFace);
+#endif
+}
+
 FontPlatformData::FontPlatformData()
     : mTypeface(NULL), mTextSize(0), mFakeBold(false), mFakeItalic(false)
 {
@@ -81,6 +91,7 @@ FontPlatformData::FontPlatformData(const FontPlatformData& src)
     mTextSize   = src.mTextSize;
     mFakeBold   = src.mFakeBold;
     mFakeItalic = src.mFakeItalic;
+    m_harfbuzzFace = src.m_harfbuzzFace;
 
     inc_count();
     trace(2);
@@ -92,13 +103,14 @@ FontPlatformData::FontPlatformData(SkTypeface* tf, float textSize, bool fakeBold
     if (hashTableDeletedFontValue() != mTypeface) {
         mTypeface->safeRef();
     }
-    
+
     inc_count();
     trace(3);
 }
 
 FontPlatformData::FontPlatformData(const FontPlatformData& src, float textSize)
-    : mTypeface(src.mTypeface), mTextSize(textSize), mFakeBold(src.mFakeBold), mFakeItalic(src.mFakeItalic)
+    : mTypeface(src.mTypeface), mTextSize(textSize), mFakeBold(src.mFakeBold), mFakeItalic(src.mFakeItalic),
+      m_harfbuzzFace(src.m_harfbuzzFace)
 {
     if (hashTableDeletedFontValue() != mTypeface) {
         mTypeface->safeRef();
@@ -107,7 +119,7 @@ FontPlatformData::FontPlatformData(const FontPlatformData& src, float textSize)
     inc_count();
     trace(4);
 }
-    
+
 FontPlatformData::FontPlatformData(float size, bool bold, bool oblique)
     : mTypeface(NULL), mTextSize(size), mFakeBold(bold), mFakeItalic(oblique)
 {
@@ -140,7 +152,8 @@ FontPlatformData& FontPlatformData::operator=(const FontPlatformData& src)
     mTextSize   = src.mTextSize;
     mFakeBold   = src.mFakeBold;
     mFakeItalic = src.mFakeItalic;
-    
+    m_harfbuzzFace = src.m_harfbuzzFace;
+
     return *this;
 }
 
@@ -157,7 +170,14 @@ void FontPlatformData::setupPaint(SkPaint* paint) const
     paint->setTypeface(mTypeface);
     paint->setFakeBoldText(mFakeBold);
     paint->setTextSkewX(mFakeItalic ? -SK_Scalar1/4 : 0);
+#ifndef SUPPORT_COMPLEX_SCRIPTS
     paint->setTextEncoding(SkPaint::kUTF16_TextEncoding);
+#endif
+}
+
+uint32_t FontPlatformData::uniqueID() const
+{
+    return mTypeface->uniqueID();
 }
 
 bool FontPlatformData::operator==(const FontPlatformData& a) const
@@ -171,13 +191,13 @@ bool FontPlatformData::operator==(const FontPlatformData& a) const
 unsigned FontPlatformData::hash() const
 {
     unsigned h;
-    
+
     if (hashTableDeletedFontValue() == mTypeface) {
         h = reinterpret_cast<unsigned>(mTypeface);
     } else {
         h = SkTypeface::UniqueID(mTypeface);
     }
-    
+
     uint32_t sizeAsInt = *reinterpret_cast<const uint32_t*>(&mTextSize);
 
     h ^= 0x01010101 * (((int)mFakeBold << 1) | (int)mFakeItalic);
@@ -185,4 +205,16 @@ unsigned FontPlatformData::hash() const
     return h;
 }
 
+HB_FaceRec_* FontPlatformData::harfbuzzFace() const
+{
+#ifdef SUPPORT_COMPLEX_SCRIPTS
+    if (!m_harfbuzzFace)
+        m_harfbuzzFace = RefCountedHarfbuzzFace::create(
+            HB_NewFace(const_cast<FontPlatformData*>(this), harfbuzzSkiaGetTable));
+
+    return m_harfbuzzFace->face();
+#else
+    return NULL;
+#endif
+}
 }
diff --git a/WebCore/platform/graphics/android/HarfbuzzSkia.cpp b/WebCore/platform/graphics/android/HarfbuzzSkia.cpp
new file mode 100644 (file)
index 0000000..19812fe
--- /dev/null
@@ -0,0 +1,220 @@
+/*
+ * 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
diff --git a/WebCore/platform/graphics/android/HarfbuzzSkia.h b/WebCore/platform/graphics/android/HarfbuzzSkia.h
new file mode 100644 (file)
index 0000000..d26bbe2
--- /dev/null
@@ -0,0 +1,40 @@
+/*
+ * 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.
+ */
+
+#ifndef HarfbuzzSkia_h
+#define HarfbuzzSkia_h
+
+extern "C" {
+#include "harfbuzz-shaper.h"
+#include "harfbuzz-unicode.h"
+}
+
+namespace WebCore {
+    HB_Error harfbuzzSkiaGetTable(void* voidface, const HB_Tag, HB_Byte* buffer, HB_UInt* len);
+    extern const HB_FontClass harfbuzzSkiaClass;
+}  // namespace WebCore
+
+#endif