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 "GraphicsContext.h"
37 #include "SimpleFontData.h"
38
39 #include <cairo.h>
40 #include <gdk/gdk.h>
41 #include <pango/pango.h>
42 #include <pango/pangocairo.h>
43 #if defined(USE_FREETYPE)
44 #include <pango/pangofc-fontmap.h>
45 #endif
46
47 namespace WebCore {
48
49 #define IS_HIGH_SURROGATE(u) ((UChar)(u) >= (UChar)0xd800 && (UChar)(u) <= (UChar)0xdbff)
50 #define IS_LOW_SURROGATE(u) ((UChar)(u) >= (UChar)0xdc00 && (UChar)(u) <= (UChar)0xdfff)
51
utf16_to_utf8(const UChar * aText,gint aLength,char * & text,gint & length)52 static void utf16_to_utf8(const UChar* aText, gint aLength, char* &text, gint &length)
53 {
54 gboolean need_copy = FALSE;
55 int i;
56
57 for (i = 0; i < aLength; i++) {
58 if (!aText[i] || IS_LOW_SURROGATE(aText[i])) {
59 need_copy = TRUE;
60 break;
61 }
62 else if (IS_HIGH_SURROGATE(aText[i])) {
63 if (i < aLength - 1 && IS_LOW_SURROGATE(aText[i+1]))
64 i++;
65 else {
66 need_copy = TRUE;
67 break;
68 }
69 }
70 }
71
72 if (need_copy) {
73
74 /* Pango doesn't correctly handle nuls. We convert them to 0xff. */
75 /* Also "validate" UTF-16 text to make sure conversion doesn't fail. */
76
77 UChar* p = (UChar*)g_memdup(aText, aLength * sizeof(aText[0]));
78
79 /* don't need to reset i */
80 for (i = 0; i < aLength; i++) {
81 if (!p[i] || IS_LOW_SURROGATE(p[i]))
82 p[i] = 0xFFFD;
83 else if (IS_HIGH_SURROGATE(p[i])) {
84 if (i < aLength - 1 && IS_LOW_SURROGATE(aText[i+1]))
85 i++;
86 else
87 p[i] = 0xFFFD;
88 }
89 }
90
91 aText = p;
92 }
93
94 glong items_written;
95 text = g_utf16_to_utf8(reinterpret_cast<const gunichar2*>(aText), aLength, NULL, &items_written, NULL);
96 length = items_written;
97
98 if (need_copy)
99 g_free((gpointer)aText);
100
101 }
102
convertUniCharToUTF8(const UChar * characters,gint length,int from,int to)103 static gchar* convertUniCharToUTF8(const UChar* characters, gint length, int from, int to)
104 {
105 gchar* utf8 = 0;
106 gint new_length = 0;
107 utf16_to_utf8(characters, length, utf8, new_length);
108 if (!utf8)
109 return NULL;
110
111 if (from > 0) {
112 // discard the first 'from' characters
113 // FIXME: we should do this before the conversion probably
114 gchar* str_left = g_utf8_offset_to_pointer(utf8, from);
115 gchar* tmp = g_strdup(str_left);
116 g_free(utf8);
117 utf8 = tmp;
118 }
119
120 gchar* pos = utf8;
121 gint len = strlen(pos);
122 GString* ret = g_string_new_len(NULL, len);
123
124 // replace line break by space
125 while (len > 0) {
126 gint index, start;
127 pango_find_paragraph_boundary(pos, len, &index, &start);
128 g_string_append_len(ret, pos, index);
129 if (index == start)
130 break;
131 g_string_append_c(ret, ' ');
132 pos += start;
133 len -= start;
134 }
135 return g_string_free(ret, FALSE);
136 }
137
setPangoAttributes(const Font * font,const TextRun & run,PangoLayout * layout)138 static void setPangoAttributes(const Font* font, const TextRun& run, PangoLayout* layout)
139 {
140 #if defined(USE_FREETYPE)
141 if (font->primaryFont()->platformData().m_pattern) {
142 PangoFontDescription* desc = pango_fc_font_description_from_pattern(font->primaryFont()->platformData().m_pattern, FALSE);
143 pango_layout_set_font_description(layout, desc);
144 pango_font_description_free(desc);
145 }
146 #elif defined(USE_PANGO)
147 if (font->primaryFont()->platformData().m_font) {
148 PangoFontDescription* desc = pango_font_describe(font->primaryFont()->platformData().m_font);
149 pango_layout_set_font_description(layout, desc);
150 pango_font_description_free(desc);
151 }
152 #endif
153
154 pango_layout_set_auto_dir(layout, FALSE);
155
156 PangoContext* pangoContext = pango_layout_get_context(layout);
157 PangoDirection direction = run.rtl() ? PANGO_DIRECTION_RTL : PANGO_DIRECTION_LTR;
158 pango_context_set_base_dir(pangoContext, direction);
159 PangoAttrList* list = pango_attr_list_new();
160 PangoAttribute* attr;
161
162 attr = pango_attr_size_new_absolute(font->pixelSize() * PANGO_SCALE);
163 attr->end_index = G_MAXUINT;
164 pango_attr_list_insert_before(list, attr);
165
166 if (!run.spacingDisabled()) {
167 attr = pango_attr_letter_spacing_new(font->letterSpacing() * PANGO_SCALE);
168 attr->end_index = G_MAXUINT;
169 pango_attr_list_insert_before(list, attr);
170 }
171
172 // Pango does not yet support synthesising small caps
173 // See http://bugs.webkit.org/show_bug.cgi?id=15610
174
175 pango_layout_set_attributes(layout, list);
176 pango_attr_list_unref(list);
177 }
178
canReturnFallbackFontsForComplexText()179 bool Font::canReturnFallbackFontsForComplexText()
180 {
181 return false;
182 }
183
drawComplexText(GraphicsContext * context,const TextRun & run,const FloatPoint & point,int from,int to) const184 void Font::drawComplexText(GraphicsContext* context, const TextRun& run, const FloatPoint& point, int from, int to) const
185 {
186 cairo_t* cr = context->platformContext();
187 cairo_save(cr);
188 cairo_translate(cr, point.x(), point.y());
189
190 PangoLayout* layout = pango_cairo_create_layout(cr);
191 setPangoAttributes(this, run, layout);
192
193 gchar* utf8 = convertUniCharToUTF8(run.characters(), run.length(), 0, run.length());
194 pango_layout_set_text(layout, utf8, -1);
195
196 // Our layouts are single line
197 PangoLayoutLine* layoutLine = pango_layout_get_line_readonly(layout, 0);
198
199 GdkRegion* partialRegion = NULL;
200 if (to - from != run.length()) {
201 // Clip the region of the run to be rendered
202 char* start = g_utf8_offset_to_pointer(utf8, from);
203 char* end = g_utf8_offset_to_pointer(start, to - from);
204 int ranges[] = {start - utf8, end - utf8};
205 partialRegion = gdk_pango_layout_line_get_clip_region(layoutLine, 0, 0, ranges, 1);
206 gdk_region_shrink(partialRegion, 0, -pixelSize());
207 }
208
209 Color fillColor = context->fillColor();
210 float red, green, blue, alpha;
211
212 // Text shadow, inspired by FontMac
213 IntSize shadowSize;
214 int shadowBlur = 0;
215 Color shadowColor;
216 bool hasShadow = context->textDrawingMode() == cTextFill &&
217 context->getShadow(shadowSize, shadowBlur, shadowColor);
218
219 // TODO: Blur support
220 if (hasShadow) {
221 // Disable graphics context shadows (not yet implemented) and paint them manually
222 context->clearShadow();
223 Color shadowFillColor(shadowColor.red(), shadowColor.green(), shadowColor.blue(), shadowColor.alpha() * fillColor.alpha() / 255);
224 cairo_save(cr);
225
226 shadowFillColor.getRGBA(red, green, blue, alpha);
227 cairo_set_source_rgba(cr, red, green, blue, alpha);
228
229 cairo_translate(cr, shadowSize.width(), shadowSize.height());
230
231 if (partialRegion) {
232 gdk_cairo_region(cr, partialRegion);
233 cairo_clip(cr);
234 }
235
236 pango_cairo_show_layout_line(cr, layoutLine);
237
238 cairo_restore(cr);
239 }
240
241 fillColor.getRGBA(red, green, blue, alpha);
242 cairo_set_source_rgba(cr, red, green, blue, alpha);
243
244 if (partialRegion) {
245 gdk_cairo_region(cr, partialRegion);
246 cairo_clip(cr);
247 }
248
249 pango_cairo_show_layout_line(cr, layoutLine);
250
251 if (context->textDrawingMode() & cTextStroke) {
252 Color strokeColor = context->strokeColor();
253 strokeColor.getRGBA(red, green, blue, alpha);
254 cairo_set_source_rgba(cr, red, green, blue, alpha);
255 pango_cairo_layout_line_path(cr, layoutLine);
256 cairo_set_line_width(cr, context->strokeThickness());
257 cairo_stroke(cr);
258 }
259
260 // Re-enable the platform shadow we disabled earlier
261 if (hasShadow)
262 context->setShadow(shadowSize, shadowBlur, shadowColor);
263
264 // Pango sometimes leaves behind paths we don't want
265 cairo_new_path(cr);
266
267 if (partialRegion)
268 gdk_region_destroy(partialRegion);
269
270 g_free(utf8);
271 g_object_unref(layout);
272
273 cairo_restore(cr);
274 }
275
276 // We should create the layout with our actual context but we can't access it from here.
getDefaultPangoLayout(const TextRun & run)277 static PangoLayout* getDefaultPangoLayout(const TextRun& run)
278 {
279 static PangoFontMap* map = pango_cairo_font_map_get_default();
280 #if PANGO_VERSION_CHECK(1,21,5)
281 static PangoContext* pangoContext = pango_font_map_create_context(map);
282 #else
283 // Deprecated in Pango 1.21.
284 static PangoContext* pangoContext = pango_cairo_font_map_create_context(PANGO_CAIRO_FONT_MAP(map));
285 #endif
286 PangoLayout* layout = pango_layout_new(pangoContext);
287
288 return layout;
289 }
290
floatWidthForComplexText(const TextRun & run,HashSet<const SimpleFontData * > *) const291 float Font::floatWidthForComplexText(const TextRun& run, HashSet<const SimpleFontData*>* /* fallbackFonts */) const
292 {
293 if (run.length() == 0)
294 return 0.0f;
295
296 PangoLayout* layout = getDefaultPangoLayout(run);
297 setPangoAttributes(this, run, layout);
298
299 gchar* utf8 = convertUniCharToUTF8(run.characters(), run.length(), 0, run.length());
300 pango_layout_set_text(layout, utf8, -1);
301
302 int width;
303 pango_layout_get_pixel_size(layout, &width, 0);
304
305 g_free(utf8);
306 g_object_unref(layout);
307
308 return width;
309 }
310
offsetForPositionForComplexText(const TextRun & run,int x,bool includePartialGlyphs) const311 int Font::offsetForPositionForComplexText(const TextRun& run, int x, bool includePartialGlyphs) const
312 {
313 PangoLayout* layout = getDefaultPangoLayout(run);
314 setPangoAttributes(this, run, layout);
315
316 gchar* utf8 = convertUniCharToUTF8(run.characters(), run.length(), 0, run.length());
317 pango_layout_set_text(layout, utf8, -1);
318
319 int index, trailing;
320 pango_layout_xy_to_index(layout, x * PANGO_SCALE, 1, &index, &trailing);
321 glong offset = g_utf8_pointer_to_offset(utf8, utf8 + index);
322 if (includePartialGlyphs)
323 offset += trailing;
324
325 g_free(utf8);
326 g_object_unref(layout);
327
328 return offset;
329 }
330
selectionRectForComplexText(const TextRun & run,const IntPoint & point,int h,int from,int to) const331 FloatRect Font::selectionRectForComplexText(const TextRun& run, const IntPoint& point, int h, int from, int to) const
332 {
333 PangoLayout* layout = getDefaultPangoLayout(run);
334 setPangoAttributes(this, run, layout);
335
336 gchar* utf8 = convertUniCharToUTF8(run.characters(), run.length(), 0, run.length());
337 pango_layout_set_text(layout, utf8, -1);
338
339 char* start = g_utf8_offset_to_pointer(utf8, from);
340 char* end = g_utf8_offset_to_pointer(start, to - from);
341
342 if (run.ltr()) {
343 from = start - utf8;
344 to = end - utf8;
345 } else {
346 from = end - utf8;
347 to = start - utf8;
348 }
349
350 PangoLayoutLine* layoutLine = pango_layout_get_line_readonly(layout, 0);
351 int x_pos;
352
353 x_pos = 0;
354 if (from < layoutLine->length)
355 pango_layout_line_index_to_x(layoutLine, from, FALSE, &x_pos);
356 float beforeWidth = PANGO_PIXELS_FLOOR(x_pos);
357
358 x_pos = 0;
359 if (run.ltr() || to < layoutLine->length)
360 pango_layout_line_index_to_x(layoutLine, to, FALSE, &x_pos);
361 float afterWidth = PANGO_PIXELS(x_pos);
362
363 g_free(utf8);
364 g_object_unref(layout);
365
366 return FloatRect(point.x() + beforeWidth, point.y(), afterWidth - beforeWidth, h);
367 }
368
369 }
370