mirror of
https://github.com/penpot/penpot.git
synced 2026-10-29 05:50:35 +00:00
✨ Define Japanese text layout attributes
Add schemas, defaults and helpers for vertical writing, ruby, emphasis and warichu. Keep readings and notes tied to their own characters when copying styles, and preserve base text when pasting HTML ruby. Cover the model, property copying, clipboard and library file builder.
This commit is contained in:
parent
f6f9df9a1a
commit
7f4cadb326
@ -795,7 +795,7 @@
|
||||
:r4})
|
||||
|
||||
(def ^:private text-extract-props
|
||||
(into #{} cat [txt/root-attrs txt/paragraph-attrs txt/text-node-attrs]))
|
||||
(into #{} cat [txt/root-attrs txt/paragraph-attrs txt/text-node-style-attrs]))
|
||||
|
||||
(def ^:private layout-extract-props
|
||||
(set ctsl/layout-attrs))
|
||||
@ -846,7 +846,7 @@
|
||||
(assoc-props node txt/paragraph-attrs)
|
||||
|
||||
(txt/is-text-node? node)
|
||||
(assoc-props node txt/text-node-attrs)))
|
||||
(assoc-props node txt/text-node-style-attrs)))
|
||||
props)))
|
||||
|
||||
(extract-layout-attrs [props shape]
|
||||
@ -889,7 +889,7 @@
|
||||
(d/patch-object (select-keys props txt/paragraph-attrs))
|
||||
|
||||
(txt/is-text-node? node)
|
||||
(d/patch-object (select-keys props txt/text-node-attrs))))))))))
|
||||
(d/patch-object (select-keys props txt/text-node-style-attrs))))))))))
|
||||
|
||||
(patch-layout-props [shape props]
|
||||
(let [shape (d/patch-object shape (select-keys props ctsl/layout-attrs))]
|
||||
|
||||
@ -7,7 +7,8 @@
|
||||
(ns app.common.types.shape.text
|
||||
(:require
|
||||
[app.common.schema :as sm]
|
||||
[app.common.types.fills :refer [schema:fills]]))
|
||||
[app.common.types.fills :refer [schema:fills]]
|
||||
[app.common.types.text.cjk-layout :as cjk]))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; SCHEMA
|
||||
@ -15,10 +16,17 @@
|
||||
|
||||
(def node-types #{"root" "paragraph-set" "paragraph"})
|
||||
|
||||
;; Values of a Japanese layout enum attr; paragraphs set the orientation
|
||||
;; of the whole shape and spans carry a copy.
|
||||
(defn- schema:jl-enum
|
||||
[attr]
|
||||
(into [:enum] (get cjk/enum-values attr)))
|
||||
|
||||
(def schema:content
|
||||
[:map
|
||||
[:type [:= "root"]]
|
||||
[:key {:optional true} :string]
|
||||
[:line-adjustment {:optional true} (schema:jl-enum :line-adjustment)]
|
||||
[:children
|
||||
[:vector {:min 1 :gen/max 2 :gen/min 1}
|
||||
[:map
|
||||
@ -36,6 +44,8 @@
|
||||
[:font-style {:optional true} ::sm/text]
|
||||
[:font-weight {:optional true} ::sm/text]
|
||||
[:direction {:optional true} ::sm/text]
|
||||
[:writing-mode {:optional true} (schema:jl-enum :writing-mode)]
|
||||
[:text-orientation {:optional true} (schema:jl-enum :text-orientation)]
|
||||
[:text-decoration {:optional true} ::sm/text]
|
||||
[:text-transform {:optional true} ::sm/text]
|
||||
[:typography-ref-id {:optional true} [:maybe ::sm/uuid]]
|
||||
@ -52,6 +62,18 @@
|
||||
[:font-style {:optional true} ::sm/text]
|
||||
[:font-weight {:optional true} ::sm/text]
|
||||
[:direction {:optional true} ::sm/text]
|
||||
[:text-combine-upright {:optional true} (schema:jl-enum :text-combine-upright)]
|
||||
[:text-emphasis {:optional true} (schema:jl-enum :text-emphasis)]
|
||||
[:ruby {:optional true} :string]
|
||||
[:ruby-hidden {:optional true} :boolean]
|
||||
[:ruby-size {:optional true} (schema:jl-enum :ruby-size)]
|
||||
[:ruby-align {:optional true} (schema:jl-enum :ruby-align)]
|
||||
[:ruby-overhang {:optional true} (schema:jl-enum :ruby-overhang)]
|
||||
[:ruby-side {:optional true} (schema:jl-enum :ruby-side)]
|
||||
[:warichu {:optional true} (schema:jl-enum :warichu)]
|
||||
[:font-features {:optional true} (schema:jl-enum :font-features)]
|
||||
[:annotation-clearance {:optional true} (schema:jl-enum :annotation-clearance)]
|
||||
[:text-orientation {:optional true} (schema:jl-enum :text-orientation)]
|
||||
[:text-decoration {:optional true} ::sm/text]
|
||||
[:text-transform {:optional true} ::sm/text]
|
||||
[:typography-ref-id {:optional true} [:maybe ::sm/uuid]]
|
||||
@ -73,6 +95,17 @@
|
||||
[:font-style {:optional true} ::sm/text]
|
||||
[:font-weight {:optional true} ::sm/text]
|
||||
[:rtl {:optional true} :boolean]
|
||||
[:writing-mode {:optional true} ::sm/text]
|
||||
[:text-orientation {:optional true} ::sm/text]
|
||||
[:text-combine-upright {:optional true} ::sm/text]
|
||||
[:emphasis-mark {:optional true} :boolean]
|
||||
[:ruby {:optional true} :string]
|
||||
[:ruby-size {:optional true} ::sm/text]
|
||||
[:ruby-align {:optional true} ::sm/text]
|
||||
[:ruby-overhang {:optional true} ::sm/text]
|
||||
[:ruby-side {:optional true} ::sm/text]
|
||||
[:warichu {:optional true} ::sm/text]
|
||||
[:font-features {:optional true} ::sm/text]
|
||||
[:text {:optional true} :string]
|
||||
[:text-decoration {:optional true} ::sm/text]
|
||||
[:text-transform {:optional true} ::sm/text]]])
|
||||
|
||||
@ -12,6 +12,7 @@
|
||||
[app.common.math :as mth]
|
||||
[app.common.types.color :as clr]
|
||||
[app.common.types.fills :as types.fills]
|
||||
[app.common.types.text.cjk-layout :as cjk]
|
||||
[clojure.set :as set]
|
||||
[clojure.walk :as walk]
|
||||
[cuerdas.core :as str]))
|
||||
@ -50,6 +51,10 @@
|
||||
(def text-valign-attrs
|
||||
[:vertical-align])
|
||||
|
||||
;; How the shape fits lines slightly too long (see `cjk/enum-values`).
|
||||
(def text-line-adjustment-attrs
|
||||
[:line-adjustment])
|
||||
|
||||
(def text-decoration-attrs
|
||||
[:text-decoration])
|
||||
|
||||
@ -97,22 +102,29 @@
|
||||
[:grow-type])
|
||||
|
||||
(def root-attrs
|
||||
text-valign-attrs)
|
||||
(d/concat-vec text-valign-attrs text-line-adjustment-attrs))
|
||||
|
||||
(def paragraph-attrs
|
||||
(d/concat-vec
|
||||
text-align-attrs
|
||||
text-direction-attrs))
|
||||
text-direction-attrs
|
||||
cjk/whole-shape-paragraph-attrs))
|
||||
|
||||
(def text-node-attrs
|
||||
(d/concat-vec
|
||||
text-typography-attrs
|
||||
text-font-attrs
|
||||
text-spacing-attrs
|
||||
cjk/span-attrs
|
||||
text-decoration-attrs
|
||||
text-transform-attrs
|
||||
text-fills))
|
||||
|
||||
(def text-node-style-attrs
|
||||
"Text node attrs that style characters, without the attrs the characters
|
||||
own (ruby reading, warichu)."
|
||||
(vec (remove (set cjk/text-content-attrs) text-node-attrs)))
|
||||
|
||||
(def text-span-attrs
|
||||
"Inline text span attrs. Line-height is paragraph-level in the DOM editor;
|
||||
it may still be stored redundantly on span nodes."
|
||||
|
||||
187
common/src/app/common/types/text/cjk_layout.cljc
Normal file
187
common/src/app/common/types/text/cjk_layout.cljc
Normal file
@ -0,0 +1,187 @@
|
||||
;; 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.common.types.text.cjk-layout
|
||||
(:require
|
||||
[app.common.data.macros :as dm]
|
||||
[cuerdas.core :as str]))
|
||||
|
||||
;; Values of the enum attrs, in the order of the renderer's discriminants
|
||||
;; (`Raw*` enums in render-wasm). The first value is the default.
|
||||
(def enum-values
|
||||
{;; Vertical writing (tategaki).
|
||||
:writing-mode ["horizontal-tb" "vertical-rl"]
|
||||
:text-orientation ["mixed" "upright"]
|
||||
:text-combine-upright ["none" "all" "digits" "digits2" "digits3"]
|
||||
;; Emphasis mark (圏点 / bouten) applied per span.
|
||||
:text-emphasis ["none" "filled-dot" "open-dot" "filled-circle"
|
||||
"open-circle" "filled-sesame" "open-sesame"]
|
||||
;; Warichu (割注): two half-size lines in one line position.
|
||||
:warichu ["none" "warichu"]
|
||||
:font-features ["none" "palt" "vpal"]
|
||||
;; "auto" adds the ruby (at its size) and emphasis marks to the line
|
||||
;; height; "none" keeps it, so annotations sit in the line gap.
|
||||
:annotation-clearance ["none" "auto"]
|
||||
:ruby-size ["half" "third" "quarter"]
|
||||
:ruby-align ["space-around" "center" "start" "space-between"]
|
||||
:ruby-overhang ["auto" "none"]
|
||||
:ruby-side ["over" "under"]
|
||||
;; Root attr: how lines slightly too long are fitted (Adobe's Kinsoku
|
||||
;; Type). It decides squeezing; alignment decides the space left over.
|
||||
:line-adjustment ["push-in-first" "push-out-first" "push-out-only"]})
|
||||
|
||||
(def ^:private enum-value-sets
|
||||
(update-vals enum-values set))
|
||||
|
||||
(defn enum-default
|
||||
"Default value of the enum `attr`."
|
||||
[attr]
|
||||
(first (get enum-values attr)))
|
||||
|
||||
(defn valid-enum-value?
|
||||
"True when `value` is one of the values of the enum `attr`."
|
||||
[attr value]
|
||||
(contains? (get enum-value-sets attr) value))
|
||||
|
||||
;; Paragraph attrs. They apply to the whole shape: every paragraph carries
|
||||
;; the first one's value.
|
||||
(def whole-shape-paragraph-attrs
|
||||
[:writing-mode :text-orientation])
|
||||
|
||||
;; Ruby attrs that change how a reading looks, not the reading itself.
|
||||
(def ruby-presentation-attrs
|
||||
[:ruby-hidden :ruby-size :ruby-align :ruby-overhang :ruby-side])
|
||||
|
||||
;; Span attrs; `:ruby` holds the reading (furigana) of the span.
|
||||
(def span-attrs
|
||||
(into [:text-combine-upright :text-emphasis :ruby]
|
||||
(concat ruby-presentation-attrs
|
||||
[:warichu :font-features :annotation-clearance])))
|
||||
|
||||
;; Span attrs owned by the span's characters, not its style: a reading or a
|
||||
;; warichu note annotates one span and is never copied to other text.
|
||||
(def text-content-attrs
|
||||
[:ruby :warichu])
|
||||
|
||||
;; Values of the span attrs above when a span does not store them.
|
||||
(def span-attr-defaults
|
||||
(into {:ruby "" :ruby-hidden false}
|
||||
(comp (filter #(contains? enum-values %))
|
||||
(map (juxt identity enum-default)))
|
||||
span-attrs))
|
||||
|
||||
;; Values of the paragraph attrs when a paragraph does not store them.
|
||||
(def paragraph-attr-defaults
|
||||
(into {} (map (juxt identity enum-default)) whole-shape-paragraph-attrs))
|
||||
|
||||
;; Glyph of each emphasis style, per CSS `text-emphasis-style`.
|
||||
(def ^:private emphasis-mark-chars
|
||||
{"filled-dot" "•"
|
||||
"open-dot" "◦"
|
||||
"filled-circle" "●"
|
||||
"open-circle" "○"
|
||||
"filled-sesame" "﹅"
|
||||
"open-sesame" "﹆"})
|
||||
|
||||
(defn emphasis-mark-char
|
||||
"Mark glyph of a text-emphasis value, or nil for none."
|
||||
[text-emphasis]
|
||||
(get emphasis-mark-chars text-emphasis))
|
||||
|
||||
;; Emphasis mark font size relative to the base font size.
|
||||
(def emphasis-font-scale 0.5)
|
||||
|
||||
;; Warichu sub-line font size relative to the base font size.
|
||||
(def warichu-font-scale 0.5)
|
||||
|
||||
(defn ruby-font-scale
|
||||
[ruby-size]
|
||||
(case ruby-size
|
||||
"third" (/ 1 3)
|
||||
"quarter" 0.25
|
||||
0.5))
|
||||
|
||||
(defn visible-ruby
|
||||
"Ruby annotation text of a text node, or nil when absent, hidden or on a
|
||||
warichu note, which shows no ruby."
|
||||
[node]
|
||||
(let [ruby (:ruby node)]
|
||||
(when (and (string? ruby)
|
||||
(not (str/blank? ruby))
|
||||
(not (true? (:ruby-hidden node)))
|
||||
(not= "warichu" (:warichu node)))
|
||||
ruby)))
|
||||
|
||||
(defn western-reading?
|
||||
"True when a ruby reading holds no CJK, kana or full-width character: it is
|
||||
set as words, sideways in vertical text, and never spread letter by
|
||||
letter."
|
||||
[ruby]
|
||||
(and (string? ruby)
|
||||
(not (str/blank? ruby))
|
||||
(not (re-find #"[\u2E80-\u9FFF\uF900-\uFAFF\uFF00-\uFFEF]" ruby))))
|
||||
|
||||
(defn ruby-span?
|
||||
"True when a text node carries a ruby reading, hidden or not."
|
||||
[node]
|
||||
(not (str/blank? (:ruby node))))
|
||||
|
||||
(defn annotated-span?
|
||||
"True when a text node carries a ruby reading or is a warichu note."
|
||||
[node]
|
||||
(or (ruby-span? node)
|
||||
(= "warichu" (:warichu node))))
|
||||
|
||||
(defn- digit?
|
||||
[c]
|
||||
(let [code #?(:clj (int c) :cljs (.charCodeAt c 0))]
|
||||
(or (<= 48 code 57) (<= 0xFF10 code 0xFF19))))
|
||||
|
||||
;; Longest digit run each `digits` tate-chu-yoko value combines.
|
||||
(def ^:private digit-combine-max-lengths
|
||||
{"digits" 4 "digits2" 2 "digits3" 3})
|
||||
|
||||
(defn digit-combine?
|
||||
"True for the tate-chu-yoko values that combine digit runs only."
|
||||
[value]
|
||||
(contains? digit-combine-max-lengths value))
|
||||
|
||||
(defn digit-combine-segments
|
||||
"Text split for a `digits` tate-chu-yoko value: `[text combine?]` pairs,
|
||||
where runs of two up to the value's maximum ASCII or full-width digits
|
||||
combine and longer runs stay ordinary text. Nil for other values."
|
||||
[text value]
|
||||
(when-let [max-len (get digit-combine-max-lengths value)]
|
||||
(->> (partition-by digit? (seq text))
|
||||
(mapv (fn [chars]
|
||||
(let [run (apply str chars)]
|
||||
[run (and (digit? (first chars)) (<= 2 (count chars) max-len))]))))))
|
||||
|
||||
(defn warichu-text?
|
||||
"True when a text node renders as warichu, which needs at least two
|
||||
characters for its two sub-lines."
|
||||
[node]
|
||||
(let [text (:text node)]
|
||||
(and (= "warichu" (:warichu node))
|
||||
(string? text)
|
||||
(>= (count text) 2))))
|
||||
|
||||
(defn whole-shape-attr
|
||||
"Value of a whole-shape paragraph `attr` (see `whole-shape-paragraph-attrs`)
|
||||
in a text content: stored per paragraph, the first paragraph's value
|
||||
decides. Nil when unset."
|
||||
[content attr]
|
||||
(dm/get-in content [:children 0 :children 0 attr]))
|
||||
|
||||
(defn content-writing-mode
|
||||
"Writing mode of a text content, or nil when unset."
|
||||
[content]
|
||||
(whole-shape-attr content :writing-mode))
|
||||
|
||||
(defn vertical-text-content?
|
||||
"True when the text content flows vertically (vertical-rl)."
|
||||
[content]
|
||||
(= "vertical-rl" (content-writing-mode content)))
|
||||
@ -8,7 +8,9 @@
|
||||
(:require
|
||||
|
||||
[app.common.types.shape :as cts]
|
||||
[app.common.types.shape.text :as ctst]
|
||||
[app.common.types.text :as cttx]
|
||||
[app.common.types.text.cjk-layout :as cjk]
|
||||
[clojure.test :as t :include-macros true]))
|
||||
|
||||
(def content-base
|
||||
@ -368,3 +370,114 @@
|
||||
|
||||
(t/is (every? #(= % "14") original-font-sizes))
|
||||
(t/is (every? #(= % "32") updated-font-sizes))))
|
||||
|
||||
(defn- with-span-attrs
|
||||
[attrs]
|
||||
(update-in content-base [:children 0 :children 0 :children 0] merge attrs))
|
||||
|
||||
(defn- with-paragraph-attrs
|
||||
[attrs]
|
||||
(update-in content-base [:children 0 :children 0] merge attrs))
|
||||
|
||||
(t/deftest japanese-span-attrs-accept-supported-values
|
||||
(t/is (ctst/valid-content?
|
||||
(with-span-attrs {:text-combine-upright "digits2"
|
||||
:text-emphasis "open-sesame"
|
||||
:ruby "かんじ"
|
||||
:ruby-hidden true
|
||||
:ruby-size "quarter"
|
||||
:ruby-align "space-between"
|
||||
:ruby-overhang "none"
|
||||
:ruby-side "under"
|
||||
:warichu "warichu"
|
||||
:font-features "vpal"
|
||||
:annotation-clearance "auto"
|
||||
:text-orientation "upright"})))
|
||||
(t/is (ctst/valid-content?
|
||||
(with-paragraph-attrs {:writing-mode "vertical-rl" :text-orientation "mixed"}))))
|
||||
|
||||
(t/deftest japanese-attrs-reject-unknown-values
|
||||
(doseq [attrs [{:text-combine-upright "digits5"}
|
||||
{:text-emphasis "dot"}
|
||||
{:ruby-hidden "yes"}
|
||||
{:ruby-size "full"}
|
||||
{:ruby-align "end"}
|
||||
{:ruby-overhang "always"}
|
||||
{:ruby-side "left"}
|
||||
{:warichu "yes"}
|
||||
{:font-features "kern"}
|
||||
{:annotation-clearance "max"}
|
||||
{:text-orientation "sideways"}]]
|
||||
(t/is (not (ctst/valid-content? (with-span-attrs attrs))) (pr-str attrs)))
|
||||
(t/is (not (ctst/valid-content? (with-paragraph-attrs {:writing-mode "vertical-lr"})))))
|
||||
|
||||
(t/deftest digit-combine-segments-combine-short-digit-runs
|
||||
(t/is (= [["平成" false] ["31" true] ["年" false]]
|
||||
(cjk/digit-combine-segments "平成31年" "digits2")))
|
||||
(t/is (= [["第" false] ["123" true]]
|
||||
(cjk/digit-combine-segments "第123" "digits3")))
|
||||
(t/is (= [["12345" false]] (cjk/digit-combine-segments "12345" "digits")))
|
||||
(t/is (= [["1" false]] (cjk/digit-combine-segments "1" "digits")))
|
||||
(t/is (nil? (cjk/digit-combine-segments "31" "all"))))
|
||||
|
||||
(t/deftest western-reading-test
|
||||
(t/is (true? (cjk/western-reading? "editor")))
|
||||
(t/is (true? (cjk/western-reading? "gross domestic product")))
|
||||
(t/is (false? (cjk/western-reading? "エディター")))
|
||||
(t/is (false? (cjk/western-reading? "GDP")))
|
||||
(t/is (false? (cjk/western-reading? "かnji")))
|
||||
(t/is (false? (cjk/western-reading? "")))
|
||||
(t/is (false? (cjk/western-reading? nil))))
|
||||
|
||||
(t/deftest schema-accepts-every-japanese-enum-value
|
||||
(doseq [[attr values] (dissoc cjk/enum-values :writing-mode :line-adjustment)
|
||||
value values]
|
||||
(t/is (ctst/valid-content? (with-span-attrs {attr value})) (pr-str attr value)))
|
||||
(doseq [value (:writing-mode cjk/enum-values)]
|
||||
(t/is (ctst/valid-content? (with-paragraph-attrs {:writing-mode value})) value))
|
||||
(doseq [value (:line-adjustment cjk/enum-values)]
|
||||
(t/is (ctst/valid-content? (assoc content-base :line-adjustment value)) value))
|
||||
(t/is (not (ctst/valid-content? (assoc content-base :line-adjustment "squeeze")))))
|
||||
|
||||
(t/deftest japanese-enum-defaults-are-the-first-values
|
||||
(t/is (= "horizontal-tb" (cjk/enum-default :writing-mode)))
|
||||
(t/is (= "space-around" (cjk/enum-default :ruby-align)))
|
||||
(t/is (= "auto" (cjk/enum-default :ruby-overhang)))
|
||||
(t/is (= "push-in-first" (cjk/enum-default :line-adjustment)))
|
||||
(t/is (= {:writing-mode "horizontal-tb" :text-orientation "mixed"}
|
||||
cjk/paragraph-attr-defaults)))
|
||||
|
||||
(t/deftest span-attr-defaults-cover-every-span-attr
|
||||
(t/is (= (set cjk/span-attrs) (set (keys cjk/span-attr-defaults))))
|
||||
(t/is (= "" (:ruby cjk/span-attr-defaults)))
|
||||
(t/is (false? (:ruby-hidden cjk/span-attr-defaults)))
|
||||
(t/is (= "half" (:ruby-size cjk/span-attr-defaults))))
|
||||
|
||||
(t/deftest valid-enum-value-checks-the-attr-values
|
||||
(t/is (cjk/valid-enum-value? :ruby-side "under"))
|
||||
(t/is (not (cjk/valid-enum-value? :ruby-side "left")))
|
||||
(t/is (not (cjk/valid-enum-value? :ruby-side nil))))
|
||||
|
||||
(t/deftest digit-combine-covers-the-digits-values-only
|
||||
(t/is (every? cjk/digit-combine? ["digits" "digits2" "digits3"]))
|
||||
(t/is (not-any? cjk/digit-combine? ["none" "all" nil])))
|
||||
|
||||
(t/deftest warichu-notes-show-no-ruby
|
||||
(t/is (= "かん" (cjk/visible-ruby {:text "漢字" :ruby "かん"})))
|
||||
(t/is (nil? (cjk/visible-ruby {:text "漢字" :ruby "かん" :warichu "warichu"})))
|
||||
(t/is (nil? (cjk/visible-ruby {:text "漢字" :ruby "かん" :ruby-hidden true}))))
|
||||
|
||||
(t/deftest ruby-span-is-true-for-hidden-readings
|
||||
(t/is (cjk/ruby-span? {:ruby "かん" :ruby-hidden true}))
|
||||
(t/is (not (cjk/ruby-span? {:ruby " "})))
|
||||
(t/is (not (cjk/ruby-span? {}))))
|
||||
|
||||
(t/deftest whole-shape-attr-reads-the-first-paragraph
|
||||
(let [content {:type "root"
|
||||
:children [{:type "paragraph-set"
|
||||
:children [{:type "paragraph" :writing-mode "vertical-rl"
|
||||
:children [{:text "a"}]}
|
||||
{:type "paragraph" :writing-mode "horizontal-tb"
|
||||
:children [{:text "b"}]}]}]}]
|
||||
(t/is (= "vertical-rl" (cjk/whole-shape-attr content :writing-mode)))
|
||||
(t/is (nil? (cjk/whole-shape-attr content :text-orientation)))))
|
||||
|
||||
@ -25,13 +25,14 @@
|
||||
"SUMMARY" "TABLE" "TBODY" "TFOOT" "THEAD" "TR" "UL"})
|
||||
|
||||
;; Elements whose text is read; any other element is skipped with its children.
|
||||
;; `O:P` is Word's paragraph filler, which marks its empty lines.
|
||||
;; `O:P` is Word's paragraph filler, which marks its empty lines. Ruby keeps its
|
||||
;; base text (`RUBY`, `RB`); the reading (`RT`, `RTC`) and fallback `RP` are skipped.
|
||||
(def ^:private allowed-tags
|
||||
(into block-tags
|
||||
#{"A" "ABBR" "B" "BDI" "BDO" "BIG" "BR" "CENTER" "CITE" "CODE" "DATA"
|
||||
"DEL" "DFN" "EM" "FONT" "I" "INS" "KBD" "LABEL" "MARK" "O:P" "Q" "S"
|
||||
"SAMP" "SMALL" "SPAN" "STRIKE" "STRONG" "SUB" "SUP" "TD" "TH" "TIME"
|
||||
"TT" "U" "VAR" "WBR"}))
|
||||
"DEL" "DFN" "EM" "FONT" "I" "INS" "KBD" "LABEL" "MARK" "O:P" "Q" "RB"
|
||||
"RUBY" "S" "SAMP" "SMALL" "SPAN" "STRIKE" "STRONG" "SUB" "SUP" "TD"
|
||||
"TH" "TIME" "TT" "U" "VAR" "WBR"}))
|
||||
|
||||
(def ^:private bold-tags #{"B" "STRONG" "TH" "H1" "H2" "H3" "H4" "H5" "H6"})
|
||||
(def ^:private italic-tags #{"I" "EM" "CITE" "VAR" "DFN"})
|
||||
|
||||
@ -276,3 +276,20 @@
|
||||
|
||||
(t/testing "tokens shared by both shapes are applied"
|
||||
(t/is (= "opacity.50" (get-in result [:applied-tokens :opacity]))))))
|
||||
|
||||
;; ---------------------------------------------------------------------------
|
||||
;; Ruby readings and warichu notes belong to their own characters
|
||||
;; ---------------------------------------------------------------------------
|
||||
|
||||
(t/deftest copy-props-leaves-ruby-reading-and-warichu-behind
|
||||
(let [source (text {:content (content {:text "漢字"
|
||||
:font-size "20"
|
||||
:ruby "かんじ"
|
||||
:ruby-size "third"
|
||||
:warichu "warichu"})})
|
||||
target (text {})
|
||||
node (-> (paste-props source target) :content :children first :children first :children first)]
|
||||
(t/is (= "20" (:font-size node)))
|
||||
(t/is (= "third" (:ruby-size node)))
|
||||
(t/is (nil? (:ruby node)))
|
||||
(t/is (nil? (:warichu node)))))
|
||||
|
||||
@ -144,6 +144,17 @@
|
||||
(html->fragment
|
||||
"<table><tr><td>a</td><td> b</td></tr><tr><td>c</td><td>d</td></tr></table>")))))
|
||||
|
||||
(t/deftest ruby-keeps-the-base-text
|
||||
(t/testing "the annotated text is pasted and the reading is dropped"
|
||||
(t/is (= [(paragraph (run "漢字です"))]
|
||||
(html->fragment "<p><ruby>漢字<rt>かんじ</rt></ruby>です</p>"))))
|
||||
|
||||
(t/testing "rb bases and rp fallback parentheses"
|
||||
(t/is (= [(paragraph (run "漢字"))]
|
||||
(html->fragment
|
||||
(str "<ruby><rb>漢</rb><rp>(</rp><rt>かん</rt><rp>)</rp></ruby>"
|
||||
"<ruby>字<rp>(</rp><rt>じ</rt><rp>)</rp></ruby>"))))))
|
||||
|
||||
(t/deftest hidden-content-is-skipped
|
||||
(t/testing "style, script and display:none give no text"
|
||||
(t/is (= [(paragraph (run "shown"))]
|
||||
|
||||
@ -110,6 +110,44 @@ test("create context with file and page", () => {
|
||||
assert.equal(rootShape.id, "00000000-0000-0000-0000-000000000000");
|
||||
});
|
||||
|
||||
test("create vertical text", () => {
|
||||
const context = penpot.createBuildContext();
|
||||
|
||||
const fileId = context.addFile({name: "file 1"});
|
||||
const pageId = context.addPage({name: "page 1"});
|
||||
const textId = context.addText({
|
||||
name: "Vertical text",
|
||||
x: 10,
|
||||
y: 20,
|
||||
width: 100,
|
||||
height: 200,
|
||||
content: {
|
||||
type: "root",
|
||||
children: [
|
||||
{
|
||||
type: "paragraph-set",
|
||||
children: [
|
||||
{
|
||||
type: "paragraph",
|
||||
writingMode: "vertical-rl",
|
||||
textOrientation: "upright",
|
||||
children: [{text: "縦書き"}],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
const internalState = context.getInternalState();
|
||||
const paragraph =
|
||||
internalState.files[fileId].data.pagesIndex[pageId].objects[textId].content
|
||||
.children[0].children[0];
|
||||
|
||||
assert.equal(paragraph.writingMode, "vertical-rl");
|
||||
assert.equal(paragraph.textOrientation, "upright");
|
||||
});
|
||||
|
||||
test("create context with color", () => {
|
||||
const context = penpot.createBuildContext();
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user