@@ -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