🐛 Fix problem when exporting texts with gradients or opacity

This commit is contained in:
alonso.torres 2021-11-02 19:20:17 +01:00 committed by Andrés Moya
parent bce0e9194c
commit 214c64c49e
7 changed files with 271 additions and 62 deletions

View file

@ -11,6 +11,10 @@
(def render-ctx (mf/create-context nil))
(def def-ctx (mf/create-context false))
;; This content is used to replace complex colors to simple ones
;; for text shapes in the export process
(def text-plain-colors-ctx (mf/create-context false))
(def current-route (mf/create-context nil))
(def current-team-id (mf/create-context nil))
(def current-project-id (mf/create-context nil))

View file

@ -16,6 +16,7 @@
[app.main.exports :as exports]
[app.main.repo :as repo]
[app.main.store :as st]
[app.main.ui.context :as muc]
[app.main.ui.shapes.embed :as embed]
[app.main.ui.shapes.export :as ed]
[app.main.ui.shapes.filters :as filters]
@ -124,14 +125,15 @@
;; Auxiliary SVG for rendering text-shapes
(when render-texts?
(for [object text-shapes]
[:svg {:id (str "screenshot-text-" (:id object))
:view-box (str "0 0 " (:width object) " " (:height object))
:width (:width object)
:height (:height object)
:version "1.1"
:xmlns "http://www.w3.org/2000/svg"
:xmlnsXlink "http://www.w3.org/1999/xlink"}
[:& shape-wrapper {:shape (-> object (assoc :x 0 :y 0))}]]))]))
[:& (mf/provider muc/text-plain-colors-ctx) {:value true}
[:svg {:id (str "screenshot-text-" (:id object))
:view-box (str "0 0 " (:width object) " " (:height object))
:width (:width object)
:height (:height object)
:version "1.1"
:xmlns "http://www.w3.org/2000/svg"
:xmlnsXlink "http://www.w3.org/1999/xlink"}
[:& shape-wrapper {:shape (-> object (assoc :x 0 :y 0))}]]]))]))
(defn- adapt-root-frame
[objects object-id]

View file

@ -10,24 +10,10 @@
[app.common.geom.point :as gpt]
[app.common.geom.shapes :as gsh]
[app.main.ui.context :as muc]
[app.main.ui.shapes.export :as ed]
[app.util.object :as obj]
[rumext.alpha :as mf]))
(mf/defc linear-gradient [{:keys [id gradient shape]}]
(let [transform (when (= :path (:type shape)) (gsh/transform-matrix shape nil (gpt/point 0.5 0.5)))]
[:> :linearGradient #js {:id id
:x1 (:start-x gradient)
:y1 (:start-y gradient)
:x2 (:end-x gradient)
:y2 (:end-y gradient)
:gradientTransform transform
:penpot:gradient "true"}
(for [{:keys [offset color opacity]} (:stops gradient)]
[:stop {:key (str id "-stop-" offset)
:offset (or offset 0)
:stop-color color
:stop-opacity opacity}])]))
(defn add-metadata [props gradient]
(-> props
(obj/set! "penpot:gradient" "true")
@ -38,6 +24,30 @@
(obj/set! "penpot:end-y" (:end-y gradient))
(obj/set! "penpot:width" (:width gradient))))
(mf/defc linear-gradient [{:keys [id gradient shape]}]
(let [transform (when (= :path (:type shape)) (gsh/transform-matrix shape nil (gpt/point 0.5 0.5)))
base-props #js {:id id
:x1 (:start-x gradient)
:y1 (:start-y gradient)
:x2 (:end-x gradient)
:y2 (:end-y gradient)
:gradientTransform transform}
include-metadata? (mf/use-ctx ed/include-metadata-ctx)
props (cond-> base-props
include-metadata?
(add-metadata gradient))]
[:> :linearGradient props
(for [{:keys [offset color opacity]} (:stops gradient)]
[:stop {:key (str id "-stop-" offset)
:offset (or offset 0)
:stop-color color
:stop-opacity opacity}])]))
(mf/defc radial-gradient [{:keys [id gradient shape]}]
(let [{:keys [x y width height]} (:selrect shape)
transform (if (= :path (:type shape))
@ -73,7 +83,11 @@
:gradientUnits "userSpaceOnUse"
:gradientTransform transform}
props (-> base-props (add-metadata gradient))]
include-metadata? (mf/use-ctx ed/include-metadata-ctx)
props (cond-> base-props
include-metadata?
(add-metadata gradient))]
[:> :radialGradient props
(for [{:keys [offset color opacity]} (:stops gradient)]
[:stop {:key (str id "-stop-" offset)

View file

@ -8,9 +8,12 @@
(:require
[app.common.data :as d]
[app.common.geom.shapes :as geom]
[app.main.ui.context :as muc]
[app.main.ui.shapes.attrs :as attrs]
[app.main.ui.shapes.text.styles :as sts]
[app.util.color :as uc]
[app.util.object :as obj]
[cuerdas.core :as str]
[rumext.alpha :as mf]))
(mf/defc render-text
@ -73,12 +76,111 @@
(obj/set! "key" index))]
[:> render-node props]))])))))
(defn- next-color
"Given a set of colors try to get a color not yet used"
[colors]
(assert (set? colors))
(loop [current-rgb [0 0 0]]
(let [current-hex (uc/rgb->hex current-rgb)]
(if (contains? colors current-hex)
(recur (uc/next-rgb current-rgb))
current-hex))))
(defn- remap-colors
"Returns a new content replacing the original colors by their mapped 'simple color'"
[content color-mapping]
(cond-> content
(and (:fill-opacity content) (< (:fill-opacity content) 1.0))
(-> (assoc :fill-color (get color-mapping [(:fill-color content) (:fill-opacity content)]))
(assoc :fill-opacity 1.0))
(some? (:fill-color-gradient content))
(-> (assoc :fill-color (get color-mapping (:fill-color-gradient content)))
(assoc :fill-opacity 1.0)
(dissoc :fill-color-gradient))
(contains? content :children)
(update :children #(mapv (fn [node] (remap-colors node color-mapping)) %))))
(defn- fill->color
"Given a content node returns the information about that node fill color"
[{:keys [fill-color fill-opacity fill-color-gradient]}]
(cond
(some? fill-color-gradient)
{:type :gradient
:gradient fill-color-gradient}
(and (string? fill-color) (some? fill-opacity) (not= fill-opacity 1))
{:type :transparent
:hex fill-color
:opacity fill-opacity}
(string? fill-color)
{:type :solid
:hex fill-color
:map-to fill-color}))
(defn- retrieve-colors
"Given a text shape returns a triple with the values:
- colors used as fills
- a mapping from simple solid colors to complex ones (transparents/gradients)
- the inverse of the previous mapping (to restore the value in the SVG)"
[shape]
(let [colors (->> (:content shape)
(tree-seq map? :children)
(into #{"#000000"} (comp (map :fill-color) (filter string?))))]
(apply str (interpose "," colors))))
(let [color-data
(->> (:content shape)
(tree-seq map? :children)
(map fill->color)
(filter some?))
colors (->> color-data
(into #{"#000000"}
(comp (filter #(= :solid (:type %)))
(map :hex))))
[colors color-data]
(loop [colors colors
head (first color-data)
tail (rest color-data)
result []]
(if (nil? head)
[colors result]
(if (= :solid (:type head))
(recur colors
(first tail)
(rest tail)
(conj result head))
(let [next-color (next-color colors)
head (assoc head :map-to next-color)
colors (conj colors next-color)]
(recur colors
(first tail)
(rest tail)
(conj result head))))))
color-mapping-inverse
(->> color-data
(remove #(= :solid (:type %)))
(group-by :map-to)
(d/mapm #(first %2)))
color-mapping
(merge
(->> color-data
(filter #(= :transparent (:type %)))
(map #(vector [(:hex %) (:opacity %)] (:map-to %)))
(into {}))
(->> color-data
(filter #(= :gradient (:type %)))
(map #(vector (:gradient %) (:map-to %)))
(into {})))]
[colors color-mapping color-mapping-inverse]))
(mf/defc text-shape
{::mf/wrap-props false
@ -88,11 +190,19 @@
grow-type (obj/get props "grow-type") ;; This is only needed in workspace
;; We add 8px to add a padding for the exporter
;; width (+ width 8)
]
[colors color-mapping color-mapping-inverse] (retrieve-colors shape)
plain-colors? (mf/use-ctx muc/text-plain-colors-ctx)
content (cond-> content
plain-colors?
(remap-colors color-mapping))]
[:foreignObject {:x x
:y y
:id id
:data-colors (retrieve-colors shape)
:data-colors (->> colors (str/join ","))
:data-mapping (-> color-mapping-inverse (clj->js) (js/JSON.stringify))
:transform (geom/transform-matrix shape)
:width (if (#{:auto-width} grow-type) 100000 width)
:height (if (#{:auto-height :auto-width} grow-type) 100000 height)

View file

@ -7,6 +7,7 @@
(ns app.util.color
"Color conversion utils."
(:require
[app.common.exceptions :as ex]
[app.util.object :as obj]
[cuerdas.core :as str]
[goog.color :as gcolor]))
@ -155,3 +156,19 @@
(def empty-color
(into {} (map #(vector % nil)) [:color :id :file-id :gradient :opacity]))
(defn next-rgb
"Given a color in rgb returns the next color"
[[r g b]]
(cond
(and (= 255 r) (= 255 g) (= 255 b))
(ex/raise "Cannot get next color")
(and (= 255 g) (= 255 b))
[(inc r) 0 0]
(= 255 b)
[r (inc g) 0]
:else
[r g (inc b)]))