#include "config.h"
#include "Font.h"
#include "CairoUtilities.h"
#include "ContextShadow.h"
#include "GOwnPtr.h"
#include "GraphicsContext.h"
#include "NotImplemented.h"
#include "PlatformContextCairo.h"
#include "SimpleFontData.h"
#include "TextRun.h"
#include <cairo.h>
#include <gdk/gdk.h>
#include <pango/pango.h>
#include <pango/pangocairo.h>
#if USE(FREETYPE)
#include <pango/pangofc-fontmap.h>
#endif
namespace WebCore {
#ifdef GTK_API_VERSION_2
typedef GdkRegion* PangoRegionType;
void destroyPangoRegion(PangoRegionType region)
{
gdk_region_destroy(region);
}
IntRect getPangoRegionExtents(PangoRegionType region)
{
GdkRectangle rectangle;
gdk_region_get_clipbox(region, &rectangle);
return IntRect(rectangle.x, rectangle.y, rectangle.width, rectangle.height);
}
#else
typedef cairo_region_t* PangoRegionType;
void destroyPangoRegion(PangoRegionType region)
{
cairo_region_destroy(region);
}
IntRect getPangoRegionExtents(PangoRegionType region)
{
cairo_rectangle_int_t rectangle;
cairo_region_get_extents(region, &rectangle);
return IntRect(rectangle.x, rectangle.y, rectangle.width, rectangle.height);
}
#endif
#define IS_HIGH_SURROGATE(u) ((UChar)(u) >= (UChar)0xd800 && (UChar)(u) <= (UChar)0xdbff)
#define IS_LOW_SURROGATE(u) ((UChar)(u) >= (UChar)0xdc00 && (UChar)(u) <= (UChar)0xdfff)
static gchar* utf16ToUtf8(const UChar* aText, gint aLength, gint &length)
{
gboolean needCopy = FALSE;
for (int i = 0; i < aLength; i++) {
if (!aText[i] || IS_LOW_SURROGATE(aText[i])) {
needCopy = TRUE;
break;
}
if (IS_HIGH_SURROGATE(aText[i])) {
if (i < aLength - 1 && IS_LOW_SURROGATE(aText[i+1]))
i++;
else {
needCopy = TRUE;
break;
}
}
}
GOwnPtr<UChar> copiedString;
if (needCopy) {
copiedString.set(static_cast<UChar*>(g_memdup(aText, aLength * sizeof(aText[0]))));
UChar* p = copiedString.get();
for (int i = 0; i < aLength; i++) {
if (!p[i] || IS_LOW_SURROGATE(p[i]))
p[i] = 0xFFFD;
else if (IS_HIGH_SURROGATE(p[i])) {
if (i < aLength - 1 && IS_LOW_SURROGATE(aText[i+1]))
i++;
else
p[i] = 0xFFFD;
}
}
aText = p;
}
gchar* utf8Text;
glong itemsWritten;
utf8Text = g_utf16_to_utf8(static_cast<const gunichar2*>(aText), aLength, 0, &itemsWritten, 0);
length = itemsWritten;
return utf8Text;
}
static gchar* convertUniCharToUTF8(const UChar* characters, gint length, int from, int to)
{
gint newLength = 0;
GOwnPtr<gchar> utf8Text(utf16ToUtf8(characters, length, newLength));
if (!utf8Text)
return 0;
gchar* pos = utf8Text.get();
if (from > 0) {
pos = g_utf8_offset_to_pointer(utf8Text.get(), from);
}
gint len = strlen(pos);
GString* ret = g_string_new_len(NULL, len);
while (len > 0) {
gint index, start;
pango_find_paragraph_boundary(pos, len, &index, &start);
g_string_append_len(ret, pos, index);
if (index == start)
break;
g_string_append_c(ret, ' ');
pos += start;
len -= start;
}
return g_string_free(ret, FALSE);
}
static void setPangoAttributes(const Font* font, const TextRun& run, PangoLayout* layout)
{
#if USE(FREETYPE)
if (font->primaryFont()->platformData().m_pattern) {
PangoFontDescription* desc = pango_fc_font_description_from_pattern(font->primaryFont()->platformData().m_pattern.get(), FALSE);
pango_layout_set_font_description(layout, desc);
pango_font_description_free(desc);
}
#elif USE(PANGO)
if (font->primaryFont()->platformData().m_font) {
PangoFontDescription* desc = pango_font_describe(font->primaryFont()->platformData().m_font);
pango_layout_set_font_description(layout, desc);
pango_font_description_free(desc);
}
#endif
pango_layout_set_auto_dir(layout, FALSE);
PangoContext* pangoContext = pango_layout_get_context(layout);
PangoDirection direction = run.rtl() ? PANGO_DIRECTION_RTL : PANGO_DIRECTION_LTR;
pango_context_set_base_dir(pangoContext, direction);
PangoAttrList* list = pango_attr_list_new();
PangoAttribute* attr;
attr = pango_attr_size_new_absolute(font->pixelSize() * PANGO_SCALE);
attr->end_index = G_MAXUINT;
pango_attr_list_insert_before(list, attr);
if (!run.spacingDisabled()) {
attr = pango_attr_letter_spacing_new(font->letterSpacing() * PANGO_SCALE);
attr->end_index = G_MAXUINT;
pango_attr_list_insert_before(list, attr);
}
pango_layout_set_attributes(layout, list);
pango_attr_list_unref(list);
}
bool Font::canReturnFallbackFontsForComplexText()
{
return false;
}
bool Font::canExpandAroundIdeographsInComplexText()
{
return false;
}
static void drawGlyphsShadow(GraphicsContext* graphicsContext, const FloatPoint& point, PangoLayoutLine* layoutLine, PangoRegionType renderRegion)
{
ContextShadow* shadow = graphicsContext->contextShadow();
ASSERT(shadow);
if (!(graphicsContext->textDrawingMode() & TextModeFill) || shadow->m_type == ContextShadow::NoShadow)
return;
FloatPoint totalOffset(point + shadow->m_offset);
if (!shadow->mustUseContextShadow(graphicsContext)) {
cairo_t* context = graphicsContext->platformContext()->cr();
cairo_save(context);
cairo_translate(context, totalOffset.x(), totalOffset.y());
setSourceRGBAFromColor(context, shadow->m_color);
gdk_cairo_region(context, renderRegion);
cairo_clip(context);
pango_cairo_show_layout_line(context, layoutLine);
cairo_restore(context);
return;
}
FloatRect extents(getPangoRegionExtents(renderRegion));
extents.setLocation(FloatPoint(point.x(), point.y() - extents.height()));
cairo_t* shadowContext = shadow->beginShadowLayer(graphicsContext, extents);
if (shadowContext) {
cairo_translate(shadowContext, point.x(), point.y());
pango_cairo_show_layout_line(shadowContext, layoutLine);
cairo_t* context = graphicsContext->platformContext()->cr();
cairo_save(context);
cairo_translate(context, totalOffset.x(), totalOffset.y());
gdk_cairo_region(context, renderRegion);
cairo_clip(context);
cairo_translate(context, -totalOffset.x(), -totalOffset.y());
shadow->endShadowLayer(graphicsContext);
cairo_restore(context);
}
}
void Font::drawComplexText(GraphicsContext* context, const TextRun& run, const FloatPoint& point, int from, int to) const
{
#if USE(FREETYPE)
if (!primaryFont()->platformData().m_pattern) {
drawSimpleText(context, run, point, from, to);
return;
}
#endif
cairo_t* cr = context->platformContext()->cr();
PangoLayout* layout = pango_cairo_create_layout(cr);
setPangoAttributes(this, run, layout);
gchar* utf8 = convertUniCharToUTF8(run.characters(), run.length(), 0, run.length());
pango_layout_set_text(layout, utf8, -1);
PangoLayoutLine* layoutLine = pango_layout_get_line_readonly(layout, 0);
PangoRegionType partialRegion = 0;
char* start = g_utf8_offset_to_pointer(utf8, from);
char* end = g_utf8_offset_to_pointer(start, to - from);
int ranges[] = {start - utf8, end - utf8};
partialRegion = gdk_pango_layout_line_get_clip_region(layoutLine, 0, 0, ranges, 1);
drawGlyphsShadow(context, point, layoutLine, partialRegion);
cairo_save(cr);
cairo_translate(cr, point.x(), point.y());
float red, green, blue, alpha;
context->fillColor().getRGBA(red, green, blue, alpha);
cairo_set_source_rgba(cr, red, green, blue, alpha);
gdk_cairo_region(cr, partialRegion);
cairo_clip(cr);
pango_cairo_show_layout_line(cr, layoutLine);
if (context->textDrawingMode() & TextModeStroke) {
Color strokeColor = context->strokeColor();
strokeColor.getRGBA(red, green, blue, alpha);
cairo_set_source_rgba(cr, red, green, blue, alpha);
pango_cairo_layout_line_path(cr, layoutLine);
cairo_set_line_width(cr, context->strokeThickness());
cairo_stroke(cr);
}
cairo_new_path(cr);
destroyPangoRegion(partialRegion);
g_free(utf8);
g_object_unref(layout);
cairo_restore(cr);
}
void Font::drawEmphasisMarksForComplexText(GraphicsContext* , const TextRun& , const AtomicString& , const FloatPoint& , int , int ) const
{
notImplemented();
}
static PangoLayout* getDefaultPangoLayout(const TextRun& run)
{
static PangoFontMap* map = pango_cairo_font_map_get_default();
#if PANGO_VERSION_CHECK(1,21,5)
static PangoContext* pangoContext = pango_font_map_create_context(map);
#else
static PangoContext* pangoContext = pango_cairo_font_map_create_context(PANGO_CAIRO_FONT_MAP(map));
#endif
PangoLayout* layout = pango_layout_new(pangoContext);
return layout;
}
float Font::floatWidthForComplexText(const TextRun& run, HashSet<const SimpleFontData*>* fallbackFonts, GlyphOverflow* overflow) const
{
#if USE(FREETYPE)
if (!primaryFont()->platformData().m_pattern)
return floatWidthForSimpleText(run, 0, fallbackFonts, overflow);
#endif
if (run.length() == 0)
return 0.0f;
PangoLayout* layout = getDefaultPangoLayout(run);
setPangoAttributes(this, run, layout);
gchar* utf8 = convertUniCharToUTF8(run.characters(), run.length(), 0, run.length());
pango_layout_set_text(layout, utf8, -1);
int width;
pango_layout_get_pixel_size(layout, &width, 0);
g_free(utf8);
g_object_unref(layout);
return width;
}
int Font::offsetForPositionForComplexText(const TextRun& run, float xFloat, bool includePartialGlyphs) const
{
#if USE(FREETYPE)
if (!primaryFont()->platformData().m_pattern)
return offsetForPositionForSimpleText(run, xFloat, includePartialGlyphs);
#endif
int x = static_cast<int>(xFloat);
PangoLayout* layout = getDefaultPangoLayout(run);
setPangoAttributes(this, run, layout);
gchar* utf8 = convertUniCharToUTF8(run.characters(), run.length(), 0, run.length());
pango_layout_set_text(layout, utf8, -1);
int index, trailing;
pango_layout_xy_to_index(layout, x * PANGO_SCALE, 1, &index, &trailing);
glong offset = g_utf8_pointer_to_offset(utf8, utf8 + index);
if (includePartialGlyphs)
offset += trailing;
g_free(utf8);
g_object_unref(layout);
return offset;
}
FloatRect Font::selectionRectForComplexText(const TextRun& run, const FloatPoint& point, int h, int from, int to) const
{
#if USE(FREETYPE)
if (!primaryFont()->platformData().m_pattern)
return selectionRectForSimpleText(run, point, h, from, to);
#endif
PangoLayout* layout = getDefaultPangoLayout(run);
setPangoAttributes(this, run, layout);
gchar* utf8 = convertUniCharToUTF8(run.characters(), run.length(), 0, run.length());
pango_layout_set_text(layout, utf8, -1);
char* start = g_utf8_offset_to_pointer(utf8, from);
char* end = g_utf8_offset_to_pointer(start, to - from);
if (run.ltr()) {
from = start - utf8;
to = end - utf8;
} else {
from = end - utf8;
to = start - utf8;
}
PangoLayoutLine* layoutLine = pango_layout_get_line_readonly(layout, 0);
int x_pos;
x_pos = 0;
if (from < layoutLine->length)
pango_layout_line_index_to_x(layoutLine, from, FALSE, &x_pos);
float beforeWidth = PANGO_PIXELS_FLOOR(x_pos);
x_pos = 0;
if (run.ltr() || to < layoutLine->length)
pango_layout_line_index_to_x(layoutLine, to, FALSE, &x_pos);
float afterWidth = PANGO_PIXELS(x_pos);
g_free(utf8);
g_object_unref(layout);
return FloatRect(point.x() + beforeWidth, point.y(), afterWidth - beforeWidth, h);
}
}