Skip to content

Commit e420350

Browse files
committed
Support text underline
1 parent 5b901cd commit e420350

3 files changed

Lines changed: 71 additions & 1 deletion

File tree

‎examples/rich_text/main.cpp‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ class RichTextNode : public Node {
7272
style_grad.fill = grad;
7373
style_grad.font_size = 64;
7474
style_grad.bold = true;
75+
style_grad.underline = true;
7576
style_grad.stroke_color = ColorU::white();
7677
style_grad.stroke_width = 2.0f;
7778
style_grad.gradient_mapping_mode = GradientMappingMode::Span;

‎vecgui/resources/font.h‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,7 @@ struct TextStyle {
218218

219219
bool italic = false;
220220
bool bold = false;
221+
bool underline = false;
221222

222223
// Fill.
223224
std::variant<ColorU, Pathfinder::Gradient> fill = ColorU::white();
@@ -266,6 +267,7 @@ struct TextStyle {
266267

267268
bool operator==(const TextStyle &rhs) const {
268269
return fill == rhs.fill && font_size == rhs.font_size && italic == rhs.italic && bold == rhs.bold &&
270+
underline == rhs.underline &&
269271
stroke_color == rhs.stroke_color && stroke_width == rhs.stroke_width &&
270272
shadow_color == rhs.shadow_color && shadow_radius == rhs.shadow_radius &&
271273
shadow_offset == rhs.shadow_offset && background_color == rhs.background_color &&

‎vecgui/servers/vector_server.cpp‎

Lines changed: 68 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -450,14 +450,32 @@ void VectorServer::draw_glyphs(std::vector<Glyph> &glyphs,
450450
if (get_line_index(j) == current_line_idx && g.style.shadow_color == style.shadow_color &&
451451
g.style.shadow_radius == style.shadow_radius && g.style.shadow_offset == style.shadow_offset &&
452452
g.style.shadow_strength == style.shadow_strength && g.style.local_transform == style.local_transform &&
453-
g.style.clipping_progress == style.clipping_progress && g.style.opacity == style.opacity) {
453+
g.style.clipping_progress == style.clipping_progress && g.style.opacity == style.opacity &&
454+
g.style.underline == style.underline) {
454455
add_glyph_to_shadow_path(j);
455456
j++;
456457
} else {
457458
break;
458459
}
459460
}
460461

462+
if (style.underline) {
463+
float min_x = 1e9f;
464+
float max_x = -1e9f;
465+
for (int k = i; k < j; k++) {
466+
if (glyphs[k].skip_drawing) continue;
467+
min_x = std::min(min_x, glyph_positions[k].x);
468+
max_x = std::max(max_x, glyph_positions[k].x + glyphs[k].x_advance);
469+
}
470+
if (min_x <= max_x) {
471+
float y = glyph_positions[i].y + glyphs[i].ascent + style.shadow_offset.y + std::max(2.0f, style.font_size * 0.08f);
472+
float thickness = std::max(1.0f, style.font_size / 16.0f);
473+
Pathfinder::Path2d underline_shadow_path;
474+
underline_shadow_path.add_rect(RectF(min_x + style.shadow_offset.x, y, max_x + style.shadow_offset.x, y + thickness), 0);
475+
combined_shadow_path.add_path(underline_shadow_path, style.local_transform);
476+
}
477+
}
478+
461479
// Execute ONE blur/fill operation for the entire batch.
462480
canvas->save_state();
463481
// The combined_shadow_path already has local transforms baked in.
@@ -520,6 +538,7 @@ void VectorServer::draw_glyphs(std::vector<Glyph> &glyphs,
520538
glyphs[j].style.local_transform == style.local_transform &&
521539
glyphs[j].style.opacity == style.opacity && glyphs[j].style.italic == style.italic &&
522540
glyphs[j].style.bold == style.bold &&
541+
glyphs[j].style.underline == style.underline &&
523542
glyphs[j].style.clipping_progress == style.clipping_progress &&
524543
get_line_index(j) == current_line_idx) {
525544
j++;
@@ -554,6 +573,28 @@ void VectorServer::draw_glyphs(std::vector<Glyph> &glyphs,
554573
const auto full_transform = base_transform * style.local_transform;
555574
canvas->set_transform(full_transform);
556575

576+
if (style.underline && style.stroke_color.is_visible() && style.stroke_width > 0) {
577+
float min_x = 1e9f;
578+
float max_x = -1e9f;
579+
for (int k = i; k < j; k++) {
580+
if (glyphs[k].skip_drawing) continue;
581+
min_x = std::min(min_x, glyph_positions[k].x);
582+
max_x = std::max(max_x, glyph_positions[k].x + glyphs[k].x_advance);
583+
}
584+
if (min_x <= max_x) {
585+
float y = glyph_positions[i].y + glyphs[i].ascent + std::max(2.0f, style.font_size * 0.08f);
586+
float thickness = std::max(1.0f, style.font_size / 16.0f);
587+
Pathfinder::Path2d underline_path;
588+
underline_path.add_rect(RectF(min_x, y, max_x, y + thickness), 0);
589+
590+
canvas->set_stroke_paint(
591+
Pathfinder::Paint::from_color(style.stroke_color.apply_alpha(opacity * style.opacity)));
592+
canvas->set_line_width(style.stroke_width);
593+
canvas->set_line_join(Pathfinder::LineJoin::Round);
594+
canvas->stroke_path(underline_path);
595+
}
596+
}
597+
557598
for (int k = i; k < j; k++) {
558599
auto& gk = glyphs[k];
559600
if (gk.emoji || gk.skip_drawing) continue;
@@ -758,6 +799,32 @@ void VectorServer::draw_glyphs(std::vector<Glyph> &glyphs,
758799
}
759800
}
760801

802+
if (style.underline) {
803+
float min_x = 1e9f;
804+
float max_x = -1e9f;
805+
for (int k = i; k < j; k++) {
806+
if (glyphs[k].skip_drawing) continue;
807+
min_x = std::min(min_x, glyph_positions[k].x);
808+
max_x = std::max(max_x, glyph_positions[k].x + glyphs[k].x_advance);
809+
}
810+
if (min_x <= max_x) {
811+
float y = glyph_positions[i].y + glyphs[i].ascent + std::max(2.0f, style.font_size * 0.08f);
812+
float thickness = std::max(1.0f, style.font_size / 16.0f);
813+
Pathfinder::Path2d underline_path;
814+
underline_path.add_rect(RectF(min_x, y, max_x, y + thickness), 0);
815+
816+
if (is_karaoke) {
817+
canvas->fill_path(underline_path, Pathfinder::FillRule::Winding);
818+
} else if (style.gradient_mapping_mode == GradientMappingMode::Span) {
819+
canvas->set_fill_paint(get_paint_for_style(style, opacity, batch_bounds));
820+
canvas->fill_path(underline_path, Pathfinder::FillRule::Winding);
821+
} else {
822+
canvas->set_fill_paint(get_paint_for_style(style, opacity, RectF(min_x, y, max_x, y + thickness)));
823+
canvas->fill_path(underline_path, Pathfinder::FillRule::Winding);
824+
}
825+
}
826+
}
827+
761828
// Handle Pseudo-Bold and Emojis within the batch (they don't support simple path-level gradients well).
762829
for (int k = i; k < j; k++) {
763830
auto &gk = glyphs[k];

0 commit comments

Comments
 (0)