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