1 /*
2 * Copyright (C) 2006 Apple Computer, Inc. All rights reserved.
3 * Copyright (C) 2006 Michael Emmel mike.emmel@gmail.com
4 * Copyright (c) 2007 Hiroyuki Ikezoe
5 * Copyright (c) 2007 Kouhei Sutou
6 * Copyright (C) 2007 Alp Toker <alp@atoker.com>
7 * Copyright (C) 2008 Xan Lopez <xan@gnome.org>
8 * Copyright (C) 2008 Nuanti Ltd.
9 * All rights reserved.
10 *
11 * Redistribution and use in source and binary forms, with or without
12 * modification, are permitted provided that the following conditions
13 * are met:
14 * 1. Redistributions of source code must retain the above copyright
15 * notice, this list of conditions and the following disclaimer.
16 * 2. Redistributions in binary form must reproduce the above copyright
17 * notice, this list of conditions and the following disclaimer in the
18 * documentation and/or other materials provided with the distribution.
19 *
20 * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
21 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
23 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR
24 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
25 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
26 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
27 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
28 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31 */
32
33 #include "config.h"
34 #include "Font.h"
35
36 #include "CairoUtilities.h"
37 #include "ContextShadow.h"
38 #include "GOwnPtr.h"
39 #include "GraphicsContext.h"
40 #include "NotImplemented.h"
41 #include "PlatformContextCairo.h"
42 #include "SimpleFontData.h"
43 #include "TextRun.h"
44 #include <cairo.h>
45 #include <gdk/gdk.h>
46 #include <pango/pango.h>
47 #include <pango/pangocairo.h>
48
49 #if USE(FREETYPE)
50 #include <pango/pangofc-fontmap.h>
51 #endif
52
53 namespace WebCore {
54
55 #ifdef GTK_API_VERSION_2
56 typedef GdkRegion* PangoRegionType;
57
destroyPangoRegion(PangoRegionType region)58 void destroyPangoRegion(PangoRegionType region)
59 {
60 gdk_region_destroy(region);
61 }
62
getPangoRegionExtents(PangoRegionType region)63 IntRect getPangoRegionExtents(PangoRegionType region)
64 {
65 GdkRectangle rectangle;
66 gdk_region_get_clipbox(region, &rectangle);
67 return IntRect(rectangle.x, rectangle.y, rectangle.width, rectangle.height);
68 }
69 #else
70 typedef cairo_region_t* PangoRegionType;
71
72 void destroyPangoRegion(PangoRegionType region)
73 {
74 cairo_region_destroy(region);
75 }
76
77 IntRect getPangoRegionExtents(PangoRegionType region)
78 {
79 cairo_rectangle_int_t rectangle;
80 cairo_region_get_extents(region, &rectangle);
81 return IntRect(rectangle.x, rectangle.y, rectangle.width, rectangle.height);
82 }
83 #endif
84
85 #define IS_HIGH_SURROGATE(u) ((UChar)(u) >= (UChar)0xd800 && (UChar)(u) <= (UChar)0xdbff)
86 #define IS_LOW_SURROGATE(u) ((UChar)(u) >= (UChar)0xdc00 && (UChar)(u) <= (UChar)0xdfff)
87
utf16ToUtf8(const UChar * aText,gint aLength,gint & length)88 static gchar* utf16ToUtf8(const UChar* aText, gint aLength, gint &length)
89 {
90 gboolean needCopy = FALSE;
91
92 for (int i = 0; i < aLength; i++) {
93 if (!aText[i] || IS_LOW_SURROGATE(aText[i])) {
94 needCopy = TRUE;
95 break;
96 }
97
98 if (IS_HIGH_SURROGATE(aText[i])) {
99 if (i < aLength - 1 && IS_LOW_SURROGATE(aText[i+1]))
100 i++;
101 else {
102 needCopy = TRUE;
103 break;
104 }
105 }
106 }
107
108 GOwnPtr<UChar> copiedString;
109 if (needCopy) {
110 /* Pango doesn't correctly handle nuls. We convert them to 0xff. */
111 /* Also "validate" UTF-16 text to make sure conversion doesn't fail. */
112
113 copiedString.set(static_cast<UChar*>(g_memdup(aText, aLength * sizeof(aText[0]))));
114 UChar* p = copiedString.get();
115
116 /* don't need to reset i */
117 for (int i = 0; i < aLength; i++) {
118 if (!p[i] || IS_LOW_SURROGATE(p[i]))
119 p[i] = 0xFFFD;
120 else if (IS_HIGH_SURROGATE(p[i])) {
121 if (i < aLength - 1 && IS_LOW_SURROGATE(aText[i+1]))
122 i++;
123 else
124 p[i] = 0xFFFD;
125 }
126 }
127
128 aText = p;
129 }
130
131 gchar* utf8Text;
132 glong itemsWritten;
133 utf8Text = g_utf16_to_utf8(static_cast<const gunichar2*>(aText), aLength, 0, &itemsWritten, 0);
134 length = itemsWritten;
135
136 return utf8Text;
137 }
138
convertUniCharToUTF8(const UChar * characters,gint length,int from,int to)139 static gchar* convertUniCharToUTF8(const UChar* characters, gint length, int from, int to)
140 {
141 gint newLength = 0;
142 GOwnPtr<gchar> utf8Text(utf16ToUtf8(characters, length, newLength));
143 if (!utf8Text)
144 return 0;
145
146 gchar* pos = utf8Text.get();
147 if (from > 0) {
148 // discard the first 'from' characters
149 // FIXME: we should do this before the conversion probably
150 pos = g_utf8_offset_to_pointer(utf8Text.get(), from);
151 }
152
153 gint len = strlen(pos);
154 GString* ret = g_string_new_len(NULL, len);
155
156 // replace line break by space
157 while (len > 0) {
158 gint index, start;
159 pango_find_paragraph_boundary(pos, len, &index, &start);
160 g_string_append_len(ret, pos, index);
161 if (index == start)
162 break;
163 g_string_append_c(ret, ' ');
164 pos += start;
165 len -= start;
166 }
167 return g_string_free(ret, FALSE);
168 }
169
setPangoAttributes(const Font * font,const TextRun & run,PangoLayout * layout)170 static void setPangoAttributes(const Font* font, const TextRun& run, PangoLayout* layout)
171 {
172 #if USE(FREETYPE)
173 if (font->primaryFont()->platformData().m_pattern) {
174 PangoFontDescription* desc = pango_fc_font_description_from_pattern(font->primaryFont()->platformData().m_pattern.get(), FALSE);
175 pango_layout_set_font_description(layout, desc);
176 pango_font_description_free(desc);
177 }
178 #elif USE(PANGO)
179 if (font->primaryFont()->platformData().m_font) {
180 PangoFontDescription* desc = pango_font_describe(font->primaryFont()->platformData().m_font);
181 pango_layout_set_font_description(layout, desc);
182 pango_font_description_free(desc);
183 }
184 #endif
185
186 pango_layout_set_auto_dir(layout, FALSE);
187
188 PangoContext* pangoContext = pango_layout_get_context(layout);
189 PangoDirection direction = run.rtl() ? PANGO_DIRECTION_RTL : PANGO_DIRECTION_LTR;
190 pango_context_set_base_dir(pangoContext, direction);
191 PangoAttrList* list = pango_attr_list_new();
192 PangoAttribute* attr;
193
194 attr = pango_attr_size_new_absolute(font->pixelSize() * PANGO_SCALE);
195 attr->end_index = G_MAXUINT;
196 pango_attr_list_insert_before(list, attr);
197
198 if (!run.spacingDisabled()) {
199 attr = pango_attr_letter_spacing_new(font->letterSpacing() * PANGO_SCALE);
200 attr->end_index = G_MAXUINT;
201 pango_attr_list_insert_before(list, attr);
202 }
203
204 // Pango does not yet support synthesising small caps
205 // See http://bugs.webkit.org/show_bug.cgi?id=15610
206
207 pango_layout_set_attributes(layout, list);
208 pango_attr_list_unref(list);
209 }
210
canReturnFallbackFontsForComplexText()211 bool Font::canReturnFallbackFontsForComplexText()
212 {
213 return false;
214 }
215
canExpandAroundIdeographsInComplexText()216 bool Font::canExpandAroundIdeographsInComplexText()
217 {
218 return false;
219 }
220
drawGlyphsShadow(GraphicsContext * graphicsContext,const FloatPoint & point,PangoLayoutLine * layoutLine,PangoRegionType renderRegion)221 static void drawGlyphsShadow(GraphicsContext* graphicsContext, const FloatPoint& point, PangoLayoutLine* layoutLine, PangoRegionType renderRegion)
222 {
223 ContextShadow* shadow = graphicsContext->contextShadow();
224 ASSERT(shadow);
225
226 if (!(graphicsContext->textDrawingMode() & TextModeFill) || shadow->m_type == ContextShadow::NoShadow)
227 return;
228
229 FloatPoint totalOffset(point + shadow->m_offset);
230
231 // Optimize non-blurry shadows, by just drawing text without the ContextShadow.
232 if (!shadow->mustUseContextShadow(graphicsContext)) {
233 cairo_t* context = graphicsContext->platformContext()->cr();
234 cairo_save(context);
235 cairo_translate(context, totalOffset.x(), totalOffset.y());
236
237 setSourceRGBAFromColor(context, shadow->m_color);
238 gdk_cairo_region(context, renderRegion);
239 cairo_clip(context);
240 pango_cairo_show_layout_line(context, layoutLine);
241
242 cairo_restore(context);
243 return;
244 }
245
246 FloatRect extents(getPangoRegionExtents(renderRegion));
247 extents.setLocation(FloatPoint(point.x(), point.y() - extents.height()));
248 cairo_t* shadowContext = shadow->beginShadowLayer(graphicsContext, extents);
249 if (shadowContext) {
250 cairo_translate(shadowContext, point.x(), point.y());
251 pango_cairo_show_layout_line(shadowContext, layoutLine);
252
253 // We need the clipping region to be active when we blit the blurred shadow back,
254 // because we don't want any bits and pieces of characters out of range to be
255 // drawn. Since ContextShadow expects a consistent transform, we have to undo the
256 // translation before calling endShadowLayer as well.
257 cairo_t* context = graphicsContext->platformContext()->cr();
258 cairo_save(context);
259 cairo_translate(context, totalOffset.x(), totalOffset.y());
260 gdk_cairo_region(context, renderRegion);
261 cairo_clip(context);
262 cairo_translate(context, -totalOffset.x(), -totalOffset.y());
263
264 shadow->endShadowLayer(graphicsContext);
265 cairo_restore(context);
266 }
267 }
268
drawComplexText(GraphicsContext * context,const TextRun & run,const FloatPoint & point,int from,int to) const269 void Font::drawComplexText(GraphicsContext* context, const TextRun& run, const FloatPoint& point, int from, int to) const
270 {
271 #if USE(FREETYPE)
272 if (!primaryFont()->platformData().m_pattern) {
273 drawSimpleText(context, run, point, from, to);
274 return;
275 }
276 #endif
277
278 cairo_t* cr = context->platformContext()->cr();
279 PangoLayout* layout = pango_cairo_create_layout(cr);
280 setPangoAttributes(this, run, layout);
281
282 gchar* utf8 = convertUniCharToUTF8(run.characters(), run.length(), 0, run.length());
283 pango_layout_set_text(layout, utf8, -1);
284
285 // Our layouts are single line
286 PangoLayoutLine* layoutLine = pango_layout_get_line_readonly(layout, 0);
287
288 // Get the region where this text will be laid out. We will use it to clip
289 // the Cairo context, for when we are only painting part of the text run and
290 // to calculate the size of the shadow buffer.
291 PangoRegionType partialRegion = 0;
292 char* start = g_utf8_offset_to_pointer(utf8, from);
293 char* end = g_utf8_offset_to_pointer(start, to - from);
294 int ranges[] = {start - utf8, end - utf8};
295 partialRegion = gdk_pango_layout_line_get_clip_region(layoutLine, 0, 0, ranges, 1);
296
297 drawGlyphsShadow(context, point, layoutLine, partialRegion);
298
299 cairo_save(cr);
300 cairo_translate(cr, point.x(), point.y());
301
302 float red, green, blue, alpha;
303 context->fillColor().getRGBA(red, green, blue, alpha);
304 cairo_set_source_rgba(cr, red, green, blue, alpha);
305 gdk_cairo_region(cr, partialRegion);
306 cairo_clip(cr);
307
308 pango_cairo_show_layout_line(cr, layoutLine);
309
310 if (context->textDrawingMode() & TextModeStroke) {
311 Color strokeColor = context->strokeColor();
312 strokeColor.getRGBA(red, green, blue, alpha);
313 cairo_set_source_rgba(cr, red, green, blue, alpha);
314 pango_cairo_layout_line_path(cr, layoutLine);
315 cairo_set_line_width(cr, context->strokeThickness());
316 cairo_stroke(cr);
317 }
318
319 // Pango sometimes leaves behind paths we don't want
320 cairo_new_path(cr);
321
322 destroyPangoRegion(partialRegion);
323 g_free(utf8);
324 g_object_unref(layout);
325
326 cairo_restore(cr);
327 }
328
drawEmphasisMarksForComplexText(GraphicsContext *,const TextRun &,const AtomicString &,const FloatPoint &,int,int) const329 void Font::drawEmphasisMarksForComplexText(GraphicsContext* /* context */, const TextRun& /* run */, const AtomicString& /* mark */, const FloatPoint& /* point */, int /* from */, int /* to */) const
330 {
331 notImplemented();
332 }
333
334 // We should create the layout with our actual context but we can't access it from here.
getDefaultPangoLayout(const TextRun & run)335 static PangoLayout* getDefaultPangoLayout(const TextRun& run)
336 {
337 static PangoFontMap* map = pango_cairo_font_map_get_default();
338 #if PANGO_VERSION_CHECK(1,21,5)
339 static PangoContext* pangoContext = pango_font_map_create_context(map);
340 #else
341 // Deprecated in Pango 1.21.
342 static PangoContext* pangoContext = pango_cairo_font_map_create_context(PANGO_CAIRO_FONT_MAP(map));
343 #endif
344 PangoLayout* layout = pango_layout_new(pangoContext);
345
346 return layout;
347 }
348
floatWidthForComplexText(const TextRun & run,HashSet<const SimpleFontData * > * fallbackFonts,GlyphOverflow * overflow) const349 float Font::floatWidthForComplexText(const TextRun& run, HashSet<const SimpleFontData*>* fallbackFonts, GlyphOverflow* overflow) const
350 {
351 #if USE(FREETYPE)
352 if (!primaryFont()->platformData().m_pattern)
353 return floatWidthForSimpleText(run, 0, fallbackFonts, overflow);
354 #endif
355
356 if (run.length() == 0)
357 return 0.0f;
358
359 PangoLayout* layout = getDefaultPangoLayout(run);
360 setPangoAttributes(this, run, layout);
361
362 gchar* utf8 = convertUniCharToUTF8(run.characters(), run.length(), 0, run.length());
363 pango_layout_set_text(layout, utf8, -1);
364
365 int width;
366 pango_layout_get_pixel_size(layout, &width, 0);
367
368 g_free(utf8);
369 g_object_unref(layout);
370
371 return width;
372 }
373
offsetForPositionForComplexText(const TextRun & run,float xFloat,bool includePartialGlyphs) const374 int Font::offsetForPositionForComplexText(const TextRun& run, float xFloat, bool includePartialGlyphs) const
375 {
376 #if USE(FREETYPE)
377 if (!primaryFont()->platformData().m_pattern)
378 return offsetForPositionForSimpleText(run, xFloat, includePartialGlyphs);
379 #endif
380 // FIXME: This truncation is not a problem for HTML, but only affects SVG, which passes floating-point numbers
381 // to Font::offsetForPosition(). Bug http://webkit.org/b/40673 tracks fixing this problem.
382 int x = static_cast<int>(xFloat);
383
384 PangoLayout* layout = getDefaultPangoLayout(run);
385 setPangoAttributes(this, run, layout);
386
387 gchar* utf8 = convertUniCharToUTF8(run.characters(), run.length(), 0, run.length());
388 pango_layout_set_text(layout, utf8, -1);
389
390 int index, trailing;
391 pango_layout_xy_to_index(layout, x * PANGO_SCALE, 1, &index, &trailing);
392 glong offset = g_utf8_pointer_to_offset(utf8, utf8 + index);
393 if (includePartialGlyphs)
394 offset += trailing;
395
396 g_free(utf8);
397 g_object_unref(layout);
398
399 return offset;
400 }
401
selectionRectForComplexText(const TextRun & run,const FloatPoint & point,int h,int from,int to) const402 FloatRect Font::selectionRectForComplexText(const TextRun& run, const FloatPoint& point, int h, int from, int to) const
403 {
404 #if USE(FREETYPE)
405 if (!primaryFont()->platformData().m_pattern)
406 return selectionRectForSimpleText(run, point, h, from, to);
407 #endif
408
409 PangoLayout* layout = getDefaultPangoLayout(run);
410 setPangoAttributes(this, run, layout);
411
412 gchar* utf8 = convertUniCharToUTF8(run.characters(), run.length(), 0, run.length());
413 pango_layout_set_text(layout, utf8, -1);
414
415 char* start = g_utf8_offset_to_pointer(utf8, from);
416 char* end = g_utf8_offset_to_pointer(start, to - from);
417
418 if (run.ltr()) {
419 from = start - utf8;
420 to = end - utf8;
421 } else {
422 from = end - utf8;
423 to = start - utf8;
424 }
425
426 PangoLayoutLine* layoutLine = pango_layout_get_line_readonly(layout, 0);
427 int x_pos;
428
429 x_pos = 0;
430 if (from < layoutLine->length)
431 pango_layout_line_index_to_x(layoutLine, from, FALSE, &x_pos);
432 float beforeWidth = PANGO_PIXELS_FLOOR(x_pos);
433
434 x_pos = 0;
435 if (run.ltr() || to < layoutLine->length)
436 pango_layout_line_index_to_x(layoutLine, to, FALSE, &x_pos);
437 float afterWidth = PANGO_PIXELS(x_pos);
438
439 g_free(utf8);
440 g_object_unref(layout);
441
442 return FloatRect(point.x() + beforeWidth, point.y(), afterWidth - beforeWidth, h);
443 }
444
445 }
446