Skip to content

Support for text-decoration CSS properties #391

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Mar 12, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,7 @@ set(HEADER_LITEHTML
include/litehtml/flex_item.h
include/litehtml/flex_line.h
include/litehtml/gradient.h
include/litehtml/font_description.h
)

set(PROJECT_LIB_VERSION ${PROJECT_MAJOR}.${PROJECT_MINOR}.0)
Expand Down Expand Up @@ -199,7 +200,7 @@ else ()
ExternalProject_Add(
litehtml-tests
GIT_REPOSITORY https://github.com/litehtml/litehtml-tests.git
GIT_TAG e90547605bda845c1b953232b1c2b3645928eac2
GIT_TAG b746fd0df7f83d6aa2fea1cfee869459e8197970
SOURCE_DIR "${CMAKE_BINARY_DIR}/litehtml-tests-src"
BINARY_DIR "${CMAKE_BINARY_DIR}/litehtml-tests-build"
CMAKE_ARGS -DLITEHTML_PATH=${CMAKE_CURRENT_SOURCE_DIR}
Expand Down
286 changes: 245 additions & 41 deletions containers/cairo/container_cairo_pango.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,10 @@ container_cairo_pango::~container_cairo_pango()
cairo_destroy(m_temp_cr);
}

litehtml::uint_ptr container_cairo_pango::create_font(const char *faceName, int size, int weight, litehtml::font_style italic,
unsigned int decoration, litehtml::font_metrics *fm)
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(faceName, tokens, ",");
litehtml::split_string(descr.family, tokens, ",");
std::string fonts;

for(auto& font : tokens)
Expand All @@ -60,32 +59,22 @@ litehtml::uint_ptr container_cairo_pango::create_font(const char *faceName, int
}

PangoFontDescription *desc = pango_font_description_from_string (fonts.c_str());
pango_font_description_set_absolute_size(desc, size * PANGO_SCALE);
if(italic == litehtml::font_style_italic)
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);
}
PangoWeight fnt_weight;
if(weight >= 0 && weight < 150) fnt_weight = PANGO_WEIGHT_THIN;
else if(weight >= 150 && weight < 250) fnt_weight = PANGO_WEIGHT_ULTRALIGHT;
else if(weight >= 250 && weight < 350) fnt_weight = PANGO_WEIGHT_LIGHT;
else if(weight >= 350 && weight < 450) fnt_weight = PANGO_WEIGHT_NORMAL;
else if(weight >= 450 && weight < 550) fnt_weight = PANGO_WEIGHT_MEDIUM;
else if(weight >= 550 && weight < 650) fnt_weight = PANGO_WEIGHT_SEMIBOLD;
else if(weight >= 650 && weight < 750) fnt_weight = PANGO_WEIGHT_BOLD;
else if(weight >= 750 && weight < 850) fnt_weight = PANGO_WEIGHT_ULTRABOLD;
else fnt_weight = PANGO_WEIGHT_HEAVY;

pango_font_description_set_weight(desc, fnt_weight);

pango_font_description_set_weight(desc, (PangoWeight) descr.weight);

cairo_font* ret = nullptr;

if(fm)
{
fm->font_size = size;
fm->font_size = descr.size;

cairo_save(m_temp_cr);
PangoLayout *layout = pango_cairo_create_layout(m_temp_cr);
Expand All @@ -95,12 +84,12 @@ litehtml::uint_ptr container_cairo_pango::create_font(const char *faceName, int
PangoFontMetrics *metrics = pango_context_get_metrics(context, desc, language);

fm->ascent = PANGO_PIXELS((double)pango_font_metrics_get_ascent(metrics));
fm->descent = PANGO_PIXELS((double)pango_font_metrics_get_descent(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 = (decoration != litehtml::font_decoration_none);
fm->sub_shift = size / 5;
fm->super_shift = size / 3;
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);

Expand All @@ -118,25 +107,61 @@ litehtml::uint_ptr container_cairo_pango::create_font(const char *faceName, int
cairo_restore(m_temp_cr);

ret = new cairo_font;
ret->font = desc;
ret->size = size;
ret->strikeout = (decoration & litehtml::font_decoration_linethrough) != 0;
ret->underline = (decoration & litehtml::font_decoration_underline) != 0;
ret->ascent = fm->ascent;
ret->descent = fm->descent;

ret->underline_thickness = pango_font_metrics_get_underline_thickness(metrics);
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 = -1;//PANGO_PIXELS(ret->underline_position);
ret->underline_position = PANGO_PIXELS(ret->underline_position);

ret->strikethrough_thickness = pango_font_metrics_get_strikethrough_thickness(metrics);
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);
}
Expand Down Expand Up @@ -176,6 +201,170 @@ int container_cairo_pango::text_width(const char *text, litehtml::uint_ptr hFont
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)
{
Expand All @@ -187,6 +376,8 @@ void container_cairo_pango::draw_text(litehtml::uint_ptr hdc, const char *text,

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);
Expand All @@ -212,24 +403,37 @@ void container_cairo_pango::draw_text(litehtml::uint_ptr hdc, const char *text,

int tw = 0;

if(fnt->underline || fnt->strikeout)
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)
{
cairo_set_line_width(cr, fnt->underline_thickness);
cairo_move_to(cr, x, pos.top() + text_baseline - fnt->underline_position + 0.5);
cairo_line_to(cr, x + tw, pos.top() + text_baseline - fnt->underline_position + 0.5);
cairo_stroke(cr);
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)
{
cairo_set_line_width(cr, fnt->strikethrough_thickness);
cairo_move_to(cr, x, pos.top() + text_baseline - fnt->strikethrough_position - 0.5);
cairo_line_to(cr, x + tw, pos.top() + text_baseline - fnt->strikethrough_position - 0.5);
cairo_stroke(cr);
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);
Expand Down
Loading