mirror of
https://github.com/penpot/penpot.git
synced 2026-09-08 11:54:36 -04:00
Compare commits
4
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b08002bc0b | ||
|
|
87d9110ec5 | ||
|
|
774f5af013 | ||
|
|
6a9bf0fda5 |
No files matched your search
@@ -96,10 +96,15 @@
|
||||
|
||||
(defn- merge-resize-debounce-opts
|
||||
[prev {:keys [undo-group undo-id skip-component-sync?]}]
|
||||
(cond-> (or prev {})
|
||||
(some? undo-group) (assoc :undo-group undo-group)
|
||||
(some? undo-id) (assoc :undo-id undo-id)
|
||||
skip-component-sync? (assoc :skip-component-sync? true)))
|
||||
;; One commit per batch, so one flag for every shape in it. Keep it on
|
||||
;; only while every resize in the batch is derived.
|
||||
(let [prev (or prev {:skip-component-sync? true})]
|
||||
(cond-> prev
|
||||
(some? undo-group) (assoc :undo-group undo-group)
|
||||
(some? undo-id) (assoc :undo-id undo-id)
|
||||
:always (assoc :skip-component-sync?
|
||||
(boolean (and skip-component-sync?
|
||||
(:skip-component-sync? prev)))))))
|
||||
|
||||
(defn resize-wasm-text-debounce-commit
|
||||
[]
|
||||
@@ -160,8 +165,7 @@
|
||||
(-> state
|
||||
(update ::resize-wasm-text-debounce-ids (fnil conj []) id)
|
||||
(update ::resize-wasm-text-reflow-tasks (fnil conj []) reflow-task)
|
||||
(cond-> (seq opts)
|
||||
(update ::resize-wasm-text-debounce-opts merge-resize-debounce-opts opts))
|
||||
(update ::resize-wasm-text-debounce-opts merge-resize-debounce-opts opts)
|
||||
(cond-> (nil? (::resize-wasm-text-debounce-event state))
|
||||
(assoc ::resize-wasm-text-debounce-event cur-event))))
|
||||
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) KALEIDOS SUBSIDIARY SL
|
||||
|
||||
(ns frontend-tests.render-wasm.resize-debounce-opts-test
|
||||
(:require
|
||||
[app.common.test-helpers.compositions :as ctho]
|
||||
[app.common.test-helpers.files :as cthf]
|
||||
[app.common.test-helpers.ids-map :as cthi]
|
||||
[app.common.test-helpers.shapes :as cths]
|
||||
[app.main.data.workspace.wasm-text :as dwwt]
|
||||
[cljs.test :as t :include-macros true]
|
||||
[frontend-tests.helpers.pages :as thp]
|
||||
[frontend-tests.helpers.state :as ths]
|
||||
[frontend-tests.helpers.wasm :as thw]))
|
||||
|
||||
(t/use-fixtures :each
|
||||
{:before (fn []
|
||||
(thp/reset-idmap!)
|
||||
(thw/setup-wasm-mocks!))
|
||||
:after thw/teardown-wasm-mocks!})
|
||||
|
||||
(defn- setup-file []
|
||||
(-> (cthf/sample-file :file-1 :page-label :page-1)
|
||||
(ctho/add-text :text-1 "Derived")
|
||||
(ctho/add-text :text-2 "User edit")))
|
||||
|
||||
(defn- batch-opts
|
||||
[state]
|
||||
(get state :app.main.data.workspace.wasm-text/resize-wasm-text-debounce-opts))
|
||||
|
||||
(t/deftest derived-flag-does-not-leak-into-a-user-resize-in-the-same-batch
|
||||
(t/async
|
||||
done
|
||||
(let [file (setup-file)
|
||||
store (ths/setup-store file)
|
||||
text-1 (cths/get-shape file :text-1)
|
||||
text-2 (cths/get-shape file :text-2)
|
||||
;; Same tick => both ids land in the same debounce batch, which is
|
||||
;; what a font-load resize racing a sidebar resize produces.
|
||||
events [(dwwt/resize-wasm-text-debounce (:id text-1) {:skip-component-sync? true})
|
||||
(dwwt/resize-wasm-text-debounce (:id text-2) nil)]]
|
||||
(ths/run-store
|
||||
store done events
|
||||
(fn [state]
|
||||
;; 3, not 2: the batch owner re-emits itself so text-1 is queued twice.
|
||||
(t/is (= 3 (count (get state :app.main.data.workspace.wasm-text/resize-wasm-text-debounce-ids))))
|
||||
(t/is (not (:skip-component-sync? (batch-opts state)))))))))
|
||||
|
||||
(t/deftest derived-only-batch-keeps-the-flag
|
||||
(t/async
|
||||
done
|
||||
(let [file (setup-file)
|
||||
store (ths/setup-store file)
|
||||
text-1 (cths/get-shape file :text-1)
|
||||
text-2 (cths/get-shape file :text-2)
|
||||
events [(dwwt/resize-wasm-text-debounce (:id text-1) {:skip-component-sync? true})
|
||||
(dwwt/resize-wasm-text-debounce (:id text-2) {:skip-component-sync? true})]]
|
||||
(ths/run-store
|
||||
store done events
|
||||
(fn [state]
|
||||
(t/is (true? (:skip-component-sync? (batch-opts state)))))))))
|
||||
@@ -63,6 +63,7 @@
|
||||
[frontend-tests.plugins.value-objects-test]
|
||||
[frontend-tests.render-dimensions-test]
|
||||
[frontend-tests.render-wasm.process-objects-test]
|
||||
[frontend-tests.render-wasm.resize-debounce-opts-test]
|
||||
[frontend-tests.render-wasm.text-editor-apply-styles-test]
|
||||
[frontend-tests.render-wasm.text-editor-caret-color-test]
|
||||
[frontend-tests.svg-fills-test]
|
||||
@@ -167,6 +168,7 @@
|
||||
'frontend-tests.plugins.utils-test
|
||||
'frontend-tests.plugins.value-objects-test
|
||||
'frontend-tests.render-wasm.process-objects-test
|
||||
'frontend-tests.render-wasm.resize-debounce-opts-test
|
||||
'frontend-tests.render-wasm.text-editor-apply-styles-test
|
||||
'frontend-tests.render-wasm.text-editor-caret-color-test
|
||||
'frontend-tests.svg-fills-test
|
||||
|
||||
@@ -1404,6 +1404,13 @@ impl RenderState {
|
||||
.inner_shadows_visible()
|
||||
.any(|s| s.is_perceptible_at_scale_for(scale, shape.is_recursive())));
|
||||
|
||||
let has_inherited_drop_shadows = !skip_drop_shadows
|
||||
&& self.nested_shadows.iter().flatten().any(|shadow| {
|
||||
!shadow.hidden()
|
||||
&& shadow.style() == crate::shapes::ShadowStyle::Drop
|
||||
&& shadow.is_perceptible_at_scale(scale)
|
||||
});
|
||||
|
||||
// Clip is allowed: we apply the same stack on Current after scale+translate.
|
||||
// Opacity < 1 with SrcOver is OK: render_shape_enter already opened a
|
||||
// save_layer on Current; painting fills/strokes into that layer matches
|
||||
@@ -1421,8 +1428,9 @@ impl RenderState {
|
||||
shape.shape_type,
|
||||
Type::Rect(_) | Type::Circle | Type::Path(_) | Type::Bool(_) | Type::Frame(_)
|
||||
) && !(shape.fills.is_empty() && has_nested_fills);
|
||||
let is_direct_text =
|
||||
matches!(shape.shape_type, Type::Text(_)) && !shape.has_visible_strokes();
|
||||
let is_direct_text = matches!(shape.shape_type, Type::Text(_))
|
||||
&& !shape.has_visible_strokes()
|
||||
&& !has_inherited_drop_shadows;
|
||||
let can_render_directly = apply_to_current_surface
|
||||
&& offset.is_none()
|
||||
&& parent_shadows.is_none()
|
||||
@@ -1632,6 +1640,7 @@ impl RenderState {
|
||||
&& parent_shadows.is_none()
|
||||
&& (skip_effects
|
||||
|| (shape.blur.is_none()
|
||||
&& !has_inherited_drop_shadows
|
||||
&& !shape
|
||||
.drop_shadows_visible()
|
||||
.any(|s| s.is_perceptible_at_scale(self.get_scale()))
|
||||
|
||||
@@ -3,8 +3,9 @@ use crate::{
|
||||
error::Result,
|
||||
math::Rect,
|
||||
shapes::{
|
||||
add_text_with_tabs, calculate_text_layout_data, set_paint_fill, ParagraphBuilderGroup,
|
||||
ParagraphLayout, Stroke, StrokeKind, TextContent, VerticalAlign,
|
||||
add_text_with_tabs, calculate_text_layout_data, paragraph_decoration_segments,
|
||||
set_paint_fill, vertical_align_offset, ParagraphBuilderGroup, ParagraphLayout, Stroke,
|
||||
StrokeKind, TextContent,
|
||||
},
|
||||
utils::{get_fallback_fonts, get_font_collection},
|
||||
};
|
||||
@@ -340,6 +341,15 @@ pub fn try_paint_from_layout_cache(
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
if text_content
|
||||
.layout
|
||||
.paragraphs
|
||||
.iter()
|
||||
.any(|group| group.is_empty())
|
||||
{
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
if let Some(render_state) = render_state {
|
||||
let target_surface = surface_id.unwrap_or(SurfaceId::Fills);
|
||||
let canvas = render_state.surfaces.canvas_and_mark_dirty(target_surface);
|
||||
@@ -358,7 +368,6 @@ pub fn try_paint_from_layout_cache(
|
||||
fn paint_from_cached_layout(canvas: &Canvas, shape: &Shape, text_content: &TextContent) {
|
||||
let selrect = shape.selrect();
|
||||
let x = selrect.x();
|
||||
let base_y = selrect.y();
|
||||
let paragraphs = &text_content.layout.paragraphs;
|
||||
let draw_decorations = text_content.has_text_decorations();
|
||||
|
||||
@@ -367,92 +376,31 @@ fn paint_from_cached_layout(canvas: &Canvas, shape: &Shape, text_content: &TextC
|
||||
.filter_map(|group| group.first())
|
||||
.map(|p| p.height())
|
||||
.sum();
|
||||
let vertical_offset = match shape.vertical_align() {
|
||||
VerticalAlign::Center => (selrect.height() - total_text_height) / 2.0,
|
||||
VerticalAlign::Bottom => selrect.height() - total_text_height,
|
||||
_ => 0.0,
|
||||
};
|
||||
let vertical_offset =
|
||||
vertical_align_offset(selrect.height(), total_text_height, shape.vertical_align());
|
||||
|
||||
let mut y_accum = base_y + vertical_offset;
|
||||
let mut y_accum = selrect.y() + vertical_offset;
|
||||
for group in paragraphs.iter() {
|
||||
let Some(paragraph) = group.first() else {
|
||||
continue;
|
||||
};
|
||||
paragraph.paint(canvas, (x, y_accum));
|
||||
if draw_decorations {
|
||||
paint_decorations_for_paragraph(canvas, paragraph, x, y_accum);
|
||||
for deco in paragraph_decoration_segments(paragraph, x, y_accum) {
|
||||
draw_text_decorations(
|
||||
canvas,
|
||||
&deco.text_style,
|
||||
Some(deco.y),
|
||||
deco.thickness,
|
||||
deco.left,
|
||||
deco.width,
|
||||
);
|
||||
}
|
||||
}
|
||||
y_accum += paragraph.height();
|
||||
}
|
||||
}
|
||||
|
||||
fn paint_decorations_for_paragraph(
|
||||
canvas: &Canvas,
|
||||
paragraph: &skia::textlayout::Paragraph,
|
||||
x: f32,
|
||||
y_accum: f32,
|
||||
) {
|
||||
let line_metrics = paragraph.get_line_metrics();
|
||||
for line in &line_metrics {
|
||||
let style_metrics: Vec<_> = line
|
||||
.get_style_metrics(line.start_index..line.end_index)
|
||||
.into_iter()
|
||||
.collect();
|
||||
let line_baseline = y_accum + line.baseline as f32;
|
||||
let (max_underline_thickness, underline_y, max_strike_thickness, strike_y) =
|
||||
calculate_decoration_metrics(&style_metrics, line_baseline);
|
||||
for (i, (style_start, style_metric)) in style_metrics.iter().enumerate() {
|
||||
let text_style = &style_metric.text_style;
|
||||
let style_end = style_metrics
|
||||
.get(i + 1)
|
||||
.map(|(next_i, _)| *next_i)
|
||||
.unwrap_or(line.end_index);
|
||||
let seg_start = (*style_start).max(line.start_index);
|
||||
let seg_end = style_end.min(line.end_index);
|
||||
if seg_start >= seg_end {
|
||||
continue;
|
||||
}
|
||||
let rects = paragraph.get_rects_for_range(
|
||||
seg_start..seg_end,
|
||||
skia::textlayout::RectHeightStyle::Tight,
|
||||
skia::textlayout::RectWidthStyle::Tight,
|
||||
);
|
||||
let (segment_width, actual_x_offset) = if !rects.is_empty() {
|
||||
let total_width: f32 = rects.iter().map(|r| r.rect.width()).sum();
|
||||
let skia_x_offset = rects
|
||||
.first()
|
||||
.map(|r| r.rect.left - line.left as f32)
|
||||
.unwrap_or(0.0);
|
||||
(total_width, skia_x_offset)
|
||||
} else {
|
||||
(0.0, 0.0)
|
||||
};
|
||||
let text_left = x + line.left as f32 + actual_x_offset;
|
||||
let text_width = segment_width;
|
||||
if text_style.decoration().ty == TextDecoration::UNDERLINE {
|
||||
draw_text_decorations(
|
||||
canvas,
|
||||
text_style,
|
||||
Some(underline_y.unwrap_or(line_baseline)),
|
||||
max_underline_thickness,
|
||||
text_left,
|
||||
text_width,
|
||||
);
|
||||
}
|
||||
if text_style.decoration().ty == TextDecoration::LINE_THROUGH {
|
||||
draw_text_decorations(
|
||||
canvas,
|
||||
text_style,
|
||||
Some(strike_y.unwrap_or(line_baseline)),
|
||||
max_strike_thickness,
|
||||
text_left,
|
||||
text_width,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn render_text_on_canvas(
|
||||
canvas: &Canvas,
|
||||
|
||||
@@ -135,9 +135,7 @@ impl Type {
|
||||
layout.scale_content(value);
|
||||
}
|
||||
}
|
||||
Type::Text(TextContent { paragraphs, .. }) => {
|
||||
paragraphs.iter_mut().for_each(|p| p.scale_content(value));
|
||||
}
|
||||
Type::Text(content) => content.scale_content(value),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@ pub fn modifier_changes_text_layout(base: &Shape, modifier: &Matrix) -> bool {
|
||||
let Type::Text(text_content) = &base.shape_type else {
|
||||
return false;
|
||||
};
|
||||
let before = oriented_container_bounds(base);
|
||||
let before = base.bounds();
|
||||
let after = before.transform(modifier);
|
||||
match text_content.grow_type() {
|
||||
GrowType::AutoWidth => !crate::math::is_close_to(before.height(), after.height()),
|
||||
@@ -51,27 +51,6 @@ pub fn modifier_changes_text_layout(base: &Shape, modifier: &Matrix) -> bool {
|
||||
}
|
||||
}
|
||||
|
||||
fn oriented_container_bounds(shape: &Shape) -> Bounds {
|
||||
let selrect = shape.selrect();
|
||||
let mut bounds = Bounds::new(
|
||||
Point::new(selrect.x(), selrect.y()),
|
||||
Point::new(selrect.x() + selrect.width(), selrect.y()),
|
||||
Point::new(
|
||||
selrect.x() + selrect.width(),
|
||||
selrect.y() + selrect.height(),
|
||||
),
|
||||
Point::new(selrect.x(), selrect.y() + selrect.height()),
|
||||
);
|
||||
if !shape.transform.is_identity() {
|
||||
let mut matrix = shape.transform;
|
||||
let center = shape.center();
|
||||
matrix.post_translate(center);
|
||||
matrix.pre_translate(-center);
|
||||
bounds.transform_mut(&matrix);
|
||||
}
|
||||
bounds
|
||||
}
|
||||
|
||||
#[repr(u8)]
|
||||
#[derive(Debug, PartialEq, Clone, Copy, ToJs)]
|
||||
pub enum GrowType {
|
||||
@@ -304,7 +283,77 @@ pub struct TextDecorationSegment {
|
||||
pub width: f32,
|
||||
}
|
||||
|
||||
fn vertical_align_offset(container_h: f32, content_h: f32, valign: VerticalAlign) -> f32 {
|
||||
/// Underline / line-through geometry for one laid-out paragraph placed at
|
||||
/// `x`, `y_accum`. Shared by [`calculate_text_layout_data`] and the
|
||||
/// layout-cache paint path.
|
||||
pub fn paragraph_decoration_segments(
|
||||
paragraph: &skia::textlayout::Paragraph,
|
||||
x: f32,
|
||||
y_accum: f32,
|
||||
) -> Vec<TextDecorationSegment> {
|
||||
let mut decorations = Vec::new();
|
||||
for line in ¶graph.get_line_metrics() {
|
||||
let style_metrics: Vec<_> = line
|
||||
.get_style_metrics(line.start_index..line.end_index)
|
||||
.into_iter()
|
||||
.collect();
|
||||
let line_baseline = y_accum + line.baseline as f32;
|
||||
let (max_underline_thickness, underline_y, max_strike_thickness, strike_y) =
|
||||
calculate_decoration_metrics(&style_metrics, line_baseline);
|
||||
for (i, (style_start, style_metric)) in style_metrics.iter().enumerate() {
|
||||
let text_style = &style_metric.text_style;
|
||||
let style_end = style_metrics
|
||||
.get(i + 1)
|
||||
.map(|(next_i, _)| *next_i)
|
||||
.unwrap_or(line.end_index);
|
||||
let seg_start = (*style_start).max(line.start_index);
|
||||
let seg_end = style_end.min(line.end_index);
|
||||
if seg_start >= seg_end {
|
||||
continue;
|
||||
}
|
||||
let rects = paragraph.get_rects_for_range(
|
||||
seg_start..seg_end,
|
||||
skia::textlayout::RectHeightStyle::Tight,
|
||||
skia::textlayout::RectWidthStyle::Tight,
|
||||
);
|
||||
let (segment_width, actual_x_offset) = if !rects.is_empty() {
|
||||
let total_width: f32 = rects.iter().map(|r| r.rect.width()).sum();
|
||||
let skia_x_offset = rects
|
||||
.first()
|
||||
.map(|r| r.rect.left - line.left as f32)
|
||||
.unwrap_or(0.0);
|
||||
(total_width, skia_x_offset)
|
||||
} else {
|
||||
(0.0, 0.0)
|
||||
};
|
||||
let text_left = x + line.left as f32 + actual_x_offset;
|
||||
let text_width = segment_width;
|
||||
if text_style.decoration().ty == skia::textlayout::TextDecoration::UNDERLINE {
|
||||
decorations.push(TextDecorationSegment {
|
||||
kind: skia::textlayout::TextDecoration::UNDERLINE,
|
||||
text_style: (*text_style).clone(),
|
||||
y: underline_y.unwrap_or(line_baseline),
|
||||
thickness: max_underline_thickness,
|
||||
left: text_left,
|
||||
width: text_width,
|
||||
});
|
||||
}
|
||||
if text_style.decoration().ty == skia::textlayout::TextDecoration::LINE_THROUGH {
|
||||
decorations.push(TextDecorationSegment {
|
||||
kind: skia::textlayout::TextDecoration::LINE_THROUGH,
|
||||
text_style: (*text_style).clone(),
|
||||
y: strike_y.unwrap_or(line_baseline),
|
||||
thickness: max_strike_thickness,
|
||||
left: text_left,
|
||||
width: text_width,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
decorations
|
||||
}
|
||||
|
||||
pub fn vertical_align_offset(container_h: f32, content_h: f32, valign: VerticalAlign) -> f32 {
|
||||
match valign {
|
||||
VerticalAlign::Center => (container_h - content_h) / 2.0,
|
||||
VerticalAlign::Bottom => container_h - content_h,
|
||||
@@ -442,8 +491,8 @@ impl TextContent {
|
||||
/// clones paragraphs into a rebound copy with an empty layout cache.
|
||||
pub fn paint_content_for_selrect<'a>(&'a self, selrect: Rect) -> Cow<'a, Self> {
|
||||
let stored_bounds = self.bounds();
|
||||
if (stored_bounds.width() - selrect.width()).abs() < 0.01
|
||||
&& (stored_bounds.height() - selrect.height()).abs() < 0.01
|
||||
if crate::math::is_close_to(stored_bounds.width(), selrect.width())
|
||||
&& crate::math::is_close_to(stored_bounds.height(), selrect.height())
|
||||
{
|
||||
Cow::Borrowed(self)
|
||||
} else {
|
||||
@@ -451,6 +500,13 @@ impl TextContent {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn scale_content(&mut self, value: f32) {
|
||||
self.paragraphs_mut()
|
||||
.iter_mut()
|
||||
.for_each(|p| p.scale_content(value));
|
||||
self.layout.clear();
|
||||
}
|
||||
|
||||
pub fn set_xywh(&mut self, x: f32, y: f32, w: f32, h: f32) {
|
||||
self.bounds = Rect::from_xywh(x, y, w, h);
|
||||
}
|
||||
@@ -582,7 +638,7 @@ impl TextContent {
|
||||
return self.content_rect(selrect, valign);
|
||||
}
|
||||
|
||||
let tight = if !self.layout.paragraphs.is_empty() {
|
||||
let tight = if self.has_usable_paint_layout(shape) {
|
||||
self.rect_from_paragraphs(selrect, valign)
|
||||
} else {
|
||||
let mut text_content = self.clone();
|
||||
@@ -955,10 +1011,7 @@ impl TextContent {
|
||||
|
||||
/// True when cached Skia paragraphs can be painted as-is (no rebuild/layout).
|
||||
pub fn has_usable_paint_layout(&self, shape: &Shape) -> bool {
|
||||
if self.layout.needs_update() || self.layout_version != self.content_version {
|
||||
return false;
|
||||
}
|
||||
self.layout_matches_paint_container(shape)
|
||||
self.layout_cache_versions_match() && self.layout_matches_paint_container(shape)
|
||||
}
|
||||
|
||||
pub(crate) fn layout_cache_versions_match(&self) -> bool {
|
||||
@@ -972,8 +1025,7 @@ impl TextContent {
|
||||
let Some(layout_w) = self.layout_width else {
|
||||
return false;
|
||||
};
|
||||
let container_w = self.get_width(shape.selrect().width());
|
||||
(layout_w - container_w).abs() < f32::EPSILON
|
||||
crate::math::is_close_to(layout_w, self.get_width(shape.selrect().width()))
|
||||
}
|
||||
|
||||
/// True when any span requests underline/overline/line-through (custom draw path).
|
||||
@@ -1013,7 +1065,7 @@ impl TextContent {
|
||||
let layout_matches_container = self.grow_type() == GrowType::AutoWidth
|
||||
|| self
|
||||
.layout_width
|
||||
.is_some_and(|w| (w - selrect.width()).abs() < f32::EPSILON);
|
||||
.is_some_and(|w| crate::math::is_close_to(w, selrect.width()));
|
||||
|
||||
if !self.layout.needs_update()
|
||||
&& self.layout_version == self.content_version
|
||||
@@ -1027,7 +1079,7 @@ impl TextContent {
|
||||
match self.grow_type() {
|
||||
GrowType::AutoHeight => {
|
||||
let result = self.text_layout_auto_height();
|
||||
self.layout_width = Some(result.2.width);
|
||||
self.layout_width = Some(selrect.width());
|
||||
self.set_layout_from_result(result, selrect.width(), selrect.height());
|
||||
}
|
||||
GrowType::AutoWidth => {
|
||||
@@ -1037,7 +1089,7 @@ impl TextContent {
|
||||
}
|
||||
GrowType::Fixed => {
|
||||
let result = self.text_layout_fixed();
|
||||
self.layout_width = Some(result.2.width);
|
||||
self.layout_width = Some(selrect.width());
|
||||
self.set_layout_from_result(result, selrect.width(), selrect.height());
|
||||
}
|
||||
}
|
||||
@@ -1680,68 +1732,7 @@ pub fn calculate_text_layout_data(
|
||||
for (i, group_paragraphs) in built_groups.into_iter().enumerate() {
|
||||
// For each paragraph in the group (e.g., fill, stroke, etc.)
|
||||
for skia_paragraph in group_paragraphs.into_iter() {
|
||||
// Calculate text decorations for this paragraph
|
||||
let mut decorations = Vec::new();
|
||||
let line_metrics = skia_paragraph.get_line_metrics();
|
||||
for line in &line_metrics {
|
||||
let style_metrics: Vec<_> = line
|
||||
.get_style_metrics(line.start_index..line.end_index)
|
||||
.into_iter()
|
||||
.collect();
|
||||
let line_baseline = y_accum + line.baseline as f32;
|
||||
let (max_underline_thickness, underline_y, max_strike_thickness, strike_y) =
|
||||
calculate_decoration_metrics(&style_metrics, line_baseline);
|
||||
for (i, (style_start, style_metric)) in style_metrics.iter().enumerate() {
|
||||
let text_style = &style_metric.text_style;
|
||||
let style_end = style_metrics
|
||||
.get(i + 1)
|
||||
.map(|(next_i, _)| *next_i)
|
||||
.unwrap_or(line.end_index);
|
||||
let seg_start = (*style_start).max(line.start_index);
|
||||
let seg_end = style_end.min(line.end_index);
|
||||
if seg_start >= seg_end {
|
||||
continue;
|
||||
}
|
||||
let rects = skia_paragraph.get_rects_for_range(
|
||||
seg_start..seg_end,
|
||||
skia::textlayout::RectHeightStyle::Tight,
|
||||
skia::textlayout::RectWidthStyle::Tight,
|
||||
);
|
||||
let (segment_width, actual_x_offset) = if !rects.is_empty() {
|
||||
let total_width: f32 = rects.iter().map(|r| r.rect.width()).sum();
|
||||
let skia_x_offset = rects
|
||||
.first()
|
||||
.map(|r| r.rect.left - line.left as f32)
|
||||
.unwrap_or(0.0);
|
||||
(total_width, skia_x_offset)
|
||||
} else {
|
||||
(0.0, 0.0)
|
||||
};
|
||||
let text_left = x + line.left as f32 + actual_x_offset;
|
||||
let text_width = segment_width;
|
||||
use skia::textlayout::TextDecoration;
|
||||
if text_style.decoration().ty == TextDecoration::UNDERLINE {
|
||||
decorations.push(TextDecorationSegment {
|
||||
kind: TextDecoration::UNDERLINE,
|
||||
text_style: (*text_style).clone(),
|
||||
y: underline_y.unwrap_or(line_baseline),
|
||||
thickness: max_underline_thickness,
|
||||
left: text_left,
|
||||
width: text_width,
|
||||
});
|
||||
}
|
||||
if text_style.decoration().ty == TextDecoration::LINE_THROUGH {
|
||||
decorations.push(TextDecorationSegment {
|
||||
kind: TextDecoration::LINE_THROUGH,
|
||||
text_style: (*text_style).clone(),
|
||||
y: strike_y.unwrap_or(line_baseline),
|
||||
thickness: max_strike_thickness,
|
||||
left: text_left,
|
||||
width: text_width,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
let decorations = paragraph_decoration_segments(&skia_paragraph, x, y_accum);
|
||||
paragraph_layouts.push(ParagraphLayout {
|
||||
paragraph: skia_paragraph,
|
||||
x,
|
||||
|
||||
Reference in new issue
Block a user