Files
penpot/frontend/src/app/util/text/content/to_dom.cljs
2025-11-06 12:20:02 +01:00

161 lines
5.5 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
(ns app.util.text.content.to-dom
(:require
[app.common.data :as d]
[app.common.types.text :as txt]
[app.util.dom :as dom]
[app.util.text.content.styles :as styles]))
(defn set-dataset
[element data]
(doseq [[data-name data-value] data]
(dom/set-data! element (name data-name) data-value)))
(defn set-styles
[element styles]
(doseq [[style-name style-value] styles]
(if (contains? styles/mapping style-name)
(let [[style-encode] (get styles/mapping style-name)
style-encoded-value (style-encode style-value)]
(dom/set-style! element (styles/get-style-name-as-css-variable style-name) style-encoded-value))
(dom/set-style! element (styles/get-style-name style-name) (styles/normalize-style-value style-name style-value)))))
(defn create-element
([tag]
(create-element tag nil nil))
([tag attrs]
(create-element tag attrs nil))
([tag attrs children]
(let [element (dom/create-element tag)]
;; set attributes to the element if necessary.
(doseq [[attr-name attr-value] attrs]
(case attr-name
:data (set-dataset element attr-value)
:style (set-styles element attr-value)
(dom/set-attribute! element (name attr-name) attr-value)))
;; add childs to the element if necessary.
(doseq [child children]
(dom/append-child! element child))
;; we need to return the DOM element
element)))
(defn get-styles-from-attrs
[node attrs defaults]
(let [styles (reduce
(fn [acc key]
(let [default-value (get defaults key)]
(assoc acc key (get node key default-value)))) {} attrs)
fills
(cond
;; DEPRECATED: still here for backward compatibility with
;; old penpot files that still has a single color.
(or (some? (:fill-color node))
(some? (:fill-opacity node))
(some? (:fill-color-gradient node)))
[(d/without-nils (select-keys node [:fill-color :fill-opacity :fill-color-gradient
:fill-color-ref-id :fill-color-ref-file]))]
(nil? (:fills node))
[{:fill-color "#000000" :fill-opacity 1}]
:else
(:fills node))]
(assoc styles :fills fills)))
(defn get-paragraph-styles
[paragraph]
(let [styles (get-styles-from-attrs
paragraph
(d/concat-set txt/paragraph-attrs txt/text-node-attrs)
txt/default-text-attrs)
;; If the text is not empty we must the paragraph font size to 0,
;; it affects to the height calculation the browser does
font-size (if (some #(not= "" (:text %)) (:children paragraph))
"0"
(:font-size styles (:font-size txt/default-typography)))
line-height (:line-height styles)
line-height (if (and (some? line-height) (not= "" line-height))
line-height
(:line-height txt/default-typography))]
(-> styles
(assoc :font-size font-size :line-height line-height))))
(defn get-root-styles
[root]
(get-styles-from-attrs root txt/root-attrs txt/default-text-attrs))
(defn get-inline-styles
[inline paragraph]
(let [node (if (= "" (:text inline)) paragraph inline)
styles (get-styles-from-attrs node txt/text-node-attrs txt/default-text-attrs)]
(dissoc styles :line-height)))
(defn normalize-spaces
"Add zero-width spaces after forward slashes to enable word breaking"
[text]
(when text
(.replace text (js/RegExp "/" "g") "/\u200B")))
(defn get-inline-children
[inline paragraph]
[(if (and (= "" (:text inline))
(= 1 (count (:children paragraph))))
(dom/create-element "br")
(dom/create-text (normalize-spaces (:text inline))))])
(defn create-random-key
[]
(.toString (.floor js/Math (* (.random js/Math) (.-MAX_SAFE_INTEGER js/Number))) 36))
(defn has-content?
[paragraph]
(some #(not= "" (:text % "")) (:children paragraph)))
(defn should-filter-empty-paragraph?
[paragraphs index]
(and (not (has-content? (nth paragraphs index)))
(< index (count paragraphs))
(some has-content? (drop (inc index) paragraphs))
(every? #(not (has-content? %)) (take (inc index) paragraphs))))
(defn create-inline
[inline paragraph]
(create-element
"span"
{:id (or (:key inline) (create-random-key))
:data {:itype "inline"}
:style (get-inline-styles inline paragraph)}
(get-inline-children inline paragraph)))
(defn create-paragraph
[paragraph]
(create-element
"div"
{:id (or (:key paragraph) (create-random-key))
:data {:itype "paragraph"}
:style (get-paragraph-styles paragraph)}
(mapv #(create-inline % paragraph) (:children paragraph))))
(defn create-root
[root]
(let [root-styles (get-root-styles root)
paragraphs (get-in root [:children 0 :children])
filtered-paragraphs (->> paragraphs
(map-indexed vector)
(remove (fn [[index _]] (should-filter-empty-paragraph? paragraphs index)))
(mapv second))]
(create-element
"div"
{:id (or (:key root) (create-random-key))
:data {:itype "root"}
:style root-styles}
(mapv create-paragraph filtered-paragraphs))))