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.
12 * Redistribution and use in source and binary forms, with or without
13 * modification, are permitted provided that the following conditions
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.
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.
37 #include "CairoUtilities.h"
39 #include "GraphicsContext.h"
40 #include "NotImplemented.h"
41 #include "PlatformContextCairo.h"
42 #include "ShadowBlur.h"
43 #include "SimpleFontData.h"
46 #include <pango/pango.h>
47 #include <pango/pangocairo.h>
52 #include "PangoUtilities.h"
56 #include <pango/pangofc-fontmap.h>
61 #ifdef GTK_API_VERSION_2
62 typedef GdkRegion* PangoRegionType;
64 void destroyPangoRegion(PangoRegionType region)
66 gdk_region_destroy(region);
69 IntRect getPangoRegionExtents(PangoRegionType region)
71 GdkRectangle rectangle;
72 gdk_region_get_clipbox(region, &rectangle);
73 return IntRect(rectangle.x, rectangle.y, rectangle.width, rectangle.height);
76 typedef cairo_region_t* PangoRegionType;
78 void destroyPangoRegion(PangoRegionType region)
80 cairo_region_destroy(region);
83 IntRect getPangoRegionExtents(PangoRegionType region)
85 cairo_rectangle_int_t rectangle;
86 cairo_region_get_extents(region, &rectangle);
87 return IntRect(rectangle.x, rectangle.y, rectangle.width, rectangle.height);
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)
94 static gchar* utf16ToUtf8(const UChar* aText, gint aLength, gint &length)
96 gboolean needCopy = FALSE;
98 for (int i = 0; i < aLength; i++) {
99 if (!aText[i] || IS_LOW_SURROGATE(aText[i])) {
104 if (IS_HIGH_SURROGATE(aText[i])) {
105 if (i < aLength - 1 && IS_LOW_SURROGATE(aText[i+1]))
114 GOwnPtr<UChar> copiedString;
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. */
119 copiedString.set(static_cast<UChar*>(g_memdup(aText, aLength * sizeof(aText[0]))));
120 UChar* p = copiedString.get();
122 /* don't need to reset i */
123 for (int i = 0; i < aLength; i++) {
124 if (!p[i] || IS_LOW_SURROGATE(p[i]))
126 else if (IS_HIGH_SURROGATE(p[i])) {
127 if (i < aLength - 1 && IS_LOW_SURROGATE(aText[i+1]))
139 utf8Text = g_utf16_to_utf8(static_cast<const gunichar2*>(aText), aLength, 0, &itemsWritten, 0);
140 length = itemsWritten;
145 static gchar* convertUniCharToUTF8(const UChar* characters, gint length, int from, int to)
148 GOwnPtr<gchar> utf8Text(utf16ToUtf8(characters, length, newLength));
152 gchar* pos = utf8Text.get();
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);
159 gint len = strlen(pos);
160 GString* ret = g_string_new_len(NULL, len);
162 // replace line break by space
165 pango_find_paragraph_boundary(pos, len, &index, &start);
166 g_string_append_len(ret, pos, index);
169 g_string_append_c(ret, ' ');
173 return g_string_free(ret, FALSE);
176 static void setPangoAttributes(const Font* font, const TextRun& run, PangoLayout* layout)
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);
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);
192 pango_layout_set_auto_dir(layout, FALSE);
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;
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);
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);
210 // Pango does not yet support synthesising small caps
211 // See http://bugs.webkit.org/show_bug.cgi?id=15610
213 pango_layout_set_attributes(layout, list);
214 pango_attr_list_unref(list);
217 bool Font::canReturnFallbackFontsForComplexText()
222 bool Font::canExpandAroundIdeographsInComplexText()
227 static void drawGlyphsShadow(GraphicsContext* graphicsContext, const FloatPoint& point, PangoLayoutLine* layoutLine, PangoRegionType renderRegion)
229 ShadowBlur& shadow = graphicsContext->platformContext()->shadowBlur();
231 if (!(graphicsContext->textDrawingMode() & TextModeFill) || shadow.type() == ShadowBlur::NoShadow)
234 FloatPoint totalOffset(point + graphicsContext->state().shadowOffset);
236 // Optimize non-blurry shadows, by just drawing text without the ShadowBlur.
237 if (!shadow.mustUseShadowBlur(graphicsContext)) {
238 cairo_t* context = graphicsContext->platformContext()->cr();
240 cairo_translate(context, totalOffset.x(), totalOffset.y());
242 setSourceRGBAFromColor(context, graphicsContext->state().shadowColor);
244 gdk_cairo_region(context, renderRegion);
246 appendRegionToCairoContext(context, renderRegion);
249 pango_cairo_show_layout_line(context, layoutLine);
251 cairo_restore(context);
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);
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();
268 cairo_translate(context, totalOffset.x(), totalOffset.y());
270 gdk_cairo_region(context, renderRegion);
272 appendRegionToCairoContext(context, renderRegion);
275 cairo_translate(context, -totalOffset.x(), -totalOffset.y());
277 shadow.endShadowLayer(graphicsContext);
278 cairo_restore(context);
282 void Font::drawComplexText(GraphicsContext* context, const TextRun& run, const FloatPoint& point, int from, int to) const
285 if (!primaryFont()->platformData().m_pattern) {
286 drawSimpleText(context, run, point, from, to);
291 cairo_t* cr = context->platformContext()->cr();
292 PangoLayout* layout = pango_cairo_create_layout(cr);
293 setPangoAttributes(this, run, layout);
295 gchar* utf8 = convertUniCharToUTF8(run.characters(), run.length(), 0, run.length());
296 pango_layout_set_text(layout, utf8, -1);
298 // Our layouts are single line
299 PangoLayoutLine* layoutLine = pango_layout_get_line_readonly(layout, 0);
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};
309 partialRegion = gdk_pango_layout_line_get_clip_region(layoutLine, 0, 0, ranges, 1);
311 partialRegion = getClipRegionFromPangoLayoutLine(layoutLine, ranges);
314 drawGlyphsShadow(context, point, layoutLine, partialRegion);
317 cairo_translate(cr, point.x(), point.y());
319 float red, green, blue, alpha;
320 context->fillColor().getRGBA(red, green, blue, alpha);
321 cairo_set_source_rgba(cr, red, green, blue, alpha);
323 gdk_cairo_region(cr, partialRegion);
325 appendRegionToCairoContext(cr, partialRegion);
329 pango_cairo_show_layout_line(cr, layoutLine);
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());
340 // Pango sometimes leaves behind paths we don't want
343 destroyPangoRegion(partialRegion);
345 g_object_unref(layout);
350 void Font::drawEmphasisMarksForComplexText(GraphicsContext* /* context */, const TextRun& /* run */, const AtomicString& /* mark */, const FloatPoint& /* point */, int /* from */, int /* to */) const
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)
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);
362 // Deprecated in Pango 1.21.
363 static PangoContext* pangoContext = pango_cairo_font_map_create_context(PANGO_CAIRO_FONT_MAP(map));
365 PangoLayout* layout = pango_layout_new(pangoContext);
370 float Font::floatWidthForComplexText(const TextRun& run, HashSet<const SimpleFontData*>* fallbackFonts, GlyphOverflow* overflow) const
373 if (!primaryFont()->platformData().m_pattern)
374 return floatWidthForSimpleText(run, 0, fallbackFonts, overflow);
380 PangoLayout* layout = getDefaultPangoLayout(run);
381 setPangoAttributes(this, run, layout);
383 gchar* utf8 = convertUniCharToUTF8(run.characters(), run.length(), 0, run.length());
384 pango_layout_set_text(layout, utf8, -1);
387 pango_layout_get_pixel_size(layout, &width, 0);
390 g_object_unref(layout);
395 int Font::offsetForPositionForComplexText(const TextRun& run, float xFloat, bool includePartialGlyphs) const
398 if (!primaryFont()->platformData().m_pattern)
399 return offsetForPositionForSimpleText(run, xFloat, includePartialGlyphs);
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);
405 PangoLayout* layout = getDefaultPangoLayout(run);
406 setPangoAttributes(this, run, layout);
408 gchar* utf8 = convertUniCharToUTF8(run.characters(), run.length(), 0, run.length());
409 pango_layout_set_text(layout, utf8, -1);
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)
418 g_object_unref(layout);
423 FloatRect Font::selectionRectForComplexText(const TextRun& run, const FloatPoint& point, int h, int from, int to) const
426 if (!primaryFont()->platformData().m_pattern)
427 return selectionRectForSimpleText(run, point, h, from, to);
430 PangoLayout* layout = getDefaultPangoLayout(run);
431 setPangoAttributes(this, run, layout);
433 gchar* utf8 = convertUniCharToUTF8(run.characters(), run.length(), 0, run.length());
434 pango_layout_set_text(layout, utf8, -1);
436 char* start = g_utf8_offset_to_pointer(utf8, from);
437 char* end = g_utf8_offset_to_pointer(start, to - from);
447 PangoLayoutLine* layoutLine = pango_layout_get_line_readonly(layout, 0);
451 if (from < layoutLine->length)
452 pango_layout_line_index_to_x(layoutLine, from, FALSE, &xPos);
453 float beforeWidth = PANGO_PIXELS_FLOOR(xPos);
456 if (run.ltr() || to < layoutLine->length)
457 pango_layout_line_index_to_x(layoutLine, to, FALSE, &xPos);
458 float afterWidth = PANGO_PIXELS(xPos);
461 g_object_unref(layout);
463 return FloatRect(point.x() + beforeWidth, point.y(), afterWidth - beforeWidth, h);