FontChromiumWin.cpp   [plain text]


/*
 * Copyright (C) 2006, 2007 Apple Computer, Inc.
 * Copyright (c) 2006, 2007, 2008, 2009, 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.
 *     * Neither the name of Google Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "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 "Font.h"

#include "FontFallbackList.h"
#include "GlyphBuffer.h"
#include "NotImplemented.h"
#include "PlatformBridge.h"
#include "PlatformContextSkia.h"
#include "SimpleFontData.h"
#include "SkiaFontWin.h"
#include "SkiaUtils.h"
#include "TransparencyWin.h"
#include "UniscribeHelperTextRun.h"

#include "skia/ext/platform_canvas.h"
#include "skia/ext/skia_utils_win.h"  // FIXME: remove this dependency.

#include <windows.h>

namespace WebCore {

namespace {

bool canvasHasMultipleLayers(const SkCanvas* canvas)
{
    SkCanvas::LayerIter iter(const_cast<SkCanvas*>(canvas), false);
    iter.next();  // There is always at least one layer.
    return !iter.done();  // There is > 1 layer if the the iterator can stil advance.
}

class TransparencyAwareFontPainter {
public:
    TransparencyAwareFontPainter(GraphicsContext*, const FloatPoint&);
    ~TransparencyAwareFontPainter();

protected:
    // Called by our subclass' constructor to initialize GDI if necessary. This
    // is a separate function so it can be called after the subclass finishes
    // construction (it calls virtual functions).
    void init();

    virtual IntRect estimateTextBounds() = 0;

    // Use the context from the transparency helper when drawing with GDI. It
    // may point to a temporary one.
    GraphicsContext* m_graphicsContext;
    PlatformGraphicsContext* m_platformContext;

    FloatPoint m_point;

    // Set when Windows can handle the type of drawing we're doing.
    bool m_useGDI;

    // These members are valid only when m_useGDI is set.
    HDC m_hdc;
    TransparencyWin m_transparency;

private:
    // Call when we're using GDI mode to initialize the TransparencyWin to help
    // us draw GDI text.
    void initializeForGDI();

    bool m_createdTransparencyLayer;  // We created a layer to give the font some alpha.
};

TransparencyAwareFontPainter::TransparencyAwareFontPainter(GraphicsContext* context,
                                                           const FloatPoint& point)
    : m_graphicsContext(context)
    , m_platformContext(context->platformContext())
    , m_point(point)
    , m_useGDI(windowsCanHandleTextDrawing(context))
    , m_hdc(0)
    , m_createdTransparencyLayer(false)
{
}

void TransparencyAwareFontPainter::init()
{
    if (m_useGDI)
        initializeForGDI();
}

void TransparencyAwareFontPainter::initializeForGDI()
{
    m_graphicsContext->save();
    SkColor color = m_platformContext->effectiveFillColor();
    // Used only when m_createdTransparencyLayer is true.
    float layerAlpha = 0.0f;
    if (SkColorGetA(color) != 0xFF) {
        // When the font has some transparency, apply it by creating a new
        // transparency layer with that opacity applied. We'll actually create
        // a new transparency layer after we calculate the bounding box.
        m_createdTransparencyLayer = true;
        layerAlpha = SkColorGetA(color) / 255.0f;
        // The color should be opaque now.
        color = SkColorSetRGB(SkColorGetR(color), SkColorGetG(color), SkColorGetB(color));
    }

    TransparencyWin::LayerMode layerMode;
    IntRect layerRect;
    if (m_platformContext->isDrawingToImageBuffer()) {
        // Assume if we're drawing to an image buffer that the background
        // is not opaque and we have to undo ClearType. We may want to
        // enhance this to actually check, since it will often be opaque
        // and we could do ClearType in that case.
        layerMode = TransparencyWin::TextComposite;
        layerRect = estimateTextBounds();
        m_graphicsContext->clip(layerRect);
        if (m_createdTransparencyLayer)
            m_graphicsContext->beginTransparencyLayer(layerAlpha);

        // The transparency helper requires that we draw text in black in
        // this mode and it will apply the color.
        m_transparency.setTextCompositeColor(color);
        color = SkColorSetRGB(0, 0, 0);
    } else if (m_createdTransparencyLayer || canvasHasMultipleLayers(m_platformContext->canvas())) {
        // When we're drawing a web page, we know the background is opaque,
        // but if we're drawing to a layer, we still need extra work.
        layerMode = TransparencyWin::OpaqueCompositeLayer;
        layerRect = estimateTextBounds();
        m_graphicsContext->clip(layerRect);
        if (m_createdTransparencyLayer)
            m_graphicsContext->beginTransparencyLayer(layerAlpha);
    } else {
        // Common case of drawing onto the bottom layer of a web page: we
        // know everything is opaque so don't need to do anything special.
        layerMode = TransparencyWin::NoLayer;
    }

    // Bug 26088 - init() might fail if layerRect is invalid. Given this, we
    // need to be careful to check for null pointers everywhere after this call
    m_transparency.init(m_graphicsContext, layerMode, 
                        TransparencyWin::KeepTransform, layerRect);

    // Set up the DC, using the one from the transparency helper.
    if (m_transparency.platformContext()) {
        m_hdc = skia::BeginPlatformPaint(m_transparency.platformContext()->canvas());
        SetTextColor(m_hdc, skia::SkColorToCOLORREF(color));
        SetBkMode(m_hdc, TRANSPARENT);
    }
}

TransparencyAwareFontPainter::~TransparencyAwareFontPainter()
{
    if (!m_useGDI || !m_graphicsContext || !m_platformContext)
        return;  // Nothing to do.
    m_transparency.composite();
    if (m_createdTransparencyLayer)
        m_graphicsContext->endTransparencyLayer();
    m_graphicsContext->restore();
    if (m_transparency.platformContext())
        skia::EndPlatformPaint(m_transparency.platformContext()->canvas());
}

// Specialization for simple GlyphBuffer painting.
class TransparencyAwareGlyphPainter : public TransparencyAwareFontPainter {
 public:
    TransparencyAwareGlyphPainter(GraphicsContext*,
                                  const SimpleFontData*,
                                  const GlyphBuffer&,
                                  int from, int numGlyphs,
                                  const FloatPoint&);
    ~TransparencyAwareGlyphPainter();

    // Draws the partial string of glyphs, starting at |startAdvance| to the
    // left of m_point. We express it this way so that if we're using the Skia
    // drawing path we can use floating-point positioning, even though we have
    // to use integer positioning in the GDI path.
    bool drawGlyphs(int numGlyphs, const WORD* glyphs, const int* advances, float startAdvance) const;

 private:
    virtual IntRect estimateTextBounds();

    const SimpleFontData* m_font;
    const GlyphBuffer& m_glyphBuffer;
    int m_from;
    int m_numGlyphs;

    // When m_useGdi is set, this stores the previous HFONT selected into the
    // m_hdc so we can restore it.
    HGDIOBJ m_oldFont;  // For restoring the DC to its original state.
};

TransparencyAwareGlyphPainter::TransparencyAwareGlyphPainter(
    GraphicsContext* context,
    const SimpleFontData* font,
    const GlyphBuffer& glyphBuffer,
    int from, int numGlyphs,
    const FloatPoint& point)
    : TransparencyAwareFontPainter(context, point)
    , m_font(font)
    , m_glyphBuffer(glyphBuffer)
    , m_from(from)
    , m_numGlyphs(numGlyphs)
    , m_oldFont(0)
{
    init();

    if (m_hdc)
        m_oldFont = ::SelectObject(m_hdc, m_font->platformData().hfont());
}

TransparencyAwareGlyphPainter::~TransparencyAwareGlyphPainter()
{
    if (m_useGDI && m_hdc)
        ::SelectObject(m_hdc, m_oldFont);
}


// Estimates the bounding box of the given text. This is copied from
// FontCGWin.cpp, it is possible, but a lot more work, to get the precide
// bounds.
IntRect TransparencyAwareGlyphPainter::estimateTextBounds()
{
    int totalWidth = 0;
    for (int i = 0; i < m_numGlyphs; i++)
        totalWidth += lroundf(m_glyphBuffer.advanceAt(m_from + i));

    const FontMetrics& fontMetrics = m_font->fontMetrics();
    return IntRect(m_point.x() - (fontMetrics.ascent() + fontMetrics.descent()) / 2,
                   m_point.y() - fontMetrics.ascent() - fontMetrics.lineGap(),
                   totalWidth + fontMetrics.ascent() + fontMetrics.descent(),
                   fontMetrics.lineSpacing()); 
}

bool TransparencyAwareGlyphPainter::drawGlyphs(int numGlyphs,
                                               const WORD* glyphs,
                                               const int* advances,
                                               float startAdvance) const
{
    if (!m_useGDI) {
        SkPoint origin = m_point;
        origin.fX += SkFloatToScalar(startAdvance);
        return paintSkiaText(m_graphicsContext, m_font->platformData().hfont(),
                             numGlyphs, glyphs, advances, 0, &origin);
    }

    if (!m_graphicsContext || !m_hdc)
        return true;

    // Windows' origin is the top-left of the bounding box, so we have
    // to subtract off the font ascent to get it.
    int x = lroundf(m_point.x() + startAdvance);
    int y = lroundf(m_point.y() - m_font->fontMetrics().ascent());

    // If there is a non-blur shadow and both the fill color and shadow color 
    // are opaque, handle without skia. 
    FloatSize shadowOffset;
    float shadowBlur;
    Color shadowColor;
    ColorSpace shadowColorSpace;
    if (m_graphicsContext->getShadow(shadowOffset, shadowBlur, shadowColor, shadowColorSpace)) {
        // If there is a shadow and this code is reached, windowsCanHandleDrawTextShadow()
        // will have already returned true during the ctor initiatization of m_useGDI
        ASSERT(shadowColor.alpha() == 255);
        ASSERT(m_graphicsContext->fillColor().alpha() == 255);
        ASSERT(shadowBlur == 0);
        COLORREF textColor = skia::SkColorToCOLORREF(SkColorSetARGB(255, shadowColor.red(), shadowColor.green(), shadowColor.blue()));
        COLORREF savedTextColor = GetTextColor(m_hdc);
        SetTextColor(m_hdc, textColor);
        ExtTextOut(m_hdc, x + shadowOffset.width(), y + shadowOffset.height(), ETO_GLYPH_INDEX, 0, reinterpret_cast<const wchar_t*>(&glyphs[0]), numGlyphs, &advances[0]);
        SetTextColor(m_hdc, savedTextColor);
    }
    
    return !!ExtTextOut(m_hdc, x, y, ETO_GLYPH_INDEX, 0, reinterpret_cast<const wchar_t*>(&glyphs[0]), numGlyphs, &advances[0]);
}

class TransparencyAwareUniscribePainter : public TransparencyAwareFontPainter {
 public:
    TransparencyAwareUniscribePainter(GraphicsContext*,
                                      const Font*,
                                      const TextRun&,
                                      int from, int to,
                                      const FloatPoint&);
    ~TransparencyAwareUniscribePainter();

    // Uniscibe will draw directly into our buffer, so we need to expose our DC.
    HDC hdc() const { return m_hdc; }

 private:
    virtual IntRect estimateTextBounds();

    const Font* m_font;
    const TextRun& m_run;
    int m_from;
    int m_to;
};

TransparencyAwareUniscribePainter::TransparencyAwareUniscribePainter(
    GraphicsContext* context,
    const Font* font,
    const TextRun& run,
    int from, int to,
    const FloatPoint& point)
    : TransparencyAwareFontPainter(context, point)
    , m_font(font)
    , m_run(run)
    , m_from(from)
    , m_to(to)
{
    init();
}

TransparencyAwareUniscribePainter::~TransparencyAwareUniscribePainter()
{
}

IntRect TransparencyAwareUniscribePainter::estimateTextBounds()
{
    // This case really really sucks. There is no convenient way to estimate
    // the bounding box. So we run Uniscribe twice. If we find this happens a
    // lot, the way to fix it is to make the extra layer after the
    // UniscribeHelper has measured the text.
    IntPoint intPoint(lroundf(m_point.x()),
                      lroundf(m_point.y()));

    UniscribeHelperTextRun state(m_run, *m_font);
    int left = lroundf(m_point.x()) + state.characterToX(m_from);
    int right = lroundf(m_point.x()) + state.characterToX(m_to);
    
    // Adjust for RTL script since we just want to know the text bounds.
    if (left > right)
        std::swap(left, right);

    // This algorithm for estimating how much extra space we need (the text may
    // go outside the selection rect) is based roughly on
    // TransparencyAwareGlyphPainter::estimateTextBounds above.
    const FontMetrics& fontMetrics = m_font->fontMetrics();
    return IntRect(left - (fontMetrics.ascent() + fontMetrics.descent()) / 2,
                   m_point.y() - fontMetrics.ascent() - fontMetrics.lineGap(),
                   (right - left) + fontMetrics.ascent() + fontMetrics.descent(),
                   fontMetrics.lineSpacing());
}

}  // namespace

bool Font::canReturnFallbackFontsForComplexText()
{
    return false;
}

bool Font::canExpandAroundIdeographsInComplexText()
{
    return false;
}

static void drawGlyphsWin(GraphicsContext* graphicsContext,
                          const SimpleFontData* font,
                          const GlyphBuffer& glyphBuffer,
                          int from,
                          int numGlyphs,
                          const FloatPoint& point) {
    graphicsContext->platformContext()->prepareForSoftwareDraw();

    TransparencyAwareGlyphPainter painter(graphicsContext, font, glyphBuffer, from, numGlyphs, point);

    // We draw the glyphs in chunks to avoid having to do a heap allocation for
    // the arrays of characters and advances. Since ExtTextOut is the
    // lowest-level text output function on Windows, there should be little
    // penalty for splitting up the text. On the other hand, the buffer cannot
    // be bigger than 4094 or the function will fail.
    const int kMaxBufferLength = 256;
    Vector<WORD, kMaxBufferLength> glyphs;
    Vector<int, kMaxBufferLength> advances;
    int glyphIndex = 0;  // The starting glyph of the current chunk.

    // In order to round all offsets to the correct pixel boundary, this code keeps track of the absolute position
    // of each glyph in floating point units and rounds to integer advances at the last possible moment.

    float horizontalOffset = point.x(); // The floating point offset of the left side of the current glyph.
    int lastHorizontalOffsetRounded = lroundf(horizontalOffset); // The rounded offset of the left side of the last glyph rendered.
    while (glyphIndex < numGlyphs) {
        // How many chars will be in this chunk?
        int curLen = std::min(kMaxBufferLength, numGlyphs - glyphIndex);
        glyphs.resize(curLen);
        advances.resize(curLen);

        float currentWidth = 0;
        for (int i = 0; i < curLen; ++i, ++glyphIndex) {
            glyphs[i] = glyphBuffer.glyphAt(from + glyphIndex);
            horizontalOffset += glyphBuffer.advanceAt(from + glyphIndex);
            advances[i] = lroundf(horizontalOffset) - lastHorizontalOffsetRounded;
            lastHorizontalOffsetRounded += advances[i];
            currentWidth += glyphBuffer.advanceAt(from + glyphIndex);
            
            // Bug 26088 - very large positive or negative runs can fail to
            // render so we clamp the size here. In the specs, negative
            // letter-spacing is implementation-defined, so this should be
            // fine, and it matches Safari's implementation. The call actually
            // seems to crash if kMaxNegativeRun is set to somewhere around
            // -32830, so we give ourselves a little breathing room.
            const int maxNegativeRun = -32768;
            const int maxPositiveRun =  32768;
            if ((currentWidth + advances[i] < maxNegativeRun) || (currentWidth + advances[i] > maxPositiveRun)) 
                advances[i] = 0;
        }

        // Actually draw the glyphs (with retry on failure).
        bool success = false;
        for (int executions = 0; executions < 2; ++executions) {
            success = painter.drawGlyphs(curLen, &glyphs[0], &advances[0], horizontalOffset - point.x() - currentWidth);
            if (!success && executions == 0) {
                // Ask the browser to load the font for us and retry.
                PlatformBridge::ensureFontLoaded(font->platformData().hfont());
                continue;
            }
            break;
        }

        if (!success)
            LOG_ERROR("Unable to draw the glyphs after second attempt");
    }
}

void Font::drawGlyphs(GraphicsContext* graphicsContext,
                      const SimpleFontData* font,
                      const GlyphBuffer& glyphBuffer,
                      int from,
                      int numGlyphs,
                      const FloatPoint& point) const
{
    SkColor color = graphicsContext->platformContext()->effectiveFillColor();
    unsigned char alpha = SkColorGetA(color);
    // Skip 100% transparent text; no need to draw anything.
    if (!alpha && graphicsContext->platformContext()->getStrokeStyle() == NoStroke && !graphicsContext->hasShadow())
        return;

    drawGlyphsWin(graphicsContext, font, glyphBuffer, from, numGlyphs, point);
}

FloatRect Font::selectionRectForComplexText(const TextRun& run,
                                            const FloatPoint& point,
                                            int h,
                                            int from,
                                            int to) const
{
    UniscribeHelperTextRun state(run, *this);
    float left = static_cast<float>(point.x() + state.characterToX(from));
    float right = static_cast<float>(point.x() + state.characterToX(to));

    // If the text is RTL, left will actually be after right.
    if (left < right)
        return FloatRect(left, point.y(),
                       right - left, static_cast<float>(h));

    return FloatRect(right, point.y(),
                     left - right, static_cast<float>(h));
}

void Font::drawComplexText(GraphicsContext* graphicsContext,
                           const TextRun& run,
                           const FloatPoint& point,
                           int from,
                           int to) const
{
    PlatformGraphicsContext* context = graphicsContext->platformContext();
    UniscribeHelperTextRun state(run, *this);

    SkColor color = graphicsContext->platformContext()->effectiveFillColor();
    unsigned char alpha = SkColorGetA(color);
    // Skip 100% transparent text; no need to draw anything.
    if (!alpha && graphicsContext->platformContext()->getStrokeStyle() == NoStroke)
        return;

    TransparencyAwareUniscribePainter painter(graphicsContext, this, run, from, to, point);

    HDC hdc = painter.hdc();
    if (windowsCanHandleTextDrawing(graphicsContext) && !hdc)
        return;

    // TODO(maruel): http://b/700464 SetTextColor doesn't support transparency.
    // Enforce non-transparent color.
    color = SkColorSetRGB(SkColorGetR(color), SkColorGetG(color), SkColorGetB(color));
    if (hdc) {
        SetTextColor(hdc, skia::SkColorToCOLORREF(color));
        SetBkMode(hdc, TRANSPARENT);
    }

    // If there is a non-blur shadow and both the fill color and shadow color 
    // are opaque, handle without skia. 
    FloatSize shadowOffset;
    float shadowBlur;
    Color shadowColor;
    ColorSpace shadowColorSpace;
    if (graphicsContext->getShadow(shadowOffset, shadowBlur, shadowColor, shadowColorSpace) && windowsCanHandleDrawTextShadow(graphicsContext)) {
        COLORREF textColor = skia::SkColorToCOLORREF(SkColorSetARGB(255, shadowColor.red(), shadowColor.green(), shadowColor.blue()));
        COLORREF savedTextColor = GetTextColor(hdc);
        SetTextColor(hdc, textColor);
        state.draw(graphicsContext, hdc, static_cast<int>(point.x()) + shadowOffset.width(),
                   static_cast<int>(point.y() - fontMetrics().ascent()) + shadowOffset.height(), from, to);
        SetTextColor(hdc, savedTextColor); 
    }

    // Uniscribe counts the coordinates from the upper left, while WebKit uses
    // the baseline, so we have to subtract off the ascent.
    state.draw(graphicsContext, hdc, lroundf(point.x()), lroundf(point.y() - fontMetrics().ascent()), from, to);
}

void Font::drawEmphasisMarksForComplexText(GraphicsContext* /* context */, const TextRun& /* run */, const AtomicString& /* mark */, const FloatPoint& /* point */, int /* from */, int /* to */) const
{
    notImplemented();
}

float Font::floatWidthForComplexText(const TextRun& run, HashSet<const SimpleFontData*>* /* fallbackFonts */, GlyphOverflow* /* glyphOverflow */) const
{
    UniscribeHelperTextRun state(run, *this);
    return static_cast<float>(state.width());
}

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.
    UniscribeHelperTextRun state(run, *this);
    int charIndex = state.xToCharacter(x);

    // XToCharacter will return -1 if the position is before the first
    // character (we get called like this sometimes).
    if (charIndex < 0)
        charIndex = 0;
    return charIndex;
}

} // namespace WebCore