🐛 Fix text shadows and blur and refactor text rendering

This commit is contained in:
Elena Torro
2025-09-04 14:46:12 +02:00
parent 0e23c9f6ab
commit 4bd2eba573
27 changed files with 14417 additions and 498 deletions

View File

@@ -1,9 +1,10 @@
use super::{RenderState, SurfaceId};
use crate::render::strokes;
use crate::render::text::{self};
use crate::shapes::{Shadow, Shape, Stroke, Type};
use skia_safe::textlayout::ParagraphBuilder;
use skia_safe::{Paint, Path};
use skia_safe::{canvas::SaveLayerRec, Paint, Path};
use crate::render::text;
use crate::textlayout::ParagraphBuilderGroup;
// Fill Shadows
pub fn render_fill_inner_shadows(
@@ -46,7 +47,6 @@ pub fn render_stroke_inner_shadows(
stroke,
Some(surface_id),
filter.as_ref(),
None,
antialias,
)
}
@@ -76,15 +76,6 @@ pub fn render_text_path_stroke_drop_shadows(
}
}
pub fn render_text_inner_shadows(
render_state: &mut RenderState,
shape: &Shape,
paragraphs: &mut [Vec<ParagraphBuilder>],
surface_id: SurfaceId,
) {
text::render(render_state, shape, paragraphs, Some(surface_id));
}
// Render text paths (unused)
#[allow(dead_code)]
pub fn render_text_path_stroke_inner_shadows(
@@ -129,3 +120,50 @@ fn render_shadow_paint(
_ => {}
}
}
pub fn render_text_shadows(
render_state: &mut RenderState,
shape: &Shape,
paragraphs: &mut [ParagraphBuilderGroup],
stroke_paragraphs_group: &mut [Vec<ParagraphBuilderGroup>],
surface_id: Option<SurfaceId>,
shadows: &[Paint],
blur_filter: &Option<skia_safe::ImageFilter>,
) {
if stroke_paragraphs_group.is_empty() {
return;
}
let canvas = render_state
.surfaces
.canvas(surface_id.unwrap_or(SurfaceId::TextDropShadows));
for shadow in shadows {
let shadow_layer = SaveLayerRec::default().paint(shadow);
canvas.save_layer(&shadow_layer);
text::render(
None,
Some(canvas),
shape,
paragraphs,
surface_id,
None,
blur_filter.as_ref(),
);
for stroke_paragraphs in stroke_paragraphs_group.iter_mut() {
text::render(
None,
Some(canvas),
shape,
stroke_paragraphs,
surface_id,
None,
blur_filter.as_ref(),
);
}
canvas.restore();
}
}

View File

@@ -3,11 +3,10 @@ use std::collections::HashMap;
use crate::math::{Matrix, Point, Rect};
use crate::shapes::{Corners, Fill, ImageFill, Path, Shape, Stroke, StrokeCap, StrokeKind, Type};
use skia_safe::{self as skia, textlayout::ParagraphBuilder, ImageFilter, RRect};
use skia_safe::{self as skia, ImageFilter, RRect};
use super::{RenderState, SurfaceId};
use crate::render::filters::compose_filters;
use crate::render::text::{self};
use crate::render::{get_dest_rect, get_source_rect};
// FIXME: See if we can simplify these arguments
@@ -519,7 +518,6 @@ pub fn render(
stroke: &Stroke,
surface_id: Option<SurfaceId>,
shadow: Option<&ImageFilter>,
paragraphs: Option<&mut Vec<Vec<ParagraphBuilder>>>,
antialias: bool,
) {
let scale = render_state.get_scale();
@@ -564,14 +562,7 @@ pub fn render(
shape.image_filter(1.).as_ref(),
antialias,
),
Type::Text(_) => {
text::render(
render_state,
shape,
paragraphs.expect("Text shapes should have paragraphs"),
surface_id,
);
}
Type::Text(_) => {}
shape_type @ (Type::Path(_) | Type::Bool(_)) => {
if let Some(path) = shape_type.path() {
draw_stroke_on_path(

View File

@@ -17,15 +17,16 @@ const TILE_SIZE_MULTIPLIER: i32 = 2;
#[repr(u32)]
#[derive(Debug, PartialEq, Clone, Copy)]
pub enum SurfaceId {
Target = 0b0_0000_0001,
Cache = 0b0_0000_0010,
Current = 0b0_0000_0100,
Fills = 0b0_0000_1000,
Strokes = 0b0_0001_0000,
DropShadows = 0b0_0010_0000,
InnerShadows = 0b0_0100_0000,
UI = 0b0_1000_0000,
Debug = 0b1_0000_0000,
Target = 0b00_0000_0001,
Cache = 0b00_0000_0010,
Current = 0b00_0000_0100,
Fills = 0b00_0000_1000,
Strokes = 0b00_0001_0000,
DropShadows = 0b00_0010_0000,
InnerShadows = 0b00_0100_0000,
TextDropShadows = 0b00_1000_0000,
UI = 0b01_0000_0000,
Debug = 0b10_0000_0001,
}
pub struct Surfaces {
@@ -42,6 +43,8 @@ pub struct Surfaces {
drop_shadows: skia::Surface,
// used for rendering over shadows.
inner_shadows: skia::Surface,
// used for rendering text drop shadows
text_drop_shadows: skia::Surface,
// used for displaying auxiliary workspace elements
ui: skia::Surface,
// for drawing debug info.
@@ -73,6 +76,8 @@ impl Surfaces {
gpu_state.create_surface_with_isize("drop_shadows".to_string(), extra_tile_dims);
let inner_shadows =
gpu_state.create_surface_with_isize("inner_shadows".to_string(), extra_tile_dims);
let text_drop_shadows =
gpu_state.create_surface_with_isize("text_drop_shadows".to_string(), extra_tile_dims);
let shape_fills =
gpu_state.create_surface_with_isize("shape_fills".to_string(), extra_tile_dims);
let shape_strokes =
@@ -88,6 +93,7 @@ impl Surfaces {
current,
drop_shadows,
inner_shadows,
text_drop_shadows,
shape_fills,
shape_strokes,
ui,
@@ -166,6 +172,9 @@ impl Surfaces {
if ids & SurfaceId::InnerShadows as u32 != 0 {
f(self.get_mut(SurfaceId::InnerShadows));
}
if ids & SurfaceId::TextDropShadows as u32 != 0 {
f(self.get_mut(SurfaceId::TextDropShadows));
}
if ids & SurfaceId::DropShadows as u32 != 0 {
f(self.get_mut(SurfaceId::DropShadows));
}
@@ -189,11 +198,15 @@ impl Surfaces {
pub fn update_render_context(&mut self, render_area: skia::Rect, scale: f32) {
let translation = self.get_render_context_translation(render_area, scale);
self.apply_mut(
SurfaceId::Fills as u32 | SurfaceId::Strokes as u32 | SurfaceId::InnerShadows as u32,
SurfaceId::Fills as u32
| SurfaceId::Strokes as u32
| SurfaceId::InnerShadows as u32
| SurfaceId::TextDropShadows as u32,
|s| {
s.canvas().restore();
s.canvas().save();
s.canvas().translate(translation);
let canvas = s.canvas();
canvas.reset_matrix();
canvas.scale((scale, scale));
canvas.translate(translation);
},
);
}
@@ -206,6 +219,7 @@ impl Surfaces {
SurfaceId::Current => &mut self.current,
SurfaceId::DropShadows => &mut self.drop_shadows,
SurfaceId::InnerShadows => &mut self.inner_shadows,
SurfaceId::TextDropShadows => &mut self.text_drop_shadows,
SurfaceId::Fills => &mut self.shape_fills,
SurfaceId::Strokes => &mut self.shape_strokes,
SurfaceId::Debug => &mut self.debug,
@@ -257,13 +271,15 @@ impl Surfaces {
pub fn reset(&mut self, color: skia::Color) {
self.canvas(SurfaceId::Fills).restore_to_count(1);
self.canvas(SurfaceId::InnerShadows).restore_to_count(1);
self.canvas(SurfaceId::TextDropShadows).restore_to_count(1);
self.canvas(SurfaceId::Strokes).restore_to_count(1);
self.canvas(SurfaceId::Current).restore_to_count(1);
self.apply_mut(
SurfaceId::Fills as u32
| SurfaceId::Strokes as u32
| SurfaceId::Current as u32
| SurfaceId::InnerShadows as u32,
| SurfaceId::InnerShadows as u32
| SurfaceId::TextDropShadows as u32,
|s| {
s.canvas().clear(color).reset_matrix();
},

View File

@@ -1,22 +1,55 @@
use super::{RenderState, Shape, SurfaceId};
use crate::shapes::VerticalAlign;
use skia_safe::{
canvas::SaveLayerRec, textlayout::LineMetrics, textlayout::Paragraph,
textlayout::ParagraphBuilder, textlayout::RectHeightStyle, textlayout::RectWidthStyle,
textlayout::StyleMetrics, textlayout::TextDecoration, textlayout::TextStyle, Canvas, Paint,
Path,
canvas::SaveLayerRec,
textlayout::{
LineMetrics, Paragraph, ParagraphBuilder, RectHeightStyle, RectWidthStyle, StyleMetrics,
TextDecoration, TextStyle,
},
Canvas, ImageFilter, Paint, Path,
};
pub fn render(
render_state: &mut RenderState,
render_state: Option<&mut RenderState>,
canvas: Option<&Canvas>,
shape: &Shape,
paragraphs: &mut [Vec<ParagraphBuilder>],
surface_id: Option<SurfaceId>,
shadow: Option<&Paint>,
blur: Option<&ImageFilter>,
) {
let canvas = render_state
.surfaces
.canvas(surface_id.unwrap_or(SurfaceId::Fills));
let render_canvas = if let Some(rs) = render_state {
rs.surfaces.canvas(surface_id.unwrap_or(SurfaceId::Fills))
} else if let Some(c) = canvas {
c
} else {
return;
};
if let Some(blur_filter) = blur {
let mut blur_paint = Paint::default();
blur_paint.set_image_filter(blur_filter.clone());
let blur_layer = SaveLayerRec::default().paint(&blur_paint);
render_canvas.save_layer(&blur_layer);
}
if let Some(shadow_paint) = shadow {
let layer_rec = SaveLayerRec::default().paint(shadow_paint);
render_canvas.save_layer(&layer_rec);
draw_text(render_canvas, shape, paragraphs);
render_canvas.restore();
} else {
draw_text(render_canvas, shape, paragraphs);
}
if blur.is_some() {
render_canvas.restore();
}
render_canvas.restore();
}
fn draw_text(canvas: &Canvas, shape: &Shape, paragraphs: &mut [Vec<ParagraphBuilder>]) {
// Width
let paragraph_width = if let crate::shapes::Type::Text(text_content) = &shape.shape_type {
text_content.width()
@@ -63,8 +96,6 @@ pub fn render(
global_offset_y = group_offset_y;
}
}
canvas.restore();
}
fn draw_text_decorations(
@@ -286,11 +317,11 @@ pub fn render_as_path(
// let text_content = text_content.new_bounds(shape.selrect());
// let paths = text_content.get_paths(antialias);
// shadows::render_text_drop_shadows(self, &shape, &paths, antialias);
// shadows::render_text_shadows(self, &shape, &paths, antialias);
// text::render(self, &paths, None, None);
// for stroke in shape.visible_strokes().rev() {
// shadows::render_text_path_stroke_drop_shadows(
// shadows::render_text_path_stroke_shadows(
// self, &shape, &paths, stroke, antialias,
// );
// strokes::render_text_paths(self, &shape, stroke, &paths, None, None, antialias);