mirror of
https://github.com/penpot/penpot.git
synced 2025-12-23 22:48:40 -05:00
Compare commits
3 Commits
develop
...
elenatorro
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b3bd7e7a28 | ||
|
|
e67d6bc7dc | ||
|
|
5690ea766f |
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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) ||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user