alonso.torres 75aeea32fb polishing
2026-07-15 17:27:15 +02:00

194 lines
6.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 INC Sucursal en España SL
(ns app.main.ui.shapes.text.fo-text
(:require
[app.common.data :as d]
[app.common.data.macros :as dm]
[app.common.geom.shapes :as gsh]
[app.common.types.color :as cc]
[app.common.types.text.japanese-layout :as jl]
[app.main.ui.shapes.text.styles :as sts]
[cuerdas.core :as str]
[rumext.v2 :as mf]))
(mf/defc render-text*
[{:keys [node parent shape]}]
(let [text (:text node)
style (if (= text "")
(sts/generate-text-styles shape parent)
(sts/generate-text-styles shape node))
ruby (:ruby node)
ruby? (and (string? ruby) (not (str/blank? ruby)))]
(if ruby?
[:ruby.ruby-node {:style (sts/generate-ruby-container-styles node)}
[:span.text-node {:style style} text]
[:rt {:style (sts/generate-ruby-styles shape node)} ruby]]
[:span.text-node {:style style}
(if (= text "") "\u00A0" text)])))
(mf/defc render-root*
[{:keys [node children shape]}]
(let [style (sts/generate-root-styles shape node)]
[:div.root.rich-text
{:style style
:xmlns "http://www.w3.org/1999/xhtml"}
children]))
(mf/defc render-paragraph-set*
[{:keys [children shape]}]
(let [style (sts/generate-paragraph-set-styles shape)]
[:div.paragraph-set {:style style} children]))
(mf/defc render-paragraph*
[{:keys [node children shape]}]
(let [style (sts/generate-paragraph-styles shape node)
dir (:text-direction node "auto")]
[:p.paragraph {:style style :dir dir} children]))
;; -- Text nodes
(mf/defc render-node*
{::mf/props :obj}
[{:keys [node] :as props}]
(let [{:keys [type text children]} node]
(if (string? text)
[:> render-text* props]
(let [component (case type
"root" render-root*
"paragraph-set" render-paragraph-set*
"paragraph" render-paragraph*
nil)]
(when component
[:> component props
(for [[index child-node] (d/enumerate children)]
[:> render-node*
(mf/spread-props props {:node child-node :index index :key index})])])))))
(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 (cc/rgb->hex current-rgb)]
(if (contains? colors current-hex)
(recur (cc/next-rgb current-rgb))
current-hex))))
(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 [color-data
(->> (:content shape)
(tree-seq map? :children)
(map fill->color)
(filter some?))
colors (->> color-data
(into #{cc/black}
(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/forward-ref true}
[{:keys [shape grow-type]} ref]
(let [transform (gsh/transform-str shape)
id (dm/get-prop shape :id)
x (dm/get-prop shape :x)
y (dm/get-prop shape :y)
width (dm/get-prop shape :width)
height (dm/get-prop shape :height)
content (get shape :content)
;; Vertical writing anchors columns to the box edges, so the oversized
;; auto-grow box used to avoid horizontal wrapping/clipping would push
;; the content off-position. Use the real selrect size instead.
vertical? (jl/vertical-text-content? content)
[colors _color-mapping color-mapping-inverse] (retrieve-colors shape)]
[:foreignObject
{:x x
:y y
:id id
:data-colors (str/join "," colors)
:data-mapping (-> color-mapping-inverse clj->js js/JSON.stringify)
:transform transform
:width (if (and (not vertical?) (#{:auto-width} grow-type)) 100000 width)
:height (if (and (not vertical?) (#{:auto-height :auto-width} grow-type)) 100000 height)
:ref ref}
;; We use a class here because react has a bug that won't use the appropriate selector for
;; `background-clip`
[:style ".text-node { background-clip: text;
-webkit-background-clip: text; }"]
[:> render-node* {:index 0
:shape shape
:node content}]]))