🔧 Improve text strokes blending

This commit is contained in:
Elena Torro
2025-08-01 15:24:08 +02:00
parent 172c6ad4b8
commit 82d2889e96
4 changed files with 69 additions and 37 deletions

View File

@@ -470,18 +470,6 @@ impl RenderState {
text::render(self, &shape, &mut paragraphs, None, None);
if shape.has_visible_inner_strokes() {
// Inner strokes paints need the text fill to apply correctly their blend modes
// (e.g., SrcATop, DstOver)
text::render(
self,
&shape,
&mut paragraphs,
Some(SurfaceId::Strokes),
None,
);
}
for stroke in shape.visible_strokes().rev() {
let mut stroke_paragraphs =
text_content.get_skia_stroke_paragraphs(stroke, &shape.selrect());

View File

@@ -24,6 +24,9 @@ pub fn render(
_ => 0.0,
};
let layer_rec = skia_safe::canvas::SaveLayerRec::default();
canvas.save_layer(&layer_rec);
for group in paragraphs {
let mut group_offset_y = global_offset_y;
let group_len = group.len();
@@ -36,15 +39,7 @@ pub fn render(
let mut paragraph_builder =
ParagraphBuilder::new(&builder.get_paragraph_style(), fonts);
let mut text_style: skia_safe::Handle<_> = builder.peek_style();
let current_paint = text_style.foreground().clone();
let blend_mode = current_paint.as_blend_mode();
let mut new_paint = paint.unwrap().clone();
if blend_mode != Some(skia_safe::BlendMode::SrcIn) {
new_paint.set_stroke_width(current_paint.stroke_width());
new_paint.set_style(skia_safe::PaintStyle::StrokeAndFill);
}
new_paint.set_anti_alias(true);
text_style.set_foreground_paint(&new_paint);
text_style.set_foreground_paint(paint.unwrap());
paragraph_builder.reset();
paragraph_builder.push_style(&text_style);
paragraph_builder.add_text(&text);
@@ -138,6 +133,8 @@ pub fn render(
global_offset_y = group_offset_y;
}
}
canvas.restore();
}
pub fn calculate_text_decoration_rect(

View File

@@ -980,6 +980,7 @@ impl Shape {
self.visible_strokes().next().is_some()
}
#[allow(dead_code)]
pub fn has_visible_inner_strokes(&self) -> bool {
self.visible_strokes().any(|s| s.kind == StrokeKind::Inner)
}

View File

@@ -118,24 +118,35 @@ impl TextContent {
bounds: &Rect,
) -> Vec<Vec<ParagraphBuilder>> {
let fallback_fonts = get_fallback_fonts();
let stroke_paints = get_text_stroke_paints(stroke, bounds);
let fonts = get_font_collection();
let mut paragraph_group = Vec::new();
for paragraph in &self.paragraphs {
let mut stroke_paragraphs = Vec::new();
for stroke_paint in &stroke_paints {
let paragraph_style = paragraph.paragraph_to_style();
let mut builder = ParagraphBuilder::new(&paragraph_style, fonts);
for leaf in &paragraph.children {
let mut stroke_paragraphs_map: std::collections::HashMap<usize, ParagraphBuilder> =
std::collections::HashMap::new();
for leaf in paragraph.children.iter() {
let text_paint = merge_fills(&leaf.fills, *bounds);
let stroke_paints = get_text_stroke_paints(stroke, bounds, &text_paint);
let text: String = leaf.apply_text_transform();
for (paint_idx, stroke_paint) in stroke_paints.iter().enumerate() {
let builder = stroke_paragraphs_map.entry(paint_idx).or_insert_with(|| {
let paragraph_style = paragraph.paragraph_to_style();
ParagraphBuilder::new(&paragraph_style, fonts)
});
let stroke_style =
leaf.to_stroke_style(paragraph, stroke_paint, fallback_fonts);
let text: String = leaf.apply_text_transform();
builder.push_style(&stroke_style);
builder.add_text(&text);
}
stroke_paragraphs.push(builder);
}
let stroke_paragraphs: Vec<ParagraphBuilder> = (0..stroke_paragraphs_map.len())
.map(|i| stroke_paragraphs_map.remove(&i).unwrap())
.collect();
paragraph_group.push(stroke_paragraphs);
}
@@ -693,20 +704,55 @@ pub fn auto_height(paragraphs: &mut [Vec<ParagraphBuilder>], width: f32) -> f32
})
}
fn get_text_stroke_paints(stroke: &Stroke, bounds: &Rect) -> Vec<Paint> {
fn get_text_stroke_paints(stroke: &Stroke, bounds: &Rect, text_paint: &Paint) -> Vec<Paint> {
let mut paints = Vec::new();
match stroke.kind {
StrokeKind::Inner => {
let mut paint = skia::Paint::default();
paint.set_style(skia::PaintStyle::Stroke);
paint.set_blend_mode(skia::BlendMode::SrcIn);
paint.set_anti_alias(true);
paint.set_stroke_width(stroke.width * 2.0);
let shader = text_paint.shader();
let mut is_opaque = true;
set_paint_fill(&mut paint, &stroke.fill, bounds);
if shader.is_some() {
is_opaque = shader.unwrap().is_opaque();
}
paints.push(paint);
if is_opaque {
let mut paint = text_paint.clone();
paint.set_style(skia::PaintStyle::Fill);
paint.set_anti_alias(true);
paints.push(paint);
let mut paint = skia::Paint::default();
paint.set_style(skia::PaintStyle::Stroke);
paint.set_blend_mode(skia::BlendMode::SrcIn);
paint.set_anti_alias(true);
paint.set_stroke_width(stroke.width * 2.0);
set_paint_fill(&mut paint, &stroke.fill, bounds);
paints.push(paint);
} else {
// outer
let mut paint = skia::Paint::default();
paint.set_style(skia::PaintStyle::Stroke);
paint.set_blend_mode(skia::BlendMode::DstATop);
paint.set_anti_alias(true);
paint.set_stroke_width(stroke.width * 2.0);
paints.push(paint);
let mut paint = skia::Paint::default();
paint.set_style(skia::PaintStyle::Fill);
paint.set_blend_mode(skia::BlendMode::Clear);
paint.set_anti_alias(true);
paints.push(paint);
// inner
let mut paint = skia::Paint::default();
paint.set_style(skia::PaintStyle::Stroke);
paint.set_stroke_width(stroke.width * 2.0);
paint.set_blend_mode(skia::BlendMode::Xor);
paint.set_anti_alias(true);
set_paint_fill(&mut paint, &stroke.fill, bounds);
paints.push(paint);
}
}
StrokeKind::Center => {
let mut paint = skia::Paint::default();