mirror of
https://github.com/penpot/penpot.git
synced 2025-12-11 22:14:05 +01:00
🐛 Fix paragraph layout width on autowidth
This commit is contained in:
@@ -295,7 +295,8 @@
|
|||||||
"uppercase" 1
|
"uppercase" 1
|
||||||
"lowercase" 2
|
"lowercase" 2
|
||||||
"capitalize" 3
|
"capitalize" 3
|
||||||
nil))
|
nil 0
|
||||||
|
0))
|
||||||
|
|
||||||
(defn translate-text-decoration
|
(defn translate-text-decoration
|
||||||
[text-decoration]
|
[text-decoration]
|
||||||
@@ -304,13 +305,15 @@
|
|||||||
"underline" 1
|
"underline" 1
|
||||||
"line-through" 2
|
"line-through" 2
|
||||||
"overline" 3
|
"overline" 3
|
||||||
nil))
|
nil 0
|
||||||
|
0))
|
||||||
|
|
||||||
(defn translate-text-direction
|
(defn translate-text-direction
|
||||||
[text-direction]
|
[text-direction]
|
||||||
(case text-direction
|
(case text-direction
|
||||||
"ltr" 0
|
"ltr" 0
|
||||||
"rtl" 1
|
"rtl" 1
|
||||||
|
nil 0
|
||||||
0))
|
0))
|
||||||
|
|
||||||
(defn translate-font-style
|
(defn translate-font-style
|
||||||
|
|||||||
@@ -511,7 +511,7 @@ impl RenderState {
|
|||||||
});
|
});
|
||||||
|
|
||||||
let text_content = text_content.new_bounds(shape.selrect());
|
let text_content = text_content.new_bounds(shape.selrect());
|
||||||
let mut paragraphs = text_content.get_skia_paragraphs(
|
let mut paragraphs = text_content.to_paragraphs(
|
||||||
shape.image_filter(1.).as_ref(),
|
shape.image_filter(1.).as_ref(),
|
||||||
shape.mask_filter(1.).as_ref(),
|
shape.mask_filter(1.).as_ref(),
|
||||||
);
|
);
|
||||||
@@ -524,7 +524,7 @@ impl RenderState {
|
|||||||
text::render(self, &shape, &mut paragraphs, None, None);
|
text::render(self, &shape, &mut paragraphs, None, None);
|
||||||
|
|
||||||
for stroke in shape.visible_strokes().rev() {
|
for stroke in shape.visible_strokes().rev() {
|
||||||
let mut stroke_paragraphs = text_content.get_skia_stroke_paragraphs(
|
let mut stroke_paragraphs = text_content.to_stroke_paragraphs(
|
||||||
stroke,
|
stroke,
|
||||||
&shape.selrect(),
|
&shape.selrect(),
|
||||||
shape.image_filter(1.).as_ref(),
|
shape.image_filter(1.).as_ref(),
|
||||||
|
|||||||
@@ -14,10 +14,17 @@ pub fn render(
|
|||||||
let canvas = render_state
|
let canvas = render_state
|
||||||
.surfaces
|
.surfaces
|
||||||
.canvas(surface_id.unwrap_or(SurfaceId::Fills));
|
.canvas(surface_id.unwrap_or(SurfaceId::Fills));
|
||||||
let container_height = shape.selrect().height();
|
|
||||||
|
|
||||||
// Calculate total height for vertical alignment
|
// Width
|
||||||
let total_content_height = calculate_all_paragraphs_height(paragraphs, shape.bounds().width());
|
let paragraph_width = if let crate::shapes::Type::Text(text_content) = &shape.shape_type {
|
||||||
|
text_content.get_width()
|
||||||
|
} else {
|
||||||
|
shape.width()
|
||||||
|
};
|
||||||
|
|
||||||
|
// Height
|
||||||
|
let container_height = shape.selrect().height();
|
||||||
|
let total_content_height = calculate_all_paragraphs_height(paragraphs, paragraph_width);
|
||||||
let mut global_offset_y = match shape.vertical_align() {
|
let mut global_offset_y = match shape.vertical_align() {
|
||||||
VerticalAlign::Center => (container_height - total_content_height) / 2.0,
|
VerticalAlign::Center => (container_height - total_content_height) / 2.0,
|
||||||
VerticalAlign::Bottom => container_height - total_content_height,
|
VerticalAlign::Bottom => container_height - total_content_height,
|
||||||
@@ -48,8 +55,7 @@ pub fn render(
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
skia_paragraph.layout(shape.bounds().width());
|
skia_paragraph.layout(paragraph_width);
|
||||||
|
|
||||||
let paragraph_height = skia_paragraph.height();
|
let paragraph_height = skia_paragraph.height();
|
||||||
let xy = (shape.selrect().x(), shape.selrect().y() + group_offset_y);
|
let xy = (shape.selrect().x(), shape.selrect().y() + group_offset_y);
|
||||||
skia_paragraph.paint(canvas, xy);
|
skia_paragraph.paint(canvas, xy);
|
||||||
@@ -126,7 +132,7 @@ pub fn render(
|
|||||||
// For stroke groups (multiple elements), increment global_offset_y once per group
|
// For stroke groups (multiple elements), increment global_offset_y once per group
|
||||||
if group_len > 1 {
|
if group_len > 1 {
|
||||||
let mut first_paragraph = group[0].build();
|
let mut first_paragraph = group[0].build();
|
||||||
first_paragraph.layout(shape.bounds().width());
|
first_paragraph.layout(paragraph_width);
|
||||||
global_offset_y += first_paragraph.height();
|
global_offset_y += first_paragraph.height();
|
||||||
} else {
|
} else {
|
||||||
// For regular paragraphs, global_offset_y was already incremented inside the loop
|
// For regular paragraphs, global_offset_y was already incremented inside the loop
|
||||||
|
|||||||
@@ -10,8 +10,8 @@ use crate::math::bools;
|
|||||||
use crate::math::{self as math, identitish, Bounds, Matrix, Point};
|
use crate::math::{self as math, identitish, Bounds, Matrix, Point};
|
||||||
|
|
||||||
use crate::shapes::{
|
use crate::shapes::{
|
||||||
auto_height, set_paragraphs_width, ConstraintH, ConstraintV, Frame, Group, GrowType, Layout,
|
auto_height, ConstraintH, ConstraintV, Frame, Group, GrowType, Layout, Modifier, Shape,
|
||||||
Modifier, Shape, StructureEntry, TransformEntry, Type,
|
StructureEntry, TransformEntry, Type,
|
||||||
};
|
};
|
||||||
use crate::state::ShapesPool;
|
use crate::state::ShapesPool;
|
||||||
use crate::state::State;
|
use crate::state::State;
|
||||||
@@ -197,10 +197,11 @@ fn propagate_transform(
|
|||||||
let mut transform = entry.transform;
|
let mut transform = entry.transform;
|
||||||
|
|
||||||
if let Type::Text(content) = &shape.shape_type {
|
if let Type::Text(content) = &shape.shape_type {
|
||||||
if content.grow_type() == GrowType::AutoHeight {
|
match content.grow_type() {
|
||||||
let mut paragraphs = content.get_skia_paragraphs(None, None);
|
GrowType::AutoHeight => {
|
||||||
set_paragraphs_width(shape_bounds_after.width(), &mut paragraphs);
|
let paragraph_width = shape_bounds_after.width();
|
||||||
let height = auto_height(&mut paragraphs, shape_bounds_after.width());
|
let mut paragraphs = content.to_paragraphs(None, None);
|
||||||
|
let height = auto_height(&mut paragraphs, paragraph_width);
|
||||||
let resize_transform = math::resize_matrix(
|
let resize_transform = math::resize_matrix(
|
||||||
&shape_bounds_after,
|
&shape_bounds_after,
|
||||||
&shape_bounds_after,
|
&shape_bounds_after,
|
||||||
@@ -210,6 +211,21 @@ fn propagate_transform(
|
|||||||
shape_bounds_after = shape_bounds_after.transform(&resize_transform);
|
shape_bounds_after = shape_bounds_after.transform(&resize_transform);
|
||||||
transform.post_concat(&resize_transform);
|
transform.post_concat(&resize_transform);
|
||||||
}
|
}
|
||||||
|
GrowType::AutoWidth => {
|
||||||
|
let paragraph_width = content.get_width();
|
||||||
|
let mut paragraphs = content.to_paragraphs(None, None);
|
||||||
|
let height = auto_height(&mut paragraphs, paragraph_width);
|
||||||
|
let resize_transform = math::resize_matrix(
|
||||||
|
&shape_bounds_after,
|
||||||
|
&shape_bounds_after,
|
||||||
|
paragraph_width,
|
||||||
|
height,
|
||||||
|
);
|
||||||
|
shape_bounds_after = shape_bounds_after.transform(&resize_transform);
|
||||||
|
transform.post_concat(&resize_transform);
|
||||||
|
}
|
||||||
|
GrowType::Fixed => {}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if pixel_precision {
|
if pixel_precision {
|
||||||
|
|||||||
@@ -41,14 +41,28 @@ pub struct TextContent {
|
|||||||
pub grow_type: GrowType,
|
pub grow_type: GrowType,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_paragraphs_width(width: f32, paragraphs: &mut Vec<Vec<ParagraphBuilder>>) {
|
pub fn build_paragraphs_with_width(
|
||||||
for group in paragraphs {
|
paragraphs: &mut [Vec<ParagraphBuilder>],
|
||||||
for p in group {
|
width: f32,
|
||||||
let mut paragraph = p.build();
|
) -> Vec<Vec<skia_safe::textlayout::Paragraph>> {
|
||||||
|
paragraphs
|
||||||
|
.iter_mut()
|
||||||
|
.map(|builders| {
|
||||||
|
builders
|
||||||
|
.iter_mut()
|
||||||
|
.map(|builder| {
|
||||||
|
let mut paragraph = builder.build();
|
||||||
|
// For auto-width, always layout with infinite width first to get intrinsic width
|
||||||
paragraph.layout(f32::MAX);
|
paragraph.layout(f32::MAX);
|
||||||
paragraph.layout(f32::max(width, paragraph.min_intrinsic_width().ceil()));
|
let intrinsic_width = paragraph.max_intrinsic_width().ceil();
|
||||||
}
|
// Use the larger of the requested width or intrinsic width to prevent line breaks
|
||||||
}
|
let final_width = f32::max(width, intrinsic_width);
|
||||||
|
paragraph.layout(final_width);
|
||||||
|
paragraph
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TextContent {
|
impl TextContent {
|
||||||
@@ -177,43 +191,14 @@ impl TextContent {
|
|||||||
paragraph_group
|
paragraph_group
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn collect_paragraphs(
|
pub fn get_width(&self) -> f32 {
|
||||||
&self,
|
|
||||||
mut paragraphs: Vec<Vec<ParagraphBuilder>>,
|
|
||||||
) -> Vec<Vec<ParagraphBuilder>> {
|
|
||||||
if self.grow_type() == GrowType::AutoWidth {
|
if self.grow_type() == GrowType::AutoWidth {
|
||||||
set_paragraphs_width(f32::MAX, &mut paragraphs);
|
let temp_paragraphs = self.to_paragraphs(None, None);
|
||||||
let max_width = auto_width(&mut paragraphs, self.width()).ceil();
|
let mut temp_paragraphs = temp_paragraphs;
|
||||||
set_paragraphs_width(max_width, &mut paragraphs);
|
auto_width(&mut temp_paragraphs, f32::MAX).ceil()
|
||||||
} else {
|
} else {
|
||||||
set_paragraphs_width(self.width(), &mut paragraphs);
|
self.width()
|
||||||
}
|
}
|
||||||
paragraphs
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_skia_paragraphs(
|
|
||||||
&self,
|
|
||||||
blur: Option<&ImageFilter>,
|
|
||||||
blur_mask: Option<&MaskFilter>,
|
|
||||||
) -> Vec<Vec<ParagraphBuilder>> {
|
|
||||||
self.collect_paragraphs(self.to_paragraphs(blur, blur_mask))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_skia_stroke_paragraphs(
|
|
||||||
&self,
|
|
||||||
stroke: &Stroke,
|
|
||||||
bounds: &Rect,
|
|
||||||
blur: Option<&ImageFilter>,
|
|
||||||
blur_mask: Option<&MaskFilter>,
|
|
||||||
count_inner_strokes: usize,
|
|
||||||
) -> Vec<Vec<ParagraphBuilder>> {
|
|
||||||
self.collect_paragraphs(self.to_stroke_paragraphs(
|
|
||||||
stroke,
|
|
||||||
bounds,
|
|
||||||
blur,
|
|
||||||
blur_mask,
|
|
||||||
count_inner_strokes,
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn grow_type(&self) -> GrowType {
|
pub fn grow_type(&self) -> GrowType {
|
||||||
@@ -225,9 +210,10 @@ impl TextContent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn visual_bounds(&self) -> (f32, f32) {
|
pub fn visual_bounds(&self) -> (f32, f32) {
|
||||||
|
let paragraph_width = self.get_width();
|
||||||
let mut paragraphs = self.to_paragraphs(None, None);
|
let mut paragraphs = self.to_paragraphs(None, None);
|
||||||
let height = auto_height(&mut paragraphs, self.width());
|
let paragraph_height = auto_height(&mut paragraphs, paragraph_width);
|
||||||
(self.width(), height)
|
(paragraph_width, paragraph_height)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn transform(&mut self, transform: &Matrix) {
|
pub fn transform(&mut self, transform: &Matrix) {
|
||||||
@@ -728,19 +714,7 @@ pub fn get_built_paragraphs(
|
|||||||
paragraphs: &mut [Vec<ParagraphBuilder>],
|
paragraphs: &mut [Vec<ParagraphBuilder>],
|
||||||
width: f32,
|
width: f32,
|
||||||
) -> Vec<Vec<skia_safe::textlayout::Paragraph>> {
|
) -> Vec<Vec<skia_safe::textlayout::Paragraph>> {
|
||||||
paragraphs
|
build_paragraphs_with_width(paragraphs, width)
|
||||||
.iter_mut()
|
|
||||||
.map(|builders| {
|
|
||||||
builders
|
|
||||||
.iter_mut()
|
|
||||||
.map(|builder_handle| {
|
|
||||||
let mut paragraph = builder_handle.build();
|
|
||||||
paragraph.layout(width);
|
|
||||||
paragraph
|
|
||||||
})
|
|
||||||
.collect()
|
|
||||||
})
|
|
||||||
.collect()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn auto_width(paragraphs: &mut [Vec<ParagraphBuilder>], width: f32) -> f32 {
|
pub fn auto_width(paragraphs: &mut [Vec<ParagraphBuilder>], width: f32) -> f32 {
|
||||||
@@ -754,15 +728,6 @@ pub fn auto_width(paragraphs: &mut [Vec<ParagraphBuilder>], width: f32) -> f32 {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn max_width(paragraphs: &mut [Vec<ParagraphBuilder>], width: f32) -> f32 {
|
|
||||||
let built_paragraphs = get_built_paragraphs(paragraphs, width);
|
|
||||||
|
|
||||||
built_paragraphs
|
|
||||||
.iter()
|
|
||||||
.flatten()
|
|
||||||
.fold(0.0, |max_width, p| f32::max(p.max_width(), max_width))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn auto_height(paragraphs: &mut [Vec<ParagraphBuilder>], width: f32) -> f32 {
|
pub fn auto_height(paragraphs: &mut [Vec<ParagraphBuilder>], width: f32) -> f32 {
|
||||||
paragraphs.iter_mut().fold(0.0, |auto_height, p| {
|
paragraphs.iter_mut().fold(0.0, |auto_height, p| {
|
||||||
p.iter_mut().fold(auto_height, |auto_height, paragraph| {
|
p.iter_mut().fold(auto_height, |auto_height, paragraph| {
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
use crate::shapes::text::TextContent;
|
use crate::shapes::text::TextContent;
|
||||||
use skia_safe::{
|
use skia_safe::{
|
||||||
self as skia, textlayout::Paragraph as SkiaParagraph, textlayout::ParagraphBuilder,
|
self as skia, textlayout::Paragraph as SkiaParagraph, FontMetrics, Point, Rect, TextBlob,
|
||||||
FontMetrics, Point, Rect, TextBlob,
|
|
||||||
};
|
};
|
||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
|
|
||||||
@@ -17,16 +16,11 @@ impl TextPaths {
|
|||||||
Self(content)
|
Self(content)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_skia_paragraphs(&self) -> Vec<Vec<ParagraphBuilder>> {
|
|
||||||
let paragraphs = self.to_paragraphs(None, None);
|
|
||||||
self.collect_paragraphs(paragraphs)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_paths(&self, antialias: bool) -> Vec<(skia::Path, skia::Paint)> {
|
pub fn get_paths(&self, antialias: bool) -> Vec<(skia::Path, skia::Paint)> {
|
||||||
let mut paths = Vec::new();
|
let mut paths = Vec::new();
|
||||||
|
|
||||||
let mut offset_y = self.bounds.y();
|
let mut offset_y = self.bounds.y();
|
||||||
let mut paragraphs = self.get_skia_paragraphs();
|
let mut paragraphs = self.to_paragraphs(None, None);
|
||||||
|
|
||||||
for paragraphs in paragraphs.iter_mut() {
|
for paragraphs in paragraphs.iter_mut() {
|
||||||
for paragraph_builder in paragraphs.iter_mut() {
|
for paragraph_builder in paragraphs.iter_mut() {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
use crate::mem;
|
use crate::mem;
|
||||||
use crate::shapes::{auto_height, auto_width, max_width, GrowType, RawTextData, Type};
|
use crate::shapes::{auto_height, build_paragraphs_with_width, GrowType, RawTextData, Type};
|
||||||
|
|
||||||
use crate::STATE;
|
use crate::STATE;
|
||||||
use crate::{with_current_shape, with_current_shape_mut};
|
use crate::{with_current_shape, with_current_shape_mut};
|
||||||
@@ -43,19 +43,27 @@ pub extern "C" fn get_text_dimensions() -> *mut u8 {
|
|||||||
height = shape.selrect.height();
|
height = shape.selrect.height();
|
||||||
|
|
||||||
if let Type::Text(content) = &shape.shape_type {
|
if let Type::Text(content) = &shape.shape_type {
|
||||||
let mut paragraphs = content.get_skia_paragraphs(
|
// 1. Reset Paragraphs
|
||||||
shape.image_filter(1.).as_ref(),
|
let paragraph_width = content.get_width();
|
||||||
shape.mask_filter(1.).as_ref(),
|
let mut paragraphs = content.to_paragraphs(None, None);
|
||||||
);
|
let built_paragraphs = build_paragraphs_with_width(&mut paragraphs, paragraph_width);
|
||||||
m_width = max_width(&mut paragraphs, width);
|
|
||||||
|
|
||||||
|
// 2. Max Width Calculation
|
||||||
|
m_width = built_paragraphs
|
||||||
|
.iter()
|
||||||
|
.flatten()
|
||||||
|
.fold(0.0, |max_width, p| f32::max(p.max_width(), max_width));
|
||||||
|
|
||||||
|
// 3. Width and Height Calculation
|
||||||
match content.grow_type() {
|
match content.grow_type() {
|
||||||
GrowType::AutoHeight => {
|
GrowType::AutoHeight => {
|
||||||
height = auto_height(&mut paragraphs, width).ceil();
|
let mut paragraph_height = content.to_paragraphs(None, None);
|
||||||
|
height = auto_height(&mut paragraph_height, paragraph_width).ceil();
|
||||||
}
|
}
|
||||||
GrowType::AutoWidth => {
|
GrowType::AutoWidth => {
|
||||||
width = auto_width(&mut paragraphs, width).ceil();
|
width = paragraph_width;
|
||||||
height = auto_height(&mut paragraphs, width).ceil();
|
let mut paragraph_height = content.to_paragraphs(None, None);
|
||||||
|
height = auto_height(&mut paragraph_height, paragraph_width).ceil();
|
||||||
}
|
}
|
||||||
GrowType::Fixed => {}
|
GrowType::Fixed => {}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user