litehtml/containers/cairo/container_cairo_pango.cpp

445 lines
13 KiB
C++

#include <array>
#include "container_cairo_pango.h"
container_cairo_pango::container_cairo_pango()
{
m_temp_surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 2, 2);
m_temp_cr = cairo_create(m_temp_surface);
cairo_save(m_temp_cr);
PangoLayout *layout = pango_cairo_create_layout(m_temp_cr);
PangoContext *context = pango_layout_get_context(layout);
PangoFontFamily** families;
int n;
pango_context_list_families(context, &families, &n);
for(int i = 0; i < n; i++)
{
PangoFontFamily* family = families[i];
if (!PANGO_IS_FONT_FAMILY(family)) continue;
std::string font_name = pango_font_family_get_name(family);
litehtml::lcase(font_name);
m_all_fonts.insert(font_name);
}
g_free(families);
cairo_restore(m_temp_cr);
g_object_unref(layout);
}
container_cairo_pango::~container_cairo_pango()
{
clear_images();
cairo_surface_destroy(m_temp_surface);
cairo_destroy(m_temp_cr);
}
litehtml::uint_ptr container_cairo_pango::create_font(const litehtml::font_description& descr, const litehtml::document* doc, litehtml::font_metrics *fm)
{
litehtml::string_vector tokens;
litehtml::split_string(descr.family, tokens, ",");
std::string fonts;
for(auto& font : tokens)
{
litehtml::trim(font, " \t\r\n\f\v\"\'");
if(litehtml::value_in_list(font, "serif;sans-serif;monospace;cursive;fantasy;"))
{
fonts = font + ",";
break;
}
litehtml::lcase(font);
if(m_all_fonts.find(font) != m_all_fonts.end())
{
fonts = font;
fonts += ",";
break;
}
}
if(fonts.empty())
{
fonts = "serif,";
}
PangoFontDescription *desc = pango_font_description_from_string (fonts.c_str());
pango_font_description_set_absolute_size(desc, descr.size * PANGO_SCALE);
if(descr.style == litehtml::font_style_italic)
{
pango_font_description_set_style(desc, PANGO_STYLE_ITALIC);
} else
{
pango_font_description_set_style(desc, PANGO_STYLE_NORMAL);
}
pango_font_description_set_weight(desc, (PangoWeight) descr.weight);
cairo_font* ret = nullptr;
if(fm)
{
fm->font_size = descr.size;
cairo_save(m_temp_cr);
PangoLayout *layout = pango_cairo_create_layout(m_temp_cr);
PangoContext *context = pango_layout_get_context(layout);
PangoLanguage *language = pango_language_get_default();
pango_layout_set_font_description(layout, desc);
PangoFontMetrics *metrics = pango_context_get_metrics(context, desc, language);
fm->ascent = PANGO_PIXELS((double)pango_font_metrics_get_ascent(metrics));
fm->height = PANGO_PIXELS((double)pango_font_metrics_get_height(metrics));
fm->descent = fm->height - fm->ascent;
fm->x_height = fm->height;
fm->draw_spaces = (descr.decoration_line != litehtml::text_decoration_line_none);
fm->sub_shift = descr.size / 5;
fm->super_shift = descr.size / 3;
pango_layout_set_text(layout, "x", 1);
PangoRectangle ink_rect;
PangoRectangle logical_rect;
pango_layout_get_pixel_extents(layout, &ink_rect, &logical_rect);
fm->x_height = ink_rect.height;
if(fm->x_height == fm->height) fm->x_height = fm->x_height * 4 / 5;
pango_layout_set_text(layout, "0", 1);
pango_layout_get_pixel_extents(layout, &ink_rect, &logical_rect);
fm->ch_width = logical_rect.width;
cairo_restore(m_temp_cr);
ret = new cairo_font;
ret->font = desc;
ret->size = descr.size;
ret->strikeout = (descr.decoration_line & litehtml::text_decoration_line_line_through) != 0;
ret->underline = (descr.decoration_line & litehtml::text_decoration_line_underline) != 0;
ret->overline = (descr.decoration_line & litehtml::text_decoration_line_overline) != 0;
ret->ascent = fm->ascent;
ret->descent = fm->descent;
ret->decoration_color = descr.decoration_color;
ret->decoration_style = descr.decoration_style;
auto thinkness = descr.decoration_thickness;
if(!thinkness.is_predefined())
{
litehtml::css_length one_em(1.0, litehtml::css_units_em);
doc->cvt_units(one_em, *fm, 0);
doc->cvt_units(thinkness, *fm, (int) one_em.val());
}
ret->underline_position = -pango_font_metrics_get_underline_position(metrics);
if(thinkness.is_predefined())
{
ret->underline_thickness = pango_font_metrics_get_underline_thickness(metrics);
} else
{
ret->underline_thickness = (int)(thinkness.val() * PANGO_SCALE);
}
pango_quantize_line_geometry(&ret->underline_thickness, &ret->underline_position);
ret->underline_thickness = PANGO_PIXELS(ret->underline_thickness);
ret->underline_position = PANGO_PIXELS(ret->underline_position);
ret->strikethrough_position = pango_font_metrics_get_strikethrough_position(metrics);
if(thinkness.is_predefined())
{
ret->strikethrough_thickness = pango_font_metrics_get_strikethrough_thickness(metrics);
} else
{
ret->strikethrough_thickness = (int)(thinkness.val() * PANGO_SCALE);
}
pango_quantize_line_geometry(&ret->strikethrough_thickness, &ret->strikethrough_position);
ret->strikethrough_thickness = PANGO_PIXELS(ret->strikethrough_thickness);
ret->strikethrough_position = PANGO_PIXELS(ret->strikethrough_position);
ret->overline_position = fm->ascent * PANGO_SCALE;
if(thinkness.is_predefined())
{
ret->overline_thickness = pango_font_metrics_get_underline_thickness(metrics);
} else
{
ret->overline_thickness = (int)(thinkness.val() * PANGO_SCALE);
}
pango_quantize_line_geometry(&ret->overline_thickness, &ret->overline_position);
ret->overline_thickness = PANGO_PIXELS(ret->overline_thickness);
ret->overline_position = PANGO_PIXELS(ret->overline_position);
g_object_unref(layout);
pango_font_metrics_unref(metrics);
}
return (litehtml::uint_ptr) ret;
}
void container_cairo_pango::delete_font(litehtml::uint_ptr hFont)
{
auto* fnt = (cairo_font*) hFont;
if(fnt)
{
pango_font_description_free(fnt->font);
delete fnt;
}
}
int container_cairo_pango::text_width(const char *text, litehtml::uint_ptr hFont)
{
auto* fnt = (cairo_font*) hFont;
cairo_save(m_temp_cr);
PangoLayout *layout = pango_cairo_create_layout(m_temp_cr);
pango_layout_set_font_description(layout, fnt->font);
pango_layout_set_text(layout, text, -1);
pango_cairo_update_layout (m_temp_cr, layout);
int x_width, x_height;
pango_layout_get_pixel_size(layout, &x_width, &x_height);
cairo_restore(m_temp_cr);
g_object_unref(layout);
return (int) x_width;
}
enum class draw_type
{
DRAW_OVERLINE,
DRAW_STRIKETHROUGH,
DRAW_UNDERLINE
};
static inline void draw_single_line(cairo_t* cr, int x, int y, int width, int thickness, draw_type type)
{
double top;
switch (type)
{
case draw_type::DRAW_UNDERLINE:
top = y + (double)thickness / 2.0;
break;
case draw_type::DRAW_OVERLINE:
top = y - (double)thickness / 2.0;
break;
case draw_type::DRAW_STRIKETHROUGH:
top = y + 0.5;
break;
default:
top = y;
break;
}
cairo_move_to(cr, x, top);
cairo_line_to(cr, x + width, top);
}
static void draw_solid_line(cairo_t* cr, int x, int y, int width, int thickness, draw_type type, litehtml::web_color& color)
{
draw_single_line(cr, x, y, width, thickness, type);
cairo_set_source_rgba(cr, (double) color.red / 255.0,
(double) color.green / 255.0,
(double) color.blue / 255.0,
(double) color.alpha / 255.0);
cairo_set_line_width(cr, thickness);
cairo_stroke(cr);
}
static void draw_dotted_line(cairo_t* cr, int x, int y, int width, int thickness, draw_type type, litehtml::web_color& color)
{
draw_single_line(cr, x, y, width, thickness, type);
std::array<double, 2> dashes {0, thickness * 2.0};
if(thickness == 1) dashes[1] += thickness / 2.0;
cairo_set_line_width(cr, thickness);
cairo_set_line_cap(cr, CAIRO_LINE_CAP_ROUND);
cairo_set_dash(cr, dashes.data(), 2, x);
cairo_set_source_rgba(cr, (double) color.red / 255.0,
(double) color.green / 255.0,
(double) color.blue / 255.0,
(double) color.alpha / 255.0);
cairo_stroke(cr);
}
static void draw_dashed_line(cairo_t* cr, int x, int y, int width, int thickness, draw_type type, litehtml::web_color& color)
{
draw_single_line(cr, x, y, width, thickness, type);
std::array<double, 2> dashes {thickness * 2.0, thickness * 3.0};
cairo_set_line_width(cr, thickness);
cairo_set_line_cap(cr, CAIRO_LINE_CAP_ROUND);
cairo_set_dash(cr, dashes.data(), 2, x);
cairo_set_source_rgba(cr, (double) color.red / 255.0,
(double) color.green / 255.0,
(double) color.blue / 255.0,
(double) color.alpha / 255.0);
cairo_stroke(cr);
}
static void draw_wavy_line(cairo_t* cr, int x, int y, int width, int thickness, draw_type type, litehtml::web_color& color)
{
double h_pad = 1.0;
int brush_height = (int) thickness * 3 + h_pad * 2;
int brush_width = brush_height * 2 - 2 * thickness;
double top;
switch (type)
{
case draw_type::DRAW_UNDERLINE:
top = y + (double)brush_height / 2.0;
break;
case draw_type::DRAW_OVERLINE:
top = y - (double)brush_height / 2.0;
break;
default:
top = y;
break;
}
cairo_surface_t* brush_surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, brush_width, brush_height);
cairo_t* brush_cr = cairo_create(brush_surface);
cairo_set_source_rgba(brush_cr, (double) color.red / 255.0,
(double) color.green / 255.0,
(double) color.blue / 255.0,
(double) color.alpha / 255.0);
cairo_set_line_width(brush_cr, thickness);
double w = thickness / 2.0;
cairo_move_to(brush_cr, 0, brush_height - (double) thickness / 2.0 - h_pad);
cairo_line_to(brush_cr, w, brush_height - (double) thickness / 2.0 - h_pad);
cairo_line_to(brush_cr, brush_width / 2.0 - w, (double) thickness / 2.0 + h_pad);
cairo_line_to(brush_cr, brush_width / 2.0 + w, (double) thickness / 2.0 + h_pad);
cairo_line_to(brush_cr, brush_width - w, brush_height - (double) thickness / 2.0 - h_pad);
cairo_line_to(brush_cr, brush_width, brush_height - (double) thickness / 2.0 - h_pad);
cairo_stroke(brush_cr);
cairo_destroy(brush_cr);
cairo_pattern_t *pattern = cairo_pattern_create_for_surface(brush_surface);
cairo_pattern_set_extend(pattern, CAIRO_EXTEND_REPEAT);
cairo_matrix_t patterm_matrix;
cairo_matrix_init_translate(&patterm_matrix, 0, -top + brush_height / 2.0);
cairo_pattern_set_matrix(pattern, &patterm_matrix);
cairo_set_source(cr, pattern);
cairo_set_line_width(cr, brush_height);
cairo_move_to(cr, x, top);
cairo_line_to(cr, x + width, top);
cairo_stroke(cr);
cairo_pattern_destroy(pattern);
cairo_surface_destroy(brush_surface);
}
static void draw_double_line(cairo_t* cr, int x, int y, int width, int thickness, draw_type type, litehtml::web_color& color)
{
cairo_set_line_width(cr, thickness);
double top1;
double top2;
switch (type)
{
case draw_type::DRAW_UNDERLINE:
top1 = y + (double)thickness / 2.0;
top2 = top1 + (double)thickness + (double)thickness / 2.0 + 0.5;
break;
case draw_type::DRAW_OVERLINE:
top1 = y - (double)thickness / 2.0;
top2 = top1 - (double)thickness - (double)thickness / 2.0 - 0.5;
break;
case draw_type::DRAW_STRIKETHROUGH:
top1 = y - (double)thickness + 0.5;
top2 = y + (double)thickness + 0.5;
break;
default:
top1 = y;
top2 = y;
break;
}
cairo_move_to(cr, x, top1);
cairo_line_to(cr, x + width, top1);
cairo_stroke(cr);
cairo_move_to(cr, x, top2);
cairo_line_to(cr, x + width, top2);
cairo_set_source_rgba(cr, (double) color.red / 255.0,
(double) color.green / 255.0,
(double) color.blue / 255.0,
(double) color.alpha / 255.0);
cairo_stroke(cr);
}
void container_cairo_pango::draw_text(litehtml::uint_ptr hdc, const char *text, litehtml::uint_ptr hFont,
litehtml::web_color color, const litehtml::position &pos)
{
auto* fnt = (cairo_font*) hFont;
auto* cr = (cairo_t*) hdc;
cairo_save(cr);
apply_clip(cr);
set_color(cr, color);
litehtml::web_color decoration_color = color;
PangoLayout *layout = pango_cairo_create_layout(cr);
pango_layout_set_font_description (layout, fnt->font);
pango_layout_set_text (layout, text, -1);
auto font_options = get_font_options();
if(font_options)
{
auto ctx = pango_layout_get_context(layout);
pango_cairo_context_set_font_options(ctx, font_options);
}
PangoRectangle ink_rect, logical_rect;
pango_layout_get_pixel_extents(layout, &ink_rect, &logical_rect);
int text_baseline = pos.height - fnt->descent;
int x = pos.left() + logical_rect.x;
int y = pos.top();
cairo_move_to(cr, x, y);
pango_cairo_update_layout (cr, layout);
pango_cairo_show_layout (cr, layout);
int tw = 0;
if(fnt->underline || fnt->strikeout || fnt->overline)
{
tw = text_width(text, hFont);
}
if(!fnt->decoration_color.is_current_color)
{
decoration_color = fnt->decoration_color;
}
std::array<decltype(&draw_solid_line), litehtml::text_decoration_style_max> draw_funcs {
draw_solid_line, // text_decoration_style_solid
draw_double_line, // text_decoration_style_double
draw_dotted_line, // text_decoration_style_dotted
draw_dashed_line, // text_decoration_style_dashed
draw_wavy_line, // text_decoration_style_wavy
};
if(fnt->underline)
{
draw_funcs[fnt->decoration_style](cr, x, pos.top() + text_baseline + fnt->underline_position, tw, fnt->underline_thickness, draw_type::DRAW_UNDERLINE, decoration_color);
}
if(fnt->strikeout)
{
draw_funcs[fnt->decoration_style](cr, x, pos.top() + text_baseline - fnt->strikethrough_position, tw, fnt->strikethrough_thickness, draw_type::DRAW_STRIKETHROUGH, decoration_color);
}
if(fnt->overline)
{
draw_funcs[fnt->decoration_style](cr, x, pos.top() + text_baseline - fnt->overline_position, tw, fnt->overline_thickness, draw_type::DRAW_OVERLINE, decoration_color);
}
cairo_restore(cr);
g_object_unref(layout);
}