diff --git a/backend/deps.edn b/backend/deps.edn index b69cfff2b..b37b71a22 100644 --- a/backend/deps.edn +++ b/backend/deps.edn @@ -47,7 +47,6 @@ org.lz4/lz4-java {:mvn/version "1.8.0"} org.clojars.pntblnk/clj-ldap {:mvn/version "0.0.17"} - integrant/integrant {:mvn/version "0.8.1"} dawran6/emoji {:mvn/version "0.1.5"} markdown-clj/markdown-clj {:mvn/version "1.11.4"} diff --git a/backend/src/app/features/components_v2.clj b/backend/src/app/features/components_v2.clj index 15017d298..2fbd02d1a 100644 --- a/backend/src/app/features/components_v2.clj +++ b/backend/src/app/features/components_v2.clj @@ -45,8 +45,6 @@ [datoteka.io :as io] [promesa.exec.semaphore :as ps])) -;; - What about use of svgo on converting graphics to components - ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; END PROMESA HELPERS ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; @@ -443,10 +441,15 @@ (defn- create-shapes-for-svg [{:keys [id] :as mobj} file-id objects position] - (let [svg-text (get-svg-content id) - svg-data (-> (csvg/parse svg-text) - (assoc :name (:name mobj)) - (collect-and-persist-images file-id))] + (let [svg-text (get-svg-content id) + + optimizer (::csvg/optimizer *system*) + svg-text (csvg/optimize optimizer svg-text) + + svg-data (-> (csvg/parse svg-text) + (assoc :name (:name mobj)) + (collect-and-persist-images file-id))] + (sbuilder/create-svg-shapes svg-data position objects uuid/zero nil #{} false))) (defn- process-media-object diff --git a/backend/src/app/main.clj b/backend/src/app/main.clj index 3a0bee728..f0d8eff2a 100644 --- a/backend/src/app/main.clj +++ b/backend/src/app/main.clj @@ -10,6 +10,7 @@ [app.auth.oidc :as-alias oidc] [app.auth.oidc.providers :as-alias oidc.providers] [app.common.logging :as l] + [app.common.svg :as csvg] [app.config :as cf] [app.db :as-alias db] [app.email :as-alias email] @@ -412,6 +413,9 @@ ;; module requires the migrations to run before initialize. ::migrations (ig/ref :app.migrations/migrations)} + ::csvg/optimizer + {} + ::audit.tasks/archive {::props (ig/ref ::setup/props) ::db/pool (ig/ref ::db/pool) diff --git a/backend/src/app/media.clj b/backend/src/app/media.clj index 9964c2824..a9a559e5b 100644 --- a/backend/src/app/media.clj +++ b/backend/src/app/media.clj @@ -14,11 +14,11 @@ [app.common.schema.generators :as sg] [app.common.schema.openapi :as-alias oapi] [app.common.spec :as us] + [app.common.svg :as csvg] [app.config :as cf] [app.db :as-alias db] [app.storage :as-alias sto] [app.storage.tmp :as tmp] - [app.util.svg :as svg] [app.util.time :as dt] [buddy.core.bytes :as bb] [buddy.core.codecs :as bc] @@ -201,7 +201,7 @@ (us/assert ::input input) (let [{:keys [path mtype]} input] (if (= mtype "image/svg+xml") - (let [info (some-> path slurp svg/pre-process svg/parse get-basic-info-from-svg)] + (let [info (some-> path slurp csvg/parse get-basic-info-from-svg)] (when-not info (ex/raise :type :validation :code :invalid-svg-file diff --git a/backend/src/app/util/svg.clj b/backend/src/app/util/svg.clj deleted file mode 100644 index 5647b1662..000000000 --- a/backend/src/app/util/svg.clj +++ /dev/null @@ -1,51 +0,0 @@ -;; 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 INC - -(ns app.util.svg - (:require - [app.common.data.macros :as dm] - [app.common.exceptions :as ex] - [app.common.logging :as l] - [clojure.xml :as xml] - [cuerdas.core :as str]) - (:import - javax.xml.XMLConstants - java.io.InputStream - javax.xml.parsers.SAXParserFactory - clojure.lang.XMLHandler - org.apache.commons.io.IOUtils)) - -(defn- secure-parser-factory - [^InputStream input ^XMLHandler handler] - (.. (doto (SAXParserFactory/newInstance) - (.setFeature XMLConstants/FEATURE_SECURE_PROCESSING true) - (.setFeature "http://apache.org/xml/features/disallow-doctype-decl" true)) - (newSAXParser) - (parse input handler))) - -(defn parse - [^String data] - (try - (dm/with-open [istream (IOUtils/toInputStream data "UTF-8")] - (xml/parse istream secure-parser-factory)) - (catch Exception e - (l/warn :hint "error on processing svg" - :message (ex-message e)) - (ex/raise :type :validation - :code :invalid-svg-file - :hint "invalid svg file" - :cause e)))) - - -;; --- PROCESSORS - -(defn strip-doctype - [data] - (cond-> data - (str/includes? data "]*>" ""))) - -(def pre-process strip-doctype) diff --git a/common/deps.edn b/common/deps.edn index 09d1e91be..8ffce485b 100644 --- a/common/deps.edn +++ b/common/deps.edn @@ -25,6 +25,10 @@ com.cognitect/transit-clj {:mvn/version "1.0.333"} com.cognitect/transit-cljs {:mvn/version "0.8.280"} java-http-clj/java-http-clj {:mvn/version "0.4.3"} + integrant/integrant {:mvn/version "0.8.1"} + + org.apache.commons/commons-pool2 {:mvn/version "2.12.0"} + org.graalvm.js/js {:mvn/version "23.0.1"} funcool/tubax {:mvn/version "2021.05.20-0"} funcool/cuerdas {:mvn/version "2022.06.16-403"} diff --git a/common/src/app/common/jsrt.clj b/common/src/app/common/jsrt.clj new file mode 100644 index 000000000..034ecfba0 --- /dev/null +++ b/common/src/app/common/jsrt.clj @@ -0,0 +1,77 @@ +;; 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 INC + +(ns app.common.jsrt + "A JS runtime for the JVM" + (:refer-clojure :exclude [run!]) + (:require + [clojure.java.io :as io]) + (:import + org.apache.commons.pool2.ObjectPool + org.apache.commons.pool2.PooledObject + org.apache.commons.pool2.PooledObjectFactory + org.apache.commons.pool2.impl.DefaultPooledObject + org.apache.commons.pool2.impl.SoftReferenceObjectPool + org.graalvm.polyglot.Context + org.graalvm.polyglot.Source + org.graalvm.polyglot.Value)) + +(defn resource->source + [path] + (let [resource (io/resource path)] + (.. (Source/newBuilder "js" resource) + (build)))) + +(defn pool? + [o] + (instance? ObjectPool o)) + +(defn pool + [& {:keys [init]}] + (SoftReferenceObjectPool. + (reify PooledObjectFactory + (activateObject [_ _]) + (destroyObject [_ o] + (let [context (.getObject ^PooledObject o)] + (.close ^java.lang.AutoCloseable context))) + + (destroyObject [_ o _] + (let [context (.getObject ^PooledObject o)] + (.close ^java.lang.AutoCloseable context))) + + (passivateObject [_ _]) + (validateObject [_ _] true) + + (makeObject [_] + (let [context (Context/create (into-array String ["js"]))] + (.initialize ^Context context "js") + (when (instance? Source init) + (.eval ^Context context ^Source init)) + (DefaultPooledObject. context)))))) + +(defn run! + [^ObjectPool pool f] + (let [ctx (.borrowObject pool)] + (try + (f ctx) + (finally + (.returnObject pool ctx))))) + +(defn eval! + [context data & {:keys [as] :or {as :string}}] + (let [result (.eval ^Context context "js" ^String data)] + (case as + (:string :str) (.asString ^Value result) + :long (.asLong ^Value result) + :int (.asInt ^Value result) + :float (.asFloat ^Value result) + :double (.asDouble ^Value result)))) + +(defn set! + [context attr value] + (let [bindings (.getBindings ^Context context "js")] + (.putMember ^Value bindings ^String attr ^String value) + context)) diff --git a/common/src/app/common/svg.cljc b/common/src/app/common/svg.cljc index 9c1e2b6c5..39379028f 100644 --- a/common/src/app/common/svg.cljc +++ b/common/src/app/common/svg.cljc @@ -9,6 +9,9 @@ #?(:cljs ["./svg/optimizer.js" :as svgo]) #?(:clj [clojure.xml :as xml] :cljs [tubax.core :as tubax]) + #?(:clj [integrant.core :as ig]) + #?(:clj [app.common.jsrt :as jsrt]) + #?(:clj [app.common.logging :as l]) [app.common.data :as d] [app.common.data.macros :as dm] [app.common.geom.matrix :as gmt] @@ -19,10 +22,10 @@ [cuerdas.core :as str]) #?(:clj (:import - javax.xml.XMLConstants - java.io.InputStream - javax.xml.parsers.SAXParserFactory clojure.lang.XMLHandler + java.io.InputStream + javax.xml.XMLConstants + javax.xml.parsers.SAXParserFactory org.apache.commons.io.IOUtils))) @@ -1054,7 +1057,16 @@ (defn optimize ([input] (optimize input nil)) ([input options] - (svgo/optimize input (clj->js options))))) + (svgo/optimize input (clj->js options)))) + :clj + (defn optimize + [pool data] + (dm/assert! "expected a valid pool" (jsrt/pool? pool)) + (dm/assert! "expect data to be a string" (string? data)) + (jsrt/run! pool + (fn [context] + (jsrt/set! context "svgData" data) + (jsrt/eval! context "penpotSvgo.optimize(svgData, {})"))))) #?(:clj (defn- secure-parser-factory @@ -1078,3 +1090,16 @@ :clj (let [text (strip-doctype text)] (dm/with-open [istream (IOUtils/toInputStream text "UTF-8")] (xml/parse istream secure-parser-factory))))) + +#?(:clj + (defmethod ig/init-key ::optimizer + [_ _] + (l/info :hint "initializing svg optimizer pool") + (let [init (jsrt/resource->source "app/common/svg/optimizer.js")] + (jsrt/pool :init init)))) + +#?(:clj + (defmethod ig/halt-key! ::optimizer + [_ pool] + (l/info :hint "stopping svg optimizer pool") + (.close ^java.lang.AutoCloseable pool)))