mirror of
https://github.com/penpot/penpot.git
synced 2025-12-12 06:24:17 +01:00
🔧 Fix text layout extrect intersection and refactor calculate_extrect function
This commit is contained in:
@@ -737,50 +737,20 @@ impl Shape {
|
|||||||
rect
|
rect
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn calculate_extrect(
|
fn apply_stroke_bounds(&self, rect: math::Rect, stroke_width: f32) -> math::Rect {
|
||||||
&self,
|
let mut expanded_rect = rect;
|
||||||
shapes_pool: &ShapesPool,
|
expanded_rect.left -= stroke_width;
|
||||||
modifiers: &HashMap<Uuid, Matrix>,
|
expanded_rect.right += stroke_width;
|
||||||
) -> math::Rect {
|
expanded_rect.top -= stroke_width;
|
||||||
let shape = self.transformed(modifiers.get(&self.id));
|
expanded_rect.bottom += stroke_width;
|
||||||
let mut max_stroke: f32 = 0.;
|
|
||||||
let is_open = if let Type::Path(p) = &shape.shape_type {
|
|
||||||
p.is_open()
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
};
|
|
||||||
|
|
||||||
for stroke in shape.strokes.iter() {
|
let mut result = rect;
|
||||||
let width = match stroke.render_kind(is_open) {
|
result.join(expanded_rect);
|
||||||
StrokeKind::Inner => 0.,
|
result
|
||||||
StrokeKind::Center => stroke.width / 2.,
|
}
|
||||||
StrokeKind::Outer => stroke.width,
|
|
||||||
};
|
|
||||||
max_stroke = max_stroke.max(width);
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut rect = if let Some(path) = shape.get_skia_path() {
|
fn apply_shadow_bounds(&self, mut rect: math::Rect) -> math::Rect {
|
||||||
path.compute_tight_bounds()
|
for shadow in self.shadows_visible() {
|
||||||
.with_outset((max_stroke, max_stroke))
|
|
||||||
} else {
|
|
||||||
let mut bounds_rect = shape.bounds().to_rect();
|
|
||||||
let mut stroke_rect = bounds_rect;
|
|
||||||
stroke_rect.left -= max_stroke;
|
|
||||||
stroke_rect.right += max_stroke;
|
|
||||||
stroke_rect.top -= max_stroke;
|
|
||||||
stroke_rect.bottom += max_stroke;
|
|
||||||
|
|
||||||
bounds_rect.join(stroke_rect);
|
|
||||||
bounds_rect
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Type::Text(text_content) = &shape.shape_type {
|
|
||||||
let (width, height) = text_content.visual_bounds();
|
|
||||||
rect.right = rect.left + width;
|
|
||||||
rect.bottom = rect.top + height;
|
|
||||||
}
|
|
||||||
|
|
||||||
for shadow in shape.shadows.iter() {
|
|
||||||
if !shadow.hidden() {
|
if !shadow.hidden() {
|
||||||
let (x, y) = shadow.offset;
|
let (x, y) = shadow.offset;
|
||||||
let mut shadow_rect = rect;
|
let mut shadow_rect = rect;
|
||||||
@@ -797,8 +767,12 @@ impl Shape {
|
|||||||
rect.join(shadow_rect);
|
rect.join(shadow_rect);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
rect
|
||||||
|
}
|
||||||
|
|
||||||
if let Some(blur) = shape.blur {
|
fn apply_blur_bounds(&self, mut rect: math::Rect) -> math::Rect {
|
||||||
|
let blur = self.blur.as_ref();
|
||||||
|
if let Some(blur) = blur {
|
||||||
if !blur.hidden {
|
if !blur.hidden {
|
||||||
rect.left -= blur.value;
|
rect.left -= blur.value;
|
||||||
rect.top -= blur.value;
|
rect.top -= blur.value;
|
||||||
@@ -806,17 +780,23 @@ impl Shape {
|
|||||||
rect.bottom += blur.value;
|
rect.bottom += blur.value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
rect
|
||||||
|
}
|
||||||
|
|
||||||
// For groups and frames without clipping, extend the bounding rectangle to include all nested shapes
|
fn apply_children_bounds(
|
||||||
// This ensures that these containers properly encompass their content
|
&self,
|
||||||
let include_children = match &shape.shape_type {
|
mut rect: math::Rect,
|
||||||
|
shapes_pool: &ShapesPool,
|
||||||
|
modifiers: &HashMap<Uuid, Matrix>,
|
||||||
|
) -> math::Rect {
|
||||||
|
let include_children = match self.shape_type {
|
||||||
Type::Group(_) => true,
|
Type::Group(_) => true,
|
||||||
Type::Frame(_) => !shape.clip_content,
|
Type::Frame(_) => !self.clip_content,
|
||||||
_ => false,
|
_ => false,
|
||||||
};
|
};
|
||||||
|
|
||||||
if include_children {
|
if include_children {
|
||||||
for child_id in shape.children_ids(false) {
|
for child_id in self.children_ids(false) {
|
||||||
if let Some(child_shape) = shapes_pool.get(&child_id) {
|
if let Some(child_shape) = shapes_pool.get(&child_id) {
|
||||||
// Create a copy of the child shape to apply any transformations
|
// Create a copy of the child shape to apply any transformations
|
||||||
let mut transformed_element: Cow<Shape> = Cow::Borrowed(child_shape);
|
let mut transformed_element: Cow<Shape> = Cow::Borrowed(child_shape);
|
||||||
@@ -834,6 +814,38 @@ impl Shape {
|
|||||||
rect
|
rect
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn calculate_extrect(
|
||||||
|
&self,
|
||||||
|
shapes_pool: &ShapesPool,
|
||||||
|
modifiers: &HashMap<Uuid, Matrix>,
|
||||||
|
) -> math::Rect {
|
||||||
|
let shape = self.transformed(modifiers.get(&self.id));
|
||||||
|
let max_stroke = Stroke::max_bounds_width(shape.strokes.iter(), shape.is_open());
|
||||||
|
|
||||||
|
let mut rect = match &shape.shape_type {
|
||||||
|
Type::Path(_) | Type::Bool(_) => {
|
||||||
|
if let Some(path) = shape.get_skia_path() {
|
||||||
|
return path
|
||||||
|
.compute_tight_bounds()
|
||||||
|
.with_outset((max_stroke, max_stroke));
|
||||||
|
}
|
||||||
|
shape.bounds().to_rect()
|
||||||
|
}
|
||||||
|
Type::Text(text_content) => {
|
||||||
|
let text_bounds = text_content.get_bounds(&shape);
|
||||||
|
text_bounds.to_rect()
|
||||||
|
}
|
||||||
|
_ => shape.bounds().to_rect(),
|
||||||
|
};
|
||||||
|
|
||||||
|
rect = self.apply_stroke_bounds(rect, max_stroke);
|
||||||
|
rect = self.apply_shadow_bounds(rect);
|
||||||
|
rect = self.apply_blur_bounds(rect);
|
||||||
|
rect = self.apply_children_bounds(rect, shapes_pool, modifiers);
|
||||||
|
|
||||||
|
rect
|
||||||
|
}
|
||||||
|
|
||||||
pub fn left_top(&self) -> Point {
|
pub fn left_top(&self) -> Point {
|
||||||
Point::new(self.selrect.left, self.selrect.top)
|
Point::new(self.selrect.left, self.selrect.top)
|
||||||
}
|
}
|
||||||
@@ -996,6 +1008,10 @@ impl Shape {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn is_open(&self) -> bool {
|
||||||
|
matches!(&self.shape_type, Type::Path(p) if p.is_open())
|
||||||
|
}
|
||||||
|
|
||||||
pub fn add_shadow(&mut self, shadow: Shadow) {
|
pub fn add_shadow(&mut self, shadow: Shadow) {
|
||||||
self.invalidate_extrect();
|
self.invalidate_extrect();
|
||||||
self.shadows.push(shadow);
|
self.shadows.push(shadow);
|
||||||
@@ -1036,6 +1052,10 @@ impl Shape {
|
|||||||
.filter(|shadow| shadow.style() == ShadowStyle::Inner && !shadow.hidden())
|
.filter(|shadow| shadow.style() == ShadowStyle::Inner && !shadow.hidden())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn shadows_visible(&self) -> impl DoubleEndedIterator<Item = &Shadow> {
|
||||||
|
self.shadows.iter().rev().filter(|shadow| !shadow.hidden())
|
||||||
|
}
|
||||||
|
|
||||||
pub fn to_path_transform(&self) -> Option<Matrix> {
|
pub fn to_path_transform(&self) -> Option<Matrix> {
|
||||||
match self.shape_type {
|
match self.shape_type {
|
||||||
Type::Path(_) | Type::Bool(_) => {
|
Type::Path(_) | Type::Bool(_) => {
|
||||||
|
|||||||
@@ -50,6 +50,20 @@ impl Stroke {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn bounds_width(&self, is_open: bool) -> f32 {
|
||||||
|
match self.render_kind(is_open) {
|
||||||
|
StrokeKind::Inner => 0.,
|
||||||
|
StrokeKind::Center => self.width / 2.,
|
||||||
|
StrokeKind::Outer => self.width,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn max_bounds_width<'a>(strokes: impl Iterator<Item = &'a Stroke>, is_open: bool) -> f32 {
|
||||||
|
strokes
|
||||||
|
.map(|stroke| stroke.bounds_width(is_open))
|
||||||
|
.fold(0.0, f32::max)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn new_center_stroke(
|
pub fn new_center_stroke(
|
||||||
width: f32,
|
width: f32,
|
||||||
style: StrokeStyle,
|
style: StrokeStyle,
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
math::{Matrix, Rect},
|
math::{Bounds, Matrix, Rect},
|
||||||
render::{default_font, DEFAULT_EMOJI_FONT},
|
render::{default_font, DEFAULT_EMOJI_FONT},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -16,7 +16,7 @@ use std::collections::HashSet;
|
|||||||
|
|
||||||
use super::FontFamily;
|
use super::FontFamily;
|
||||||
use crate::math::Point;
|
use crate::math::Point;
|
||||||
use crate::shapes::{self, merge_fills};
|
use crate::shapes::{self, merge_fills, Shape};
|
||||||
use crate::utils::{get_fallback_fonts, get_font_collection};
|
use crate::utils::{get_fallback_fonts, get_font_collection};
|
||||||
use crate::Uuid;
|
use crate::Uuid;
|
||||||
|
|
||||||
@@ -194,11 +194,7 @@ impl TextContent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn width(&self) -> f32 {
|
pub fn width(&self) -> f32 {
|
||||||
if self.grow_type() == GrowType::AutoWidth {
|
self.size.width
|
||||||
self.size.width
|
|
||||||
} else {
|
|
||||||
self.bounds.width()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn grow_type(&self) -> GrowType {
|
pub fn grow_type(&self) -> GrowType {
|
||||||
@@ -209,8 +205,34 @@ impl TextContent {
|
|||||||
self.grow_type = grow_type;
|
self.grow_type = grow_type;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn visual_bounds(&self) -> (f32, f32) {
|
pub fn get_bounds(&self, shape: &Shape) -> Bounds {
|
||||||
(self.size.width, self.size.height)
|
let (x, y, transform, center) = (
|
||||||
|
shape.selrect.x(),
|
||||||
|
shape.selrect.y(),
|
||||||
|
&shape.transform,
|
||||||
|
&shape.center(),
|
||||||
|
);
|
||||||
|
let (width, height) = (self.size.width, self.size.height);
|
||||||
|
let text_rect = Rect::from_xywh(x, y, width, height);
|
||||||
|
|
||||||
|
let mut bounds = Bounds::new(
|
||||||
|
Point::new(text_rect.x(), text_rect.y()),
|
||||||
|
Point::new(text_rect.x() + text_rect.width(), text_rect.y()),
|
||||||
|
Point::new(
|
||||||
|
text_rect.x() + text_rect.width(),
|
||||||
|
text_rect.y() + text_rect.height(),
|
||||||
|
),
|
||||||
|
Point::new(text_rect.x(), text_rect.y() + text_rect.height()),
|
||||||
|
);
|
||||||
|
|
||||||
|
if !transform.is_identity() {
|
||||||
|
let mut matrix = *transform;
|
||||||
|
matrix.post_translate(*center);
|
||||||
|
matrix.pre_translate(-*center);
|
||||||
|
bounds.transform_mut(&matrix);
|
||||||
|
}
|
||||||
|
|
||||||
|
bounds
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn transform(&mut self, transform: &Matrix) {
|
pub fn transform(&mut self, transform: &Matrix) {
|
||||||
@@ -315,14 +337,13 @@ impl TextContent {
|
|||||||
let width = self.width();
|
let width = self.width();
|
||||||
let mut paragraph_builders = self.paragraph_builder_group_from_text(None);
|
let mut paragraph_builders = self.paragraph_builder_group_from_text(None);
|
||||||
let paragraphs =
|
let paragraphs =
|
||||||
self.build_paragraphs_from_paragraph_builders(&mut paragraph_builders, f32::INFINITY);
|
self.build_paragraphs_from_paragraph_builders(&mut paragraph_builders, width);
|
||||||
let height = paragraphs
|
let height = paragraphs
|
||||||
.iter()
|
.iter()
|
||||||
.flatten()
|
.flatten()
|
||||||
.fold(0.0, |auto_height, paragraph| {
|
.fold(0.0, |auto_height, paragraph| {
|
||||||
auto_height + paragraph.height()
|
auto_height + paragraph.height()
|
||||||
});
|
});
|
||||||
|
|
||||||
let size = TextContentSize::new_with_size(width.ceil(), height.ceil());
|
let size = TextContentSize::new_with_size(width.ceil(), height.ceil());
|
||||||
TextContentLayoutResult(paragraph_builders, paragraphs, size)
|
TextContentLayoutResult(paragraph_builders, paragraphs, size)
|
||||||
}
|
}
|
||||||
@@ -349,6 +370,7 @@ impl TextContent {
|
|||||||
|
|
||||||
pub fn update_layout(&mut self, selrect: Rect) -> TextContentSize {
|
pub fn update_layout(&mut self, selrect: Rect) -> TextContentSize {
|
||||||
self.size.set_size(selrect.width(), selrect.height());
|
self.size.set_size(selrect.width(), selrect.height());
|
||||||
|
|
||||||
match self.grow_type() {
|
match self.grow_type() {
|
||||||
GrowType::AutoHeight => {
|
GrowType::AutoHeight => {
|
||||||
let result = self.text_layout_auto_height();
|
let result = self.text_layout_auto_height();
|
||||||
|
|||||||
@@ -348,6 +348,7 @@ pub extern "C" fn update_shape_text_layout_for(a: u32, b: u32, c: u32, d: u32) {
|
|||||||
with_state_mut!(state, {
|
with_state_mut!(state, {
|
||||||
let shape_id = uuid_from_u32_quartet(a, b, c, d);
|
let shape_id = uuid_from_u32_quartet(a, b, c, d);
|
||||||
if let Some(shape) = state.shapes.get_mut(&shape_id) {
|
if let Some(shape) = state.shapes.get_mut(&shape_id) {
|
||||||
|
shape.invalidate_extrect();
|
||||||
if let Type::Text(text_content) = &mut shape.shape_type {
|
if let Type::Text(text_content) = &mut shape.shape_type {
|
||||||
text_content.update_layout(shape.selrect);
|
text_content.update_layout(shape.selrect);
|
||||||
}
|
}
|
||||||
@@ -359,6 +360,7 @@ pub extern "C" fn update_shape_text_layout_for(a: u32, b: u32, c: u32, d: u32) {
|
|||||||
pub extern "C" fn update_shape_text_layout_for_all() {
|
pub extern "C" fn update_shape_text_layout_for_all() {
|
||||||
with_state_mut!(state, {
|
with_state_mut!(state, {
|
||||||
for shape in state.shapes.iter_mut() {
|
for shape in state.shapes.iter_mut() {
|
||||||
|
shape.invalidate_extrect();
|
||||||
if let Type::Text(text_content) = &mut shape.shape_type {
|
if let Type::Text(text_content) = &mut shape.shape_type {
|
||||||
text_content.update_layout(shape.selrect);
|
text_content.update_layout(shape.selrect);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user