1 /* 2 * Copyright (C) 2006 Apple Computer, Inc. All rights reserved. 3 * Copyright (C) 2006 Michael Emmel mike.emmel (at) gmail.com 4 * Copyright (c) 2007 Hiroyuki Ikezoe 5 * Copyright (c) 2007 Kouhei Sutou 6 * Copyright (C) 2007 Alp Toker <alp (at) atoker.com> 7 * Copyright (C) 2008 Xan Lopez <xan (at) 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 58 void destroyPangoRegion(PangoRegionType region) 59 { 60 gdk_region_destroy(region); 61 } 62 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 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 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 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 211 bool Font::canReturnFallbackFontsForComplexText() 212 { 213 return false; 214 } 215 216 bool Font::canExpandAroundIdeographsInComplexText() 217 { 218 return false; 219 } 220 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 269 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 329 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. 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 349 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 374 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 402 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