;; 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.rasterizer "A main entry point for the rasterizer process that is executed on a separated iframe." (:require [app.common.data :as d] [app.common.data.macros :as dm] [app.common.exceptions :as ex] [app.common.logging :as log] [app.config :as cf] [app.util.dom :as dom] [app.util.http :as http] [app.util.object :as obj] [app.util.webapi :as wapi] [beicon.v2.core :as rx] [cuerdas.core :as str])) (log/set-level! :info) (declare send-success!) (declare send-failure!) (defonce data-uri-cache (js/Map.)) (defonce parent-origin (dm/str cf/public-uri)) (defn- get-document-element [^js svg] (.-documentElement svg)) (defn- create-image [uri] (rx/create (fn [subs] (let [image (js/Image.)] (obj/set! image "onload" #(do (rx/push! subs image) (rx/end! subs))) (obj/set! image "crossOrigin" "anonymous") (obj/set! image "onerror" #(rx/error! subs %)) (obj/set! image "onabort" #(rx/error! subs (ex/error :type :internal :code :abort :hint "operation aborted"))) (obj/set! image "src" uri) (fn [] (obj/set! image "src" "") (obj/set! image "onload" nil) (obj/set! image "onerror" nil) (obj/set! image "onabort" nil)))))) (defn- svg-get-adjusted-size "Returns the adjusted size of an SVG." [width height max] (let [ratio (/ width height)] (if (< width height) [max (* max (/ 1 ratio))] [(* max ratio) max]))) (defn- svg-get-size-from-viewbox "Returns the size of an SVG from its viewbox." [svg max] (let [doc (get-document-element svg) vbox (dom/get-attribute doc "viewBox")] (when (string? vbox) (let [[_ _ width height] (str/split vbox #"\s+") width (d/parse-integer width 0) height (d/parse-integer height 0)] (svg-get-adjusted-size width height max))))) (defn- svg-get-size-from-intrinsic-size "Returns the size of an SVG from its intrinsic size." [svg max] (let [doc (get-document-element svg) width (dom/get-attribute doc "width") height (dom/get-attribute doc "height") width (d/parse-integer width 0) height (d/parse-integer height 0)] (svg-get-adjusted-size width height max))) (defn- svg-has-intrinsic-size? "Returns true if the SVG has an intrinsic size." [svg] (let [doc (get-document-element svg) width (dom/get-attribute doc "width") height (dom/get-attribute doc "height")] (d/num? width height))) (defn- svg-get-size [svg max] (if (svg-has-intrinsic-size? svg) (svg-get-size-from-intrinsic-size svg max) (svg-get-size-from-viewbox svg max))) (defn- svg-set-intrinsic-size! "Sets the intrinsic size of an SVG to the given max size." [^js svg max] (let [doc (get-document-element svg) [w h] (svg-get-size svg max)] (dom/set-attribute! doc "width" (dm/str w)) (dom/set-attribute! doc "height" (dm/str h))) svg) (defn- fetch-as-data-uri "Fetches a URL as a Data URI." [uri] (if (.has data-uri-cache uri) (let [blob (.get data-uri-cache uri)] (rx/from (.text blob))) (->> (http/send! {:uri uri :response-type :blob :method :get :mode :cors :omit-default-headers true}) (rx/catch (fn [cause] (log/error :hint "fetching data uri" :cause cause) (rx/of nil))) (rx/mapcat (fn [response] (if (nil? response) (rx/of uri) (->> (rx/of (:body response)) (rx/mapcat wapi/read-file-as-data-url) (rx/tap (fn [data-uri] (.set data-uri-cache uri (wapi/create-blob data-uri "text/plain"))))))))))) (defn- svg-update-image! "Updates an image in an SVG to a Data URI." [image] (if-let [href (dom/get-attribute image "href")] (if (str/starts-with? href "data:") (rx/of image) (->> (fetch-as-data-uri href) (rx/map (fn [url] (dom/set-attribute! image "href" url) image)))) (rx/empty))) (defn- svg-resolve-images! "Resolves all images in an SVG to Data URIs." [svg] (->> (rx/from (dom/query-all svg "image")) (rx/mapcat svg-update-image!) (rx/ignore))) (defn- svg-add-style! "Adds a