penpot/common/src/app/common/buffer.cljc
2026-08-25 11:55:10 +02:00

289 lines
9.4 KiB
Clojure

;; 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 app.common.buffer
"A collection of helpers and macros for work with byte
buffer (ByteBuffer on JVM and DataView on JS)."
(:refer-clojure :exclude [clone])
(:require
[app.common.uuid :as uuid])
#?(:cljs
(:require-macros [app.common.buffer])
:clj
(:import [java.nio ByteBuffer ByteOrder])))
(defmacro read-byte
[target offset]
(if (:ns &env)
`(.getInt8 ~target ~offset true)
(let [target (with-meta target {:tag 'java.nio.ByteBuffer})]
`(long (.get ~target (unchecked-int ~offset))))))
(defmacro read-unsigned-byte
[target offset]
(if (:ns &env)
`(.getUint8 ~target ~offset true)
(let [target (with-meta target {:tag 'java.nio.ByteBuffer})]
`(bit-and (long (.get ~target (unchecked-int ~offset))) 0xff))))
(defmacro read-bool
[target offset]
(if (:ns &env)
`(== 1 (.getInt8 ~target ~offset true))
(let [target (with-meta target {:tag 'java.nio.ByteBuffer})]
`(== 1 (.get ~target (unchecked-int ~offset))))))
(defmacro read-short
[target offset]
(if (:ns &env)
`(.getInt16 ~target ~offset true)
(let [target (with-meta target {:tag 'java.nio.ByteBuffer})]
`(.getShort ~target (unchecked-int ~offset)))))
(defmacro read-int
[target offset]
(if (:ns &env)
`(.getInt32 ~target ~offset true)
(let [target (with-meta target {:tag 'java.nio.ByteBuffer})]
`(long (.getInt ~target (unchecked-int ~offset))))))
(defmacro read-long
[target offset]
(if (:ns &env)
`(.getInt64 ~target ~offset true)
(let [target (with-meta target {:tag 'java.nio.ByteBuffer})]
`(.getLong ~target (unchecked-int ~offset)))))
(defmacro read-float
[target offset]
(if (:ns &env)
`(.getFloat32 ~target ~offset true)
(let [target (with-meta target {:tag 'java.nio.ByteBuffer})]
`(double (.getFloat ~target (unchecked-int ~offset))))))
(defmacro read-uuid
[target offset]
(if (:ns &env)
`(let [a# (.getUint32 ~target (+ ~offset 0) true)
b# (.getUint32 ~target (+ ~offset 4) true)
c# (.getUint32 ~target (+ ~offset 8) true)
d# (.getUint32 ~target (+ ~offset 12) true)]
(uuid/from-unsigned-parts a# b# c# d#))
(let [target (with-meta target {:tag 'java.nio.ByteBuffer})]
`(try
(.order ~target ByteOrder/BIG_ENDIAN)
(let [msb# (.getLong ~target (unchecked-int (+ ~offset 0)))
lsb# (.getLong ~target (unchecked-int (+ ~offset 8)))]
(java.util.UUID. (long msb#) (long lsb#)))
(finally
(.order ~target ByteOrder/LITTLE_ENDIAN))))))
(defmacro read-bytes
"Get a byte array from buffer. It is potentially unsafe because on
JS/CLJS it returns a subarray without doing any copy of data."
[target offset size]
(if (:ns &env)
`(new js/Uint8Array
(.-buffer ~target)
(+ (.-byteOffset ~target) ~offset)
~size)
(let [target (with-meta target {:tag 'java.nio.ByteBuffer})
bbuf (with-meta (gensym "bbuf") {:tag bytes})]
`(let [~bbuf (byte-array ~size)]
(.get ~target
(unchecked-int ~offset)
~bbuf
0
~size)
~bbuf))))
;; FIXME: implement in cljs
(defmacro write-bytes
([target offset src size]
`(write-bytes ~target ~offset ~src 0 ~size))
([target offset src src-offset size]
(if (:ns &env)
(throw (ex-info "not implemented" {}))
(let [target (with-meta target {:tag 'java.nio.ByteBuffer})
src (with-meta src {:tag 'bytes})]
`(.put ~target
(unchecked-int ~offset)
~src
(unchecked-int ~src-offset)
(unchecked-int ~size))))))
(defmacro write-byte
[target offset value]
(if (:ns &env)
`(.setInt8 ~target ~offset ~value true)
(let [target (with-meta target {:tag 'java.nio.ByteBuffer})]
`(.put ~target (unchecked-int ~offset) (unchecked-byte ~value)))))
(defmacro write-u8
[target offset value]
(if (:ns &env)
`(.setUint8 ~target ~offset ~value true)
(let [target (with-meta target {:tag 'java.nio.ByteBuffer})]
`(.put ~target ~offset (unchecked-byte ~value)))))
(defmacro write-bool
[target offset value]
(if (:ns &env)
`(.setInt8 ~target ~offset (if ~value 0x01 0x00) true)
(let [target (with-meta target {:tag 'java.nio.ByteBuffer})]
`(.put ~target (unchecked-int ~offset) (unchecked-byte (if ~value 0x01 0x00))))))
(defmacro write-short
[target offset value]
(if (:ns &env)
`(.setInt16 ~target ~offset ~value true)
(let [target (with-meta target {:tag 'java.nio.ByteBuffer})]
`(.putShort ~target (unchecked-int ~offset) (unchecked-short ~value)))))
(defmacro write-int
[target offset value]
(if (:ns &env)
`(.setInt32 ~target ~offset ~value true)
(let [target (with-meta target {:tag 'java.nio.ByteBuffer})]
`(.putInt ~target (unchecked-int ~offset) (unchecked-int ~value)))))
(defmacro write-u32
[target offset value]
(if (:ns &env)
`(.setUint32 ~target ~offset ~value true)
(let [target (with-meta target {:tag 'java.nio.ByteBuffer})]
`(.putInt ~target ~offset (unchecked-int ~value)))))
(defmacro write-i32
"Idiomatic alias for `write-int`"
[target offset value]
`(write-int ~target ~offset ~value))
(defmacro write-float
[target offset value]
(if (:ns &env)
`(.setFloat32 ~target ~offset ~value true)
(let [target (with-meta target {:tag 'java.nio.ByteBuffer})]
`(.putFloat ~target (unchecked-int ~offset) (unchecked-float ~value)))))
(defmacro write-f32
"Idiomatic alias for `write-float`."
[target offset value]
`(write-float ~target ~offset ~value))
(defmacro write-uuid
[target offset value]
(if (:ns &env)
`(let [barray# (uuid/get-u32 ~value)]
(.setUint32 ~target (+ ~offset 0) (aget barray# 0) true)
(.setUint32 ~target (+ ~offset 4) (aget barray# 1) true)
(.setUint32 ~target (+ ~offset 8) (aget barray# 2) true)
(.setUint32 ~target (+ ~offset 12) (aget barray# 3) true))
(let [target (with-meta target {:tag 'java.nio.ByteBuffer})
value (with-meta value {:tag 'java.util.UUID})
prev (with-meta (gensym "prev-") {:tag 'java.nio.ByteOrder})]
`(let [~prev (.order ~target)]
(try
(.order ~target ByteOrder/BIG_ENDIAN)
(.putLong ~target (unchecked-int (+ ~offset 0)) (.getMostSignificantBits ~value))
(.putLong ~target (unchecked-int (+ ~offset 8)) (.getLeastSignificantBits ~value))
(finally
(.order ~target ~prev)))))))
(defn wrap
[data]
#?(:clj (let [buffer (ByteBuffer/wrap ^bytes data)]
(.order buffer ByteOrder/LITTLE_ENDIAN))
:cljs (new js/DataView (.-buffer ^js data))))
(defn allocate
[size]
#?(:clj (let [buffer (ByteBuffer/allocate (unchecked-int size))]
(.order buffer ByteOrder/LITTLE_ENDIAN))
:cljs (new js/DataView (new js/ArrayBuffer size))))
(defn clone
[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-off (.-byteOffset ^js/DataView buffer)
src-len (.-byteLength ^js/DataView buffer)
src-buf (.-buffer ^js/DataView buffer)
src-view (js/Uint8Array. src-buf src-off src-len)
dst-buff (js/ArrayBuffer. src-len)
dst-view (js/Uint8Array. dst-buff)]
(.set dst-view src-view)
(js/DataView. dst-buff))))
;; FIXME: cljs impl
#?(:clj
(defn copy-bytes
[src src-offset size dst dst-offset]
(let [tmp (byte-array size)]
(.get ^ByteBuffer src src-offset tmp 0 size)
(.put ^ByteBuffer dst dst-offset tmp 0 size))))
(defn equals?
[buffer-a buffer-b]
#?(:clj
(.equals ^ByteBuffer buffer-a
^ByteBuffer buffer-b)
:cljs
(let [len-a (.-byteLength ^js/DataView buffer-a)
len-b (.-byteLength ^js/DataView buffer-b)]
(if (= len-a len-b)
(let [cb (js/Uint8Array. (.-buffer ^js/DataView buffer-a)
(.-byteOffset ^js/DataView buffer-a)
len-a)
ob (js/Uint8Array. (.-buffer ^js/DataView buffer-b)
(.-byteOffset ^js/DataView buffer-b)
len-b)
sz (alength cb)]
(loop [i 0]
(if (< i sz)
(if (== (aget ob i)
(aget cb i))
(recur (inc i))
false)
true)))
false))))
(defn buffer?
[o]
#?(:clj (instance? ByteBuffer o)
:cljs (instance? js/DataView o)))
(defn slice
[buffer offset size]
#?(:cljs
(let [offset (+ (.-byteOffset buffer) offset)]
(new js/DataView (.-buffer buffer) offset size))
:clj
(-> (.slice ^ByteBuffer buffer (unchecked-int offset) (unchecked-int size))
(.order ByteOrder/LITTLE_ENDIAN))))
(defn size
[o]
#?(:cljs (.-byteLength ^js o)
:clj (.capacity ^ByteBuffer o)))
#?(:clj
(defn set-order
"Set the byte order on a ByteBuffer. Returns the buffer."
[^ByteBuffer buffer ^ByteOrder order]
(.order buffer order)
buffer))