♻️ Use LITTLE_ENDIAN instead of BIG_ENDIAND for path encoding

This commit is contained in:
Andrey Antukh 2025-04-17 14:26:30 +02:00
parent b48faf8fe0
commit c6f68e6ed1
4 changed files with 59 additions and 56 deletions

View file

@ -22,7 +22,7 @@
[app.common.types.path :as-alias path]) [app.common.types.path :as-alias path])
(:import (:import
#?(:cljs [goog.string StringBuffer] #?(:cljs [goog.string StringBuffer]
:clj [java.nio ByteBuffer]))) :clj [java.nio ByteBuffer ByteOrder])))
#?(:clj (set! *warn-on-reflection* true)) #?(:clj (set! *warn-on-reflection* true))
@ -45,33 +45,29 @@
(defmacro read-short (defmacro read-short
[target offset] [target offset]
(if (:ns &env) (if (:ns &env)
`(.getInt16 ~target ~offset) `(.getInt16 ~target ~offset true)
(let [target (with-meta target {:tag 'java.nio.ByteBuffer}) (let [target (with-meta target {:tag 'java.nio.ByteBuffer})]
offset (with-meta target {:tag 'int})]
`(.getShort ~target ~offset)))) `(.getShort ~target ~offset))))
(defmacro read-float (defmacro read-float
[target offset] [target offset]
(if (:ns &env) (if (:ns &env)
`(.getFloat32 ~target ~offset) `(.getFloat32 ~target ~offset true)
(let [target (with-meta target {:tag 'java.nio.ByteBuffer}) (let [target (with-meta target {:tag 'java.nio.ByteBuffer})]
offset (with-meta target {:tag 'int})] `(double (.getFloat ~target ~offset)))))
`(.getFloat ~target ~offset))))
(defmacro write-float (defmacro write-float
[target offset value] [target offset value]
(if (:ns &env) (if (:ns &env)
`(.setFloat32 ~target ~offset ~value) `(.setFloat32 ~target ~offset ~value true)
(let [target (with-meta target {:tag 'java.nio.ByteBuffer}) (let [target (with-meta target {:tag 'java.nio.ByteBuffer})]
offset (with-meta target {:tag 'int})]
`(.putFloat ~target ~offset ~value)))) `(.putFloat ~target ~offset ~value))))
(defmacro write-short (defmacro write-short
[target offset value] [target offset value]
(if (:ns &env) (if (:ns &env)
`(.setInt16 ~target ~offset ~value) `(.setInt16 ~target ~offset ~value true)
(let [target (with-meta target {:tag 'java.nio.ByteBuffer}) (let [target (with-meta target {:tag 'java.nio.ByteBuffer})]
offset (with-meta target {:tag 'int})]
`(.putShort ~target ~offset ~value)))) `(.putShort ~target ~offset ~value))))
(defmacro with-cache (defmacro with-cache
@ -91,6 +87,28 @@
~'result)))) ~'result))))
`(do ~@expr))) `(do ~@expr)))
(defn- allocate
[n-segments]
#?(:clj (let [buffer (ByteBuffer/allocate (* n-segments SEGMENT-BYTE-SIZE))]
(.order buffer ByteOrder/LITTLE_ENDIAN))
:cljs (new js/ArrayBuffer (* n-segments SEGMENT-BYTE-SIZE))))
(defn- clone-buffer
[buffer]
#?(:clj
(let [src (.array ^ByteBuffer buffer)
len (alength ^bytes src)
dst (byte-array len)]
(System/arraycopy src 0 dst 0 len)
(let [buffer (ByteBuffer/wrap dst)]
(.order buffer ByteOrder/LITTLE_ENDIAN)))
:cljs
(let [src-view (js/Uint32Array. buffer)
dst-buff (js/ArrayBuffer. (.-byteLength buffer))
dst-view (js/Uint32Array. dst-buff)]
(.set dst-view src-view)
dst-buff)))
(defn- impl-transform-segment (defn- impl-transform-segment
"Apply a transformation to a segment located under specified offset" "Apply a transformation to a segment located under specified offset"
[buffer offset a b c d e f] [buffer offset a b c d e f]
@ -302,21 +320,6 @@
[size i] [size i]
(and (< i size) (>= i 0))) (and (< i size) (>= i 0)))
(defn- clone-buffer
[buffer]
#?(:clj
(let [src (.array ^ByteBuffer buffer)
len (alength ^bytes src)
dst (byte-array len)]
(System/arraycopy src 0 dst 0 len)
(ByteBuffer/wrap dst))
:cljs
(let [src-view (js/Uint32Array. buffer)
dst-buff (js/ArrayBuffer. (.-byteLength buffer))
dst-view (js/Uint32Array. dst-buff)]
(.set dst-view src-view)
dst-buff)))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; TYPE: PATH-DATA ;; TYPE: PATH-DATA
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@ -628,17 +631,18 @@
#?(:clj #?(:clj
(cond (cond
(instance? ByteBuffer buffer) (instance? ByteBuffer buffer)
(let [size (.capacity ^ByteBuffer buffer) (let [size (.capacity ^ByteBuffer buffer)
count (long (/ size SEGMENT-BYTE-SIZE))] count (long (/ size SEGMENT-BYTE-SIZE))
buffer (.order ^ByteBuffer buffer ByteOrder/LITTLE_ENDIAN)]
(PathData. count buffer nil)) (PathData. count buffer nil))
(bytes? buffer) (bytes? buffer)
(let [size (alength ^bytes buffer) (let [size (alength ^bytes buffer)
count (long (/ size SEGMENT-BYTE-SIZE))] count (long (/ size SEGMENT-BYTE-SIZE))
buffer (ByteBuffer/wrap buffer)]
(PathData. count (PathData. count
(ByteBuffer/wrap buffer) (.order buffer ByteOrder/LITTLE_ENDIAN)
nil)) nil))
:else :else
(throw (java.lang.IllegalArgumentException. "invalid data provided"))) (throw (java.lang.IllegalArgumentException. "invalid data provided")))
@ -678,9 +682,9 @@
(assert (check-segments segments)) (assert (check-segments segments))
(let [total (count segments) (let [total (count segments)
#?@(:cljs [buffer' (new js/ArrayBuffer (* total SEGMENT-BYTE-SIZE)) #?@(:cljs [buffer' (allocate total)
buffer (new js/DataView buffer')] buffer (new js/DataView buffer')]
:clj [buffer (ByteBuffer/allocate (* total SEGMENT-BYTE-SIZE))])] :clj [buffer (allocate total)])]
(loop [index 0] (loop [index 0]
(when (< index total) (when (< index total)
(let [segment (nth segments index) (let [segment (nth segments index)

View file

@ -69,10 +69,10 @@
{:command :close-path :params {}}]) {:command :close-path :params {}}])
(def sample-bytes (def sample-bytes
[0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 67 -16 0 0 68 81 -64 0 [1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -16 67 0 -64 81 68
0 2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 67 -37 -128 0 68 72 -128 0 2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -128 -37 67 0 -128 72 68
0 3 0 0 67 -72 0 0 68 56 64 0 67 -101 0 0 68 42 64 0 67 -124 0 0 68 30 -128 0 3 0 0 0 0 0 -72 67 0 64 56 68 0 0 -101 67 0 64 42 68 0 0 -124 67 0 -128 30 68
0 4 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]) 4 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0])
;; This means it implements IReduceInit/IReduce protocols ;; This means it implements IReduceInit/IReduce protocols
(t/deftest path-data-to-vector (t/deftest path-data-to-vector
@ -116,10 +116,11 @@
(t/deftest path-data-transit-roundtrip (t/deftest path-data-transit-roundtrip
(let [pdata (path/content sample-content) (let [pdata (path/content sample-content)
result1 (trans/encode-str pdata) result1 (trans/encode-str pdata)
expected (str "[\"~#penpot/path-data\",\"~bAAEAAAAAAAAAAAAAAAAAAAAAAA" expected (str "[\"~#penpot/path-data\",\"~bAQAAAAAAAAAAAAA"
"BD8AAARFHAAAACAAAAAAAAAAAAAAAAAAAAAAAAQ9uAAERIgAAAAwAA" "AAAAAAAAAAAAAAPBDAMBRRAIAAAAAAAAAAAAAAAAAAA"
"Q7gAAEQ4QABDmwAARCpAAEOEAABEHoAAAAQAAAAAAAAAAAAAAAAAAA" "AAAAAAAIDbQwCASEQDAAAAAAC4QwBAOEQAAJtDAEAqR"
"AAAAAAAAAAAAAAAA==\"]") "AAAhEMAgB5EBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
"AAAAAA==\"]")
result2 (trans/decode-str result1)] result2 (trans/decode-str result1)]
(t/is (= expected result1)) (t/is (= expected result1))
(t/is (= pdata result2)))) (t/is (= pdata result2))))
@ -352,7 +353,6 @@
(t/is (= result5 expect2)) (t/is (= result5 expect2))
(t/is (= result6 expect3)))) (t/is (= result6 expect3))))
(defn get-handlers (defn get-handlers
"Retrieve a map where for every point will retrieve a list of "Retrieve a map where for every point will retrieve a list of
the handlers that are associated with that point. the handlers that are associated with that point.
@ -375,7 +375,6 @@
(t/deftest content-to-handlers (t/deftest content-to-handlers
(let [content (path/content sample-content-large) (let [content (path/content sample-content-large)
result1 (get-handlers content) result1 (get-handlers sample-content-large)
result2 (path.segment/get-handlers content)] result2 (path.segment/get-handlers content)]
(t/is (= result1 result2)))) (t/is (= result1 result2))))

View file

@ -448,7 +448,7 @@
}, },
"~:rotation": 0, "~:rotation": 0,
"~:grow-type": "~:fixed", "~:grow-type": "~:fixed",
"~:content": ["~#penpot/path-data","~bAAEAAAAAAAAAAAAAAAAAAAAAAABEjCAARAqAAAACAAAAAAAAAAAAAAAAAAAAAAAARJmgAEPlAAAAAwAARJmgAEPlAABEpQAAQ/YAAESi4ABEAYAAAAMAAESgwABECAAARJ1AAEQPAABEmGAARBIAAAADAABEk4AARBUAAESMIABECoAARIwgAEQKgAA="], "~:content": ["~#penpot/path-data","~bAQAAAAAAAAAAAAAAAAAAAAAAAAAAIIxEAIAKRAIAAAAAAAAAAAAAAAAAAAAAAAAAAKCZRAAA5UMDAAAAAKCZRAAA5UMAAKVEAAD2QwDgokQAgAFEAwAAAADAoEQAAAhEAECdRAAAD0QAYJhEAAASRAMAAAAAgJNEAAAVRAAgjEQAgApEACCMRACACkQ="],
"~:name": "Path", "~:name": "Path",
"~:width": null, "~:width": null,
"~:type": "~:path", "~:type": "~:path",

View file

@ -14,26 +14,26 @@ pub struct RawPathData {
impl RawPathData { impl RawPathData {
fn command(&self) -> Result<u16, String> { fn command(&self) -> Result<u16, String> {
let cmd = u16::from_be_bytes(self.data[0..2].try_into().map_err(stringify_slice_err)?); let cmd = u16::from_le_bytes(self.data[0..2].try_into().map_err(stringify_slice_err)?);
Ok(cmd) Ok(cmd)
} }
fn xy(&self) -> Result<Point, String> { fn xy(&self) -> Result<Point, String> {
let x = f32::from_be_bytes(self.data[20..24].try_into().map_err(stringify_slice_err)?); let x = f32::from_le_bytes(self.data[20..24].try_into().map_err(stringify_slice_err)?);
let y = f32::from_be_bytes(self.data[24..].try_into().map_err(stringify_slice_err)?); let y = f32::from_le_bytes(self.data[24..].try_into().map_err(stringify_slice_err)?);
Ok((x, y)) Ok((x, y))
} }
fn c1(&self) -> Result<Point, String> { fn c1(&self) -> Result<Point, String> {
let c1_x = f32::from_be_bytes(self.data[4..8].try_into().map_err(stringify_slice_err)?); let c1_x = f32::from_le_bytes(self.data[4..8].try_into().map_err(stringify_slice_err)?);
let c1_y = f32::from_be_bytes(self.data[8..12].try_into().map_err(stringify_slice_err)?); let c1_y = f32::from_le_bytes(self.data[8..12].try_into().map_err(stringify_slice_err)?);
Ok((c1_x, c1_y)) Ok((c1_x, c1_y))
} }
fn c2(&self) -> Result<Point, String> { fn c2(&self) -> Result<Point, String> {
let c2_x = f32::from_be_bytes(self.data[12..16].try_into().map_err(stringify_slice_err)?); let c2_x = f32::from_le_bytes(self.data[12..16].try_into().map_err(stringify_slice_err)?);
let c2_y = f32::from_be_bytes(self.data[16..20].try_into().map_err(stringify_slice_err)?); let c2_y = f32::from_le_bytes(self.data[16..20].try_into().map_err(stringify_slice_err)?);
Ok((c2_x, c2_y)) Ok((c2_x, c2_y))
} }