d14b052bc4c3f86b0363be23dc8fa12ae781f52e
[WebKit-https.git] / Source / WebCore / platform / graphics / gtk / FontGtk.cpp
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 "PlatformContextCairo.h"
39 #include "GraphicsContext.h"
40 #include "NotImplemented.h"
41 #include "SimpleFontData.h"
42 #include "TextRun.h"
43 #include <cairo.h>
44 #include <gdk/gdk.h>
45 #include <pango/pango.h>
46 #include <pango/pangocairo.h>
47
48 #if USE(FREETYPE)
49 #include <pango/pangofc-fontmap.h>
50 #endif
51
52 namespace WebCore {
53
54 #ifdef GTK_API_VERSION_2
55 typedef GdkRegion* PangoRegionType;
56
57 void destroyPangoRegion(PangoRegionType region)
58 {
59     gdk_region_destroy(region);
60 }
61
62 IntRect getPangoRegionExtents(PangoRegionType region)
63 {
64     GdkRectangle rectangle;
65     gdk_region_get_clipbox(region, &rectangle);
66     return IntRect(rectangle.x, rectangle.y, rectangle.width, rectangle.height);
67 }
68 #else
69 typedef cairo_region_t* PangoRegionType;
70
71 void destroyPangoRegion(PangoRegionType region)
72 {
73     cairo_region_destroy(region);
74 }
75
76 IntRect getPangoRegionExtents(PangoRegionType region)
77 {
78     cairo_rectangle_int_t rectangle;
79     cairo_region_get_extents(region, &rectangle);
80     return IntRect(rectangle.x, rectangle.y, rectangle.width, rectangle.height);
81 }
82 #endif
83
84 #define IS_HIGH_SURROGATE(u)  ((UChar)(u) >= (UChar)0xd800 && (UChar)(u) <= (UChar)0xdbff)
85 #define IS_LOW_SURROGATE(u)  ((UChar)(u) >= (UChar)0xdc00 && (UChar)(u) <= (UChar)0xdfff)
86
87 static void utf16_to_utf8(const UChar* aText, gint aLength, char* &text, gint &length)
88 {
89   gboolean need_copy = FALSE;
90   int i;
91
92   for (i = 0; i < aLength; i++) {
93     if (!aText[i] || IS_LOW_SURROGATE(aText[i])) {
94       need_copy = TRUE;
95       break;
96     }
97     else if (IS_HIGH_SURROGATE(aText[i])) {
98       if (i < aLength - 1 && IS_LOW_SURROGATE(aText[i+1]))
99         i++;
100       else {
101         need_copy = TRUE;
102         break;
103       }
104     }
105   }
106
107   if (need_copy) {
108
109     /* Pango doesn't correctly handle nuls.  We convert them to 0xff. */
110     /* Also "validate" UTF-16 text to make sure conversion doesn't fail. */
111
112     UChar* p = (UChar*)g_memdup(aText, aLength * sizeof(aText[0]));
113
114     /* don't need to reset i */
115     for (i = 0; i < aLength; i++) {
116       if (!p[i] || IS_LOW_SURROGATE(p[i]))
117         p[i] = 0xFFFD;
118       else if (IS_HIGH_SURROGATE(p[i])) {
119         if (i < aLength - 1 && IS_LOW_SURROGATE(aText[i+1]))
120           i++;
121         else
122           p[i] = 0xFFFD;
123       }
124     }
125
126     aText = p;
127   }
128
129   glong items_written;
130   text = g_utf16_to_utf8(reinterpret_cast<const gunichar2*>(aText), aLength, NULL, &items_written, NULL);
131   length = items_written;
132
133   if (need_copy)
134     g_free((gpointer)aText);
135
136 }
137
138 static gchar* convertUniCharToUTF8(const UChar* characters, gint length, int from, int to)
139 {
140     gchar* utf8 = 0;
141     gint new_length = 0;
142     utf16_to_utf8(characters, length, utf8, new_length);
143     if (!utf8)
144         return NULL;
145
146     if (from > 0) {
147         // discard the first 'from' characters
148         // FIXME: we should do this before the conversion probably
149         gchar* str_left = g_utf8_offset_to_pointer(utf8, from);
150         gchar* tmp = g_strdup(str_left);
151         g_free(utf8);
152         utf8 = tmp;
153     }
154
155     gchar* pos = utf8;
156     gint len = strlen(pos);
157     GString* ret = g_string_new_len(NULL, len);
158
159     // replace line break by space
160     while (len > 0) {
161         gint index, start;
162         pango_find_paragraph_boundary(pos, len, &index, &start);
163         g_string_append_len(ret, pos, index);
164         if (index == start)
165             break;
166         g_string_append_c(ret, ' ');
167         pos += start;
168         len -= start;
169     }
170     return g_string_free(ret, FALSE);
171 }
172
173 static void setPangoAttributes(const Font* font, const TextRun& run, PangoLayout* layout)
174 {
175 #if USE(FREETYPE)
176     if (font->primaryFont()->platformData().m_pattern) {
177         PangoFontDescription* desc = pango_fc_font_description_from_pattern(font->primaryFont()->platformData().m_pattern.get(), FALSE);
178         pango_layout_set_font_description(layout, desc);
179         pango_font_description_free(desc);
180     }
181 #elif USE(PANGO)
182     if (font->primaryFont()->platformData().m_font) {
183         PangoFontDescription* desc = pango_font_describe(font->primaryFont()->platformData().m_font);
184         pango_layout_set_font_description(layout, desc);
185         pango_font_description_free(desc);
186     }
187 #endif
188
189     pango_layout_set_auto_dir(layout, FALSE);
190
191     PangoContext* pangoContext = pango_layout_get_context(layout);
192     PangoDirection direction = run.rtl() ? PANGO_DIRECTION_RTL : PANGO_DIRECTION_LTR;
193     pango_context_set_base_dir(pangoContext, direction);
194     PangoAttrList* list = pango_attr_list_new();
195     PangoAttribute* attr;
196
197     attr = pango_attr_size_new_absolute(font->pixelSize() * PANGO_SCALE);
198     attr->end_index = G_MAXUINT;
199     pango_attr_list_insert_before(list, attr);
200
201     if (!run.spacingDisabled()) {
202         attr = pango_attr_letter_spacing_new(font->letterSpacing() * PANGO_SCALE);
203         attr->end_index = G_MAXUINT;
204         pango_attr_list_insert_before(list, attr);
205     }
206
207     // Pango does not yet support synthesising small caps
208     // See http://bugs.webkit.org/show_bug.cgi?id=15610
209
210     pango_layout_set_attributes(layout, list);
211     pango_attr_list_unref(list);
212 }
213
214 bool Font::canReturnFallbackFontsForComplexText()
215 {
216     return false;
217 }
218
219 bool Font::canExpandAroundIdeographsInComplexText()
220 {
221     return false;
222 }
223
224 static void drawGlyphsShadow(GraphicsContext* graphicsContext, const FloatPoint& point, PangoLayoutLine* layoutLine, PangoRegionType renderRegion)
225 {
226     ContextShadow* shadow = graphicsContext->contextShadow();
227     ASSERT(shadow);
228
229     if (!(graphicsContext->textDrawingMode() & TextModeFill) || shadow->m_type == ContextShadow::NoShadow)
230         return;
231
232     FloatPoint totalOffset(point + shadow->m_offset);
233
234     // Optimize non-blurry shadows, by just drawing text without the ContextShadow.
235     if (!shadow->mustUseContextShadow(graphicsContext)) {
236         cairo_t* context = graphicsContext->platformContext()->cr();
237         cairo_save(context);
238         cairo_translate(context, totalOffset.x(), totalOffset.y());
239
240         setSourceRGBAFromColor(context, shadow->m_color);
241         gdk_cairo_region(context, renderRegion);
242         cairo_clip(context);
243         pango_cairo_show_layout_line(context, layoutLine);
244
245         cairo_restore(context);
246         return;
247     }
248
249     FloatRect extents(getPangoRegionExtents(renderRegion));
250     extents.setLocation(FloatPoint(point.x(), point.y() - extents.height()));
251     cairo_t* shadowContext = shadow->beginShadowLayer(graphicsContext, extents);
252     if (shadowContext) {
253         cairo_translate(shadowContext, point.x(), point.y());
254         pango_cairo_show_layout_line(shadowContext, layoutLine);
255
256         // We need the clipping region to be active when we blit the blurred shadow back,
257         // because we don't want any bits and pieces of characters out of range to be
258         // drawn. Since ContextShadow expects a consistent transform, we have to undo the
259         // translation before calling endShadowLayer as well.
260         cairo_t* context = graphicsContext->platformContext()->cr();
261         cairo_save(context);
262         cairo_translate(context, totalOffset.x(), totalOffset.y());
263         gdk_cairo_region(context, renderRegion);
264         cairo_clip(context);
265         cairo_translate(context, -totalOffset.x(), -totalOffset.y());
266
267         shadow->endShadowLayer(graphicsContext);
268         cairo_restore(context);
269     }
270 }
271
272 void Font::drawComplexText(GraphicsContext* context, const TextRun& run, const FloatPoint& point, int from, int to) const
273 {
274 #if USE(FREETYPE)
275     if (!primaryFont()->platformData().m_pattern) {
276         drawSimpleText(context, run, point, from, to);
277         return;
278     }
279 #endif
280
281     cairo_t* cr = context->platformContext()->cr();
282     PangoLayout* layout = pango_cairo_create_layout(cr);
283     setPangoAttributes(this, run, layout);
284
285     gchar* utf8 = convertUniCharToUTF8(run.characters(), run.length(), 0, run.length());
286     pango_layout_set_text(layout, utf8, -1);
287
288     // Our layouts are single line
289     PangoLayoutLine* layoutLine = pango_layout_get_line_readonly(layout, 0);
290
291     // Get the region where this text will be laid out. We will use it to clip
292     // the Cairo context, for when we are only painting part of the text run and
293     // to calculate the size of the shadow buffer.
294     PangoRegionType partialRegion = 0;
295     char* start = g_utf8_offset_to_pointer(utf8, from);
296     char* end = g_utf8_offset_to_pointer(start, to - from);
297     int ranges[] = {start - utf8, end - utf8};
298     partialRegion = gdk_pango_layout_line_get_clip_region(layoutLine, 0, 0, ranges, 1);
299
300     drawGlyphsShadow(context, point, layoutLine, partialRegion);
301
302     cairo_save(cr);
303     cairo_translate(cr, point.x(), point.y());
304
305     float red, green, blue, alpha;
306     context->fillColor().getRGBA(red, green, blue, alpha);
307     cairo_set_source_rgba(cr, red, green, blue, alpha);
308     gdk_cairo_region(cr, partialRegion);
309     cairo_clip(cr);
310
311     pango_cairo_show_layout_line(cr, layoutLine);
312
313     if (context->textDrawingMode() & TextModeStroke) {
314         Color strokeColor = context->strokeColor();
315         strokeColor.getRGBA(red, green, blue, alpha);
316         cairo_set_source_rgba(cr, red, green, blue, alpha);
317         pango_cairo_layout_line_path(cr, layoutLine);
318         cairo_set_line_width(cr, context->strokeThickness());
319         cairo_stroke(cr);
320     }
321
322     // Pango sometimes leaves behind paths we don't want
323     cairo_new_path(cr);
324
325     destroyPangoRegion(partialRegion);
326     g_free(utf8);
327     g_object_unref(layout);
328
329     cairo_restore(cr);
330 }
331
332 void Font::drawEmphasisMarksForComplexText(GraphicsContext* /* context */, const TextRun& /* run */, const AtomicString& /* mark */, const FloatPoint& /* point */, int /* from */, int /* to */) const
333 {
334     notImplemented();
335 }
336
337 // We should create the layout with our actual context but we can't access it from here.
338 static PangoLayout* getDefaultPangoLayout(const TextRun& run)
339 {
340     static PangoFontMap* map = pango_cairo_font_map_get_default();
341 #if PANGO_VERSION_CHECK(1,21,5)
342     static PangoContext* pangoContext = pango_font_map_create_context(map);
343 #else
344     // Deprecated in Pango 1.21.
345     static PangoContext* pangoContext = pango_cairo_font_map_create_context(PANGO_CAIRO_FONT_MAP(map));
346 #endif
347     PangoLayout* layout = pango_layout_new(pangoContext);
348
349     return layout;
350 }
351
352 float Font::floatWidthForComplexText(const TextRun& run, HashSet<const SimpleFontData*>* fallbackFonts, GlyphOverflow* overflow) const
353 {
354 #if USE(FREETYPE)
355     if (!primaryFont()->platformData().m_pattern)
356         return floatWidthForSimpleText(run, 0, fallbackFonts, overflow);
357 #endif
358
359     if (run.length() == 0)
360         return 0.0f;
361
362     PangoLayout* layout = getDefaultPangoLayout(run);
363     setPangoAttributes(this, run, layout);
364
365     gchar* utf8 = convertUniCharToUTF8(run.characters(), run.length(), 0, run.length());
366     pango_layout_set_text(layout, utf8, -1);
367
368     int width;
369     pango_layout_get_pixel_size(layout, &width, 0);
370
371     g_free(utf8);
372     g_object_unref(layout);
373
374     return width;
375 }
376
377 int Font::offsetForPositionForComplexText(const TextRun& run, float xFloat, bool includePartialGlyphs) const
378 {
379 #if USE(FREETYPE)
380     if (!primaryFont()->platformData().m_pattern)
381         return offsetForPositionForSimpleText(run, xFloat, includePartialGlyphs);
382 #endif
383     // FIXME: This truncation is not a problem for HTML, but only affects SVG, which passes floating-point numbers
384     // to Font::offsetForPosition(). Bug http://webkit.org/b/40673 tracks fixing this problem.
385     int x = static_cast<int>(xFloat);
386
387     PangoLayout* layout = getDefaultPangoLayout(run);
388     setPangoAttributes(this, run, layout);
389
390     gchar* utf8 = convertUniCharToUTF8(run.characters(), run.length(), 0, run.length());
391     pango_layout_set_text(layout, utf8, -1);
392
393     int index, trailing;
394     pango_layout_xy_to_index(layout, x * PANGO_SCALE, 1, &index, &trailing);
395     glong offset = g_utf8_pointer_to_offset(utf8, utf8 + index);
396     if (includePartialGlyphs)
397         offset += trailing;
398
399     g_free(utf8);
400     g_object_unref(layout);
401
402     return offset;
403 }
404
405 FloatRect Font::selectionRectForComplexText(const TextRun& run, const FloatPoint& point, int h, int from, int to) const
406 {
407 #if USE(FREETYPE)
408     if (!primaryFont()->platformData().m_pattern)
409         return selectionRectForSimpleText(run, point, h, from, to);
410 #endif
411
412     PangoLayout* layout = getDefaultPangoLayout(run);
413     setPangoAttributes(this, run, layout);
414
415     gchar* utf8 = convertUniCharToUTF8(run.characters(), run.length(), 0, run.length());
416     pango_layout_set_text(layout, utf8, -1);
417
418     char* start = g_utf8_offset_to_pointer(utf8, from);
419     char* end = g_utf8_offset_to_pointer(start, to - from);
420
421     if (run.ltr()) {
422         from = start - utf8;
423         to = end - utf8;
424     } else {
425         from = end - utf8;
426         to = start - utf8;
427     }
428
429     PangoLayoutLine* layoutLine = pango_layout_get_line_readonly(layout, 0);
430     int x_pos;
431
432     x_pos = 0;
433     if (from < layoutLine->length)
434         pango_layout_line_index_to_x(layoutLine, from, FALSE, &x_pos);
435     float beforeWidth = PANGO_PIXELS_FLOOR(x_pos);
436
437     x_pos = 0;
438     if (run.ltr() || to < layoutLine->length)
439         pango_layout_line_index_to_x(layoutLine, to, FALSE, &x_pos);
440     float afterWidth = PANGO_PIXELS(x_pos);
441
442     g_free(utf8);
443     g_object_unref(layout);
444
445     return FloatRect(point.x() + beforeWidth, point.y(), afterWidth - beforeWidth, h);
446 }
447
448 }