diff --git a/frontend/resources/images/icons/text-align-bottom.svg b/frontend/resources/images/icons/text-align-bottom.svg
new file mode 100644
index 0000000000..590e7a053f
--- /dev/null
+++ b/frontend/resources/images/icons/text-align-bottom.svg
@@ -0,0 +1,5 @@
+
diff --git a/frontend/resources/images/icons/text-align-middle.svg b/frontend/resources/images/icons/text-align-middle.svg
new file mode 100644
index 0000000000..9480d4a6b0
--- /dev/null
+++ b/frontend/resources/images/icons/text-align-middle.svg
@@ -0,0 +1,5 @@
+
diff --git a/frontend/resources/images/icons/text-align-top.svg b/frontend/resources/images/icons/text-align-top.svg
new file mode 100644
index 0000000000..c3179a781a
--- /dev/null
+++ b/frontend/resources/images/icons/text-align-top.svg
@@ -0,0 +1,5 @@
+
diff --git a/frontend/resources/images/icons/text-auto-height-vertical.svg b/frontend/resources/images/icons/text-auto-height-vertical.svg
new file mode 100644
index 0000000000..8de6bd7048
--- /dev/null
+++ b/frontend/resources/images/icons/text-auto-height-vertical.svg
@@ -0,0 +1,5 @@
+
diff --git a/frontend/resources/images/icons/text-auto-width-vertical.svg b/frontend/resources/images/icons/text-auto-width-vertical.svg
new file mode 100644
index 0000000000..51a0e12e80
--- /dev/null
+++ b/frontend/resources/images/icons/text-auto-width-vertical.svg
@@ -0,0 +1,3 @@
+
diff --git a/frontend/resources/images/icons/text-horizontal-center.svg b/frontend/resources/images/icons/text-horizontal-center.svg
new file mode 100644
index 0000000000..782b8d6592
--- /dev/null
+++ b/frontend/resources/images/icons/text-horizontal-center.svg
@@ -0,0 +1,5 @@
+
diff --git a/frontend/resources/images/icons/text-justify-vertical.svg b/frontend/resources/images/icons/text-justify-vertical.svg
new file mode 100644
index 0000000000..2ddbbef9c3
--- /dev/null
+++ b/frontend/resources/images/icons/text-justify-vertical.svg
@@ -0,0 +1,5 @@
+
diff --git a/frontend/resources/images/icons/text-left.svg b/frontend/resources/images/icons/text-left.svg
new file mode 100644
index 0000000000..f489023b88
--- /dev/null
+++ b/frontend/resources/images/icons/text-left.svg
@@ -0,0 +1,5 @@
+
diff --git a/frontend/resources/images/icons/text-right.svg b/frontend/resources/images/icons/text-right.svg
new file mode 100644
index 0000000000..a7f77b7b31
--- /dev/null
+++ b/frontend/resources/images/icons/text-right.svg
@@ -0,0 +1,5 @@
+
diff --git a/frontend/src/app/main/ui/ds/foundations/assets/icon.cljs b/frontend/src/app/main/ui/ds/foundations/assets/icon.cljs
index 1278e80fd3..4c140b6df9 100644
--- a/frontend/src/app/main/ui/ds/foundations/assets/icon.cljs
+++ b/frontend/src/app/main/ui/ds/foundations/assets/icon.cljs
@@ -270,11 +270,16 @@
(def ^:icon-id swatches "swatches")
(def ^:icon-id switch "switch")
(def ^:icon-id text "text")
+(def ^:icon-id text-align-bottom "text-align-bottom")
(def ^:icon-id text-align-center "text-align-center")
(def ^:icon-id text-align-left "text-align-left")
+(def ^:icon-id text-align-middle "text-align-middle")
(def ^:icon-id text-align-right "text-align-right")
+(def ^:icon-id text-align-top "text-align-top")
(def ^:icon-id text-auto-height "text-auto-height")
+(def ^:icon-id text-auto-height-vertical "text-auto-height-vertical")
(def ^:icon-id text-auto-width "text-auto-width")
+(def ^:icon-id text-auto-width-vertical "text-auto-width-vertical")
(def ^:icon-id text-bottom "text-bottom")
(def ^:icon-id text-combine-upright-all "text-combine-upright-all")
(def ^:icon-id text-combine-upright-digits "text-combine-upright-digits")
@@ -283,7 +288,10 @@
(def ^:icon-id text-font-family "text-font-family")
(def ^:icon-id text-font-size "text-font-size")
(def ^:icon-id text-font-weight "text-font-weight")
+(def ^:icon-id text-horizontal-center "text-horizontal-center")
(def ^:icon-id text-justify "text-justify")
+(def ^:icon-id text-justify-vertical "text-justify-vertical")
+(def ^:icon-id text-left "text-left")
(def ^:icon-id text-letterspacing "text-letterspacing")
(def ^:icon-id text-lineheight "text-lineheight")
(def ^:icon-id text-lowercase "text-lowercase")
@@ -294,6 +302,7 @@
(def ^:icon-id text-orientation-upright "text-orientation-upright")
(def ^:icon-id text-palette "text-palette")
(def ^:icon-id text-paragraph "text-paragraph")
+(def ^:icon-id text-right "text-right")
(def ^:icon-id text-rtl "text-rtl")
(def ^:icon-id text-stroked "text-stroked")
(def ^:icon-id text-top "text-top")
diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/text.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/menus/text.cljs
index b571baed15..ffbf56784d 100644
--- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/text.cljs
+++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/text.cljs
@@ -40,6 +40,7 @@
[app.util.object :as obj]
[app.util.text.content :as content]
[app.util.text.ui :as txu]
+ [app.util.text.writing-mode :as wm]
[app.util.timers :as ts]
[beicon.v2.core :as rx]
[potok.v2.core :as ptk]
@@ -56,24 +57,32 @@
(mf/defc text-align-options*
[{:keys [values on-change on-blur]}]
- (let [options
- (mf/with-memo []
+ (let [vertical? (and (wm/vertical-layout-active?)
+ (tjl/vertical-japanese-layout? values))
+ options
+ (mf/with-memo [vertical?]
[{:value "left"
:id "text-align-left"
- :label (tr "workspace.options.text-options.text-align-left")
- :icon i/text-align-left}
+ :label (if vertical?
+ (tr "workspace.options.text-options.align-top")
+ (tr "workspace.options.text-options.text-align-left"))
+ :icon (if vertical? i/text-align-top i/text-align-left)}
{:value "center"
:id "text-align-center"
- :label (tr "workspace.options.text-options.text-align-center")
- :icon i/text-align-center}
+ :label (if vertical?
+ (tr "workspace.options.text-options.align-middle")
+ (tr "workspace.options.text-options.text-align-center"))
+ :icon (if vertical? i/text-align-middle i/text-align-center)}
{:value "right"
:id "text-align-right"
- :label (tr "workspace.options.text-options.text-align-right")
- :icon i/text-align-right}
+ :label (if vertical?
+ (tr "workspace.options.text-options.align-bottom")
+ (tr "workspace.options.text-options.text-align-right"))
+ :icon (if vertical? i/text-align-bottom i/text-align-right)}
{:value "justify"
:id "text-align-justify"
:label (tr "workspace.options.text-options.text-align-justify")
- :icon i/text-justify}])
+ :icon (if vertical? i/text-justify-vertical i/text-justify)}])
handle-change
(mf/use-fn
@@ -90,7 +99,9 @@
(mf/defc text-direction-options*
[{:keys [values on-change on-blur]}]
- (let [direction (radio-selected (:text-direction values))
+ (let [vertical? (and (wm/vertical-layout-active?)
+ (tjl/vertical-japanese-layout? values))
+ direction (radio-selected (:text-direction values))
options
(mf/with-memo []
[{:value "ltr"
@@ -109,29 +120,38 @@
(on-change {:text-direction (if (= value direction) "none" value)})
(when (some? on-blur) (on-blur))))]
- [:div {:class (stl/css :text-direction-options)}
- [:> radio-buttons* {:selected direction
- :on-change handle-change
- :name "text-direction-options"
- :options options}]]))
+ (when-not vertical?
+ [:div {:class (stl/css :text-direction-options)}
+ [:> radio-buttons* {:selected direction
+ :on-change handle-change
+ :name "text-direction-options"
+ :options options}]])))
(mf/defc vertical-align*
[{:keys [values on-change on-blur]}]
- (let [vertical-align (radio-selected (:vertical-align values) "top")
+ (let [vertical? (and (wm/vertical-layout-active?)
+ (tjl/vertical-japanese-layout? values))
+ vertical-align (radio-selected (:vertical-align values) "top")
options
- (mf/with-memo []
+ (mf/with-memo [vertical?]
[{:value "top"
:id "vertical-text-align-top"
- :label (tr "workspace.options.text-options.align-top")
- :icon i/text-top}
+ :label (if vertical?
+ (tr "workspace.options.text-options.text-align-right")
+ (tr "workspace.options.text-options.align-top"))
+ :icon (if vertical? i/text-right i/text-top)}
{:value "center"
:id "vertical-text-align-center"
- :label (tr "workspace.options.text-options.align-middle")
- :icon i/text-middle}
+ :label (if vertical?
+ (tr "workspace.options.text-options.text-align-center")
+ (tr "workspace.options.text-options.align-middle"))
+ :icon (if vertical? i/text-horizontal-center i/text-middle)}
{:value "bottom"
:id "vertical-text-align-bottom"
- :label (tr "workspace.options.text-options.align-bottom")
- :icon i/text-bottom}])
+ :label (if vertical?
+ (tr "workspace.options.text-options.text-align-left")
+ (tr "workspace.options.text-options.align-bottom"))
+ :icon (if vertical? i/text-left i/text-bottom)}])
handle-change
(mf/use-fn
@@ -148,23 +168,29 @@
(mf/defc grow-options*
[{:keys [ids values on-blur]}]
- (let [grow-type (:grow-type values)
+ (let [vertical? (and (wm/vertical-layout-active?)
+ (tjl/vertical-japanese-layout? values))
+ grow-type (:grow-type values)
selected (radio-selected grow-type)
editor-instance (mf/deref refs/workspace-editor)
options
- (mf/with-memo []
+ (mf/with-memo [vertical?]
[{:value "fixed"
:id "text-fixed-grow"
:label (tr "workspace.options.text-options.grow-fixed")
:icon i/text-fixed}
{:value "auto-width"
:id "text-auto-width-grow"
- :label (tr "workspace.options.text-options.grow-auto-width")
- :icon i/text-auto-width}
+ :label (if vertical?
+ (tr "workspace.options.text-options.grow-auto-height")
+ (tr "workspace.options.text-options.grow-auto-width"))
+ :icon (if vertical? i/text-auto-width-vertical i/text-auto-width)}
{:value "auto-height"
:id "text-auto-height-grow"
- :label (tr "workspace.options.text-options.grow-auto-height")
- :icon i/text-auto-height}])
+ :label (if vertical?
+ (tr "workspace.options.text-options.grow-auto-width")
+ (tr "workspace.options.text-options.grow-auto-height"))
+ :icon (if vertical? i/text-auto-height-vertical i/text-auto-height)}])
handle-change
(mf/use-fn
@@ -651,9 +677,6 @@
:on-click toggle-more-options
:icon i/menu}]]
- (when japanese-layout-config-enabled?
- [:> tjl/line-adjustment-option* common-props])
-
(when more-options-open?
[:div {:class (stl/css :text-decoration-options)}
[:> vertical-align* common-props]
diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/text_japanese_layout.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/menus/text_japanese_layout.cljs
index 8714f09158..441fa3a41e 100644
--- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/text_japanese_layout.cljs
+++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/text_japanese_layout.cljs
@@ -506,6 +506,7 @@
(when ^boolean vertical?
[:> text-combine-upright-count-options* common-props])
[:> font-features-options* common-props]
+ [:> line-adjustment-option* common-props]
;; Emphasis marks (圏点 / bouten) apply to the selected characters.
(when ^boolean text-selection-active
[:> span-select-option* (mf/spread-props common-props
diff --git a/frontend/test/frontend_tests/ui/text_options_test.cljs b/frontend/test/frontend_tests/ui/text_options_test.cljs
index e0999e870b..e94f233f85 100644
--- a/frontend/test/frontend_tests/ui/text_options_test.cljs
+++ b/frontend/test/frontend_tests/ui/text_options_test.cljs
@@ -10,9 +10,11 @@
[app.common.types.text.cjk-layout :as cjk]
[app.main.data.workspace.texts :as dwt]
[app.main.ui.ds.controls.select :as select]
+ [app.main.ui.hooks :as hooks]
[app.main.ui.shapes.text.html-text :as html-text]
[app.main.ui.shapes.text.styles :as text-styles]
[app.main.ui.workspace.sidebar.options.common :refer [radio-selected]]
+ [app.main.ui.workspace.sidebar.options.menus.text :as text-menu]
[app.main.ui.workspace.sidebar.options.menus.text-japanese-layout :as tjl]
[app.util.text.writing-mode :as wm]
[cljs.test :as t :include-macros true]
@@ -246,3 +248,115 @@
(t/is (not (str/includes? (markup) "vertical-rl"))))
(with-redefs [wm/vertical-layout-active? (constantly true)]
(t/is (str/includes? (markup) "writing-mode:vertical-rl")))))
+
+(defn- alignment-markup
+ ([component writing-mode wasm-enabled]
+ (alignment-markup component writing-mode wasm-enabled {}))
+ ([component writing-mode wasm-enabled values]
+ ;; Server rendering has no DOM container for tooltip portals.
+ (with-redefs [wm/vertical-layout-active? (constantly wasm-enabled)
+ hooks/use-portal-container (fn ([] nil) ([_] nil))]
+ (rds/renderToStaticMarkup
+ (mf/element component #js {:values (assoc values :writing-mode writing-mode)
+ :on-change identity})))))
+
+(defn- alignment-labels
+ [markup]
+ (mapv second (re-seq #"aria-label=\"([^\"]+)\"" markup)))
+
+(defn- alignment-icons
+ [markup]
+ (mapv second (re-seq #"href=\"#icon-([^\"]+)\"" markup)))
+
+(t/deftest vertical-paragraph-alignment-shows-top-middle-bottom-and-vertical-justify
+ (let [markup (alignment-markup text-menu/text-align-options* "vertical-rl" true)]
+ (t/is (= ["text-align-top" "text-align-middle" "text-align-bottom" "text-justify-vertical"]
+ (alignment-icons markup)))
+ (t/is (= ["workspace.options.text-options.align-top"
+ "workspace.options.text-options.align-middle"
+ "workspace.options.text-options.align-bottom"
+ "workspace.options.text-options.text-align-justify"]
+ (alignment-labels markup)))
+ (t/is (= ["left" "center" "right" "justify"]
+ (mapv second (re-seq #"value=\"([^\"]+)\"" markup))))))
+
+(t/deftest vertical-column-block-alignment-shows-right-center-and-left
+ (let [markup (alignment-markup text-menu/vertical-align* "vertical-rl" true)]
+ (t/is (= ["text-right" "text-horizontal-center" "text-left"]
+ (alignment-icons markup)))
+ (t/is (= ["workspace.options.text-options.text-align-right"
+ "workspace.options.text-options.text-align-center"
+ "workspace.options.text-options.text-align-left"]
+ (alignment-labels markup)))
+ (t/is (= ["top" "center" "bottom"]
+ (mapv second (re-seq #"value=\"([^\"]+)\"" markup))))))
+
+(t/deftest horizontal-and-mixed-writing-modes-use-horizontal-alignment-controls
+ (doseq [writing-mode [nil "horizontal-tb" :multiple]]
+ (let [paragraph (alignment-markup text-menu/text-align-options* writing-mode true)
+ block (alignment-markup text-menu/vertical-align* writing-mode true)]
+ (t/is (= ["text-align-left" "text-align-center" "text-align-right" "text-justify"]
+ (alignment-icons paragraph)))
+ (t/is (= ["text-top" "text-middle" "text-bottom"] (alignment-icons block))))))
+
+(t/deftest svg-renderer-keeps-horizontal-alignment-for-stored-vertical-text
+ (let [paragraph (alignment-markup text-menu/text-align-options* "vertical-rl" false)
+ block (alignment-markup text-menu/vertical-align* "vertical-rl" false)]
+ (t/is (= ["text-align-left" "text-align-center" "text-align-right" "text-justify"]
+ (alignment-icons paragraph)))
+ (t/is (= ["text-top" "text-middle" "text-bottom"] (alignment-icons block)))
+ (t/is (= "workspace.options.text-options.text-align-left" (first (alignment-labels paragraph))))
+ (t/is (= "workspace.options.text-options.align-top" (first (alignment-labels block))))))
+
+
+(t/deftest vertical-text-growth-controls-show-the-physical-growth-direction
+ (let [markup (alignment-markup text-menu/grow-options* "vertical-rl" true)]
+ (t/is (= ["text-fixed" "text-auto-width-vertical" "text-auto-height-vertical"]
+ (alignment-icons markup)))
+ (t/is (= ["workspace.options.text-options.grow-fixed"
+ "workspace.options.text-options.grow-auto-height"
+ "workspace.options.text-options.grow-auto-width"]
+ (alignment-labels markup)))
+ (t/is (= ["fixed" "auto-width" "auto-height"]
+ (mapv second (re-seq #"value=\"([^\"]+)\"" markup))))))
+
+(t/deftest horizontal-and-svg-text-growth-controls-keep-horizontal-directions
+ (doseq [[writing-mode wasm-enabled] [[nil true]
+ ["horizontal-tb" true]
+ [:multiple true]
+ ["vertical-rl" false]]]
+ (let [markup (alignment-markup text-menu/grow-options* writing-mode wasm-enabled)]
+ (t/is (= ["text-fixed" "text-auto-width" "text-auto-height"]
+ (alignment-icons markup)))
+ (t/is (= ["workspace.options.text-options.grow-fixed"
+ "workspace.options.text-options.grow-auto-width"
+ "workspace.options.text-options.grow-auto-height"]
+ (alignment-labels markup))))))
+
+
+(t/deftest japanese-layout-controls-include-line-adjustment
+ (let [markup (alignment-markup tjl/japanese-layout-options* "vertical-rl" true)]
+ (t/is (str/includes? markup "workspace.options.text-options.line-adjustment"))))
+
+
+(t/deftest vertical-text-hides-ltr-and-rtl-controls
+ (t/is (empty? (alignment-markup text-menu/text-direction-options* "vertical-rl" true))))
+
+(t/deftest horizontal-and-svg-text-show-ltr-and-rtl-controls
+ (doseq [[writing-mode wasm-enabled] [[nil true]
+ ["horizontal-tb" true]
+ [:multiple true]
+ ["vertical-rl" false]]]
+ (let [markup (alignment-markup text-menu/text-direction-options* writing-mode wasm-enabled)]
+ (t/is (= ["text-ltr" "text-rtl"] (alignment-icons markup))))))
+
+(t/deftest hiding-direction-controls-preserves-the-stored-direction
+ (let [values {:text-direction "rtl"}
+ render #(alignment-markup text-menu/text-direction-options* % true values)]
+ (t/is (str/includes? (re-find #"]*id=\"rtl-text-direction\"[^>]*>"
+ (render "horizontal-tb"))
+ "checked=\"\""))
+ (t/is (empty? (render "vertical-rl")))
+ (t/is (str/includes? (re-find #"]*id=\"rtl-text-direction\"[^>]*>"
+ (render "horizontal-tb"))
+ "checked=\"\""))))