• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
41 namespace WebCore {
42 
43 const int syntheticObliqueAngle = 14;
44 
toCGFloat(FIXED f)45 static inline CGFloat toCGFloat(FIXED f)
46 {
47     return f.value + f.fract / CGFloat(65536.0);
48 }
49 
createPathForGlyph(HDC hdc,Glyph glyph)50 static 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 
drawGDIGlyphs(GraphicsContext * graphicsContext,const SimpleFontData * font,const GlyphBuffer & glyphBuffer,int from,int numGlyphs,const FloatPoint & point)130 static 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 
drawGlyphs(GraphicsContext * graphicsContext,const SimpleFontData * font,const GlyphBuffer & glyphBuffer,int from,int numGlyphs,const FloatPoint & point) const300 void 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