1/*
2 * Copyright (C) 2006, 2007, 2008, 2009 Apple Inc.  All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 * 1. Redistributions of source code must retain the above copyright
8 *    notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 *    notice, this list of conditions and the following disclaimer in the
11 *    documentation and/or other materials provided with the distribution.
12 *
13 * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
17 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26#include "config.h"
27#include "Font.h"
28
29#include "AffineTransform.h"
30#include "FloatConversion.h"
31#include "GlyphBuffer.h"
32#include "GraphicsContext.h"
33#include "IntRect.h"
34#include "SimpleFontData.h"
35#include "UniscribeController.h"
36#include "WebCoreTextRenderer.h"
37#include <ApplicationServices/ApplicationServices.h>
38#include <WebKitSystemInterface/WebKitSystemInterface.h>
39#include <wtf/MathExtras.h>
40
41namespace WebCore {
42
43const int syntheticObliqueAngle = 14;
44
45static inline CGFloat toCGFloat(FIXED f)
46{
47    return f.value + f.fract / CGFloat(65536.0);
48}
49
50static CGPathRef createPathForGlyph(HDC hdc, Glyph glyph)
51{
52    CGMutablePathRef path = CGPathCreateMutable();
53
54    static const MAT2 identity = { 0, 1,  0, 0,  0, 0,  0, 1 };
55    GLYPHMETRICS glyphMetrics;
56    // GGO_NATIVE matches the outline perfectly when Windows font smoothing is off.
57    // GGO_NATIVE | GGO_UNHINTED does not match perfectly either when Windows font smoothing is on or off.
58    DWORD outlineLength = GetGlyphOutline(hdc, glyph, GGO_GLYPH_INDEX | GGO_NATIVE, &glyphMetrics, 0, 0, &identity);
59    ASSERT(outlineLength >= 0);
60    if (outlineLength < 0)
61        return path;
62
63    Vector<UInt8> outline(outlineLength);
64    GetGlyphOutline(hdc, glyph, GGO_GLYPH_INDEX | GGO_NATIVE, &glyphMetrics, outlineLength, outline.data(), &identity);
65
66    unsigned offset = 0;
67    while (offset < outlineLength) {
68        LPTTPOLYGONHEADER subpath = reinterpret_cast<LPTTPOLYGONHEADER>(outline.data() + offset);
69        ASSERT(subpath->dwType == TT_POLYGON_TYPE);
70        if (subpath->dwType != TT_POLYGON_TYPE)
71            return path;
72
73        CGPathMoveToPoint(path, 0, toCGFloat(subpath->pfxStart.x), toCGFloat(subpath->pfxStart.y));
74
75        unsigned subpathOffset = sizeof(*subpath);
76        while (subpathOffset < subpath->cb) {
77            LPTTPOLYCURVE segment = reinterpret_cast<LPTTPOLYCURVE>(reinterpret_cast<UInt8*>(subpath) + subpathOffset);
78            switch (segment->wType) {
79                case TT_PRIM_LINE:
80                    for (unsigned i = 0; i < segment->cpfx; i++)
81                        CGPathAddLineToPoint(path, 0, toCGFloat(segment->apfx[i].x), toCGFloat(segment->apfx[i].y));
82                    break;
83
84                case TT_PRIM_QSPLINE:
85                    for (unsigned i = 0; i < segment->cpfx; i++) {
86                        CGFloat x = toCGFloat(segment->apfx[i].x);
87                        CGFloat y = toCGFloat(segment->apfx[i].y);
88                        CGFloat cpx;
89                        CGFloat cpy;
90
91                        if (i == segment->cpfx - 2) {
92                            cpx = toCGFloat(segment->apfx[i + 1].x);
93                            cpy = toCGFloat(segment->apfx[i + 1].y);
94                            i++;
95                        } else {
96                            cpx = (toCGFloat(segment->apfx[i].x) + toCGFloat(segment->apfx[i + 1].x)) / 2;
97                            cpy = (toCGFloat(segment->apfx[i].y) + toCGFloat(segment->apfx[i + 1].y)) / 2;
98                        }
99
100                        CGPathAddQuadCurveToPoint(path, 0, x, y, cpx, cpy);
101                    }
102                    break;
103
104                case TT_PRIM_CSPLINE:
105                    for (unsigned i = 0; i < segment->cpfx; i += 3) {
106                        CGFloat cp1x = toCGFloat(segment->apfx[i].x);
107                        CGFloat cp1y = toCGFloat(segment->apfx[i].y);
108                        CGFloat cp2x = toCGFloat(segment->apfx[i + 1].x);
109                        CGFloat cp2y = toCGFloat(segment->apfx[i + 1].y);
110                        CGFloat x = toCGFloat(segment->apfx[i + 2].x);
111                        CGFloat y = toCGFloat(segment->apfx[i + 2].y);
112
113                        CGPathAddCurveToPoint(path, 0, cp1x, cp1y, cp2x, cp2y, x, y);
114                    }
115                    break;
116
117                default:
118                    ASSERT_NOT_REACHED();
119                    return path;
120            }
121
122            subpathOffset += sizeof(*segment) + (segment->cpfx - 1) * sizeof(segment->apfx[0]);
123        }
124        CGPathCloseSubpath(path);
125        offset += subpath->cb;
126    }
127    return path;
128}
129
130static void drawGDIGlyphs(GraphicsContext* graphicsContext, const SimpleFontData* font, const GlyphBuffer& glyphBuffer,
131                      int from, int numGlyphs, const FloatPoint& point)
132{
133    Color fillColor = graphicsContext->fillColor();
134
135    bool drawIntoBitmap = false;
136    TextDrawingModeFlags drawingMode = graphicsContext->textDrawingMode();
137    if (drawingMode == TextModeFill) {
138        if (!fillColor.alpha())
139            return;
140
141        drawIntoBitmap = fillColor.alpha() != 255 || graphicsContext->inTransparencyLayer();
142        if (!drawIntoBitmap) {
143            FloatSize offset;
144            float blur;
145            Color color;
146            ColorSpace shadowColorSpace;
147
148            graphicsContext->getShadow(offset, blur, color, shadowColorSpace);
149            drawIntoBitmap = offset.width() || offset.height() || blur;
150        }
151    }
152
153    // We have to convert CG's two-dimensional floating point advances to just horizontal integer advances.
154    Vector<int, 2048> gdiAdvances;
155    int totalWidth = 0;
156    for (int i = 0; i < numGlyphs; i++) {
157        gdiAdvances.append(lroundf(glyphBuffer.advanceAt(from + i)));
158        totalWidth += gdiAdvances[i];
159    }
160
161    HDC hdc = 0;
162    OwnPtr<GraphicsContext::WindowsBitmap> bitmap;
163    IntRect textRect;
164    if (!drawIntoBitmap)
165        hdc = graphicsContext->getWindowsContext(textRect, true, false);
166    if (!hdc) {
167        drawIntoBitmap = true;
168        // We put slop into this rect, since glyphs can overflow the ascent/descent bounds and the left/right edges.
169        // FIXME: Can get glyphs' optical bounds (even from CG) to get this right.
170        const FontMetrics& fontMetrics = font->fontMetrics();
171        int lineGap = fontMetrics.lineGap();
172        textRect = IntRect(point.x() - (fontMetrics.ascent() + fontMetrics.descent()) / 2,
173                           point.y() - fontMetrics.ascent() - lineGap,
174                           totalWidth + fontMetrics.ascent() + fontMetrics.descent(),
175                           fontMetrics.lineSpacing());
176        bitmap.set(graphicsContext->createWindowsBitmap(textRect.size()));
177        memset(bitmap->buffer(), 255, bitmap->bufferLength());
178        hdc = bitmap->hdc();
179
180        XFORM xform;
181        xform.eM11 = 1.0f;
182        xform.eM12 = 0.0f;
183        xform.eM21 = 0.0f;
184        xform.eM22 = 1.0f;
185        xform.eDx = -textRect.x();
186        xform.eDy = -textRect.y();
187        SetWorldTransform(hdc, &xform);
188    }
189
190    SelectObject(hdc, font->platformData().hfont());
191
192    // Set the correct color.
193    if (drawIntoBitmap)
194        SetTextColor(hdc, RGB(0, 0, 0));
195    else
196        SetTextColor(hdc, RGB(fillColor.red(), fillColor.green(), fillColor.blue()));
197
198    SetBkMode(hdc, TRANSPARENT);
199    SetTextAlign(hdc, TA_LEFT | TA_BASELINE);
200
201    // Uniscribe gives us offsets to help refine the positioning of combining glyphs.
202    FloatSize translation = glyphBuffer.offsetAt(from);
203    if (translation.width() || translation.height()) {
204        XFORM xform;
205        xform.eM11 = 1.0;
206        xform.eM12 = 0;
207        xform.eM21 = 0;
208        xform.eM22 = 1.0;
209        xform.eDx = translation.width();
210        xform.eDy = translation.height();
211        ModifyWorldTransform(hdc, &xform, MWT_LEFTMULTIPLY);
212    }
213
214    if (drawingMode == TextModeFill) {
215        XFORM xform;
216        xform.eM11 = 1.0;
217        xform.eM12 = 0;
218        xform.eM21 = font->platformData().syntheticOblique() ? -tanf(syntheticObliqueAngle * piFloat / 180.0f) : 0;
219        xform.eM22 = 1.0;
220        xform.eDx = point.x();
221        xform.eDy = point.y();
222        ModifyWorldTransform(hdc, &xform, MWT_LEFTMULTIPLY);
223        ExtTextOut(hdc, 0, 0, ETO_GLYPH_INDEX, 0, reinterpret_cast<const WCHAR*>(glyphBuffer.glyphs(from)), numGlyphs, gdiAdvances.data());
224        if (font->syntheticBoldOffset()) {
225            xform.eM21 = 0;
226            xform.eDx = font->syntheticBoldOffset();
227            xform.eDy = 0;
228            ModifyWorldTransform(hdc, &xform, MWT_LEFTMULTIPLY);
229            ExtTextOut(hdc, 0, 0, ETO_GLYPH_INDEX, 0, reinterpret_cast<const WCHAR*>(glyphBuffer.glyphs(from)), numGlyphs, gdiAdvances.data());
230        }
231    } else {
232        XFORM xform;
233        GetWorldTransform(hdc, &xform);
234        AffineTransform hdcTransform(xform.eM11, xform.eM21, xform.eM12, xform.eM22, xform.eDx, xform.eDy);
235        CGAffineTransform initialGlyphTransform = hdcTransform.isInvertible() ? hdcTransform.inverse() : CGAffineTransformIdentity;
236        if (font->platformData().syntheticOblique())
237            initialGlyphTransform = CGAffineTransformConcat(initialGlyphTransform, CGAffineTransformMake(1, 0, tanf(syntheticObliqueAngle * piFloat / 180.0f), 1, 0, 0));
238        initialGlyphTransform.tx = 0;
239        initialGlyphTransform.ty = 0;
240        CGContextRef cgContext = graphicsContext->platformContext();
241
242        CGContextSaveGState(cgContext);
243
244        BOOL fontSmoothingEnabled = false;
245        SystemParametersInfo(SPI_GETFONTSMOOTHING, 0, &fontSmoothingEnabled, 0);
246        CGContextSetShouldAntialias(cgContext, fontSmoothingEnabled);
247
248        CGContextScaleCTM(cgContext, 1.0, -1.0);
249        CGContextTranslateCTM(cgContext, point.x() + glyphBuffer.offsetAt(from).width(), -(point.y() + glyphBuffer.offsetAt(from).height()));
250
251        for (unsigned i = 0; i < numGlyphs; ++i) {
252            RetainPtr<CGPathRef> glyphPath(AdoptCF, createPathForGlyph(hdc, glyphBuffer.glyphAt(from + i)));
253            CGContextSaveGState(cgContext);
254            CGContextConcatCTM(cgContext, initialGlyphTransform);
255
256            if (drawingMode & TextModeFill) {
257                CGContextAddPath(cgContext, glyphPath.get());
258                CGContextFillPath(cgContext);
259                if (font->syntheticBoldOffset()) {
260                    CGContextTranslateCTM(cgContext, font->syntheticBoldOffset(), 0);
261                    CGContextAddPath(cgContext, glyphPath.get());
262                    CGContextFillPath(cgContext);
263                    CGContextTranslateCTM(cgContext, -font->syntheticBoldOffset(), 0);
264                }
265            }
266            if (drawingMode & TextModeStroke) {
267                CGContextAddPath(cgContext, glyphPath.get());
268                CGContextStrokePath(cgContext);
269                if (font->syntheticBoldOffset()) {
270                    CGContextTranslateCTM(cgContext, font->syntheticBoldOffset(), 0);
271                    CGContextAddPath(cgContext, glyphPath.get());
272                    CGContextStrokePath(cgContext);
273                    CGContextTranslateCTM(cgContext, -font->syntheticBoldOffset(), 0);
274                }
275            }
276
277            CGContextRestoreGState(cgContext);
278            CGContextTranslateCTM(cgContext, gdiAdvances[i], 0);
279        }
280
281        CGContextRestoreGState(cgContext);
282    }
283
284    if (drawIntoBitmap) {
285        UInt8* buffer = bitmap->buffer();
286        unsigned bufferLength = bitmap->bufferLength();
287        for (unsigned i = 0; i < bufferLength; i += 4) {
288            // Use green, which is always in the middle.
289            UInt8 alpha = (255 - buffer[i + 1]) * fillColor.alpha() / 255;
290            buffer[i] = fillColor.blue();
291            buffer[i + 1] = fillColor.green();
292            buffer[i + 2] = fillColor.red();
293            buffer[i + 3] = alpha;
294        }
295        graphicsContext->drawWindowsBitmap(bitmap.get(), textRect.location());
296    } else
297        graphicsContext->releaseWindowsContext(hdc, textRect, true, false);
298}
299
300void Font::drawGlyphs(GraphicsContext* graphicsContext, const SimpleFontData* font, const GlyphBuffer& glyphBuffer,
301                      int from, int numGlyphs, const FloatPoint& point) const
302{
303    CGContextRef cgContext = graphicsContext->platformContext();
304    bool shouldUseFontSmoothing = WebCoreShouldUseFontSmoothing();
305
306    switch(fontDescription().fontSmoothing()) {
307    case Antialiased: {
308        graphicsContext->setShouldAntialias(true);
309        shouldUseFontSmoothing = false;
310        break;
311    }
312    case SubpixelAntialiased: {
313        graphicsContext->setShouldAntialias(true);
314        shouldUseFontSmoothing = true;
315        break;
316    }
317    case NoSmoothing: {
318        graphicsContext->setShouldAntialias(false);
319        shouldUseFontSmoothing = false;
320        break;
321    }
322    case AutoSmoothing: {
323        // For the AutoSmooth case, don't do anything! Keep the default settings.
324        break;
325    }
326    default:
327        ASSERT_NOT_REACHED();
328    }
329
330    if (font->platformData().useGDI() && !shouldUseFontSmoothing) {
331        drawGDIGlyphs(graphicsContext, font, glyphBuffer, from, numGlyphs, point);
332        return;
333    }
334
335    uint32_t oldFontSmoothingStyle = wkSetFontSmoothingStyle(cgContext, shouldUseFontSmoothing);
336
337    const FontPlatformData& platformData = font->platformData();
338
339    CGContextSetFont(cgContext, platformData.cgFont());
340
341    CGAffineTransform matrix = CGAffineTransformIdentity;
342    matrix.b = -matrix.b;
343    matrix.d = -matrix.d;
344
345    if (platformData.syntheticOblique()) {
346        static float skew = -tanf(syntheticObliqueAngle * piFloat / 180.0f);
347        matrix = CGAffineTransformConcat(matrix, CGAffineTransformMake(1, 0, skew, 1, 0, 0));
348    }
349
350    CGContextSetTextMatrix(cgContext, matrix);
351
352    // Uniscribe gives us offsets to help refine the positioning of combining glyphs.
353    FloatSize translation = glyphBuffer.offsetAt(from);
354
355    CGContextSetFontSize(cgContext, platformData.size());
356    wkSetCGContextFontRenderingStyle(cgContext, font->isSystemFont(), false, font->platformData().useGDI());
357
358    FloatSize shadowOffset;
359    float shadowBlur;
360    Color shadowColor;
361    ColorSpace shadowColorSpace;
362    graphicsContext->getShadow(shadowOffset, shadowBlur, shadowColor, shadowColorSpace);
363
364    bool hasSimpleShadow = graphicsContext->textDrawingMode() == TextModeFill && shadowColor.isValid() && !shadowBlur && (!graphicsContext->shadowsIgnoreTransforms() || graphicsContext->getCTM().isIdentityOrTranslationOrFlipped());
365    if (hasSimpleShadow) {
366        // Paint simple shadows ourselves instead of relying on CG shadows, to avoid losing subpixel antialiasing.
367        graphicsContext->clearShadow();
368        Color fillColor = graphicsContext->fillColor();
369        Color shadowFillColor(shadowColor.red(), shadowColor.green(), shadowColor.blue(), shadowColor.alpha() * fillColor.alpha() / 255);
370        graphicsContext->setFillColor(shadowFillColor, ColorSpaceDeviceRGB);
371        float shadowTextX = point.x() + translation.width() + shadowOffset.width();
372        // If shadows are ignoring transforms, then we haven't applied the Y coordinate flip yet, so down is negative.
373        float shadowTextY = point.y() + translation.height() + shadowOffset.height() * (graphicsContext->shadowsIgnoreTransforms() ? -1 : 1);
374        CGContextSetTextPosition(cgContext, shadowTextX, shadowTextY);
375        CGContextShowGlyphsWithAdvances(cgContext, glyphBuffer.glyphs(from), glyphBuffer.advances(from), numGlyphs);
376        if (font->syntheticBoldOffset()) {
377            CGContextSetTextPosition(cgContext, point.x() + translation.width() + shadowOffset.width() + font->syntheticBoldOffset(), point.y() + translation.height() + shadowOffset.height());
378            CGContextShowGlyphsWithAdvances(cgContext, glyphBuffer.glyphs(from), glyphBuffer.advances(from), numGlyphs);
379        }
380        graphicsContext->setFillColor(fillColor, ColorSpaceDeviceRGB);
381    }
382
383    CGContextSetTextPosition(cgContext, point.x() + translation.width(), point.y() + translation.height());
384    CGContextShowGlyphsWithAdvances(cgContext, glyphBuffer.glyphs(from), glyphBuffer.advances(from), numGlyphs);
385    if (font->syntheticBoldOffset()) {
386        CGContextSetTextPosition(cgContext, point.x() + translation.width() + font->syntheticBoldOffset(), point.y() + translation.height());
387        CGContextShowGlyphsWithAdvances(cgContext, glyphBuffer.glyphs(from), glyphBuffer.advances(from), numGlyphs);
388    }
389
390    if (hasSimpleShadow)
391        graphicsContext->setShadow(shadowOffset, shadowBlur, shadowColor, ColorSpaceDeviceRGB);
392
393    wkRestoreFontSmoothingStyle(cgContext, oldFontSmoothingStyle);
394}
395
396}
397