mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-09 17:40:14 -06:00
Merge pull request #5206 from dotCooCoo/feat/tate-chu-yoko-settings
Type: Tate-chu-yoko Settings move a block up, down, left or right
This commit is contained in:
@@ -104,6 +104,17 @@ pub struct CharStyle {
|
||||
/// <https://helpx.adobe.com/illustrator/using/formatting-asian-characters.html>
|
||||
#[serde(default, rename = "tateChuYoko", skip_serializing_if = "crate::skip::is_default")]
|
||||
pub tate_chu_yoko: bool,
|
||||
/// Tate-chu-yoko Settings › Up/Down (Character panel menu, East Asian options), in points: a
|
||||
/// block of marked characters in vertical type moves up by a positive value and down by a
|
||||
/// negative one. The block takes the value of its first character; automatic digit blocks and
|
||||
/// horizontal type do not move. Saved only when not 0.
|
||||
/// <https://helpx.adobe.com/illustrator/using/formatting-asian-characters.html>
|
||||
#[serde(default, rename = "tcyUpDown", skip_serializing_if = "crate::skip::is_default")]
|
||||
pub tcy_up_down: f64,
|
||||
/// Tate-chu-yoko Settings › Left/Right, in points: a block of marked characters in vertical type
|
||||
/// moves right by a positive value and left by a negative one, as [`Self::tcy_up_down`] does.
|
||||
#[serde(default, rename = "tcyLeftRight", skip_serializing_if = "crate::skip::is_default")]
|
||||
pub tcy_left_right: f64,
|
||||
/// Standard Vertical Roman Alignment (Character panel menu, East Asian options): in vertical
|
||||
/// type the half-width characters among these (Latin letters, digits and other characters that
|
||||
/// would lie on their side) stand upright one below the other instead of being rotated
|
||||
@@ -396,6 +407,8 @@ impl Default for CharStyle {
|
||||
char_direction: None,
|
||||
proportional_metrics: false,
|
||||
tate_chu_yoko: false,
|
||||
tcy_up_down: 0.0,
|
||||
tcy_left_right: 0.0,
|
||||
standard_vertical_roman: false,
|
||||
small_caps: None,
|
||||
digits: Digits::Default,
|
||||
@@ -414,7 +427,8 @@ impl CharStyle {
|
||||
/// or scale of 0 or less becomes the default new type takes (12 pt, 100%), a negative leading
|
||||
/// becomes Auto, and a value past [`crate::MAX_COORD`] (a baseline shift past it either way)
|
||||
/// becomes the nearer end. A leading of 0, which legacy `.ai` import stores, stays, and a
|
||||
/// baseline shift that is not a number becomes 0.
|
||||
/// baseline shift that is not a number becomes 0. The Tate-chu-yoko Up/Down and Left/Right
|
||||
/// offsets are kept as the baseline shift is.
|
||||
pub fn keep_dimensions_in_range(&mut self) {
|
||||
let default = Self::default();
|
||||
let within = |v: f64, or: f64| if v > 0.0 { v.min(crate::MAX_COORD) } else { or };
|
||||
@@ -422,7 +436,10 @@ impl CharStyle {
|
||||
self.h_scale = within(self.h_scale, default.h_scale);
|
||||
self.v_scale = within(self.v_scale, default.v_scale);
|
||||
self.leading = self.leading.filter(|l| *l >= 0.0).map(|l| l.min(crate::MAX_COORD));
|
||||
self.baseline_shift = if self.baseline_shift.is_nan() { 0.0 } else { self.baseline_shift.clamp(-crate::MAX_COORD, crate::MAX_COORD) };
|
||||
let shift = |v: f64| if v.is_nan() { 0.0 } else { v.clamp(-crate::MAX_COORD, crate::MAX_COORD) };
|
||||
self.baseline_shift = shift(self.baseline_shift);
|
||||
self.tcy_up_down = shift(self.tcy_up_down);
|
||||
self.tcy_left_right = shift(self.tcy_left_right);
|
||||
}
|
||||
/// Do the characters draw a stroke (a paint and a positive weight)?
|
||||
pub fn has_stroke(&self) -> bool {
|
||||
|
||||
@@ -32,8 +32,13 @@ impl TextObject {
|
||||
style.size *= scale.points;
|
||||
style.leading = style.leading.map(|v| v * scale.points);
|
||||
style.baseline_shift *= scale.points;
|
||||
style.tcy_up_down *= scale.points;
|
||||
style.tcy_left_right *= scale.points;
|
||||
style.h_scale *= scale.horizontal;
|
||||
if ![style.size, style.leading.unwrap_or(0.0), style.baseline_shift, style.h_scale].iter().all(|v| v.is_finite()) {
|
||||
if ![style.size, style.leading.unwrap_or(0.0), style.baseline_shift, style.tcy_up_down, style.tcy_left_right, style.h_scale]
|
||||
.iter()
|
||||
.all(|v| v.is_finite())
|
||||
{
|
||||
return None;
|
||||
}
|
||||
Some(style)
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -39,7 +39,7 @@ pub fn specs() -> Vec<CommandSpec> {
|
||||
"Character",
|
||||
[],
|
||||
None,
|
||||
"{id? (default: the one selected type object), start?: byte, end?: byte (default: all text), font?, style?, size?: pt, leading?: pt|\"auto\", tracking?, kerning?: 1/1000 em|\"auto\"|\"optical\", baselineShift?: pt, hScale?: %, vScale?: %, rotation?: deg, fill?: colour|\"none\", stroke?: colour|\"none\", keepModel?: bool (fill and stroke, as paint.setFill), strokeWidth?: pt, strokeOptions?: {weight?, cap?, join?, miterLimit?, align?, dash?, dashOffset?, alignDashes?} (as stroke.set: the character stroke), underline?, strikethrough?, allCaps?: bool, smallCaps?: bool, position?: \"normal\"|\"superscript\"|\"subscript\" (sizes from Document Setup), features?: [\"dlig\", \"-liga\", …], featureChanges?: [\"tnum\", \"-onum\", …] (OpenType features turned on, or with - off, on top of each character's own; its other features are kept), charAlign?: \"romanBaseline\"|\"emBoxTop\"|\"emBoxCenter\"|\"emBoxBottom\"|\"icfTop\"|\"icfBottom\", proportionalMetrics?: bool (full-width glyphs on the font's proportional widths: `palt` in horizontal type, `vpal` in vertical type), tateChuYoko?: bool (Character panel menu › Tate-chu-yoko, with Show East Asian Options: in vertical type those characters stand side by side across one em of the column; kept on the characters and ignored in horizontal type), standardVerticalRoman?: bool (Character panel menu › Standard Vertical Roman Alignment, with Show East Asian Options: in vertical type those half-width characters, such as Latin letters and digits, stand upright one below the other instead of being rotated; spaces and tate-chu-yoko blocks are not changed; kept on the characters and ignored in horizontal type; off by default), noBreak?: bool (Character panel menu › No Break: these characters stay on one line, including across a space inside the run), charDirection?: \"default\"|\"leftToRight\"|\"rightToLeft\" (Character panel menu › Character Direction: the characters are set left to right or right to left inside their paragraph, as a Unicode bidi embedding; default: as the bidi algorithm sets them), digits?: \"default\"|\"arabic\"|\"hindi\"|\"farsi\"|\"devanagari\"|\"bengali\"|\"gurmukhi\"|\"gujarati\"|\"odia\"|\"tamil\"|\"telugu\"|\"kannada\"|\"malayalam\" (Character panel menu › Digits: the numeral system every digit is drawn in, whatever was typed: Western, Arabic-Indic (hindi), Eastern Arabic-Indic (farsi) or an Indic script's own digits), fontHeightReference?: \"emBox\"|\"capHeight\"|\"xHeight\"|\"icfBox\" (Character panel › Set Font Height Reference: the height of the font that size measures, the em box by default; with a font that has no such height, or one outside 0.1 to 2 em, the em box), language?: name|tag (Character panel › Language: one of Document Setup's languages by name or BCP 47 tag, case-insensitive, such as \"Turkish\" or \"tr\"; the characters are shaped in it, so the font uses that language's forms (OpenType `locl`); saved as the tag, English: USA, the default, as none; new type takes the document's language), fontAxes?: {\"wght\": 650, …} (Character panel › Variable Font: the setting of each variation axis named, in the axis's units and clamped to its range; null: back to the style's own setting; other axes keep theirs; tags the font has no axis for are left out; a font or style change clears them; text.fontList {family, style} lists the axes)} style a character range (size/leading/baselineShift are document points and hScale includes the object transform; runs are split at the range ends) → {id, runs, warning?: text (the font or style set isn't available: its type is drawn in the fallback font or the family's closest style)}",
|
||||
"{id? (default: the one selected type object), start?: byte, end?: byte (default: all text), font?, style?, size?: pt, leading?: pt|\"auto\", tracking?, kerning?: 1/1000 em|\"auto\"|\"optical\", baselineShift?: pt, hScale?: %, vScale?: %, rotation?: deg, fill?: colour|\"none\", stroke?: colour|\"none\", keepModel?: bool (fill and stroke, as paint.setFill), strokeWidth?: pt, strokeOptions?: {weight?, cap?, join?, miterLimit?, align?, dash?, dashOffset?, alignDashes?} (as stroke.set: the character stroke), underline?, strikethrough?, allCaps?: bool, smallCaps?: bool, position?: \"normal\"|\"superscript\"|\"subscript\" (sizes from Document Setup), features?: [\"dlig\", \"-liga\", …], featureChanges?: [\"tnum\", \"-onum\", …] (OpenType features turned on, or with - off, on top of each character's own; its other features are kept), charAlign?: \"romanBaseline\"|\"emBoxTop\"|\"emBoxCenter\"|\"emBoxBottom\"|\"icfTop\"|\"icfBottom\", proportionalMetrics?: bool (full-width glyphs on the font's proportional widths: `palt` in horizontal type, `vpal` in vertical type), tateChuYoko?: bool (Character panel menu › Tate-chu-yoko, with Show East Asian Options: in vertical type those characters stand side by side across one em of the column; kept on the characters and ignored in horizontal type), tcyUpDown?, tcyLeftRight?: pt -1296..1296 (Character panel menu › Tate-chu-yoko Settings: in vertical type a block of tate-chu-yoko characters moves up by a positive Up/Down and down by a negative one, right by a positive Left/Right and left by a negative one, as one piece with the values of its first character; 0 by default; automatic digit blocks and horizontal type do not move; neither turns tateChuYoko on), standardVerticalRoman?: bool (Character panel menu › Standard Vertical Roman Alignment, with Show East Asian Options: in vertical type those half-width characters, such as Latin letters and digits, stand upright one below the other instead of being rotated; spaces and tate-chu-yoko blocks are not changed; kept on the characters and ignored in horizontal type; off by default), noBreak?: bool (Character panel menu › No Break: these characters stay on one line, including across a space inside the run), charDirection?: \"default\"|\"leftToRight\"|\"rightToLeft\" (Character panel menu › Character Direction: the characters are set left to right or right to left inside their paragraph, as a Unicode bidi embedding; default: as the bidi algorithm sets them), digits?: \"default\"|\"arabic\"|\"hindi\"|\"farsi\"|\"devanagari\"|\"bengali\"|\"gurmukhi\"|\"gujarati\"|\"odia\"|\"tamil\"|\"telugu\"|\"kannada\"|\"malayalam\" (Character panel menu › Digits: the numeral system every digit is drawn in, whatever was typed: Western, Arabic-Indic (hindi), Eastern Arabic-Indic (farsi) or an Indic script's own digits), fontHeightReference?: \"emBox\"|\"capHeight\"|\"xHeight\"|\"icfBox\" (Character panel › Set Font Height Reference: the height of the font that size measures, the em box by default; with a font that has no such height, or one outside 0.1 to 2 em, the em box), language?: name|tag (Character panel › Language: one of Document Setup's languages by name or BCP 47 tag, case-insensitive, such as \"Turkish\" or \"tr\"; the characters are shaped in it, so the font uses that language's forms (OpenType `locl`); saved as the tag, English: USA, the default, as none; new type takes the document's language), fontAxes?: {\"wght\": 650, …} (Character panel › Variable Font: the setting of each variation axis named, in the axis's units and clamped to its range; null: back to the style's own setting; other axes keep theirs; tags the font has no axis for are left out; a font or style change clears them; text.fontList {family, style} lists the axes)} style a character range (size/leading/baselineShift are document points and hScale includes the object transform; runs are split at the range ends) → {id, runs, warning?: text (the font or style set isn't available: its type is drawn in the fallback font or the family's closest style)}",
|
||||
has_doc,
|
||||
set_range_style
|
||||
),
|
||||
@@ -363,6 +363,9 @@ pub(crate) struct CharChange {
|
||||
char_direction: Option<Option<vectorcraft_doc::ParaDirection>>,
|
||||
proportional_metrics: Option<bool>,
|
||||
tate_chu_yoko: Option<bool>,
|
||||
/// Tate-chu-yoko Settings › Up/Down and Left/Right, in points.
|
||||
tcy_up_down: Option<f64>,
|
||||
tcy_left_right: Option<f64>,
|
||||
standard_vertical_roman: Option<bool>,
|
||||
no_break: Option<bool>,
|
||||
digits: Option<vectorcraft_doc::Digits>,
|
||||
@@ -385,6 +388,15 @@ pub(crate) fn char_direction_param(p: &Value, cmd: &str) -> Result<Option<Option
|
||||
}))
|
||||
}
|
||||
|
||||
/// `tcyUpDown` or `tcyLeftRight` (Tate-chu-yoko Settings): a number of points from -1296 to 1296.
|
||||
/// Any other value is an error.
|
||||
pub(crate) fn tcy_offset_param(p: &Value, key: &str, cmd: &str) -> Result<Option<f64>> {
|
||||
let Some(v) = p.get(key).filter(|v| !v.is_null()) else { return Ok(None) };
|
||||
let v = v.as_f64().filter(|v| v.is_finite()).ok_or_else(|| bad(cmd, format!("`{key}` must be a number")))?;
|
||||
let (lo, hi) = (-1296.0, 1296.0);
|
||||
if (lo..=hi).contains(&v) { Ok(Some(v)) } else { Err(bad(cmd, format!("`{key}` must be from {lo} to {hi}"))) }
|
||||
}
|
||||
|
||||
/// `digits: "default"|"arabic"|"hindi"|"farsi"|"devanagari"|…` ([`Digits::id`]) → the numeral
|
||||
/// system the digits are drawn in.
|
||||
///
|
||||
@@ -635,6 +647,8 @@ impl CharChange {
|
||||
char_direction: char_direction_param(p, cmd)?,
|
||||
proportional_metrics: flag("proportionalMetrics"),
|
||||
tate_chu_yoko: flag("tateChuYoko"),
|
||||
tcy_up_down: tcy_offset_param(p, "tcyUpDown", cmd)?,
|
||||
tcy_left_right: tcy_offset_param(p, "tcyLeftRight", cmd)?,
|
||||
standard_vertical_roman: flag("standardVerticalRoman"),
|
||||
no_break: flag("noBreak"),
|
||||
digits: digits_param(p, cmd)?,
|
||||
@@ -684,6 +698,8 @@ impl CharChange {
|
||||
&& self.char_direction.is_none()
|
||||
&& self.proportional_metrics.is_none()
|
||||
&& self.tate_chu_yoko.is_none()
|
||||
&& self.tcy_up_down.is_none()
|
||||
&& self.tcy_left_right.is_none()
|
||||
&& self.standard_vertical_roman.is_none()
|
||||
&& self.no_break.is_none()
|
||||
&& self.digits.is_none()
|
||||
@@ -697,6 +713,8 @@ impl CharChange {
|
||||
self.size = self.size.map(|v| local_type_value(t, v, (0.1, 1296.0), false, cmd)).transpose()?;
|
||||
self.leading = self.leading.map(|v| v.map(|v| local_type_value(t, v, (0.1, 5000.0), false, cmd)).transpose()).transpose()?;
|
||||
self.baseline_shift = self.baseline_shift.map(|v| local_type_value(t, v, (-1296.0, 1296.0), false, cmd)).transpose()?;
|
||||
self.tcy_up_down = self.tcy_up_down.map(|v| local_type_value(t, v, (-1296.0, 1296.0), false, cmd)).transpose()?;
|
||||
self.tcy_left_right = self.tcy_left_right.map(|v| local_type_value(t, v, (-1296.0, 1296.0), false, cmd)).transpose()?;
|
||||
self.h_scale = self.h_scale.map(|v| local_type_value(t, v, (1.0, 10000.0), true, cmd)).transpose()?;
|
||||
Ok(())
|
||||
}
|
||||
@@ -723,6 +741,31 @@ impl CharChange {
|
||||
}
|
||||
}
|
||||
|
||||
/// The character alignment, direction and East Asian settings of [`Self::apply`].
|
||||
fn apply_cjk(&self, st: &mut CharStyle) {
|
||||
if let Some(v) = self.char_align {
|
||||
st.char_align = v;
|
||||
}
|
||||
if let Some(v) = self.char_direction {
|
||||
st.char_direction = v;
|
||||
}
|
||||
if let Some(v) = self.proportional_metrics {
|
||||
st.proportional_metrics = v;
|
||||
}
|
||||
if let Some(v) = self.tate_chu_yoko {
|
||||
st.tate_chu_yoko = v;
|
||||
}
|
||||
if let Some(v) = self.tcy_up_down {
|
||||
st.tcy_up_down = v;
|
||||
}
|
||||
if let Some(v) = self.tcy_left_right {
|
||||
st.tcy_left_right = v;
|
||||
}
|
||||
if let Some(v) = self.standard_vertical_roman {
|
||||
st.standard_vertical_roman = v;
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn apply(&self, st: &mut CharStyle) {
|
||||
self.apply_font(st);
|
||||
if let Some(v) = self.size {
|
||||
@@ -782,21 +825,7 @@ impl CharChange {
|
||||
if let Some(v) = self.small_caps {
|
||||
st.small_caps = v;
|
||||
}
|
||||
if let Some(v) = self.char_align {
|
||||
st.char_align = v;
|
||||
}
|
||||
if let Some(v) = self.char_direction {
|
||||
st.char_direction = v;
|
||||
}
|
||||
if let Some(v) = self.proportional_metrics {
|
||||
st.proportional_metrics = v;
|
||||
}
|
||||
if let Some(v) = self.tate_chu_yoko {
|
||||
st.tate_chu_yoko = v;
|
||||
}
|
||||
if let Some(v) = self.standard_vertical_roman {
|
||||
st.standard_vertical_roman = v;
|
||||
}
|
||||
self.apply_cjk(st);
|
||||
if let Some(v) = self.no_break {
|
||||
st.no_break = v;
|
||||
}
|
||||
|
||||
@@ -175,6 +175,8 @@ pub fn char_attrs(v: &CharStyle) -> Map<String, Value> {
|
||||
("charDirection", json!(v.char_direction)),
|
||||
("proportionalMetrics", json!(v.proportional_metrics)),
|
||||
("tateChuYoko", json!(v.tate_chu_yoko)),
|
||||
("tcyUpDown", json!(v.tcy_up_down)),
|
||||
("tcyLeftRight", json!(v.tcy_left_right)),
|
||||
("standardVerticalRoman", json!(v.standard_vertical_roman)),
|
||||
("noBreak", json!(v.no_break)),
|
||||
("digits", json!(v.digits)),
|
||||
|
||||
@@ -1206,6 +1206,61 @@ fn tate_chu_yoko_is_a_character_attribute_saved_only_when_on() {
|
||||
assert!(runs(&s).iter().all(|r| !r.style.tate_chu_yoko));
|
||||
}
|
||||
|
||||
/// Tate-chu-yoko Settings (Character panel menu): Up/Down and Left/Right are character attributes
|
||||
/// in points, 0 by default and saved only when not 0. Both commands set them without changing the
|
||||
/// Tate-chu-yoko mark, refuse a value that is not a number or lies outside -1296 to 1296, and a
|
||||
/// character style captured from the characters carries them.
|
||||
#[test]
|
||||
fn tate_chu_yoko_settings_are_character_attributes_saved_only_when_set() {
|
||||
let mut s = Session::new();
|
||||
s.execute("file.new", &json!({"width": 400, "height": 400})).unwrap();
|
||||
let id = s.execute("text.create", &json!({"x": 10, "y": 50, "text": "2026年"})).unwrap()["id"].as_u64().unwrap();
|
||||
let runs = |s: &Session| match &s.doc().unwrap().doc.node(NodeId(id)).unwrap().kind {
|
||||
NodeKind::Text(t) => t.runs.clone(),
|
||||
_ => panic!("text"),
|
||||
};
|
||||
let offsets = |s: &Session| runs(s).iter().map(|r| (r.text.clone(), r.style.tcy_up_down, r.style.tcy_left_right)).collect::<Vec<_>>();
|
||||
let saved = serde_json::to_value(&runs(&s)[0].style).unwrap();
|
||||
assert!(saved.get("tcyUpDown").is_none() && saved.get("tcyLeftRight").is_none());
|
||||
s.execute("select.set", &json!({"ids": [id]})).unwrap();
|
||||
s.execute("text.setFormat", &json!({"tcyUpDown": 3.5, "tcyLeftRight": -2})).unwrap();
|
||||
assert_eq!(offsets(&s), [("2026年".to_string(), 3.5, -2.0)]);
|
||||
assert!(!runs(&s)[0].style.tate_chu_yoko, "the mark stays off");
|
||||
let saved = serde_json::to_value(&runs(&s)[0].style).unwrap();
|
||||
assert_eq!((saved["tcyUpDown"].clone(), saved["tcyLeftRight"].clone()), (json!(3.5), json!(-2.0)));
|
||||
s.execute("edit.undo", &json!({})).unwrap();
|
||||
assert_eq!(offsets(&s), [("2026年".to_string(), 0.0, 0.0)], "one undo step");
|
||||
s.execute("edit.redo", &json!({})).unwrap();
|
||||
assert_eq!(offsets(&s), [("2026年".to_string(), 3.5, -2.0)]);
|
||||
s.execute("edit.undo", &json!({})).unwrap();
|
||||
|
||||
s.execute("text.setRangeStyle", &json!({"id": id, "start": 0, "end": 4, "tateChuYoko": true, "tcyUpDown": -4})).unwrap();
|
||||
assert_eq!(offsets(&s), [("2026".to_string(), -4.0, 0.0), ("年".to_string(), 0.0, 0.0)]);
|
||||
s.execute("text.setRangeStyle", &json!({"id": id, "start": 0, "end": 4, "tcyLeftRight": 1296})).unwrap();
|
||||
assert_eq!(offsets(&s)[0], ("2026".to_string(), -4.0, 1296.0));
|
||||
assert!(runs(&s)[0].style.tate_chu_yoko);
|
||||
// Saved and reopened as they were.
|
||||
let back = vectorcraft_format::load(&vectorcraft_format::save(&s.doc().unwrap().doc, false)).unwrap();
|
||||
let NodeKind::Text(t) = &back.node(NodeId(id)).unwrap().kind else { panic!("text") };
|
||||
let reopened: Vec<_> = t.runs.iter().map(|r| (r.text.clone(), r.style.tcy_up_down, r.style.tcy_left_right)).collect();
|
||||
assert_eq!(reopened, offsets(&s));
|
||||
let attrs = super::super::textstyles::char_attrs(&runs(&s)[0].style);
|
||||
assert_eq!((attrs["tcyUpDown"].clone(), attrs["tcyLeftRight"].clone()), (json!(-4.0), json!(1296.0)));
|
||||
assert_eq!(super::super::textstyles::char_attrs(&runs(&s)[1].style)["tcyUpDown"], json!(0.0));
|
||||
|
||||
let before = offsets(&s);
|
||||
for (cmd, p, want) in [
|
||||
("text.setFormat", json!({"tcyUpDown": "3"}), "`tcyUpDown` must be a number"),
|
||||
("text.setFormat", json!({"tcyLeftRight": 1296.5}), "`tcyLeftRight` must be from -1296 to 1296"),
|
||||
("text.setRangeStyle", json!({"id": id, "tcyLeftRight": true}), "`tcyLeftRight` must be a number"),
|
||||
("text.setRangeStyle", json!({"id": id, "tcyUpDown": -2000}), "`tcyUpDown` must be from -1296 to 1296"),
|
||||
] {
|
||||
let e = s.execute(cmd, &p).unwrap_err();
|
||||
assert!(matches!(&e, EngineError::BadParams { msg, .. } if msg == want), "{cmd} {p}: {e}");
|
||||
}
|
||||
assert_eq!(offsets(&s), before);
|
||||
}
|
||||
|
||||
/// Standard Vertical Roman Alignment (Character panel menu, East Asian options): a character
|
||||
/// attribute, off by default and saved only when on. Documents written before it read as off; a
|
||||
/// character style captured from the characters carries it.
|
||||
|
||||
@@ -37,7 +37,7 @@ use breaking::tab_advance;
|
||||
#[cfg(test)]
|
||||
use breaking::{break_line, candidates};
|
||||
pub use driver::{decorations, fit_first_line, layout, layout_with, scaled};
|
||||
use vertical::{stands_upright, tate_chu_yoko, upright_cell, upright_centre, whole_character_widths};
|
||||
use vertical::{TcyStyle, stands_upright, tate_chu_yoko, upright_cell, upright_centre, whole_character_widths};
|
||||
|
||||
const EPS: f64 = 1e-6;
|
||||
|
||||
@@ -114,7 +114,7 @@ impl Ctx<'_> {
|
||||
if self.vertical {
|
||||
tate_chu_yoko(v, |g| {
|
||||
let st = style(g);
|
||||
(st.size, st.tate_chu_yoko)
|
||||
TcyStyle { size: st.size, marked: st.tate_chu_yoko, up: st.tcy_up_down, right: st.tcy_left_right }
|
||||
});
|
||||
// An upright glyph advances down the column by its vertical advance (the font's vertical
|
||||
// metrics; without them at least one em, as CJK fonts' is), centred across it: a narrow
|
||||
@@ -254,13 +254,16 @@ impl Ctx<'_> {
|
||||
// Vertical path type keeps the baseline path and turns each glyph across it.
|
||||
m = Affine::rotate_about(std::f64::consts::FRAC_PI_2, origin) * m;
|
||||
} else if let (true, Some(t)) = (self.vertical, g.tcy) {
|
||||
// Tate-chu-yoko: the block set across the column, centred on its em of it.
|
||||
// Tate-chu-yoko: the block set across the column, centred on its em of it, then moved up
|
||||
// by its Up/Down and right by its Left/Right (line x runs down the column and line y
|
||||
// from right to left across it).
|
||||
let em = self.style_at(g.byte).size;
|
||||
let start = origin.x - t.pen;
|
||||
let centre = Point::new(start + em * 0.5, origin.y - g.face.ideographic_centre() * em);
|
||||
let across = Affine::translate((centre.x - t.width * 0.5 + t.ink - origin.x, 0.0));
|
||||
let squeeze = Affine::translate((centre.x, 0.0)) * Affine::scale_non_uniform(t.squeeze, 1.0) * Affine::translate((-centre.x, 0.0));
|
||||
m = Affine::rotate_about(-std::f64::consts::FRAC_PI_2, centre) * squeeze * across * m;
|
||||
let offset = Affine::translate((-t.up, -t.right));
|
||||
m = offset * Affine::rotate_about(-std::f64::consts::FRAC_PI_2, centre) * squeeze * across * m;
|
||||
} else if self.vertical && stands_upright(g) {
|
||||
let next = self.out.glyphs.len();
|
||||
let after_base = self.out.glyphs.last().is_some_and(|p| p.byte == g.byte && p.line == line && p.len > 0);
|
||||
|
||||
@@ -182,7 +182,7 @@ fn shrink_to_fit(db: &FontDb, t: &TextObject, opts: &LayoutOptions, min: f64) ->
|
||||
Some(best.unwrap_or_else(|| at(lo)))
|
||||
}
|
||||
|
||||
/// `t` with every run's size, explicit leading and baseline shift scaled by `f`, inline art's own
|
||||
/// `t` with every run's size, explicit leading, baseline shift and tate-chu-yoko offsets scaled by `f`, inline art's own
|
||||
/// shift included (auto leading and the art's size follow the size; paragraph spacing stays).
|
||||
/// Shrink Text to Fit lays `t` out as `scaled(t, f)`, where `f` is the layout's
|
||||
/// [`TextLayout::fit_scale`], so the result holds the styles the text is drawn in.
|
||||
@@ -192,6 +192,8 @@ pub fn scaled(t: &TextObject, f: f64) -> TextObject {
|
||||
r.style.size *= f;
|
||||
r.style.leading = r.style.leading.map(|l| l * f);
|
||||
r.style.baseline_shift *= f;
|
||||
r.style.tcy_up_down *= f;
|
||||
r.style.tcy_left_right *= f;
|
||||
if let Some(a) = &mut r.inline {
|
||||
a.baseline_shift *= f;
|
||||
}
|
||||
|
||||
@@ -20,10 +20,11 @@ pub(super) fn whole_character_widths(glyphs: &mut [SGlyph]) {
|
||||
///
|
||||
/// Manual (Character panel menu › Tate-chu-yoko): any characters marked `tateChuYoko`, including
|
||||
/// one character, letters and numbers longer than three digits. Marked characters are taken
|
||||
/// before the automatic digit rule.
|
||||
/// before the automatic digit rule. A marked block moves by the Tate-chu-yoko Settings Up/Down
|
||||
/// and Left/Right of its first character; an automatic block does not move.
|
||||
/// <https://helpx.adobe.com/illustrator/using/formatting-asian-characters.html>
|
||||
pub(super) fn tate_chu_yoko(g: &mut [SGlyph], style: impl Fn(&SGlyph) -> (f64, bool)) {
|
||||
let manual: Vec<bool> = g.iter().map(|gl| gl.ch != '\u{2028}' && style(gl).1).collect();
|
||||
pub(super) fn tate_chu_yoko(g: &mut [SGlyph], style: impl Fn(&SGlyph) -> TcyStyle) {
|
||||
let manual: Vec<bool> = g.iter().map(|gl| gl.ch != '\u{2028}' && style(gl).marked).collect();
|
||||
let digits: Vec<bool> = g.iter().map(|gl| gl.inline.is_none() && gl.ch.is_ascii_digit()).collect();
|
||||
let mut i = 0;
|
||||
while i < g.len() {
|
||||
@@ -32,8 +33,8 @@ pub(super) fn tate_chu_yoko(g: &mut [SGlyph], style: impl Fn(&SGlyph) -> (f64, b
|
||||
while manual.get(end).copied().unwrap_or(false) {
|
||||
end += 1;
|
||||
}
|
||||
let em = style(&g[i]).0;
|
||||
place_tcy_block(&mut g[i..end], em);
|
||||
let first = style(&g[i]);
|
||||
place_tcy_block(&mut g[i..end], first.size, (first.up, first.right));
|
||||
i = end;
|
||||
continue;
|
||||
}
|
||||
@@ -46,21 +47,31 @@ pub(super) fn tate_chu_yoko(g: &mut [SGlyph], style: impl Fn(&SGlyph) -> (f64, b
|
||||
end += 1;
|
||||
}
|
||||
if (2..=3).contains(&(end - i)) {
|
||||
let em = style(&g[i]).0;
|
||||
place_tcy_block(&mut g[i..end], em);
|
||||
let em = style(&g[i]).size;
|
||||
place_tcy_block(&mut g[i..end], em, (0.0, 0.0));
|
||||
}
|
||||
i = end;
|
||||
}
|
||||
}
|
||||
|
||||
/// Lay `g` across one em of the column. The first glyph takes the em; the rest share its cell.
|
||||
fn place_tcy_block(g: &mut [SGlyph], em: f64) {
|
||||
/// What [`tate_chu_yoko`] reads from a glyph's character style: its size, whether it is marked
|
||||
/// Tate-chu-yoko, and its Tate-chu-yoko Settings Up/Down and Left/Right in points.
|
||||
pub(super) struct TcyStyle {
|
||||
pub size: f64,
|
||||
pub marked: bool,
|
||||
pub up: f64,
|
||||
pub right: f64,
|
||||
}
|
||||
|
||||
/// Lay `g` across one em of the column, moved by `(up, right)` points. The first glyph takes the
|
||||
/// em; the rest share its cell.
|
||||
fn place_tcy_block(g: &mut [SGlyph], em: f64, (up, right): (f64, f64)) {
|
||||
let width: f64 = g.iter().map(|gl| gl.adv).sum();
|
||||
let squeeze = if width > em && width > 0.0 { em / width } else { 1.0 };
|
||||
let mut ink = 0.0;
|
||||
for (k, gl) in g.iter_mut().enumerate() {
|
||||
let adv = gl.adv;
|
||||
gl.tcy = Some(Tcy { pen: if k == 0 { 0.0 } else { em }, ink, width, squeeze });
|
||||
gl.tcy = Some(Tcy { pen: if k == 0 { 0.0 } else { em }, ink, width, squeeze, up, right });
|
||||
gl.adv = if k == 0 { em } else { 0.0 };
|
||||
ink += adv;
|
||||
}
|
||||
|
||||
@@ -134,6 +134,10 @@ pub(crate) struct Tcy {
|
||||
pub width: f64,
|
||||
/// Horizontal scale that fits the block into one em (1 when it fits as it is).
|
||||
pub squeeze: f64,
|
||||
/// Tate-chu-yoko Settings › Up/Down of the block, in points (positive moves it up).
|
||||
pub up: f64,
|
||||
/// Tate-chu-yoko Settings › Left/Right of the block, in points (positive moves it right).
|
||||
pub right: f64,
|
||||
}
|
||||
|
||||
impl SGlyph {
|
||||
@@ -724,6 +728,8 @@ fn shapes_alike(a: &CharStyle, b: &CharStyle) -> bool {
|
||||
char_direction: _,
|
||||
proportional_metrics,
|
||||
tate_chu_yoko,
|
||||
tcy_up_down: _,
|
||||
tcy_left_right: _,
|
||||
standard_vertical_roman: _,
|
||||
digits,
|
||||
font_height_reference,
|
||||
|
||||
@@ -53,7 +53,7 @@ fn area(runs: Vec<TextRun>, frame: Rect) -> TextObject {
|
||||
}
|
||||
|
||||
/// `t` as Shrink Text to Fit lays it out at factor `f`, without fitting: sizes, explicit leading
|
||||
/// and baseline shifts scaled (inline art's own shift too), paragraph spacing kept.
|
||||
/// and baseline shifts scaled (inline art's own shift and tate-chu-yoko offsets too), paragraph spacing kept.
|
||||
fn scaled_by(t: &TextObject, f: f64) -> TextObject {
|
||||
let mut s = t.clone();
|
||||
s.area.fit = AreaFit::None;
|
||||
@@ -61,6 +61,8 @@ fn scaled_by(t: &TextObject, f: f64) -> TextObject {
|
||||
r.style.size *= f;
|
||||
r.style.leading = r.style.leading.map(|l| l * f);
|
||||
r.style.baseline_shift *= f;
|
||||
r.style.tcy_up_down *= f;
|
||||
r.style.tcy_left_right *= f;
|
||||
if let Some(a) = &mut r.inline {
|
||||
a.baseline_shift *= f;
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
//! Japanese text only with craft-fonts' faces, skipped without them).
|
||||
use crate::*;
|
||||
use kurbo::Shape;
|
||||
use vectorcraft_doc::{CharStyle, Justify, TextKind, TextRun};
|
||||
use vectorcraft_doc::{AreaFit, CharStyle, Justify, TextKind, TextRun};
|
||||
use vectorcraft_geom::PathData;
|
||||
|
||||
fn vertical(text: &str) -> TextObject {
|
||||
@@ -381,6 +381,71 @@ fn manual_tate_chu_yoko_sets_marked_characters_across_one_em() {
|
||||
assert!(ink(&row, 1).center().x > ink(&row, 0).center().x + 5.0, "horizontal type stays a row");
|
||||
}
|
||||
|
||||
/// Character panel menu › Tate-chu-yoko Settings: a marked block moves up by a positive Up/Down
|
||||
/// and right by a positive Left/Right, in points, as one piece (the offsets of its first
|
||||
/// character), and keeps its one em down the column. An automatic two-digit block and horizontal
|
||||
/// type do not move.
|
||||
#[test]
|
||||
fn tate_chu_yoko_settings_move_a_marked_block_up_down_left_and_right() {
|
||||
let marked = CharStyle { size: 20.0, tate_chu_yoko: true, ..CharStyle::default() };
|
||||
let union = |l: &TextLayout, r: std::ops::Range<usize>| r.map(|i| ink(l, i)).reduce(|a, b| a.union(b)).unwrap();
|
||||
let at = |up: f64, right: f64| {
|
||||
let mut t = vertical_styled("", marked.clone());
|
||||
t.runs = vec![
|
||||
TextRun::new("AB", CharStyle { tcy_up_down: up, tcy_left_right: right, ..marked.clone() }),
|
||||
TextRun::new("C", CharStyle { tcy_up_down: -up, tcy_left_right: -right, ..marked.clone() }),
|
||||
TextRun::new("§", CharStyle { size: 20.0, ..CharStyle::default() }),
|
||||
];
|
||||
layout(FontDb::global(), &t)
|
||||
};
|
||||
let still = at(0.0, 0.0);
|
||||
for (up, right) in [(5.0, 0.0), (-3.5, 0.0), (0.0, 4.0), (0.0, -2.0), (6.0, -7.0)] {
|
||||
let moved = at(up, right);
|
||||
let (a, b) = (union(&still, 0..3), union(&moved, 0..3));
|
||||
assert!((b.y0 - (a.y0 - up)).abs() < 1e-6 && (b.y1 - (a.y1 - up)).abs() < 1e-6, "up {up}: {a:?} {b:?}");
|
||||
assert!((b.x0 - (a.x0 + right)).abs() < 1e-6 && (b.x1 - (a.x1 + right)).abs() < 1e-6, "right {right}: {a:?} {b:?}");
|
||||
assert!((moved.glyphs[0].advance - 20.0).abs() < 1e-6 && moved.glyphs[1..3].iter().all(|g| g.advance == 0.0), "one em");
|
||||
assert_eq!(ink(&moved, 3), ink(&still, 3), "the mark after the block stays");
|
||||
assert_eq!(moved.glyphs[0].origin, still.glyphs[0].origin, "the caret and hit region stay");
|
||||
}
|
||||
|
||||
// An automatic block of two digits takes no offsets, and horizontal type does not move.
|
||||
let shifted = CharStyle { size: 20.0, tcy_up_down: 5.0, tcy_left_right: 5.0, ..CharStyle::default() };
|
||||
let digits = layout(FontDb::global(), &vertical_styled("10", shifted.clone()));
|
||||
let plain = layout(FontDb::global(), &vertical("10"));
|
||||
assert_eq!(union(&digits, 0..2), union(&plain, 0..2));
|
||||
let row = |st: CharStyle| {
|
||||
let mut t = TextObject::point(Point::ZERO, "AB", st);
|
||||
t.xf = Affine::IDENTITY;
|
||||
layout(FontDb::global(), &t)
|
||||
};
|
||||
assert_eq!(union(&row(CharStyle { tate_chu_yoko: true, ..shifted }), 0..2), union(&row(marked.clone()), 0..2));
|
||||
}
|
||||
|
||||
/// Shrink Text to Fit scales a marked block's Up/Down and Left/Right offsets with the size, as it
|
||||
/// scales the baseline shift.
|
||||
#[test]
|
||||
fn shrink_text_to_fit_scales_tate_chu_yoko_offsets() {
|
||||
let marked = CharStyle { size: 20.0, tate_chu_yoko: true, ..CharStyle::default() };
|
||||
let union = |l: &TextLayout, r: std::ops::Range<usize>| r.map(|i| ink(l, i)).reduce(|a, b| a.union(b)).unwrap();
|
||||
let (up, right) = (6.0, -4.0);
|
||||
let at = |up: f64, right: f64| {
|
||||
let mut t = vertical_styled("", marked.clone());
|
||||
t.kind = TextKind::Area { frame: PathData::from_bezpath(&Rect::new(0.0, 0.0, 60.0, 60.0).to_path(0.1)) };
|
||||
t.area.fit = AreaFit::ShrinkText { min_percent: 10.0 };
|
||||
t.runs = vec![
|
||||
TextRun::new("AB", CharStyle { tcy_up_down: up, tcy_left_right: right, ..marked.clone() }),
|
||||
TextRun::new("§§§§§§§§§§§§", CharStyle { size: 20.0, ..CharStyle::default() }),
|
||||
];
|
||||
layout(FontDb::global(), &t)
|
||||
};
|
||||
let (still, moved) = (at(0.0, 0.0), at(up, right));
|
||||
let f = moved.fit_scale;
|
||||
assert!(f < 1.0 && f == still.fit_scale, "{f} {}", still.fit_scale);
|
||||
let (a, b) = (union(&still, 0..2), union(&moved, 0..2));
|
||||
assert!((b.y0 - (a.y0 - up * f)).abs() < 1e-6 && (b.x0 - (a.x0 + right * f)).abs() < 1e-6, "{f}: {a:?} {b:?}");
|
||||
}
|
||||
|
||||
/// A marked run is one block; the unmarked digits beside it still follow the automatic rule.
|
||||
/// A column shorter than two ems holds the marked number on one line.
|
||||
#[test]
|
||||
|
||||
@@ -113,6 +113,7 @@ pub mod swatch_options;
|
||||
pub mod symbol_delete;
|
||||
pub mod symbol_options;
|
||||
pub mod symbolism_options;
|
||||
pub mod tate_chu_yoko;
|
||||
mod text_export;
|
||||
pub mod text_import;
|
||||
mod tiff_bmp_tga;
|
||||
@@ -331,6 +332,7 @@ registry! {
|
||||
Corners: [corners::KIND] => corners::SPEC,
|
||||
Hyphenation: [hyphenation::KIND] => hyphenation::SPEC,
|
||||
Justification: [justification::KIND] => justification::SPEC,
|
||||
TateChuYokoSettings: [tate_chu_yoko::KIND] => tate_chu_yoko::SPEC,
|
||||
SavePdf: [save_pdf::KIND] => save_pdf::SPEC,
|
||||
SvgOptions: [svg_options::KIND] => svg_options::SPEC,
|
||||
JavaScriptFiles: [javascript_files::KIND] => javascript_files::SPEC,
|
||||
|
||||
@@ -0,0 +1,192 @@
|
||||
//! Character panel menu › Tate-chu-yoko Settings… (or `ui.tateChuYokoSettings`): the Up/Down and
|
||||
//! Left/Right of the characters the Character panel acts on, in points, which move a block of
|
||||
//! tate-chu-yoko characters in vertical type up or down and right or left. The dialog opens on
|
||||
//! the values the selected characters share (a blank field where they differ). OK runs
|
||||
//! `text.setFormat` (`text.setRangeStyle` while the Type tool edits text) with the values that
|
||||
//! changed, as one undo step; characters that differ keep a value left blank.
|
||||
//!
|
||||
//! Fields: `tcyUpDown` and `tcyLeftRight` (pt, -1296..1296), as `text.setFormat` takes them, and
|
||||
//! `__initial` (the values it opened with).
|
||||
//!
|
||||
//! Behavior reference (public documentation):
|
||||
//! <https://helpx.adobe.com/illustrator/desktop/design-with-text/edit-format-text/format-east-asian-characters-in-vertical-text.html>.
|
||||
|
||||
use serde_json::{Map, Value, json};
|
||||
|
||||
use super::swatch_options::{grid, label};
|
||||
use super::{DialogSpec, form};
|
||||
use crate::VectorcraftApp;
|
||||
use crate::state::Dialog;
|
||||
|
||||
/// The dialog kind of Tate-chu-yoko Settings.
|
||||
pub const KIND: &str = "tateChuYokoSettings";
|
||||
|
||||
/// The field holding the values the dialog opened with.
|
||||
const INITIAL: &str = "__initial";
|
||||
|
||||
/// The fields, as `text.setFormat` names them.
|
||||
const KEYS: [&str; 2] = ["tcyUpDown", "tcyLeftRight"];
|
||||
|
||||
/// The range of both fields, in points.
|
||||
const RANGE: std::ops::RangeInclusive<f64> = -1296.0..=1296.0;
|
||||
|
||||
pub(super) const SPEC: DialogSpec = DialogSpec {
|
||||
heading: |_| tl!("Tate-chu-yoko Settings").into(),
|
||||
body,
|
||||
confirm,
|
||||
min_width: 280.0,
|
||||
// A value the characters don't share opens blank, without its field.
|
||||
inputs: |_| &KEYS,
|
||||
..DialogSpec::FORM
|
||||
};
|
||||
|
||||
/// Open Tate-chu-yoko Settings on the characters the Character panel acts on.
|
||||
pub fn open(app: &mut VectorcraftApp) -> Result<Value, String> {
|
||||
crate::panels::character::text_style(app).ok_or("select some type first")?;
|
||||
let (up, right) = crate::panels::character::shared_tcy_offsets(app);
|
||||
let mut values = Map::new();
|
||||
for (key, v) in KEYS.into_iter().zip([up, right]) {
|
||||
if let Some(v) = v {
|
||||
values.insert(key.into(), json!(v));
|
||||
}
|
||||
}
|
||||
let mut fields = Value::Object(values.clone());
|
||||
fields[INITIAL] = Value::Object(values);
|
||||
app.ui.dialog = Some(Dialog::new(KIND, fields));
|
||||
Ok(Value::Null)
|
||||
}
|
||||
|
||||
fn body(app: &mut VectorcraftApp, ui: &mut egui::Ui, d: &mut Dialog) -> bool {
|
||||
let unit = app.session.type_unit();
|
||||
grid(ui, |ui| {
|
||||
for (key, text) in KEYS.into_iter().zip([tl!("Up/Down:"), tl!("Left/Right:")]) {
|
||||
label(ui, text);
|
||||
form::length_within(ui, d, key, unit, 90.0, Some(RANGE));
|
||||
ui.end_row();
|
||||
}
|
||||
});
|
||||
false
|
||||
}
|
||||
|
||||
/// `text.setFormat` parameters: the fields that differ from the values the dialog opened with,
|
||||
/// in points (a length typed with its unit, "1 mm", is converted) and kept within -1296..1296.
|
||||
fn params(d: &Dialog) -> Map<String, Value> {
|
||||
let initial = d.fields.get(INITIAL).and_then(Value::as_object);
|
||||
let mut p = Map::new();
|
||||
for key in KEYS {
|
||||
if !d.fields.contains_key(key) {
|
||||
continue;
|
||||
}
|
||||
let v = d.f64(key, 0.0).clamp(*RANGE.start(), *RANGE.end());
|
||||
if initial.and_then(|m| m.get(key)).and_then(Value::as_f64) != Some(v) {
|
||||
p.insert(key.into(), json!(v));
|
||||
}
|
||||
}
|
||||
p
|
||||
}
|
||||
|
||||
fn confirm(app: &mut VectorcraftApp, d: &Dialog) -> Result<Value, String> {
|
||||
let p = params(d);
|
||||
app.ui.dialog = None;
|
||||
if p.is_empty() {
|
||||
return Ok(Value::Null);
|
||||
}
|
||||
crate::panels::character::run_format(app, Value::Object(p))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use vectorcraft_engine::Session;
|
||||
use vectorcraft_engine::doc::{NodeId, NodeKind};
|
||||
|
||||
/// A selected point type object with three characters.
|
||||
fn app() -> (VectorcraftApp, u64) {
|
||||
let mut app = VectorcraftApp::new(Session::new(), Default::default());
|
||||
app.run("file.new", json!({"width": 400, "height": 400})).unwrap();
|
||||
let id = app.run("text.create", json!({"x": 10, "y": 50, "text": "2026"})).unwrap()["id"].as_u64().unwrap();
|
||||
app.run("select.set", json!({"ids": [id]})).unwrap();
|
||||
(app, id)
|
||||
}
|
||||
|
||||
/// (Up/Down, Left/Right) of each run.
|
||||
fn offsets(app: &VectorcraftApp, id: u64) -> Vec<(f64, f64)> {
|
||||
match &app.session.active().unwrap().doc.node(NodeId(id)).unwrap().kind {
|
||||
NodeKind::Text(t) => t.runs.iter().map(|r| (r.style.tcy_up_down, r.style.tcy_left_right)).collect(),
|
||||
_ => panic!("not text"),
|
||||
}
|
||||
}
|
||||
|
||||
/// `ui.tateChuYokoSettings` opens on the characters' values; OK sets both in one undo step,
|
||||
/// and a length typed with its unit is converted to points.
|
||||
#[test]
|
||||
fn tate_chu_yoko_settings_dialog_sets_up_down_and_left_right() {
|
||||
let (mut app, id) = app();
|
||||
app.run("ui.tateChuYokoSettings", json!({})).unwrap();
|
||||
let d = app.ui.dialog.clone().expect("Tate-chu-yoko Settings opened");
|
||||
assert_eq!(d.kind, KIND);
|
||||
assert_eq!((d.f64("tcyUpDown", -1.0), d.f64("tcyLeftRight", -1.0)), (0.0, 0.0));
|
||||
let d = app.ui.dialog.as_mut().unwrap();
|
||||
d.fields.insert("tcyUpDown".into(), json!(3));
|
||||
d.fields.insert("tcyLeftRight".into(), json!("-1 in"));
|
||||
let undo = app.session.active().unwrap().history.undo.len();
|
||||
super::super::confirm(&mut app).unwrap();
|
||||
assert!(app.ui.dialog.is_none());
|
||||
assert_eq!(offsets(&app, id), [(3.0, -72.0)]);
|
||||
assert_eq!(app.session.active().unwrap().history.undo.len(), undo + 1);
|
||||
// It opens on the values set, and OK with nothing changed adds no undo step.
|
||||
app.run("ui.tateChuYokoSettings", json!({})).unwrap();
|
||||
let d = app.ui.dialog.clone().unwrap();
|
||||
assert_eq!((d.f64("tcyUpDown", 0.0), d.f64("tcyLeftRight", 0.0)), (3.0, -72.0));
|
||||
super::super::confirm(&mut app).unwrap();
|
||||
assert_eq!(app.session.active().unwrap().history.undo.len(), undo + 1);
|
||||
// Cancel changes nothing.
|
||||
app.run("ui.tateChuYokoSettings", json!({})).unwrap();
|
||||
app.ui.dialog.as_mut().unwrap().fields.insert("tcyUpDown".into(), json!(9));
|
||||
app.ui.dialog = None;
|
||||
assert_eq!(offsets(&app, id), [(3.0, -72.0)]);
|
||||
// With no type selected the dialog does not open.
|
||||
app.run("select.set", json!({"ids": []})).unwrap();
|
||||
assert!(app.run("ui.tateChuYokoSettings", json!({})).is_err());
|
||||
assert!(app.ui.dialog.is_none());
|
||||
}
|
||||
|
||||
/// Characters that differ open a blank field; OK leaves that value alone and sets the other.
|
||||
#[test]
|
||||
fn tate_chu_yoko_settings_dialog_leaves_mixed_values_blank() {
|
||||
let (mut app, id) = app();
|
||||
app.run("text.setRangeStyle", json!({"id": id, "start": 0, "end": 2, "tcyUpDown": 4})).unwrap();
|
||||
app.run("ui.tateChuYokoSettings", json!({})).unwrap();
|
||||
let d = app.ui.dialog.clone().unwrap();
|
||||
assert!(!d.fields.contains_key("tcyUpDown"));
|
||||
assert_eq!(d.f64("tcyLeftRight", -1.0), 0.0);
|
||||
app.ui.dialog.as_mut().unwrap().fields.insert("tcyLeftRight".into(), json!(2));
|
||||
super::super::confirm(&mut app).unwrap();
|
||||
assert_eq!(offsets(&app, id), [(4.0, 2.0), (0.0, 2.0)]);
|
||||
}
|
||||
|
||||
/// OK returns the error of a command that is refused, and the characters keep their values.
|
||||
#[test]
|
||||
fn tate_chu_yoko_settings_dialog_returns_a_refused_command_error() {
|
||||
let (mut app, id) = app();
|
||||
app.run("ui.tateChuYokoSettings", json!({})).unwrap();
|
||||
app.ui.dialog.as_mut().unwrap().fields.insert("tcyUpDown".into(), json!(5));
|
||||
app.run("select.set", json!({"ids": []})).unwrap();
|
||||
assert!(super::super::confirm(&mut app).is_err());
|
||||
assert!(app.ui.dialog.is_none());
|
||||
assert_eq!(offsets(&app, id), [(0.0, 0.0)]);
|
||||
}
|
||||
|
||||
/// While the Type tool edits text, OK sets the selected characters and returns the result.
|
||||
#[test]
|
||||
fn tate_chu_yoko_settings_dialog_sets_the_range_the_type_tool_selects() {
|
||||
let (mut app, id) = app();
|
||||
app.session.select_tool("type", Default::default()).unwrap();
|
||||
app.session.set_tool_option("textSelection", &json!({"editing": id, "anchor": 0, "caret": 2}));
|
||||
assert_eq!(crate::panels::character::text_editing(&app), Some((NodeId(id), 0, 2)));
|
||||
app.run("ui.tateChuYokoSettings", json!({})).unwrap();
|
||||
app.ui.dialog.as_mut().unwrap().fields.insert("tcyLeftRight".into(), json!(6));
|
||||
assert!(super::super::confirm(&mut app).is_ok());
|
||||
assert_eq!(offsets(&app, id), [(0.0, 6.0), (0.0, 0.0)]);
|
||||
}
|
||||
}
|
||||
@@ -3279,6 +3279,10 @@ digits Malayalam Малаялам
|
||||
Target Contents Целево съдържание
|
||||
Target: Цел:
|
||||
Tate-chu-yoko Тате-чу-йоко
|
||||
Tate-chu-yoko Settings… Настройки на Тате-чу-йоко…
|
||||
Tate-chu-yoko Settings Настройки на Тате-чу-йоко
|
||||
Up/Down: Нагоре/надолу:
|
||||
Left/Right: Наляво/надясно:
|
||||
Temperature Температура
|
||||
Temperature/Tint… Температура/Нюанс…
|
||||
Template Шаблон
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -1451,6 +1451,10 @@ digits Malayalam Malajalam
|
||||
Superscript (on the roadmap) Eksponent (u planu)
|
||||
System Layout Sistemski raspored
|
||||
Tate-chu-yoko Tate-chu-yoko
|
||||
Tate-chu-yoko Settings… Postavke za Tate-chu-yoko…
|
||||
Tate-chu-yoko Settings Postavke za Tate-chu-yoko
|
||||
Up/Down: Gore/dolje:
|
||||
Left/Right: Lijevo/desno:
|
||||
Underline Podvučeno
|
||||
Vertical Scale % Okomita skala %
|
||||
Axis Osa
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -4194,6 +4194,10 @@ digits Malayalam Malaiàlam
|
||||
Target Contents Contingut del destí
|
||||
Target: Destí:
|
||||
Tate-chu-yoko Tate-chu-yoko
|
||||
Tate-chu-yoko Settings… Configuració de Tate-chu-yoko…
|
||||
Tate-chu-yoko Settings Configuració de Tate-chu-yoko
|
||||
Up/Down: Amunt/avall:
|
||||
Left/Right: Esquerra/dreta:
|
||||
Technical Drawing Dibuix tècnic
|
||||
Temperature Temperatura
|
||||
Temperature/Tint… Temperatura/To…
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -1475,6 +1475,10 @@ digits Malayalam Malajálamské
|
||||
Superscript (on the roadmap) Horní index (v plánu)
|
||||
System Layout Systémové rozvržení
|
||||
Tate-chu-yoko Tate-chu-yoko
|
||||
Tate-chu-yoko Settings… Nastavení Tate-chu-yoko…
|
||||
Tate-chu-yoko Settings Nastavení Tate-chu-yoko
|
||||
Up/Down: Nahoru/dolů:
|
||||
Left/Right: Vlevo/vpravo:
|
||||
Underline Podtržení
|
||||
Vertical Scale % Svislá změna velikosti %
|
||||
Axis Osa
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -1385,6 +1385,10 @@ digits Malayalam Malayalam
|
||||
Superscript (on the roadmap) Hævet skrift (på vejkortet)
|
||||
System Layout Systemlayout
|
||||
Tate-chu-yoko Tate-chu-yoko
|
||||
Tate-chu-yoko Settings… Indstillinger for Tate-chu-yoko…
|
||||
Tate-chu-yoko Settings Indstillinger for Tate-chu-yoko
|
||||
Up/Down: Op/ned:
|
||||
Left/Right: Venstre/højre:
|
||||
Underline Understreget
|
||||
Vertical Scale % Lodret skalering %
|
||||
Axis Akse
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -3300,6 +3300,10 @@ effect Points Punkte
|
||||
Target Contents Inhalt als Ziel auswählen
|
||||
Target: Ziel:
|
||||
Tate-chu-yoko Tate-chu-yoko
|
||||
Tate-chu-yoko Settings… Tate-chu-yoko-Einstellungen…
|
||||
Tate-chu-yoko Settings Tate-chu-yoko-Einstellungen
|
||||
Up/Down: Oben/Unten:
|
||||
Left/Right: Links/Rechts:
|
||||
Temperature Temperatur
|
||||
Temperature/Tint… Temperatur/Farbstich…
|
||||
Template Vorlage
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -3697,6 +3697,10 @@ digits Malayalam Μαλαγιάλαμ
|
||||
Target Στόχος
|
||||
Target Contents Περιεχόμενα στόχου
|
||||
Tate-chu-yoko Tate-chu-yoko
|
||||
Tate-chu-yoko Settings… Ρυθμίσεις Tate-chu-yoko…
|
||||
Tate-chu-yoko Settings Ρυθμίσεις Tate-chu-yoko
|
||||
Up/Down: Πάνω/Κάτω:
|
||||
Left/Right: Αριστερά/Δεξιά:
|
||||
Temperature/Tint… Θερμοκρασία/Απόχρωση…
|
||||
Templates Folder Φάκελος προτύπων
|
||||
Text Displayed in Cell: Κείμενο που εμφανίζεται στο κελί:
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 163 and column 5.
|
@@ -3357,6 +3357,10 @@ digits Malayalam Malayálam
|
||||
Target Contents Contenido de destino
|
||||
Target: Destino:
|
||||
Tate-chu-yoko Tate-chu-yoko
|
||||
Tate-chu-yoko Settings… Ajustes de Tate-chu-yoko…
|
||||
Tate-chu-yoko Settings Ajustes de Tate-chu-yoko
|
||||
Up/Down: Arriba/abajo:
|
||||
Left/Right: Izquierda/derecha:
|
||||
Temperature Temperatura
|
||||
Temperature/Tint… Temperatura/tinte…
|
||||
Template Plantilla
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -1448,6 +1448,10 @@ digits Malayalam Malajalam
|
||||
Superscript (on the roadmap) Yläindeksi (suunnitteilla)
|
||||
System Layout Järjestelmän asettelu
|
||||
Tate-chu-yoko Tate-chu-yoko
|
||||
Tate-chu-yoko Settings… Tate-chu-yoko-asetukset…
|
||||
Tate-chu-yoko Settings Tate-chu-yoko-asetukset
|
||||
Up/Down: Ylös/alas:
|
||||
Left/Right: Vasen/oikea:
|
||||
Underline Alleviivaus
|
||||
Vertical Scale % Pystyskaalaus %
|
||||
Axis Akseli
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -3349,6 +3349,10 @@ digits Malayalam Malayalam
|
||||
Target Contents Contenu de la cible
|
||||
Target: Cible :
|
||||
Tate-chu-yoko Tate-chu-yoko
|
||||
Tate-chu-yoko Settings… Paramètres Tate-chu-yoko…
|
||||
Tate-chu-yoko Settings Paramètres Tate-chu-yoko
|
||||
Up/Down: Haut/bas :
|
||||
Left/Right: Gauche/droite :
|
||||
Temperature Température
|
||||
Temperature/Tint… Température/teinte…
|
||||
Template Modèle
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -4004,6 +4004,10 @@ mark-style Japanese Japán
|
||||
Target Contents Tartalom célzása
|
||||
Target: Cél:
|
||||
Tate-chu-yoko Tate-chu-yoko
|
||||
Tate-chu-yoko Settings… Tate-chu-yoko beállításai…
|
||||
Tate-chu-yoko Settings Tate-chu-yoko beállításai
|
||||
Up/Down: Fel/le:
|
||||
Left/Right: Balra/jobbra:
|
||||
Technical Drawing Műszaki rajz
|
||||
Temperature Színhőmérséklet
|
||||
Temperature/Tint… Színhőmérséklet/tónus…
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -1385,6 +1385,10 @@ digits Malayalam Malayalam
|
||||
Superscript (on the roadmap) Superskrip (masih dalam roadmap)
|
||||
System Layout Tata letak sistem
|
||||
Tate-chu-yoko Tate-chu-yoko
|
||||
Tate-chu-yoko Settings… Pengaturan Tate-chu-yoko…
|
||||
Tate-chu-yoko Settings Pengaturan Tate-chu-yoko
|
||||
Up/Down: Atas/bawah:
|
||||
Left/Right: Kiri/kanan:
|
||||
Underline Garis bawah
|
||||
Vertical Scale % Skala vertikal %
|
||||
Axis Sumbu
|
||||
|
||||
|
@@ -3392,6 +3392,10 @@ digits Malayalam Malayalam
|
||||
Target Contents Contenuto destinazione
|
||||
Target: Destinazione:
|
||||
Tate-chu-yoko Tate-chu-yoko
|
||||
Tate-chu-yoko Settings… Impostazioni Tate-chu-yoko…
|
||||
Tate-chu-yoko Settings Impostazioni Tate-chu-yoko
|
||||
Up/Down: Su/giù:
|
||||
Left/Right: Sinistra/destra:
|
||||
Temperature Temperatura
|
||||
Temperature/Tint… Temperatura/tinta…
|
||||
Template Modello
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -3707,6 +3707,10 @@ file-filter Templates テンプレート
|
||||
Tabular Oldstyle 等幅オールドスタイル
|
||||
Titling Alternates タイトル代替字
|
||||
Tate-chu-yoko 縦中横
|
||||
Tate-chu-yoko Settings… 縦中横の設定…
|
||||
Tate-chu-yoko Settings 縦中横の設定
|
||||
Up/Down: 上下:
|
||||
Left/Right: 左右:
|
||||
Temperature 色温度
|
||||
Template テンプレート
|
||||
Templates テンプレート
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -1302,6 +1302,10 @@ digits Malayalam მალაიალამური
|
||||
Superscript (on the roadmap) ზედა ინდექსი (სამომავლო გეგმაშია)
|
||||
System Layout სისტემის განლაგება
|
||||
Tate-chu-yoko Tate-chu-yoko
|
||||
Tate-chu-yoko Settings… Tate-chu-yoko პარამეტრები…
|
||||
Tate-chu-yoko Settings Tate-chu-yoko პარამეტრები
|
||||
Up/Down: ზემოთ/ქვემოთ:
|
||||
Left/Right: მარცხნივ/მარჯვნივ:
|
||||
Underline ხაზგასმული
|
||||
Vertical Scale % ვერტიკალური მასშტაბი %
|
||||
Axis ღერძი
|
||||
|
||||
|
Can't render this file because it is too large.
|
@@ -3695,6 +3695,10 @@ file-filter Templates 템플릿
|
||||
Tabular Oldstyle 고정폭 올드스타일
|
||||
Titling Alternates 제목 대체 글자
|
||||
Tate-chu-yoko 다테추요코
|
||||
Tate-chu-yoko Settings… 다테추요코 설정…
|
||||
Tate-chu-yoko Settings 다테추요코 설정
|
||||
Up/Down: 위/아래:
|
||||
Left/Right: 왼쪽/오른쪽:
|
||||
Temperature 색온도
|
||||
Template 템플릿
|
||||
Templates 템플릿
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -1399,6 +1399,10 @@ digits Malayalam Malayalam
|
||||
Superscript (on the roadmap) Superscript (op de roadmap)
|
||||
System Layout Systeemlayout
|
||||
Tate-chu-yoko Tate-chu-yoko
|
||||
Tate-chu-yoko Settings… Tate-chu-yoko-instellingen…
|
||||
Tate-chu-yoko Settings Tate-chu-yoko-instellingen
|
||||
Up/Down: Omhoog/omlaag:
|
||||
Left/Right: Links/rechts:
|
||||
Underline Onderstrepen
|
||||
Vertical Scale % Verticale schaal %
|
||||
Axis As
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -1388,6 +1388,10 @@ digits Malayalam Malajalam
|
||||
Superscript (on the roadmap) Indeks górny (w planach)
|
||||
System Layout Układ systemowy
|
||||
Tate-chu-yoko Tate-chu-yoko
|
||||
Tate-chu-yoko Settings… Ustawienia Tate-chu-yoko…
|
||||
Tate-chu-yoko Settings Ustawienia Tate-chu-yoko
|
||||
Up/Down: W górę/w dół:
|
||||
Left/Right: W lewo/w prawo:
|
||||
Underline Podkreślenie
|
||||
Vertical Scale % Skala pionowa %
|
||||
Axis Oś
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -4770,6 +4770,10 @@ axis Both Ambos
|
||||
Tabular Oldstyle Antigo Tabular
|
||||
Titling Alternates Variantes de Titulação
|
||||
Tate-chu-yoko Tate-chu-yoko
|
||||
Tate-chu-yoko Settings… Configurações de Tate-chu-yoko…
|
||||
Tate-chu-yoko Settings Configurações de Tate-chu-yoko
|
||||
Up/Down: Para Cima/Para Baixo:
|
||||
Left/Right: Esquerda/Direita:
|
||||
Temperature Temperatura
|
||||
Templates Modelos
|
||||
Text Wrap Options Opções de Quebra de Texto
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 47.
|
@@ -3387,6 +3387,10 @@ digits Malayalam Малаялам
|
||||
Target Contents Содержимое объекта
|
||||
Target: Объект:
|
||||
Tate-chu-yoko Tate-chu-yoko
|
||||
Tate-chu-yoko Settings… Параметры Tate-chu-yoko…
|
||||
Tate-chu-yoko Settings Параметры Tate-chu-yoko
|
||||
Up/Down: Вверх/вниз:
|
||||
Left/Right: Влево/вправо:
|
||||
Temperature Температура
|
||||
Temperature/Tint… Температура/оттенок…
|
||||
Template Шаблон
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -3279,6 +3279,10 @@ digits Malayalam Malayalam
|
||||
Target Contents Målets innehåll
|
||||
Target: Mål:
|
||||
Tate-chu-yoko Tate-chu-yoko
|
||||
Tate-chu-yoko Settings… Inställningar för Tate-chu-yoko…
|
||||
Tate-chu-yoko Settings Inställningar för Tate-chu-yoko
|
||||
Up/Down: Upp/ned:
|
||||
Left/Right: Vänster/höger:
|
||||
Temperature Temperatur
|
||||
Temperature/Tint… Temperatur/färgton…
|
||||
Template Mall
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 5 and column 25.
|
@@ -3172,6 +3172,10 @@ digits Malayalam Malayalam
|
||||
Target Contents Hedef İçerikler
|
||||
Target: Hedef:
|
||||
Tate-chu-yoko Tate-chu-yoko
|
||||
Tate-chu-yoko Settings… Tate-chu-yoko Ayarları…
|
||||
Tate-chu-yoko Settings Tate-chu-yoko Ayarları
|
||||
Up/Down: Yukarı/Aşağı:
|
||||
Left/Right: Sol/Sağ:
|
||||
Temperature Sıcaklık
|
||||
Temperature/Tint… Sıcaklık/Renk Tonu…
|
||||
Template Şablon
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 5 and column 25.
|
@@ -3385,6 +3385,10 @@ effect Points Точки
|
||||
Target Contents Вміст об’єкта
|
||||
Target: Об’єкт:
|
||||
Tate-chu-yoko Tate-chu-yoko
|
||||
Tate-chu-yoko Settings… Параметри Tate-chu-yoko…
|
||||
Tate-chu-yoko Settings Параметри Tate-chu-yoko
|
||||
Up/Down: Вгору/вниз:
|
||||
Left/Right: Ліворуч/праворуч:
|
||||
Temperature Температура
|
||||
Temperature/Tint… Температура/відтінок…
|
||||
Template Шаблон
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 177 and column 5.
|
@@ -3714,6 +3714,10 @@ mark-style Japanese Kiểu Nhật
|
||||
Target Contents Nội dung mục tiêu
|
||||
Target: Mục tiêu:
|
||||
Tate-chu-yoko Tate-chu-yoko
|
||||
Tate-chu-yoko Settings… Cài đặt Tate-chu-yoko…
|
||||
Tate-chu-yoko Settings Cài đặt Tate-chu-yoko
|
||||
Up/Down: Lên/Xuống:
|
||||
Left/Right: Trái/Phải:
|
||||
Temperature Nhiệt độ màu
|
||||
Temperature/Tint… Nhiệt độ màu/Tông…
|
||||
Template Mẫu
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 199 and column 5.
|
@@ -3224,6 +3224,10 @@ digits Malayalam 马拉雅拉姆数字
|
||||
Target Contents 目标内容
|
||||
Target: 目标:
|
||||
Tate-chu-yoko 纵中横
|
||||
Tate-chu-yoko Settings… 纵中横设置…
|
||||
Tate-chu-yoko Settings 纵中横设置
|
||||
Up/Down: 上/下:
|
||||
Left/Right: 左/右:
|
||||
Temperature 色温
|
||||
Temperature/Tint… 色温/色调…
|
||||
Template 模板
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -4234,6 +4234,10 @@ digits Malayalam 馬來亞拉姆數字
|
||||
Target Contents 目標內容
|
||||
Target: 目標:
|
||||
Tate-chu-yoko 縱中橫
|
||||
Tate-chu-yoko Settings… 縱中橫設定…
|
||||
Tate-chu-yoko Settings 縱中橫設定
|
||||
Up/Down: 上/下:
|
||||
Left/Right: 左/右:
|
||||
Temperature 色溫
|
||||
Temperature/Tint… 色溫/色調…
|
||||
Template 範本
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 47.
|
@@ -743,6 +743,7 @@ fn run_ui_part10(app: &mut VectorcraftApp, id: &str, p: &Value) -> Option<Result
|
||||
"ui.corners" => crate::dialogs::corners::open(app, p),
|
||||
"ui.hyphenation" => crate::dialogs::hyphenation::open(app),
|
||||
"ui.justification" => crate::dialogs::justification::open(app),
|
||||
"ui.tateChuYokoSettings" => crate::dialogs::tate_chu_yoko::open(app),
|
||||
"ui.colorGuideLimit" => crate::panels::color_guide::set_limit(app, p),
|
||||
"ui.colorPanel" => crate::panels::color::command(app, p),
|
||||
"ui.savePdfDialog" => crate::dialogs::open_save_pdf(app, p),
|
||||
|
||||
@@ -235,7 +235,7 @@ pub fn enabled(app: &VectorcraftApp, id: &str) -> bool {
|
||||
"ui.sampleScreenColor" => crate::screen_color::available(app),
|
||||
"ui.newBrush" => app.session.active().is_some(),
|
||||
"ui.brushOptions" => crate::dialogs::brush_options::available(app),
|
||||
"ui.hyphenation" | "ui.justification" => crate::panels::character::text_style(app).is_some(),
|
||||
"ui.hyphenation" | "ui.justification" | "ui.tateChuYokoSettings" => crate::panels::character::text_style(app).is_some(),
|
||||
"ui.colorBalanceDialog" | "ui.saturateDialog" => app.session.active().is_some_and(|d| !d.selection.is_empty()),
|
||||
"ui.saveSwatchLibrary" => app.session.active().is_some(),
|
||||
id if id.starts_with(crate::panels::swatches::USER_SLOT) => crate::panels::swatches::user_library(app, id).is_some(),
|
||||
|
||||
@@ -990,6 +990,12 @@ pub const UI_COMMANDS: &[(&str, &str, &str, &str)] = &[
|
||||
"",
|
||||
"{} open Hyphenation (the Paragraph panel menu's Hyphenation…) on the paragraphs the Paragraph panel acts on, filled in with the values it shows (dialog `hyphenation`: hyphenate, wordsLongerThan 1..25, afterFirst 1..15, beforeLast 1..15, hyphenLimit 0..25 (0: no limit), hyphenationZone (pt), hyphenateCapitalized): OK runs text.setFormat with the values that changed, as one undo step; an error with no type selected",
|
||||
),
|
||||
(
|
||||
"ui.tateChuYokoSettings",
|
||||
"Tate-chu-yoko Settings…",
|
||||
"",
|
||||
"{} open Tate-chu-yoko Settings (the Character panel menu's Tate-chu-yoko Settings…, with Show East Asian Options) on the characters the Character panel acts on, filled in with the values they share (dialog `tateChuYokoSettings`: tcyUpDown, tcyLeftRight (pt, -1296..1296)): OK runs text.setFormat (text.setRangeStyle while the Type tool edits text) with the values that changed, as one undo step; an error with no type selected",
|
||||
),
|
||||
(
|
||||
"ui.colorGuideLimit",
|
||||
"Limit Color Guide to Library",
|
||||
|
||||
@@ -218,10 +218,30 @@ impl<T: PartialEq> Shared<T> {
|
||||
fn style(app: &mut VectorcraftApp, p: Value) {
|
||||
char_cmd(app, "text.setStyle", p);
|
||||
}
|
||||
fn format(app: &mut VectorcraftApp, p: Value) {
|
||||
pub(crate) fn format(app: &mut VectorcraftApp, p: Value) {
|
||||
char_cmd(app, "text.setFormat", p);
|
||||
}
|
||||
|
||||
/// [`format`] that returns the command's result: `text.setRangeStyle` on the range the Type tool
|
||||
/// has selected (the whole text when nothing is selected) while it edits text, else
|
||||
/// `text.setFormat` on the selected text objects.
|
||||
pub(crate) fn run_format(app: &mut VectorcraftApp, mut p: Value) -> Result<Value, String> {
|
||||
let Some((id, a, b)) = text_editing(app) else { return app.run("text.setFormat", p) };
|
||||
end_typing(app);
|
||||
p["id"] = json!(id.0);
|
||||
if b > a {
|
||||
p["start"] = json!(a);
|
||||
p["end"] = json!(b);
|
||||
}
|
||||
app.run("text.setRangeStyle", p)
|
||||
}
|
||||
|
||||
/// The Tate-chu-yoko Settings Up/Down and Left/Right the characters the panels act on share, in
|
||||
/// document points; `None` where they differ, or with no type.
|
||||
pub(crate) fn shared_tcy_offsets(app: &VectorcraftApp) -> (Option<f64>, Option<f64>) {
|
||||
(shared_effective(app, |s| s.tcy_up_down), shared_effective(app, |s| s.tcy_left_right))
|
||||
}
|
||||
|
||||
/// Apply character attributes: to the selected range while the Type tool edits text (the whole
|
||||
/// object when nothing is selected), else to the selected text objects.
|
||||
fn char_cmd(app: &mut VectorcraftApp, cmd: &str, p: Value) {
|
||||
@@ -1168,6 +1188,12 @@ pub fn menu(app: &mut VectorcraftApp, ui: &mut Ui) {
|
||||
if menu_item(ui, tl!("Tate-chu-yoko"), has, tcy) {
|
||||
format(app, json!({"tateChuYoko": !tcy}));
|
||||
}
|
||||
// Tate-chu-yoko Settings…: the Up/Down and Left/Right that move a tate-chu-yoko block.
|
||||
if menu_item(ui, tl!("Tate-chu-yoko Settings…"), has, false)
|
||||
&& let Err(e) = app.run("ui.tateChuYokoSettings", json!({}))
|
||||
{
|
||||
app.status(e);
|
||||
}
|
||||
// Standard Vertical Roman Alignment: the selected half-width characters stand upright in
|
||||
// vertical type instead of being rotated. Checked when every selected character has it; a
|
||||
// mixed selection turns it on.
|
||||
@@ -1239,9 +1265,9 @@ fn fractional_widths_on(app: &VectorcraftApp) -> bool {
|
||||
/// kerning, no baseline shift, 100% scales, no rotation, no underline or strikethrough, All Caps
|
||||
/// and Small Caps off, the normal position (neither superscript nor subscript), Roman Baseline
|
||||
/// character alignment, Proportional Metrics, Tate-chu-yoko, Standard Vertical Roman Alignment and
|
||||
/// No Break off, the Default character direction, the Default digits and the Em Box font height
|
||||
/// reference, whether or not Show East Asian Options, Show Indic Options and Show Font Height
|
||||
/// Options are on.
|
||||
/// No Break off, Tate-chu-yoko Settings Up/Down and Left/Right 0, the Default character direction,
|
||||
/// the Default digits and the Em Box font height reference, whether or not Show East Asian
|
||||
/// Options, Show Indic Options and Show Font Height Options are on.
|
||||
fn reset_panel(app: &mut VectorcraftApp) {
|
||||
let d = CharStyle::default();
|
||||
style(app, json!({"tracking": 0, "leading": "auto"}));
|
||||
@@ -1251,6 +1277,7 @@ fn reset_panel(app: &mut VectorcraftApp) {
|
||||
"kerning": "auto", "baselineShift": 0, "hScale": 100, "vScale": 100, "rotation": 0, "underline": false, "strikethrough": false,
|
||||
"allCaps": false, "smallCaps": d.small_caps.is_some(), "position": d.position.id(), "charAlign": d.char_align,
|
||||
"proportionalMetrics": d.proportional_metrics, "tateChuYoko": d.tate_chu_yoko,
|
||||
"tcyUpDown": d.tcy_up_down, "tcyLeftRight": d.tcy_left_right,
|
||||
"standardVerticalRoman": d.standard_vertical_roman, "noBreak": d.no_break, "charDirection": "default",
|
||||
"digits": d.digits, "fontHeightReference": d.font_height_reference
|
||||
}),
|
||||
|
||||
@@ -673,6 +673,23 @@ fn tate_chu_yoko_menu_item_shows_with_east_asian_options_and_toggles() {
|
||||
assert!(!on(&app));
|
||||
}
|
||||
|
||||
/// Tate-chu-yoko Settings… is in the panel menu with the East Asian options, and choosing it opens
|
||||
/// the dialog on the selected characters.
|
||||
#[test]
|
||||
fn tate_chu_yoko_settings_menu_item_shows_with_east_asian_options_and_opens_the_dialog() {
|
||||
let mut app = VectorcraftApp::new(Session::new(), Default::default());
|
||||
app.run("file.new", json!({"width": 300, "height": 200})).unwrap();
|
||||
let id = app.session.execute("text.create", &json!({"x": 20, "y": 50, "text": "2026"})).unwrap()["id"].as_u64().unwrap();
|
||||
app.session.execute("select.set", &json!({"ids": [id]})).unwrap();
|
||||
let ctx = egui::Context::default();
|
||||
assert!(!crate::tests_labels::painted_text(&mut app, menu).contains("Tate-chu-yoko Settings…"));
|
||||
app.session.prefs.show_east_asian_options = true;
|
||||
let texts = super::super::tests_appearance::frame_events(&ctx, &mut app, vec![], menu);
|
||||
let at = texts.iter().find(|(t, _)| t.trim() == "Tate-chu-yoko Settings…").unwrap_or_else(|| panic!("{texts:?}")).1;
|
||||
super::super::tests_appearance::click(&ctx, &mut app, at.center(), menu);
|
||||
assert_eq!(app.ui.dialog.as_ref().map(|d| d.kind.as_str()), Some(crate::dialogs::tate_chu_yoko::KIND));
|
||||
}
|
||||
|
||||
/// Standard Vertical Roman Alignment is in the panel menu with the East Asian options. Choosing it
|
||||
/// marks the selected characters, one undo step each way; its check shows only while every
|
||||
/// selected character has it, and a mixed selection turns it on.
|
||||
@@ -995,7 +1012,7 @@ fn no_break_menu_item_toggles_the_selected_characters() {
|
||||
|
||||
/// Reset Panel sets every character attribute the panel shows, except the font family, style
|
||||
/// and size, back to its default (Small Caps, the position, Character Alignment, Proportional
|
||||
/// Metrics, Tate-chu-yoko, No Break, Character Direction, Digits and the font height reference
|
||||
/// Metrics, Tate-chu-yoko and its settings, No Break, Character Direction, Digits and the font height reference
|
||||
/// included), in the selected type and in the text the Type tool has selected while it edits.
|
||||
#[test]
|
||||
fn reset_panel_resets_every_attribute_but_the_font() {
|
||||
@@ -1023,6 +1040,8 @@ fn reset_panel_resets_every_attribute_but_the_font() {
|
||||
s.char_align != d.char_align,
|
||||
s.proportional_metrics != d.proportional_metrics,
|
||||
s.tate_chu_yoko != d.tate_chu_yoko,
|
||||
s.tcy_up_down != d.tcy_up_down,
|
||||
s.tcy_left_right != d.tcy_left_right,
|
||||
s.standard_vertical_roman != d.standard_vertical_roman,
|
||||
s.no_break != d.no_break,
|
||||
s.char_direction != d.char_direction,
|
||||
@@ -1036,17 +1055,17 @@ fn reset_panel_resets_every_attribute_but_the_font() {
|
||||
json!({
|
||||
"id": id, "tracking": 50, "leading": 30, "kerning": 20, "baselineShift": 3, "hScale": 120, "vScale": 90, "rotation": 15,
|
||||
"underline": true, "strikethrough": true, "allCaps": true, "smallCaps": true, "position": "superscript",
|
||||
"charAlign": "emBoxCenter", "proportionalMetrics": true, "tateChuYoko": true, "standardVerticalRoman": true,
|
||||
"noBreak": true, "charDirection": "rightToLeft", "digits": "hindi", "fontHeightReference": "capHeight"
|
||||
"charAlign": "emBoxCenter", "proportionalMetrics": true, "tateChuYoko": true, "tcyUpDown": 2, "tcyLeftRight": -3,
|
||||
"standardVerticalRoman": true, "noBreak": true, "charDirection": "rightToLeft", "digits": "hindi", "fontHeightReference": "capHeight"
|
||||
}),
|
||||
)
|
||||
.unwrap();
|
||||
};
|
||||
let plain = runs(&app)[0].1.clone();
|
||||
assert_eq!(differs(&plain, &CharStyle::default()), [false; 20]);
|
||||
assert_eq!(differs(&plain, &CharStyle::default()), [false; 22]);
|
||||
custom(&mut app);
|
||||
let styled = runs(&app)[0].1.clone();
|
||||
assert_eq!(differs(&styled, &CharStyle::default()), [true; 20]);
|
||||
assert_eq!(differs(&styled, &CharStyle::default()), [true; 22]);
|
||||
app.run("select.set", json!({"ids": [id]})).unwrap();
|
||||
reset_panel(&mut app);
|
||||
assert_eq!(runs(&app), [("One 12".to_string(), plain.clone())]);
|
||||
|
||||
+18
@@ -4963,6 +4963,24 @@ undo step.
|
||||
{"name":"run_command","arguments":{"command":"text.setFormat","params":{"burasagari":"forced"}}}
|
||||
```
|
||||
|
||||
## Tate-chu-yoko Settings
|
||||
|
||||
`text.setFormat` and `text.setRangeStyle {tcyUpDown?, tcyLeftRight?}` set the Character panel menu's Tate-chu-yoko
|
||||
Settings of the characters, in points from -1296 to 1296 (document points, as `baselineShift`). In vertical type a
|
||||
block of tate-chu-yoko characters (`tateChuYoko`) moves up by a positive Up/Down and down by a negative one, and right
|
||||
by a positive Left/Right and left by a negative one. The block moves as one piece with the values of its first
|
||||
character, and keeps its one em down the column, its caret positions and its hit region. Two or three digits set
|
||||
across the column without the mark do not move, and horizontal type keeps the values without moving the characters.
|
||||
Neither value turns `tateChuYoko` on. Both are 0 by default and saved only when not 0, and character styles carry
|
||||
them. A value that is not a number is refused with `` `tcyUpDown` must be a number ``, and one outside the range with
|
||||
`` `tcyUpDown` must be from -1296 to 1296 ``, naming the key. One undo step. `ui.tateChuYokoSettings` opens the
|
||||
Tate-chu-yoko Settings dialog (the menu item shows with Show East Asian Options) on the values the selected characters
|
||||
share, a blank field where they differ; OK sends the values that changed.
|
||||
|
||||
```json
|
||||
{"name":"run_command","arguments":{"command":"text.setRangeStyle","params":{"id":12,"start":0,"end":4,"tateChuYoko":true,"tcyUpDown":2,"tcyLeftRight":-1}}}
|
||||
```
|
||||
|
||||
## Optical Margin Alignment
|
||||
|
||||
`type.opticalMarginAlignment {on?: bool}` (Type › Optical Margin Alignment) turns Optical Margin Alignment on or off
|
||||
|
||||
@@ -908,6 +908,7 @@ Moved from the ROADMAP's "Shipped so far" on 2026-10-10. Update it when a featur
|
||||
The Tabs panel menu's Clear All Tabs (`text.tabs.clear`), Delete Tab and Repeat Tab (`text.tabs.set`) can be chosen while the panel's ruler shows tab stops. Delete Tab removes the stop selected on the ruler. Repeat Tab replaces the stops after the selected stop with up to 20 copies of it. The copies are spaced by the selected stop's distance from the stop before it, or from the paragraph's left indent when it is the first stop, and no copy is placed at 2,000 pt or beyond. Clear All Tabs removes every stop, so the default stops every ½ inch apply again.
|
||||
- **Interface languages, vertical type:** Preferences ▸ User Interface (or VectorCraft ▸ Language on macOS) picks Automatic (the system locale) or a registered language, persisted as `interfaceLanguage`. Translations come from per-language catalogs (`i18n/*.tsv`): Traditional and Simplified Chinese, Japanese, German (every `de-*` locale), Spanish (neutral international Spanish, for every `es-*` locale), French (every `fr-*` locale), Italian (every `it-*` locale), Russian (every `ru-*` locale) and Ukrainian (every `uk-*` locale), Catalan (every `ca-*` locale) and Polish (every `pl-*` locale; AI-drafted, native review pending) cover the whole interface, Czech every menu label; Czech, German, Greek, Spanish, French, Hungarian, Italian, Japanese, Korean, Vietnamese, Polish, Brazilian Portuguese, Russian, Simplified Chinese, Traditional Chinese, Swedish, Ukrainian and Finnish also translate status-bar and error messages as they are shown (`@msg` templates, every message literal tested; the messages themselves stay English for agents); untranslated text stays English. Japanese and Chinese glyphs come from the craft-fonts build input (release builds) or the installed fonts, Czech, German, Spanish, French, Italian, Russian and Ukrainian ones from the bundled UI fonts. The web app follows the browser's languages (or `?lang=`). The Basic toolbar's Type button holds Vertical Type in its flyout. Vertical Type, Vertical Area Type and Vertical Type on a Path create vertical text (columns right to left, upright CJK glyphs and marks with `vert` alternates, Latin and longer numbers on their side, two- and three-digit numbers set as tate-chu-yoko in one em of the column (kept whole when justified), upright glyphs centred on the column (tracked or justified too), point type's anchor on the first column's centre line); kinsoku applies to horizontal and vertical Japanese (Paragraph panel › Kinsoku Set, `text.setFormat {kinsoku}`: Hard, the default, keeps closing marks, iteration marks, the prolonged sound mark and small kana off the line start and opening brackets off its end; Soft lets 々, ー and small kana start a line as JLREQ's level 3 rules do; None turns it off), and justified Japanese lines spread their room between characters (not inside Latin words); Paragraph panel › Mojikumi Set (shown with Preferences › Type › Show East Asian Options, `text.setFormat {mojikumi}`): None or Line-end Punctuation Half Width (new type's default, as in Illustrator; older documents and imported text keep None) shares one half-em space between consecutive punctuation, sets a closing mark ending a line half width and an opening bracket starting a line flush with its start (a paragraph's first line at its first-line indent, JIS X 4051's principle in JLREQ 3.1.5), and puts a quarter em between Japanese and Latin letters or digits; Paragraph panel menu › Top-to-Top / Bottom-to-Bottom Leading measures leading from em box top to top (area type's first line touches the frame) or from baseline to baseline (new type in a Japanese interface starts top to top, with em box centre character alignment, #432); Paragraph panel menu › Burasagari None / Regular / Force (`text.setFormat {burasagari: "none"|"standard"|"forced"}`) hangs an East Asian comma or full stop (、。,.、。) ending an area type line outside it, Regular (new type's default) when it doesn't fit and Force always (the rest of the line then fills the measure; closing brackets and Latin punctuation don't hang); Character panel menu › Character Alignment (with the East Asian options; `text.setFormat` or `text.setRangeStyle {charAlign}`) lines characters smaller than the largest on their line up on the Roman baseline, the em box top/right, centre or bottom/left, or the ICF (ideographic character face) top/right or bottom/left, from the font's BASE table or, without one, the average ink box of some ideographs and kana; Proportional Metrics (the same menu; `{proportionalMetrics}`) sets full-width glyphs on the font's proportional widths (OpenType `palt`) in horizontal type and upright glyphs on its proportional heights (`vpal`, shaped top to bottom; fonts without vertical metrics keep their cells) in vertical type, and Line-end Punctuation Half Width takes nothing more off the punctuation they re-spaced; Type ▸ Type Orientation switches existing text; caret, selection, hit testing and arrow keys follow the writing direction. Vertical text exports to PDF as real text and to SVG as outlines. The interface is available in Polish (`pl`): menus, panels, dialogs and messages, with one/few/many plurals; AI-drafted, native review pending (#1498).
|
||||
Character panel menu › Standard Vertical Roman Alignment (with the East Asian options; `text.setFormat` or `text.setRangeStyle {standardVerticalRoman}`, off by default) stands the selected Latin letters, digits and other half-width characters upright one below the other in vertical type instead of rotating them ([formatting Asian characters](https://helpx.adobe.com/illustrator/using/formatting-asian-characters.html)); spaces keep their width, tate-chu-yoko blocks stay across the column, and Character Style Options gives it its own checkbox beside Character Alignment.
|
||||
Character panel menu › Tate-chu-yoko Settings… (with the East Asian options; `ui.tateChuYokoSettings`, `text.setFormat` or `text.setRangeStyle {tcyUpDown, tcyLeftRight}`, in points, 0 by default) moves a marked tate-chu-yoko block in vertical type up by a positive Up/Down and right by a positive Left/Right ([format East Asian characters in vertical text](https://helpx.adobe.com/illustrator/desktop/design-with-text/edit-format-text/format-east-asian-characters-in-vertical-text.html), [formatting Asian characters](https://helpx.adobe.com/illustrator/using/formatting-asian-characters.html)); the block moves as one piece with its first character's values and keeps its one em down the column, automatic digit blocks do not move, Reset Panel sets both back to 0, and character styles and the native file keep them.
|
||||
The Character panel's All Caps, Underline and Strikethrough buttons and its menu's All Caps, Underline, Strikethrough, Proportional Metrics, Tate-chu-yoko and No Break show off while only some of the selected characters have the attribute, and choosing one then turns it on for all of them, as `type.underline` (Cmd+Shift+U) does for underlining; the Character Alignment and Digits submenus check no item while the selected characters differ. `text.setFormat` refuses a value that is not true or false for its on/off attributes, as `text.setRangeStyle` does.
|
||||
- **Transparency:** opacity masks (clip/invert/disable/link), exported as SVG `<mask>` and PDF soft masks. The constant alpha soft mask that PDF and `.ai` export draw a CMYK page with transparency through is white, so applications that read every soft mask by its luminosity show the page too (#1359, part of #1286).
|
||||
Isolation mode and opacity mask editing exclude each other ([transparency and blending modes](https://web.archive.org/web/20230102045151/https://helpx.adobe.com/illustrator/using/transparency-blending-modes.html)): in isolation mode a click or Alt-click on the mask thumbnail does not edit the mask, and Make Mask is off for one object; while a mask is edited, Enter Isolation Mode and Object › Perspective › Edit Text are off, the Contextual Task Bar leaves out Isolate, and Quick Actions leave out Isolate Group.
|
||||
|
||||
+1
-1
@@ -58,7 +58,7 @@ against Illustrator's public documentation; **estimated** unless noted.
|
||||
| CJK: input methods (marked text, candidate window) | done | |
|
||||
| CJK: kinsoku (Hard, Soft, None), Line-end Punctuation Half Width, tate-chu-yoko (auto) | done / partial | custom kinsoku sets, the Mojikumi Settings dialog and the other mojikumi sets missing |
|
||||
| CJK: Proportional Metrics (`palt` in horizontal type, `vpal` in vertical type) | done | #966 |
|
||||
| CJK: manual tate-chu-yoko | done | Character panel menu › Tate-chu-yoko (`text.setFormat` / `text.setRangeStyle {tateChuYoko}`), with Show East Asian Options: marked characters stand side by side across one em of a vertical column, squeezed to the em when wider; two or three half-width digits still do so without the mark; horizontal type keeps the mark and does not move them. Up/Down and Left/Right settings remain. Evidence: `tests_vertical::manual_tate_chu_yoko_sets_marked_characters_across_one_em`, `typecmd::tate_chu_yoko_is_a_character_attribute_saved_only_when_on` |
|
||||
| CJK: manual tate-chu-yoko | done | Character panel menu › Tate-chu-yoko (`text.setFormat` / `text.setRangeStyle {tateChuYoko}`), with Show East Asian Options: marked characters stand side by side across one em of a vertical column, squeezed to the em when wider; two or three half-width digits still do so without the mark; horizontal type keeps the mark and does not move them. Tate-chu-yoko Settings… (`ui.tateChuYokoSettings`, `{tcyUpDown, tcyLeftRight}` in points) moves a marked block up or down and right or left as one piece. Evidence: `tests_vertical::manual_tate_chu_yoko_sets_marked_characters_across_one_em`, `tests_vertical::tate_chu_yoko_settings_move_a_marked_block_up_down_left_and_right`, `typecmd::tate_chu_yoko_is_a_character_attribute_saved_only_when_on`, `typecmd::tate_chu_yoko_settings_are_character_attributes_saved_only_when_set`, `dialogs::tate_chu_yoko::tests` |
|
||||
| CJK: Standard Vertical Roman Alignment | done | Character panel menu › Standard Vertical Roman Alignment (`text.setFormat` / `text.setRangeStyle {standardVerticalRoman}`), with Show East Asian Options, off by default: in vertical type the marked half-width characters (Latin letters, digits and other characters that would lie on their side) stand upright one below the other, each in its upright cell; spaces keep their width and tate-chu-yoko blocks stay across the column; horizontal type keeps the mark and does not change. Character Style Options has its own checkbox for it. Evidence: `tests_vertical::standard_vertical_roman_alignment_stands_half_width_characters_upright`, `typecmd::standard_vertical_roman_is_a_character_attribute_saved_only_when_on` |
|
||||
| CJK: ruby, manual tate-chu-yoko | partial | ruby (ふりがな) for live text (`type.ruby`): a reading over a byte range, above (right of the column in vertical type) or below, start/centre/end alignment, size, overhang or squeeze; saved natively, laid out with its line growing, drawn on the canvas, in SVG and PDF (as outlines where ruby is present), in EPS, EMF/WMF and legacy `.ai` (legacy `.ai` writes type with ruby as outlines), and kept by Create Outlines, Expand Appearance and flattening, each glyph where the layout puts it (`typecmd::ruby_tests::create_outlines_keeps_the_ruby_where_it_is_laid_out`); manual tate-chu-yoko missing | #633 |
|
||||
| Language per character (Character panel › Language, character styles), text shaped in it | done | `text.setFormat` / `text.setRangeStyle {language}` by name or BCP 47 tag, one of Document Setup's languages (Hindi, Marathi, Gujarati, Bengali, Punjabi, Tamil, Telugu, Kannada, Malayalam, Odia and Urdu added, #4943; Serbian: Cyrillic, Serbian: Latin and Croatian); the shaper gets the tag, so the font's language forms apply (OpenType `locl`: Turkish keeps `fi` apart and the dot on its i, #4940); new type takes the document's language; saved as the tag only when set, carried by character styles (#4946). Not yet: hyphenation and spelling by it, `.ai`/PDF import of a text's language, paragraph styles. Evidence: `features::tests::language_picks_the_fonts_forms`, `features::tests::serbian_gets_the_fonts_serbian_forms`, `typecmd::area_tests::language_is_a_character_attribute_saved_only_when_set` |
|
||||
|
||||
Reference in New Issue
Block a user