From fbce59e81f6e4d9770f7f7222563a7e39e904f05 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Thu, 27 Jun 2024 15:17:32 +0200 Subject: [PATCH] :sparkles: Improved text handling in plugins --- frontend/src/app/main/fonts.cljs | 12 ++ frontend/src/app/plugins/fonts.cljs | 7 +- frontend/src/app/plugins/format.cljs | 17 +- frontend/src/app/plugins/text.cljs | 230 +++++++++++++++++---------- 4 files changed, 175 insertions(+), 91 deletions(-) diff --git a/frontend/src/app/main/fonts.cljs b/frontend/src/app/main/fonts.cljs index 31949e7fe..d563da84e 100644 --- a/frontend/src/app/main/fonts.cljs +++ b/frontend/src/app/main/fonts.cljs @@ -71,6 +71,13 @@ (defn get-font-data [id] (get @fontsdb id)) +(defn find-font-data [data] + (d/seek + (fn [font] + (= (select-keys font (keys data)) + data)) + (vals @fontsdb))) + (defn resolve-variants [id] (get-in @fontsdb [id :variants])) @@ -249,6 +256,11 @@ (or (d/seek #(= (:id %) font-variant-id) variants) (get-default-variant font))) +(defn find-variant + [{:keys [variants] :as font} variant-data] + (let [props (keys variant-data)] + (d/seek #(= (select-keys % props) variant-data) variants))) + ;; Font embedding functions (defn get-node-fonts "Extracts the fonts used by some node" diff --git a/frontend/src/app/plugins/fonts.cljs b/frontend/src/app/plugins/fonts.cljs index 74631e1a4..cc73ee4b1 100644 --- a/frontend/src/app/plugins/fonts.cljs +++ b/frontend/src/app/plugins/fonts.cljs @@ -19,6 +19,9 @@ (deftype PenpotFontVariant [name fontVariantId fontWeight fontStyle]) +(defn variant-proxy? [p] + (instance? PenpotFontVariant p)) + (deftype PenpotFont [name fontId fontFamily fontStyle fontVariantId fontWeight variants] Object @@ -60,13 +63,13 @@ (instance? PenpotFont p)) (defn font-proxy - [{:keys [id name variants] :as font}] + [{:keys [id family name variants] :as font}] (when (some? font) (let [default-variant (fonts/get-default-variant font)] (PenpotFont. name id - id + family (:style default-variant) (:id default-variant) (:weight default-variant) diff --git a/frontend/src/app/plugins/format.cljs b/frontend/src/app/plugins/format.cljs index 9df43773b..4f6047df3 100644 --- a/frontend/src/app/plugins/format.cljs +++ b/frontend/src/app/plugins/format.cljs @@ -23,6 +23,12 @@ (when (some? coll) (apply array (keep format-fn coll)))) +(defn format-mixed + [value] + (if (= value :multiple) + "mixed" + value)) + ;; export type PenpotPoint = { x: number; y: number }; (defn format-point [{:keys [x y] :as point}] @@ -183,7 +189,14 @@ (defn format-fills [fills] - (when (some? fills) + (cond + (= fills :multiple) + "mixed" + + (= fills "mixed") + "mixed" + + (some? fills) (format-array format-fill fills))) ;; export interface PenpotStroke { @@ -393,7 +406,7 @@ (format-array format-command content))) ;; export type PenpotTrackType = 'flex' | 'fixed' | 'percent' | 'auto'; -;; +;; ;; export interface PenpotTrack { ;; type: PenpotTrackType; ;; value: number | null; diff --git a/frontend/src/app/plugins/text.cljs b/frontend/src/app/plugins/text.cljs index 5a05ebaaf..05750c9bb 100644 --- a/frontend/src/app/plugins/text.cljs +++ b/frontend/src/app/plugins/text.cljs @@ -7,12 +7,14 @@ (ns app.plugins.text (:require [app.common.data :as d] + [app.common.data.macros :as dm] [app.common.record :as crc] [app.common.schema :as sm] [app.common.text :as txt] [app.common.types.shape :as cts] [app.main.data.workspace.shapes :as dwsh] [app.main.data.workspace.texts :as dwt] + [app.main.fonts :as fonts] [app.main.store :as st] [app.plugins.format :as format] [app.plugins.parser :as parser] @@ -21,11 +23,39 @@ [app.util.text-editor :as ted] [cuerdas.core :as str])) +;; This regex seems duplicated but probably in the future when we support diferent units +;; this will need to reflect changes for each property + +(def font-size-re #"^\d*\.?\d*$") +(def line-height-re #"^\d*\.?\d*$") +(def letter-spacing-re #"^\d*\.?\d*$") +(def text-transform-re #"uppercase|capitalize|lowercase|none") +(def text-decoration-re #"underline|line-through|none") +(def text-direction-re #"ltr|rtl") +(def text-align-re #"left|center|right|justify") +(def vertical-align-re #"top|center|bottom") + (defn mixed-value [values] (let [s (set values)] (if (= (count s) 1) (first s) "mixed"))) +(defn font-data + [font variant] + (d/without-nils + {:font-id (:id font) + :font-family (:family font) + :font-variant-id (:id variant) + :font-style (:style variant) + :font-weight (:weight variant)})) + +(defn variant-data + [variant] + (d/without-nils + {:font-variant-id (:id variant) + :font-style (:style variant) + :font-weight (:weight variant)})) + (deftype TextRange [$plugin $file $page $id start end] Object (applyTypography [_ typography] @@ -71,12 +101,14 @@ :set (fn [_ value] - (cond - (not (string? value)) - (u/display-not-valid :fontId value) + (let [font (when (string? value) (fonts/get-font-data value)) + variant (fonts/get-default-variant font)] + (cond + (not (some? font)) + (u/display-not-valid :fontId value) - :else - (st/emit! (dwt/update-text-range id start end {:font-id value}))))} + :else + (st/emit! (dwt/update-text-range id start end (font-data font variant))))))} {:name "fontFamily" :get #(let [range-data @@ -85,25 +117,29 @@ :set (fn [_ value] - (cond - (not (string? value)) - (u/display-not-valid :fontFamily value) + (let [font (fonts/find-font-data {:font-family value}) + variant (fonts/get-default-variant font)] + (cond + (not (string? value)) + (u/display-not-valid :fontFamily value) - :else - (st/emit! (dwt/update-text-range id start end {:font-family value}))))} + :else + (st/emit! (dwt/update-text-range id start end (font-data font variant))))))} {:name "fontVariantId" :get #(let [range-data (-> % u/proxy->shape :content (txt/content-range->text+styles start end))] (->> range-data (map :font-variant-id) mixed-value)) :set - (fn [_ value] - (cond - (not (string? value)) - (u/display-not-valid :fontVariantId value) + (fn [self value] + (let [font (fonts/get-font-data (obj/get self "fontId")) + variant (fonts/get-variant font value)] + (cond + (not (string? value)) + (u/display-not-valid :fontVariantId value) - :else - (st/emit! (dwt/update-text-range id start end {:font-variant-id value}))))} + :else + (st/emit! (dwt/update-text-range id start end (variant-data variant))))))} {:name "fontSize" :get #(let [range-data @@ -111,38 +147,43 @@ (->> range-data (map :font-size) mixed-value)) :set (fn [_ value] - (cond - (not (string? value)) - (u/display-not-valid :fontSize value) + (let [value (str/trim (dm/str value))] + (cond + (or (empty? value) (not (re-matches font-size-re value))) + (u/display-not-valid :fontSize value) - :else - (st/emit! (dwt/update-text-range id start end {:font-size value}))))} + :else + (st/emit! (dwt/update-text-range id start end {:font-size value})))))} {:name "fontWeight" :get #(let [range-data (-> % u/proxy->shape :content (txt/content-range->text+styles start end))] (->> range-data (map :font-weight) mixed-value)) :set - (fn [_ value] - (cond - (not (string? value)) - (u/display-not-valid :fontWeight value) + (fn [self value] + (let [font (fonts/get-font-data (obj/get self "fontId")) + variant (fonts/find-variant font {:weight (dm/str value)})] + (cond + (nil? variant) + (u/display-not-valid :fontWeight (dm/str "Font weight '" value "' not supported for the current font")) - :else - (st/emit! (dwt/update-text-range id start end {:font-weight value}))))} + :else + (st/emit! (dwt/update-text-range id start end (variant-data variant))))))} {:name "fontStyle" :get #(let [range-data (-> % u/proxy->shape :content (txt/content-range->text+styles start end))] (->> range-data (map :font-style) mixed-value)) :set - (fn [_ value] - (cond - (not (string? value)) - (u/display-not-valid :fontStyle value) + (fn [self value] + (let [font (fonts/get-font-data (obj/get self "fontId")) + variant (fonts/find-variant font {:weight (dm/str value)})] + (cond + (nil? variant) + (u/display-not-valid :fontStyle (dm/str "Font style '" value "' not supported for the current font")) - :else - (st/emit! (dwt/update-text-range id start end {:font-style value}))))} + :else + (st/emit! (dwt/update-text-range id start end (variant-data variant))))))} {:name "lineHeight" :get #(let [range-data @@ -150,12 +191,13 @@ (->> range-data (map :line-height) mixed-value)) :set (fn [_ value] - (cond - (not (string? value)) - (u/display-not-valid :lineHeight value) + (let [value (str/trim (dm/str value))] + (cond + (or (empty? value) (not (re-matches line-height-re value))) + (u/display-not-valid :lineHeight value) - :else - (st/emit! (dwt/update-text-range id start end {:line-height value}))))} + :else + (st/emit! (dwt/update-text-range id start end {:line-height value})))))} {:name "letterSpacing" :get #(let [range-data @@ -163,12 +205,13 @@ (->> range-data (map :letter-spacing) mixed-value)) :set (fn [_ value] - (cond - (not (string? value)) - (u/display-not-valid :letterSpacing value) + (let [value (str/trim (dm/str value))] + (cond + (or (empty? value) (re-matches letter-spacing-re value)) + (u/display-not-valid :letterSpacing value) - :else - (st/emit! (dwt/update-text-range id start end {:letter-spacing value}))))} + :else + (st/emit! (dwt/update-text-range id start end {:letter-spacing value})))))} {:name "textTransform" :get #(let [range-data @@ -177,7 +220,7 @@ :set (fn [_ value] (cond - (not (string? value)) + (and (string? value) (re-matches text-transform-re value)) (u/display-not-valid :textTransform value) :else @@ -190,7 +233,7 @@ :set (fn [_ value] (cond - (not (string? value)) + (and (string? value) (re-matches text-decoration-re value)) (u/display-not-valid :textDecoration value) :else @@ -203,7 +246,7 @@ :set (fn [_ value] (cond - (not (string? value)) + (and (string? value) (re-matches text-direction-re value)) (u/display-not-valid :direction value) :else @@ -216,7 +259,7 @@ :set (fn [_ value] (cond - (not (string? value)) + (and (string? value) (re-matches text-align-re value)) (u/display-not-valid :text-align value) :else @@ -278,144 +321,157 @@ (st/emit! (dwsh/update-shapes [id] #(assoc % :grow-type value))))))} {:name "fontId" - :get #(-> % u/proxy->shape text-props :font-id) + :get #(-> % u/proxy->shape text-props :font-id format/format-mixed) :set (fn [self value] - (let [id (obj/get self "$id")] + (let [id (obj/get self "$id") + font (when (string? value) (fonts/get-font-data value)) + variant (fonts/get-default-variant font)] (cond - (not (string? value)) + (not (some? font)) (u/display-not-valid :fontId value) :else - (st/emit! (dwt/update-attrs id {:font-id value})))))} + (st/emit! (dwt/update-attrs id (font-data font variant))))))} {:name "fontFamily" - :get #(-> % u/proxy->shape text-props :font-family) + :get #(-> % u/proxy->shape text-props :font-family format/format-mixed) :set (fn [self value] - (let [id (obj/get self "$id")] + (let [id (obj/get self "$id") + font (fonts/find-font-data {:font-family value}) + variant (fonts/get-default-variant font)] (cond - (not (string? value)) + (not (some? font)) (u/display-not-valid :fontFamily value) :else - (st/emit! (dwt/update-attrs id {:font-family value})))))} + (st/emit! (dwt/update-attrs id (font-data font variant))))))} {:name "fontVariantId" - :get #(-> % u/proxy->shape text-props :font-variant-id) + :get #(-> % u/proxy->shape text-props :font-variant-id format/format-mixed) :set (fn [self value] - (let [id (obj/get self "$id")] + (let [id (obj/get self "$id") + font (fonts/get-font-data (obj/get self "fontId")) + variant (fonts/get-variant font value)] (cond - (not (string? value)) + (not (some? variant)) (u/display-not-valid :fontVariantId value) :else - (st/emit! (dwt/update-attrs id {:font-variant-id value})))))} + (st/emit! (dwt/update-attrs id (variant-data variant))))))} {:name "fontSize" - :get #(-> % u/proxy->shape text-props :font-size) + :get #(-> % u/proxy->shape text-props :font-size format/format-mixed) :set (fn [self value] - (let [id (obj/get self "$id")] + (let [id (obj/get self "$id") + value (str/trim (dm/str value))] (cond - (not (string? value)) + (or (empty? value) (not (re-matches font-size-re value))) (u/display-not-valid :fontSize value) :else (st/emit! (dwt/update-attrs id {:font-size value})))))} {:name "fontWeight" - :get #(-> % u/proxy->shape text-props :font-weight) + :get #(-> % u/proxy->shape text-props :font-weight format/format-mixed) :set (fn [self value] - (let [id (obj/get self "$id")] + (let [id (obj/get self "$id") + font (fonts/get-font-data (obj/get self "fontId")) + variant (fonts/find-variant font {:weight (dm/str value)})] (cond - (not (string? value)) - (u/display-not-valid :fontWeight value) + (nil? variant) + (u/display-not-valid :fontWeight (dm/str "Font weight '" value "' not supported for the current font")) :else - (st/emit! (dwt/update-attrs id {:font-weight value})))))} + (st/emit! (dwt/update-attrs id (variant-data variant))))))} {:name "fontStyle" - :get #(-> % u/proxy->shape text-props :font-style) + :get #(-> % u/proxy->shape text-props :font-style format/format-mixed) :set (fn [self value] - (let [id (obj/get self "$id")] + (let [id (obj/get self "$id") + font (fonts/get-font-data (obj/get self "fontId")) + variant (fonts/find-variant font {:weight (dm/str value)})] (cond - (not (string? value)) - (u/display-not-valid :fontStyle value) + (nil? variant) + (u/display-not-valid :fontStyle (dm/str "Font style '" value "' not supported for the current font")) :else - (st/emit! (dwt/update-attrs id {:font-style value})))))} + (st/emit! (dwt/update-attrs id (variant-data variant))))))} {:name "lineHeight" - :get #(-> % u/proxy->shape text-props :line-height) + :get #(-> % u/proxy->shape text-props :line-height format/format-mixed) :set (fn [self value] - (let [id (obj/get self "$id")] + (let [id (obj/get self "$id") + value (str/trim (dm/str value))] (cond - (not (string? value)) + (or (empty? value) (not (re-matches line-height-re value))) (u/display-not-valid :lineHeight value) :else (st/emit! (dwt/update-attrs id {:line-height value})))))} {:name "letterSpacing" - :get #(-> % u/proxy->shape text-props :letter-spacing) + :get #(-> % u/proxy->shape text-props :letter-spacing format/format-mixed) :set (fn [self value] - (let [id (obj/get self "$id")] + (let [id (obj/get self "$id") + value (str/trim (dm/str value))] (cond - (not (string? value)) + (or (empty? value) (re-matches letter-spacing-re value)) (u/display-not-valid :letterSpacing value) :else (st/emit! (dwt/update-attrs id {:letter-spacing value})))))} {:name "textTransform" - :get #(-> % u/proxy->shape text-props :text-transform) + :get #(-> % u/proxy->shape text-props :text-transform format/format-mixed) :set (fn [self value] (let [id (obj/get self "$id")] (cond - (not (string? value)) + (and (string? value) (re-matches text-transform-re value)) (u/display-not-valid :textTransform value) :else (st/emit! (dwt/update-attrs id {:text-transform value})))))} {:name "textDecoration" - :get #(-> % u/proxy->shape text-props :text-decoration) + :get #(-> % u/proxy->shape text-props :text-decoration format/format-mixed) :set (fn [self value] (let [id (obj/get self "$id")] (cond - (not (string? value)) + (and (string? value) (re-matches text-decoration-re value)) (u/display-not-valid :textDecoration value) :else (st/emit! (dwt/update-attrs id {:text-decoration value})))))} {:name "direction" - :get #(-> % u/proxy->shape text-props :text-direction) + :get #(-> % u/proxy->shape text-props :text-direction format/format-mixed) :set (fn [self value] (let [id (obj/get self "$id")] (cond - (not (string? value)) + (and (string? value) (re-matches text-direction-re value)) (u/display-not-valid :textDecoration value) :else (st/emit! (dwt/update-attrs id {:text-decoration value})))))} {:name "align" - :get #(-> % u/proxy->shape text-props :text-align) + :get #(-> % u/proxy->shape text-props :text-align format/format-mixed) :set (fn [self value] (let [id (obj/get self "$id")] (cond - (not (string? value)) + (and (string? value) (re-matches text-align-re value)) (u/display-not-valid :align value) :else @@ -427,7 +483,7 @@ (fn [self value] (let [id (obj/get self "$id")] (cond - (not (string? value)) + (and (string? value) (re-matches vertical-align-re value)) (u/display-not-valid :verticalAlign value) :else