Merge pull request #7735 from penpot/superalex-fix-create-empty-text

🐛 Fix some text issues
This commit is contained in:
Elena Torró 2025-11-12 17:48:41 +01:00 committed by GitHub
commit 3f05dae455
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 151 additions and 21 deletions

View File

@ -96,6 +96,16 @@
(->> (rx/from ids)
(rx/map resize-wasm-text)))))
;; -- Content helpers
(defn- v2-content-has-text?
[content]
(boolean
(when content
(some (fn [node]
(not (str/blank? (:text node ""))))
(txt/node-seq txt/is-text-node? content)))))
;; -- Editor
(defn update-editor
@ -948,28 +958,34 @@
(let [objects (dsh/lookup-page-objects state)
shape (get objects id)
new-shape? (nil? (:content shape))]
(rx/of
(dwsh/update-shapes
[id]
(fn [shape]
(let [new-shape (-> shape
(assoc :content content)
(cond-> (and update-name? (some? name))
(assoc :name name)))]
new-shape))
{:undo-group (when new-shape? id)})
(rx/concat
(rx/of
(dwsh/update-shapes
[id]
(fn [shape]
(let [new-shape (-> shape
(assoc :content content)
(cond-> (and update-name? (some? name))
(assoc :name name)))]
new-shape))
{:undo-group (when new-shape? id)})
(if (and (not= :fixed (:grow-type shape)) finalize?)
(dwm/apply-wasm-modifiers
(resize-wasm-text-modifiers shape content)
{:undo-group (when new-shape? id)})
(if (and (not= :fixed (:grow-type shape)) finalize?)
(dwm/apply-wasm-modifiers
(resize-wasm-text-modifiers shape content)
{:undo-group (when new-shape? id)})
(dwm/set-wasm-modifiers
(resize-wasm-text-modifiers shape content)
{:undo-group (when new-shape? id)}))
(dwm/set-wasm-modifiers
(resize-wasm-text-modifiers shape content)
{:undo-group (when new-shape? id)})))
(when finalize?
(dwt/finish-transform))))
(rx/concat
(when (and (not (v2-content-has-text? content)) (some? id))
(rx/of
(dws/deselect-shape id)
(dwsh/delete-shapes #{id})))
(rx/of (dwt/finish-transform))))))
(let [objects (dsh/lookup-page-objects state)
shape (get objects id)

View File

@ -35,6 +35,7 @@
[app.render-wasm.wasm :as wasm]
[app.util.debug :as dbg]
[app.util.functions :as fns]
[app.util.text.content :as tc]
[beicon.v2.core :as rx]
[promesa.core :as p]
[rumext.v2 :as mf]))
@ -106,6 +107,14 @@
(reset! pending-render false)
(render ts)))))
(defn- ensure-text-content
"Guarantee that the shape always sends a valid text tree to WASM. When the
content is nil (freshly created text) we fall back to
tc/default-text-content so the renderer receives typography information."
[content]
(or content (tc/v2-default-text-content)))
(defn use-shape
[id]
(when wasm/context-initialized?
@ -850,7 +859,10 @@
blend-mode (get shape :blend-mode)
opacity (get shape :opacity)
hidden (get shape :hidden)
content (get shape :content)
content (let [content (get shape :content)]
(if (= type :text)
(ensure-text-content content)
content))
bool-type (get shape :bool-type)
grow-type (get shape :grow-type)
blur (get shape :blur)

View File

@ -6,6 +6,8 @@
(ns app.util.text.content
(:require
[app.common.types.text :as txt]
[app.main.refs :as refs]
[app.util.text.content.from-dom :as fd]
[app.util.text.content.to-dom :as td]))
@ -18,3 +20,22 @@
"Sets the editor content from a CLJS structure"
[root]
(td/create-root root))
(defn v2-default-text-content
"Build the base text tree (root -> paragraph-set -> paragraph -> span) with the
current default typography. Used by the V2 editor/WASM path when a shape is
created with no content yet."
[]
(let [default-font (deref refs/default-font)
text-defaults (merge (txt/get-default-text-attrs) default-font)
default-span (merge {:text ""}
(select-keys text-defaults txt/text-node-attrs))
default-paragraph (merge {:type "paragraph"
:children [default-span]}
(select-keys text-defaults txt/paragraph-attrs))
default-paragraph-set {:type "paragraph-set"
:children [default-paragraph]}]
(merge {:type "root"
:children [default-paragraph-set]}
txt/default-root-attrs
(select-keys text-defaults txt/root-attrs))))

View File

@ -428,8 +428,17 @@ pub fn resize_matrix(
new_height: f32,
) -> Matrix {
let mut result = Matrix::default();
let scale_width = new_width / child_bounds.width();
let scale_height = new_height / child_bounds.height();
let safe_scale = |value: f32, base: f32| -> f32 {
if !value.is_finite() || !base.is_finite() || is_close_to(base, 0.0) {
1.0
} else {
value / base
}
};
let scale_width = safe_scale(new_width, child_bounds.width());
let scale_height = safe_scale(new_height, child_bounds.height());
let center = child_bounds.center();
let mut parent_transform = parent_bounds.transform_matrix().unwrap_or_default();

View File

@ -508,8 +508,80 @@ impl TextContent {
self.set_layout_from_result(result, selrect.width(), selrect.height());
}
}
if self.is_empty() {
let (placeholder_width, placeholder_height) = self.placeholder_dimensions(selrect);
self.size.width = placeholder_width;
self.size.height = placeholder_height;
self.size.max_width = placeholder_width;
}
self.size
}
/// Return true when the content represents a freshly created empty text.
/// We consider it empty only if there is exactly one paragraph with a single
/// span whose text buffer is empty. Any additional paragraphs or characters
/// mean the user has already entered content.
fn is_empty(&self) -> bool {
if self.paragraphs.len() != 1 {
return false;
}
let paragraph = match self.paragraphs.first() {
Some(paragraph) => paragraph,
None => return true,
};
if paragraph.children().len() != 1 {
return false;
}
let span = match paragraph.children().first() {
Some(span) => span,
None => return true,
};
span.text.is_empty()
}
/// Compute the placeholder size used while the text is still empty. We ask
/// Skia to measure a single glyph using the span's typography so the editor
/// shows a caret-sized box that reflects the selected font, size and spacing.
/// If that fails we fall back to the previous WASM size or the incoming
/// selrect dimensions.
fn placeholder_dimensions(&self, selrect: Rect) -> (f32, f32) {
if let Some(paragraph) = self.paragraphs.first() {
if let Some(span) = paragraph.children().first() {
let fonts = get_font_collection();
let fallback_fonts = get_fallback_fonts();
let paragraph_style = paragraph.paragraph_to_style();
let mut builder = ParagraphBuilder::new(&paragraph_style, fonts);
let text_style = span.to_style(
&self.bounds(),
fallback_fonts,
false,
paragraph.line_height(),
);
builder.push_style(&text_style);
builder.add_text("0");
let mut paragraph_layout = builder.build();
paragraph_layout.layout(f32::MAX);
let width = paragraph_layout.max_intrinsic_width();
let height = paragraph_layout.height();
return (width, height);
}
}
let fallback_width = selrect.width().max(self.size.width);
let fallback_height = selrect.height().max(self.size.height);
(fallback_width, fallback_height)
}
}
impl Default for TextContent {