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