• 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 "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