Home | History | Annotate | Download | only in gtk
      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