mirror of
https://github.com/penpot/penpot.git
synced 2026-09-08 21:19:49 +00:00
Merge pull request #9361 from penpot/elenatorro-14100-wasm-fast-paths
⚡ Improve drag rendering on text and paths
This commit is contained in:
commit
b34054940f
@ -26,7 +26,7 @@ use crate::error::{Error, Result};
|
|||||||
use crate::performance;
|
use crate::performance;
|
||||||
use crate::shapes::{
|
use crate::shapes::{
|
||||||
all_with_ancestors, radius_to_sigma, Blur, BlurType, Corners, Fill, Shadow, Shape, SolidColor,
|
all_with_ancestors, radius_to_sigma, Blur, BlurType, Corners, Fill, Shadow, Shape, SolidColor,
|
||||||
Stroke, StrokeKind, Type,
|
Stroke, StrokeKind, TextContent, Type,
|
||||||
};
|
};
|
||||||
use crate::state::{ShapesPoolMutRef, ShapesPoolRef};
|
use crate::state::{ShapesPoolMutRef, ShapesPoolRef};
|
||||||
use crate::tiles::{self, PendingTiles, TileRect};
|
use crate::tiles::{self, PendingTiles, TileRect};
|
||||||
@ -1229,12 +1229,23 @@ impl RenderState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Type::Text(text_content) => {
|
Type::Text(stored_text_content) => {
|
||||||
self.surfaces.apply_mut(surface_ids, |s| {
|
self.surfaces.apply_mut(surface_ids, |s| {
|
||||||
s.canvas().concat(&matrix);
|
s.canvas().concat(&matrix);
|
||||||
});
|
});
|
||||||
|
|
||||||
let text_content = text_content.new_bounds(shape.selrect());
|
// Skip the paragraph-cloning `new_bounds` when shape size is unchanged.
|
||||||
|
let selrect = shape.selrect();
|
||||||
|
let stored_bounds = stored_text_content.bounds();
|
||||||
|
let bounds_match = (stored_bounds.width() - selrect.width()).abs() < 0.01
|
||||||
|
&& (stored_bounds.height() - selrect.height()).abs() < 0.01;
|
||||||
|
let rebound_text_content = if bounds_match {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(stored_text_content.new_bounds(selrect))
|
||||||
|
};
|
||||||
|
let text_content: &TextContent =
|
||||||
|
rebound_text_content.as_ref().unwrap_or(stored_text_content);
|
||||||
let count_inner_strokes = shape.count_visible_inner_strokes();
|
let count_inner_strokes = shape.count_visible_inner_strokes();
|
||||||
// Erode the main text fill by 1px when there are inner strokes, to avoid a visible seam at the glyph edge.
|
// Erode the main text fill by 1px when there are inner strokes, to avoid a visible seam at the glyph edge.
|
||||||
let text_fill_inset = (count_inner_strokes > 0).then(|| 1.0 / self.get_scale());
|
let text_fill_inset = (count_inner_strokes > 0).then(|| 1.0 / self.get_scale());
|
||||||
@ -1248,7 +1259,7 @@ impl RenderState {
|
|||||||
.rev()
|
.rev()
|
||||||
.map(|stroke| {
|
.map(|stroke| {
|
||||||
text::stroke_paragraph_builder_group_from_text(
|
text::stroke_paragraph_builder_group_from_text(
|
||||||
&text_content,
|
text_content,
|
||||||
stroke,
|
stroke,
|
||||||
&shape.selrect(),
|
&shape.selrect(),
|
||||||
None,
|
None,
|
||||||
@ -1324,7 +1335,7 @@ impl RenderState {
|
|||||||
.rev()
|
.rev()
|
||||||
.map(|stroke| {
|
.map(|stroke| {
|
||||||
text::stroke_paragraph_builder_group_from_text(
|
text::stroke_paragraph_builder_group_from_text(
|
||||||
&text_content,
|
text_content,
|
||||||
stroke,
|
stroke,
|
||||||
&shape.selrect(),
|
&shape.selrect(),
|
||||||
Some(true),
|
Some(true),
|
||||||
@ -1357,7 +1368,7 @@ impl RenderState {
|
|||||||
&parent_shadows,
|
&parent_shadows,
|
||||||
&blur_filter,
|
&blur_filter,
|
||||||
&stroke_kinds,
|
&stroke_kinds,
|
||||||
&text_content,
|
text_content,
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -1401,7 +1412,7 @@ impl RenderState {
|
|||||||
&drop_shadows,
|
&drop_shadows,
|
||||||
&blur_filter,
|
&blur_filter,
|
||||||
&stroke_kinds,
|
&stroke_kinds,
|
||||||
&text_content,
|
text_content,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
// 4. Stroke fills
|
// 4. Stroke fills
|
||||||
@ -1453,7 +1464,7 @@ impl RenderState {
|
|||||||
&inner_shadows,
|
&inner_shadows,
|
||||||
&blur_filter,
|
&blur_filter,
|
||||||
&stroke_kinds,
|
&stroke_kinds,
|
||||||
&text_content,
|
text_content,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
// 6. Fill Inner shadows
|
// 6. Fill Inner shadows
|
||||||
|
|||||||
@ -1015,6 +1015,7 @@ impl Shape {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn calculate_extrect(&self, shapes_pool: ShapesPoolRef, scale: f32) -> math::Rect {
|
pub fn calculate_extrect(&self, shapes_pool: ShapesPoolRef, scale: f32) -> math::Rect {
|
||||||
|
// `scale` is forwarded to children but intentionally NOT part of the cache key.
|
||||||
if let Some(cached_extrect) = *self.extrect_cache.borrow() {
|
if let Some(cached_extrect) = *self.extrect_cache.borrow() {
|
||||||
return cached_extrect;
|
return cached_extrect;
|
||||||
}
|
}
|
||||||
@ -1346,8 +1347,10 @@ impl Shape {
|
|||||||
pub fn get_skia_path(&self) -> Option<skia::Path> {
|
pub fn get_skia_path(&self) -> Option<skia::Path> {
|
||||||
if let Some(path) = self.shape_type.path() {
|
if let Some(path) = self.shape_type.path() {
|
||||||
let mut skia_path = path.to_skia_path(self.svg_attrs.as_ref());
|
let mut skia_path = path.to_skia_path(self.svg_attrs.as_ref());
|
||||||
if let Some(path_transform) = self.to_path_transform() {
|
if !math::identitish(&self.transform) {
|
||||||
skia_path = skia_path.make_transform(&path_transform);
|
if let Some(path_transform) = self.to_path_transform() {
|
||||||
|
skia_path = skia_path.make_transform(&path_transform);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Some(skia_path)
|
Some(skia_path)
|
||||||
} else {
|
} else {
|
||||||
@ -1356,6 +1359,19 @@ impl Shape {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn transform_selrect(&mut self, transform: &Matrix) {
|
fn transform_selrect(&mut self, transform: &Matrix) {
|
||||||
|
if math::is_move_only_matrix(transform) {
|
||||||
|
let tx = transform.translate_x();
|
||||||
|
let ty = transform.translate_y();
|
||||||
|
// `self.transform` (rotation/scale around center) is unchanged by translation.
|
||||||
|
self.selrect = math::Rect::from_xywh(
|
||||||
|
self.selrect.left + tx,
|
||||||
|
self.selrect.top + ty,
|
||||||
|
self.selrect.width(),
|
||||||
|
self.selrect.height(),
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
let mut center = self.selrect.center();
|
let mut center = self.selrect.center();
|
||||||
center = transform.map_point(center);
|
center = transform.map_point(center);
|
||||||
|
|
||||||
@ -1377,9 +1393,23 @@ impl Shape {
|
|||||||
pub fn apply_transform(&mut self, transform: &Matrix) {
|
pub fn apply_transform(&mut self, transform: &Matrix) {
|
||||||
self.transform_selrect(transform);
|
self.transform_selrect(transform);
|
||||||
|
|
||||||
// TODO: See if we can change this invalidation to a transformation
|
// Outsets (strokes, shadows, blur, children) are translation-invariant,
|
||||||
self.invalidate_extrect();
|
// so the cached extrect can be shifted instead of invalidated.
|
||||||
self.invalidate_bounds();
|
if math::is_move_only_matrix(transform) {
|
||||||
|
let tx = transform.translate_x();
|
||||||
|
let ty = transform.translate_y();
|
||||||
|
if let Some(rect) = self.extrect_cache.borrow_mut().as_mut() {
|
||||||
|
*rect = math::Rect::from_xywh(
|
||||||
|
rect.left + tx,
|
||||||
|
rect.top + ty,
|
||||||
|
rect.width(),
|
||||||
|
rect.height(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self.invalidate_extrect();
|
||||||
|
self.invalidate_bounds();
|
||||||
|
}
|
||||||
|
|
||||||
if let shape_type @ (Type::Path(_) | Type::Bool(_)) = &mut self.shape_type {
|
if let shape_type @ (Type::Path(_) | Type::Bool(_)) = &mut self.shape_type {
|
||||||
if let Some(path) = shape_type.path_mut() {
|
if let Some(path) = shape_type.path_mut() {
|
||||||
|
|||||||
@ -259,15 +259,21 @@ pub fn get_fill_shader(fill: &Fill, bounding_box: &Rect) -> Option<skia::Shader>
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn merge_fills(fills: &[Fill], bounding_box: Rect) -> skia::Paint {
|
pub fn merge_fills(fills: &[Fill], bounding_box: Rect) -> skia::Paint {
|
||||||
let mut combined_shader: Option<skia::Shader> = None;
|
|
||||||
let mut fills_paint = skia::Paint::default();
|
let mut fills_paint = skia::Paint::default();
|
||||||
|
|
||||||
if fills.is_empty() {
|
if fills.is_empty() {
|
||||||
combined_shader = Some(skia::shaders::color(skia::Color::TRANSPARENT));
|
fills_paint.set_color(skia::Color::TRANSPARENT);
|
||||||
fills_paint.set_shader(combined_shader);
|
|
||||||
return fills_paint;
|
return fills_paint;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if fills.len() == 1 {
|
||||||
|
if let Fill::Solid(SolidColor(color)) = &fills[0] {
|
||||||
|
fills_paint.set_color(*color);
|
||||||
|
return fills_paint;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut combined_shader: Option<skia::Shader> = None;
|
||||||
for fill in fills {
|
for fill in fills {
|
||||||
let shader = get_fill_shader(fill, &bounding_box);
|
let shader = get_fill_shader(fill, &bounding_box);
|
||||||
|
|
||||||
@ -287,7 +293,7 @@ pub fn merge_fills(fills: &[Fill], bounding_box: Rect) -> skia::Paint {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fills_paint.set_shader(combined_shader.clone());
|
fills_paint.set_shader(combined_shader);
|
||||||
fills_paint
|
fills_paint
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -236,6 +236,28 @@ impl Path {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn transform(&mut self, mtx: &Matrix) {
|
pub fn transform(&mut self, mtx: &Matrix) {
|
||||||
|
if math::is_move_only_matrix(mtx) {
|
||||||
|
let tx = mtx.translate_x();
|
||||||
|
let ty = mtx.translate_y();
|
||||||
|
self.segments.iter_mut().for_each(|s| match s {
|
||||||
|
Segment::MoveTo(p) | Segment::LineTo(p) => {
|
||||||
|
p.0 += tx;
|
||||||
|
p.1 += ty;
|
||||||
|
}
|
||||||
|
Segment::CurveTo((c1, c2, p)) => {
|
||||||
|
c1.0 += tx;
|
||||||
|
c1.1 += ty;
|
||||||
|
c2.0 += tx;
|
||||||
|
c2.1 += ty;
|
||||||
|
p.0 += tx;
|
||||||
|
p.1 += ty;
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
});
|
||||||
|
self.skia_path = self.skia_path.with_offset((tx, ty));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
self.segments.iter_mut().for_each(|s| match s {
|
self.segments.iter_mut().for_each(|s| match s {
|
||||||
Segment::MoveTo(p) => {
|
Segment::MoveTo(p) => {
|
||||||
let np = mtx.map_point(skia::Point::new(p.0, p.1));
|
let np = mtx.map_point(skia::Point::new(p.0, p.1));
|
||||||
|
|||||||
@ -1424,11 +1424,14 @@ pub fn calculate_text_layout_data(
|
|||||||
let mut previous_line_height = text_content.normalized_line_height();
|
let mut previous_line_height = text_content.normalized_line_height();
|
||||||
let text_paragraphs = text_content.paragraphs();
|
let text_paragraphs = text_content.paragraphs();
|
||||||
|
|
||||||
// 1. Calculate paragraph heights
|
// 1. Build + layout each paragraph once, recording heights as we go.
|
||||||
let mut paragraph_heights: Vec<f32> = Vec::new();
|
let mut paragraph_heights: Vec<f32> = Vec::new();
|
||||||
|
let mut built_groups: Vec<Vec<skia::textlayout::Paragraph>> =
|
||||||
|
Vec::with_capacity(paragraph_builder_groups.len());
|
||||||
for paragraph_builder_group in paragraph_builder_groups.iter_mut() {
|
for paragraph_builder_group in paragraph_builder_groups.iter_mut() {
|
||||||
let group_len = paragraph_builder_group.len();
|
let group_len = paragraph_builder_group.len();
|
||||||
let mut paragraph_offset_y = previous_line_height;
|
let mut paragraph_offset_y = previous_line_height;
|
||||||
|
let mut group_paragraphs: Vec<skia::textlayout::Paragraph> = Vec::with_capacity(group_len);
|
||||||
for (builder_index, paragraph_builder) in paragraph_builder_group.iter_mut().enumerate() {
|
for (builder_index, paragraph_builder) in paragraph_builder_group.iter_mut().enumerate() {
|
||||||
let mut skia_paragraph = paragraph_builder.build();
|
let mut skia_paragraph = paragraph_builder.build();
|
||||||
skia_paragraph.layout(text_width);
|
skia_paragraph.layout(text_width);
|
||||||
@ -1442,11 +1445,13 @@ pub fn calculate_text_layout_data(
|
|||||||
if builder_index == 0 {
|
if builder_index == 0 {
|
||||||
paragraph_heights.push(skia_paragraph.height());
|
paragraph_heights.push(skia_paragraph.height());
|
||||||
}
|
}
|
||||||
|
group_paragraphs.push(skia_paragraph);
|
||||||
}
|
}
|
||||||
previous_line_height = paragraph_offset_y;
|
previous_line_height = paragraph_offset_y;
|
||||||
|
built_groups.push(group_paragraphs);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. Calculate vertical offset and build paragraphs with positions
|
// 2. Position each built paragraph using the heights from step 1.
|
||||||
let total_text_height: f32 = paragraph_heights.iter().sum();
|
let total_text_height: f32 = paragraph_heights.iter().sum();
|
||||||
let vertical_offset = match shape.vertical_align() {
|
let vertical_offset = match shape.vertical_align() {
|
||||||
VerticalAlign::Center => (selrect_height - total_text_height) / 2.0,
|
VerticalAlign::Center => (selrect_height - total_text_height) / 2.0,
|
||||||
@ -1455,12 +1460,9 @@ pub fn calculate_text_layout_data(
|
|||||||
};
|
};
|
||||||
let mut paragraph_layouts: Vec<ParagraphLayout> = Vec::new();
|
let mut paragraph_layouts: Vec<ParagraphLayout> = Vec::new();
|
||||||
let mut y_accum = base_y + vertical_offset;
|
let mut y_accum = base_y + vertical_offset;
|
||||||
for (i, paragraph_builder_group) in paragraph_builder_groups.iter_mut().enumerate() {
|
for (i, group_paragraphs) in built_groups.into_iter().enumerate() {
|
||||||
// For each paragraph in the group (e.g., fill, stroke, etc.)
|
// For each paragraph in the group (e.g., fill, stroke, etc.)
|
||||||
for paragraph_builder in paragraph_builder_group.iter_mut() {
|
for skia_paragraph in group_paragraphs.into_iter() {
|
||||||
let mut skia_paragraph = paragraph_builder.build();
|
|
||||||
skia_paragraph.layout(text_width);
|
|
||||||
|
|
||||||
let spans = if let Some(text_para) = text_paragraphs.get(i) {
|
let spans = if let Some(text_para) = text_paragraphs.get(i) {
|
||||||
text_para.children().to_vec()
|
text_para.children().to_vec()
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user