Compare commits

...

3 Commits

Author SHA1 Message Date
Elena Torro
b3bd7e7a28 🔧 Keep max line-height 2025-10-31 09:05:30 +01:00
Elena Torro
e67d6bc7dc 🔧 Add logs and fallbacks 2025-10-31 09:05:30 +01:00
Elena Torro
5690ea766f 🔧 Remove unused text attrs 2025-10-31 09:05:28 +01:00
6 changed files with 195 additions and 97 deletions

View File

@@ -14,6 +14,7 @@
[app.main.fonts :as fonts]
[app.main.store :as st]
[app.render-wasm.helpers :as h]
[app.render-wasm.mem :as mem]
[app.render-wasm.wasm :as wasm]
[app.util.http :as http]
[beicon.v2.core :as rx]
@@ -82,31 +83,57 @@
(let [font-id-buffer (:family-id-buffer font-data)
shape-id-buffer (uuid/get-u32 shape-id)
size (.-byteLength font-array-buffer)
ptr (h/call wasm/internal-module "_alloc_bytes" size)
heap (gobj/get ^js wasm/internal-module "HEAPU8")
mem (js/Uint8Array. (.-buffer heap) ptr size)]
(.set mem (js/Uint8Array. font-array-buffer))
(h/call wasm/internal-module "_store_font"
(aget shape-id-buffer 0)
(aget shape-id-buffer 1)
(aget shape-id-buffer 2)
(aget shape-id-buffer 3)
(aget font-id-buffer 0)
(aget font-id-buffer 1)
(aget font-id-buffer 2)
(aget font-id-buffer 3)
(:weight font-data)
(:style font-data)
emoji?
fallback?)
ptr (h/call wasm/internal-module "_alloc_bytes" size)]
(h/call wasm/internal-module "_update_shape_text_layout_for"
(aget shape-id-buffer 0)
(aget shape-id-buffer 1)
(aget shape-id-buffer 2)
(aget shape-id-buffer 3))
(if (or (nil? ptr) (zero? ptr))
;; Allocation failed - log error and return false
(do
(log/error :hint "Failed to allocate memory for font buffer"
:font-id (:font-id font-data)
:size size
:shape-id shape-id)
false)
true))
;; Allocation succeeded - proceed with font storage
(try
(let [heap (gobj/get ^js wasm/internal-module "HEAPU8")
mem (js/Uint8Array. (.-buffer heap) ptr size)]
(.set mem (js/Uint8Array. font-array-buffer))
(h/call wasm/internal-module "_store_font"
(aget shape-id-buffer 0)
(aget shape-id-buffer 1)
(aget shape-id-buffer 2)
(aget shape-id-buffer 3)
(aget font-id-buffer 0)
(aget font-id-buffer 1)
(aget font-id-buffer 2)
(aget font-id-buffer 3)
(:weight font-data)
(:style font-data)
emoji?
fallback?)
(h/call wasm/internal-module "_update_shape_text_layout_for"
(aget shape-id-buffer 0)
(aget shape-id-buffer 1)
(aget shape-id-buffer 2)
(aget shape-id-buffer 3))
true)
(catch :default e
;; Error during font storage - free allocated memory and log error
(log/error :hint "Error storing font buffer"
:font-id (:font-id font-data)
:shape-id shape-id
:cause e)
(try
(h/call wasm/internal-module "_free_bytes" ptr size)
(catch :default free-error
(log/error :hint "Failed to free memory after font storage error"
:ptr ptr
:size size
:cause free-error)))
false)))))
(defn- fetch-font
[shape-id font-data font-url emoji? fallback?]
@@ -120,6 +147,7 @@
(log/error :hint "Could not fetch font"
:font-url font-url
:cause cause)
(mem/free)
(rx/empty))))})
(defn- google-font-ttf-url

View File

@@ -15,7 +15,7 @@
[app.render-wasm.serializers :as sr]
[app.render-wasm.wasm :as wasm]))
(def ^:const PARAGRAPH-ATTR-U8-SIZE 44)
(def ^:const PARAGRAPH-ATTR-U8-SIZE 12)
(def ^:const SPAN-ATTR-U8-SIZE 64)
(def ^:const MAX-TEXT-FILLS types.fills.impl/MAX-FILLS)
@@ -56,10 +56,7 @@
text-decoration (sr/translate-text-decoration (get paragraph :text-decoration))
text-transform (sr/translate-text-transform (get paragraph :text-transform))
line-height (get paragraph :line-height)
letter-spacing (get paragraph :letter-spacing)
typography-ref-file (get paragraph :typography-ref-file)
typography-ref-id (get paragraph :typography-ref-id)]
letter-spacing (get paragraph :letter-spacing)]
(-> offset
(mem/write-u8 dview text-align)
@@ -70,8 +67,6 @@
(mem/write-f32 dview line-height)
(mem/write-f32 dview letter-spacing)
(mem/write-uuid dview (d/nilv typography-ref-file uuid/zero))
(mem/write-uuid dview (d/nilv typography-ref-id uuid/zero))
(mem/assert-written offset PARAGRAPH-ATTR-U8-SIZE))))
(defn- write-spans
@@ -80,19 +75,18 @@
paragraph-font-weight (-> paragraph :font-weight f/serialize-font-weight)
paragraph-line-height (get paragraph :line-height)]
(reduce (fn [offset span]
(let [font-style (sr/translate-font-style (get span :font-style))
(let [font-style (sr/translate-font-style (get span :font-style "normal"))
font-size (get span :font-size paragraph-font-size)
line-height (get span :line-height paragraph-line-height)
letter-spacing (get span :letter-spacing)
letter-spacing (get span :letter-spacing 0.0)
font-weight (get span :font-weight paragraph-font-weight)
font-weight (f/serialize-font-weight font-weight)
font-id (f/normalize-font-id (get span :font-id "sourcesanspro"))
font-family (hash (get span :font-family "sourcesanspro"))
font-id (f/normalize-font-id (get span :font-id))
font-family (hash (get span :font-family))
text-buffer (encode-text (get span :text))
text-buffer (encode-text (get span :text ""))
text-length (mem/size text-buffer)
fills (take MAX-TEXT-FILLS (get span :fills))
fills (take MAX-TEXT-FILLS (get span :fills []))
font-variant-id
(get span :font-variant-id)

View File

@@ -8,6 +8,7 @@
(:require
[app.common.data :as d]
[app.common.data.macros :as dm]
[app.common.logging :as log]
[app.common.transit :as t]
[app.common.types.shape :as shape]
[app.common.types.shape.layout :as ctl]
@@ -21,12 +22,66 @@
(declare ^:private impl-conj)
(declare ^:private impl-dissoc)
;; Track pending async operations to prevent race conditions
(defonce ^:private pending-operations (atom {}))
(defn- cancel-pending-operations!
"Cancel all pending async operations for a given shape"
[shape-id]
(when-let [operations (get @pending-operations shape-id)]
(doseq [subscription operations]
(try
(rx/dispose! subscription)
(catch :default e
(log/error :hint "Error disposing subscription"
:shape-id shape-id
:cause e))))
(swap! pending-operations dissoc shape-id)))
(defn shape-in-current-page?
"Check if a shape is in the current page by looking up the current page objects"
[shape-id]
(let [objects (deref refs/workspace-page-objects)]
(contains? objects shape-id)))
(defn- add-pending-operation!
"Track a pending async operation for cleanup"
[shape-id subscription]
(swap! pending-operations update shape-id (fnil conj []) subscription)
subscription)
(defn- execute-with-safety-checks
"Execute callback only if shape is still valid, with proper error handling and memory validation"
[shape-id callback-fn]
(try
(when (shape-in-current-page? shape-id)
;; Validate that we can safely use the shape before proceeding
(api/use-shape shape-id)
;; Execute the callback with additional error protection
(try
(callback-fn)
(catch :default inner-e
(log/error :hint "Error in shape callback inner execution"
:shape-id shape-id
:cause inner-e)
;; Attempt to clean up any potential memory corruption
(try
(api/clear-drawing-cache)
(catch :default _cleanup-e
(log/warn :hint "Could not clear cache during error recovery"
:shape-id shape-id))))))
(catch :default e
(log/error :hint "Error in shape callback execution"
:shape-id shape-id
:cause e)
;; Cancel any remaining operations for this shape on critical error
(cancel-pending-operations! shape-id))))
(defn cleanup-shape-operations!
"Public function to cleanup all pending operations for a shape (e.g., when shape is deleted)"
[shape-id]
(cancel-pending-operations! shape-id))
(defn map-entry
[k v]
(cljs.core/MapEntry. k v nil))
@@ -231,47 +286,92 @@
(defn set-wasm-multi-attrs!
[shape properties]
(let [shape-id (dm/get-prop shape :id)]
(when (shape-in-current-page? shape-id)
(api/use-shape shape-id)
(let [result
(->> properties
(mapcat #(set-wasm-single-attr! shape %)))
pending (-> (d/index-by :key :callback result) vals)]
(if (and pending (seq pending))
(->> (rx/from pending)
(rx/mapcat (fn [callback] (callback)))
(rx/reduce conj [])
(rx/subs!
(fn [_]
(api/update-shape-tiles)
(api/clear-drawing-cache)
(api/request-render "set-wasm-attrs-pending"))))
(do
(api/update-shape-tiles)
(api/request-render "set-wasm-attrs")))))))
(when (and shape-id (shape-in-current-page? shape-id))
;; Cancel any existing pending operations for this shape to prevent race conditions
(cancel-pending-operations! shape-id)
(try
(api/use-shape shape-id)
(let [result
(->> properties
(mapcat #(set-wasm-single-attr! shape %)))
pending (-> (d/index-by :key :callback result) vals)]
(if (and pending (seq pending))
(let [subscription
(->> (rx/from pending)
(rx/mapcat (fn [callback] (callback)))
(rx/reduce conj [])
(rx/subs!
(fn [_]
(execute-with-safety-checks shape-id
(fn []
(api/update-shape-tiles)
(api/clear-drawing-cache)
(api/request-render "set-wasm-attrs-pending"))))
(fn [error]
(log/error :hint "Error in async shape operation"
:shape-id shape-id
:cause error))))]
;; Cancel any existing pending operations for this shape
(cancel-pending-operations! shape-id)
;; Track the new operation
(add-pending-operation! shape-id subscription))
(do
(api/update-shape-tiles)
(api/request-render "set-wasm-attrs"))))
(catch :default e
(log/error :hint "Error in set-wasm-multi-attrs!"
:shape-id shape-id
:properties properties
:cause e)
;; Clean up on error
(cancel-pending-operations! shape-id))))))
(defn set-wasm-attrs!
[shape k v]
(let [shape-id (dm/get-prop shape :id)
old-value (get shape k)]
(when (and (shape-in-current-page? shape-id)
(when (and shape-id
(shape-in-current-page? shape-id)
(not (identical? old-value v)))
;; Cancel any existing pending operations for this shape to prevent race conditions
(cancel-pending-operations! shape-id)
(let [shape (assoc shape k v)]
(api/use-shape shape-id)
(let [result (set-wasm-single-attr! shape k)
pending (-> (d/index-by :key :callback result) vals)]
(if (and pending (seq pending))
(->> (rx/from pending)
(rx/mapcat (fn [callback] (callback)))
(rx/reduce conj [])
(rx/subs!
(fn [_]
(api/update-shape-tiles)
(api/clear-drawing-cache)
(api/request-render "set-wasm-attrs-pending"))))
(do
(api/update-shape-tiles)
(api/request-render "set-wasm-attrs"))))))))
(try
(api/use-shape shape-id)
(let [result (set-wasm-single-attr! shape k)
pending (-> (d/index-by :key :callback result) vals)]
(if (and pending (seq pending))
(let [subscription
(->> (rx/from pending)
(rx/mapcat (fn [callback] (callback)))
(rx/reduce conj [])
(rx/subs!
(fn [_]
(execute-with-safety-checks shape-id
(fn []
(api/update-shape-tiles)
(api/clear-drawing-cache)
(api/request-render "set-wasm-attrs-pending"))))
(fn [error]
(log/error :hint "Error in async shape operation"
:shape-id shape-id
:cause error))))]
;; Cancel any existing pending operations for this shape
(cancel-pending-operations! shape-id)
;; Track the new operation
(add-pending-operation! shape-id subscription))
(do
(api/update-shape-tiles)
(api/request-render "set-wasm-attrs"))))
(catch :default e
(log/error :hint "Error in set-wasm-attrs!"
:shape-id shape-id
:attribute k
:cause e)
;; Clean up on error
(cancel-pending-operations! shape-id)))))))
(defn- impl-assoc
[self k v]

View File

@@ -168,8 +168,6 @@ export class TextParagraph {
textDirection = 0;
lineHeight = 1.2;
letterSpacing = 0;
typographyRefFile = UUID.ZERO;
typographyRefId = UUID.ZERO;
constructor(init) {
this.textAlign = init?.textAlign ?? TextAlign.LEFT;
@@ -178,8 +176,6 @@ export class TextParagraph {
this.textDirection = init?.textDirection ?? TextDirection.LTR;
this.lineHeight = init?.lineHeight ?? 1.2;
this.letterSpacing = init?.letterSpacing ?? 0.0;
this.typographyRefFile = init?.typographyRefFile ?? UUID.ZERO;
this.typographyRefId = init?.typographyRefId ?? UUID.ZERO;
this.#leaves = init?.leaves ?? [];
if (
!Array.isArray(this.#leaves) ||

View File

@@ -542,8 +542,6 @@ pub struct Paragraph {
text_transform: Option<TextTransform>,
line_height: f32,
letter_spacing: f32,
typography_ref_file: Uuid,
typography_ref_id: Uuid,
children: Vec<TextSpan>,
}
@@ -556,8 +554,6 @@ impl Default for Paragraph {
text_transform: None,
line_height: 1.0,
letter_spacing: 0.0,
typography_ref_file: Uuid::nil(),
typography_ref_id: Uuid::nil(),
children: vec![],
}
}
@@ -572,8 +568,6 @@ impl Paragraph {
text_transform: Option<TextTransform>,
line_height: f32,
letter_spacing: f32,
typography_ref_file: Uuid,
typography_ref_id: Uuid,
children: Vec<TextSpan>,
) -> Self {
Self {
@@ -583,8 +577,6 @@ impl Paragraph {
text_transform,
line_height,
letter_spacing,
typography_ref_file,
typography_ref_id,
children,
}
}
@@ -695,13 +687,8 @@ impl TextSpan {
paint = merge_fills(&self.fills, *content_bounds);
}
// FIXME
if self.line_height <= 0.0 {
style.set_height(paragraph_line_height);
} else {
style.set_height(self.line_height);
}
let max_line_height = f32::max(paragraph_line_height, self.line_height);
style.set_height(max_line_height);
style.set_height_override(true);
style.set_foreground_paint(&paint);
style.set_decoration_type(match self.text_decoration {

View File

@@ -101,8 +101,6 @@ pub struct RawParagraphData {
text_transform: RawTextTransform,
line_height: f32,
letter_spacing: f32,
typography_ref_file: [u32; 4],
typography_ref_id: [u32; 4],
}
impl From<[u8; RAW_PARAGRAPH_DATA_SIZE]> for RawParagraphData {
@@ -226,9 +224,6 @@ impl TryFrom<&Vec<u8>> for RawParagraph {
impl From<RawParagraph> for shapes::Paragraph {
fn from(value: RawParagraph) -> Self {
let typography_ref_file = uuid_from_u32(value.attrs.typography_ref_file);
let typography_ref_id = uuid_from_u32(value.attrs.typography_ref_id);
let mut spans = vec![];
let mut offset = 0;
@@ -252,8 +247,6 @@ impl From<RawParagraph> for shapes::Paragraph {
value.attrs.text_transform.into(),
value.attrs.line_height,
value.attrs.letter_spacing,
typography_ref_file,
typography_ref_id,
spans,
)
}