🎉 Add text solid strokes (#6384)

* 🎉 Add text strokes

* 🔧 Minor refactor
This commit is contained in:
Elena Torró 2025-05-07 17:28:36 +02:00 committed by GitHub
parent 44bf276c49
commit 61eb2f4a19
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 166 additions and 23 deletions

View file

@ -350,15 +350,32 @@ impl RenderState {
}
}
}
Type::Text(text_content) => {
self.surfaces.apply_mut(&[SurfaceId::Fills], |s| {
s.canvas().concat(&matrix);
});
let paragraphs = text_content.to_skia_paragraphs(&self.fonts.font_collection());
let paragraphs = text_content.get_skia_paragraphs(&self.fonts.font_collection());
shadows::render_text_drop_shadows(self, &shape, &paragraphs, antialias);
text::render(self, &shape, &paragraphs, None, None);
for stroke in shape.strokes().rev() {
let stroke_paints = shape.get_text_stroke_paint(&stroke);
let stroke_paragraphs = text_content
.get_skia_stroke_paragraphs(&self.fonts.font_collection(), &stroke_paints);
shadows::render_text_drop_shadows(self, &shape, &stroke_paragraphs, antialias);
text::render(
self,
&shape,
&stroke_paragraphs,
Some(SurfaceId::Strokes),
None,
);
shadows::render_text_inner_shadows(self, &shape, &stroke_paragraphs, antialias);
}
shadows::render_text_inner_shadows(self, &shape, &paragraphs, antialias);
}
_ => {

View file

@ -86,7 +86,7 @@ pub fn render_stroke_inner_shadows(
pub fn render_text_drop_shadows(
render_state: &mut RenderState,
shape: &Shape,
paragraphs: &[Paragraph],
paragraphs: &[Vec<Paragraph>],
antialias: bool,
) {
for shadow in shape.drop_shadows().rev().filter(|s| !s.hidden()) {
@ -98,7 +98,7 @@ pub fn render_text_drop_shadow(
render_state: &mut RenderState,
shape: &Shape,
shadow: &Shadow,
paragraphs: &[Paragraph],
paragraphs: &[Vec<Paragraph>],
antialias: bool,
) {
let paint = &shadow.get_drop_shadow_paint(antialias);
@ -115,7 +115,7 @@ pub fn render_text_drop_shadow(
pub fn render_text_inner_shadows(
render_state: &mut RenderState,
shape: &Shape,
paragraphs: &[Paragraph],
paragraphs: &[Vec<Paragraph>],
antialias: bool,
) {
for shadow in shape.inner_shadows().rev().filter(|s| !s.hidden()) {
@ -127,7 +127,7 @@ pub fn render_text_inner_shadow(
render_state: &mut RenderState,
shape: &Shape,
shadow: &Shadow,
paragraphs: &[Paragraph],
paragraphs: &[Vec<Paragraph>],
antialias: bool,
) {
let paint = &shadow.get_inner_shadow_paint(antialias);

View file

@ -4,11 +4,10 @@ use skia_safe::{self as skia, canvas::SaveLayerRec, paint, textlayout::Paragraph
pub fn render(
render_state: &mut RenderState,
shape: &Shape,
paragraphs: &[Paragraph],
paragraphs: &[Vec<Paragraph>],
surface_id: Option<SurfaceId>,
paint: Option<&paint::Paint>,
) {
let mut offset_y = 0.0;
let default_paint = skia::Paint::default();
let mask = SaveLayerRec::default().paint(&paint.unwrap_or(&default_paint));
let canvas = render_state
@ -16,10 +15,13 @@ pub fn render(
.canvas(surface_id.unwrap_or(SurfaceId::Fills));
canvas.save_layer(&mask);
for skia_paragraph in paragraphs {
for group in paragraphs {
let mut offset_y = 0.0;
for skia_paragraph in group {
let xy = (shape.selrect().x(), shape.selrect.y() + offset_y);
skia_paragraph.paint(canvas, xy);
offset_y += skia_paragraph.height();
}
}
canvas.restore();
}

View file

@ -1,4 +1,4 @@
use skia_safe::{self as skia};
use skia_safe::{self as skia, paint::Paint};
use crate::render::BlendMode;
use crate::uuid::Uuid;
@ -811,6 +811,60 @@ impl Shape {
pub fn has_fills(&self) -> bool {
!self.fills.is_empty()
}
pub fn get_text_stroke_paint(&self, stroke: &Stroke) -> Vec<Paint> {
let mut paints = Vec::new();
match stroke.kind {
StrokeKind::InnerStroke => {
let mut paint = skia::Paint::default();
paint.set_blend_mode(skia::BlendMode::DstOver);
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::SrcATop);
paint.set_anti_alias(true);
paint.set_stroke_width(stroke.width * 2.0);
paint.set_color(match &stroke.fill {
Fill::Solid(color) => *color,
_ => Color::BLACK,
});
paints.push(paint);
}
StrokeKind::CenterStroke => {
let mut paint = skia::Paint::default();
paint.set_style(skia::PaintStyle::Stroke);
paint.set_anti_alias(true);
paint.set_stroke_width(stroke.width);
paint.set_color(match &stroke.fill {
Fill::Solid(color) => *color,
_ => Color::BLACK,
});
paints.push(paint);
}
StrokeKind::OuterStroke => {
let mut paint = skia::Paint::default();
paint.set_style(skia::PaintStyle::Stroke);
paint.set_blend_mode(skia::BlendMode::DstOver);
paint.set_anti_alias(true);
paint.set_stroke_width(stroke.width * 2.0);
paint.set_color(match &stroke.fill {
Fill::Solid(color) => *color,
_ => Color::BLACK,
});
paints.push(paint);
let mut paint = skia::Paint::default();
paint.set_blend_mode(skia::BlendMode::Clear);
paint.set_anti_alias(true);
paints.push(paint);
}
}
paints
}
}
/*

View file

@ -4,6 +4,7 @@ use crate::{
};
use skia_safe::{
self as skia,
paint::Paint,
textlayout::{FontCollection, ParagraphBuilder, ParagraphStyle},
};
@ -47,32 +48,90 @@ impl TextContent {
self.paragraphs.push(paragraph);
}
pub fn to_paragraphs(&self, fonts: &FontCollection) -> Vec<skia::textlayout::Paragraph> {
self.paragraphs
pub fn to_paragraphs(&self, fonts: &FontCollection) -> Vec<Vec<skia::textlayout::Paragraph>> {
let mut paragraph_group = Vec::new();
let paragraphs = self
.paragraphs
.iter()
.map(|p| {
let paragraph_style = p.paragraph_to_style();
let mut builder = ParagraphBuilder::new(&paragraph_style, fonts);
for leaf in &p.children {
let text_style = leaf.to_style(&p);
let text_style = leaf.to_style(p);
let text = leaf.apply_text_transform(p.text_transform);
builder.push_style(&text_style);
builder.add_text(&text);
builder.pop();
}
builder.build()
})
.collect();
paragraph_group.push(paragraphs);
paragraph_group
}
pub fn to_stroke_paragraphs(
&self,
fonts: &FontCollection,
stroke_paints: &Vec<Paint>,
) -> Vec<Vec<skia::textlayout::Paragraph>> {
let mut paragraph_group = Vec::new();
for stroke_paint in stroke_paints {
let mut stroke_paragraphs = Vec::new();
for paragraph in &self.paragraphs {
let paragraph_style = paragraph.paragraph_to_style();
let mut builder = ParagraphBuilder::new(&paragraph_style, fonts);
for leaf in &paragraph.children {
let stroke_style = leaf.to_stroke_style(paragraph, &stroke_paint);
let text: String = leaf.apply_text_transform(paragraph.text_transform);
builder.push_style(&stroke_style);
builder.add_text(&text);
let p = builder.build();
stroke_paragraphs.push(p);
}
builder.reset();
}
paragraph_group.push(stroke_paragraphs);
}
paragraph_group
}
pub fn get_skia_paragraphs(
&self,
fonts: &FontCollection,
) -> Vec<Vec<skia::textlayout::Paragraph>> {
self.to_paragraphs(fonts)
.into_iter()
.map(|group| {
group
.into_iter()
.map(|mut paragraph| {
paragraph.layout(self.width());
paragraph
})
.collect()
})
.collect()
}
pub fn to_skia_paragraphs(&self, fonts: &FontCollection) -> Vec<skia::textlayout::Paragraph> {
let mut paragraphs = Vec::new();
for mut skia_paragraph in self.to_paragraphs(fonts) {
skia_paragraph.layout(self.width());
paragraphs.push(skia_paragraph);
}
paragraphs
pub fn get_skia_stroke_paragraphs(
&self,
fonts: &FontCollection,
paints: &Vec<Paint>,
) -> Vec<Vec<skia::textlayout::Paragraph>> {
self.to_stroke_paragraphs(fonts, paints)
.into_iter()
.map(|group| {
group
.into_iter()
.map(|mut paragraph| {
paragraph.layout(self.width());
paragraph
})
.collect()
})
.collect()
}
}
@ -216,6 +275,7 @@ impl TextLeaf {
3 => skia::textlayout::TextDecoration::OVERLINE,
_ => skia::textlayout::TextDecoration::NO_DECORATION,
});
style.set_font_families(&[
self.serialized_font_family(),
default_font(),
@ -225,6 +285,16 @@ impl TextLeaf {
style
}
pub fn to_stroke_style(
&self,
paragraph: &Paragraph,
stroke_paint: &Paint,
) -> skia::textlayout::TextStyle {
let mut style = self.to_style(paragraph);
style.set_foreground_paint(stroke_paint);
style
}
fn serialized_font_family(&self) -> String {
format!("{}", self.font_family)
}