;; 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.main.ui.shapes.fills (:require [app.common.data :as d] [app.common.data.macros :as dm] [app.common.files.helpers :as cfh] [app.common.geom.point :as gpt] [app.common.geom.shapes :as gsh] [app.common.geom.shapes.text :as gst] [app.config :as cf] [app.main.ui.shapes.attrs :as attrs] [app.main.ui.shapes.embed :as embed] [app.main.ui.shapes.gradients :as grad] [app.util.object :as obj] [rumext.v2 :as mf])) (def no-repeat-padding 1.05) (mf/defc internal-fills {::mf/wrap-props false} [props] (let [shape (unchecked-get props "shape") render-id (unchecked-get props "render-id") type (dm/get-prop shape :type) image (get shape :fill-image) fills (get shape :fills []) selrect (dm/get-prop shape :selrect) bounds (when (cfh/text-shape? shape) (gst/shape->rect shape)) metadata (get shape :metadata) x (dm/get-prop selrect :x) y (dm/get-prop selrect :y) width (dm/get-prop selrect :width) height (dm/get-prop selrect :height) has-image? (or (some? metadata) (some? image)) uri (cond (some? metadata) (cf/resolve-file-media metadata) (some? image) (cf/resolve-file-media image)) uris (into [uri] (comp (keep :fill-image) (map cf/resolve-file-media)) fills) embed (embed/use-data-uris uris) transform (gsh/transform-str shape) pat-props #js {:patternUnits "userSpaceOnUse" :x x :y y :width width :height height} pat-props (if (or (= :path type) (= :bool type)) (obj/set! pat-props "patternTransform" transform) pat-props)] (for [[obj-index obj] (d/enumerate (or (:position-data shape) [shape]))] [:* {:key (dm/str obj-index)} (for [[fill-index value] (reverse (d/enumerate (get obj :fills [])))] (when (some? (:fill-color-gradient value)) (let [gradient (:fill-color-gradient value) from-p (-> (gpt/point (+ x (* width (:start-x gradient))) (+ y (* height (:start-y gradient))))) to-p (-> (gpt/point (+ x (* width (:end-x gradient))) (+ y (* height (:end-y gradient))))) gradient (cond-> gradient (some? bounds) (assoc :start-x (/ (- (:x from-p) (:x bounds)) (:width bounds)) :start-y (/ (- (:y from-p) (:y bounds)) (:height bounds)) :end-x (/ (- (:x to-p) (:x bounds)) (:width bounds)) :end-y (/ (- (:y to-p) (:y bounds)) (:height bounds)))) props #js {:id (dm/str "fill-color-gradient-" render-id "-" fill-index) :key (dm/str fill-index) :gradient gradient :shape obj}] (case (d/name (:type gradient)) "linear" [:> grad/linear-gradient props] "radial" [:> grad/radial-gradient props])))) (let [fill-id (dm/str "fill-" obj-index "-" render-id)] [:> :pattern (-> (obj/clone pat-props) (obj/set! "id" fill-id) (cond-> (and has-image? (nil? bounds)) (-> (obj/set! "width" (* width no-repeat-padding)) (obj/set! "height" (* height no-repeat-padding)))) (cond-> (some? bounds) (-> (obj/set! "width" (:width bounds)) (obj/set! "height" (:height bounds))))) [:g (for [[fill-index value] (reverse (d/enumerate (get obj :fills [])))] (let [style (attrs/get-fill-style value fill-index render-id type) props #js {:key (dm/str fill-index) :width (d/nilv (:width bounds) width) :height (d/nilv (:height bounds) height) :style style}] (if (:fill-image value) (let [uri (cf/resolve-file-media (:fill-image value)) keep-ar? (-> value :fill-image :keep-aspect-ratio) image-props #js {:id (dm/str "fill-image-" render-id "-" fill-index) :href (get embed uri uri) :preserveAspectRatio (if keep-ar? "xMidYMid slice" "none") :width width :height height :key (dm/str fill-index) :opacity (:fill-opacity value)}] [:> :image image-props]) [:> :rect props]))) (when ^boolean has-image? [:g ;; We add this shape to add a padding so the patter won't repeat ;; Issue: https://tree.taiga.io/project/penpot/issue/5583 [:rect {:x 0 :y 0 :width (* width no-repeat-padding) :height (* height no-repeat-padding) :fill "none"}] [:image {:href uri :preserveAspectRatio "none" :x 0 :y 0 :width width :height height}]])]])]))) (mf/defc fills {::mf/wrap-props false} [props] (let [shape (unchecked-get props "shape") type (dm/get-prop shape :type) image (:fill-image shape) fills (:fills shape [])] (when (or (some? image) (or (= type :image) (= type :text)) (> (count fills) 1) (some :fill-color-gradient fills) (some :fill-image fills)) [:> internal-fills props])))