• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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