mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-01 21:57:12 -06:00
Type: Standard Vertical Roman Alignment stands half-width characters upright in vertical type
Character panel menu > Standard Vertical Roman Alignment, with Show East Asian Options, is now a check item. It sets a new character attribute, `standardVerticalRoman` (text.setFormat, text.setRangeStyle), off by default and saved only when on. In vertical type the marked 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. A combining mark shaped as its own glyph turns with its letter, in Latin and CJK upright type alike. Character Style Options labels the Character Alignment row "Character Alignment" and gives Standard Vertical Roman Alignment its own checkbox. Reset Panel turns the attribute off. PDF and SVG export place the glyphs from the layout and need no change.
This commit is contained in:
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\", …], 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), 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), 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\", …], 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), 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
|
||||
),
|
||||
@@ -319,6 +319,7 @@ pub(crate) struct CharChange {
|
||||
char_direction: Option<Option<vectorcraft_doc::ParaDirection>>,
|
||||
proportional_metrics: Option<bool>,
|
||||
tate_chu_yoko: Option<bool>,
|
||||
standard_vertical_roman: Option<bool>,
|
||||
no_break: Option<bool>,
|
||||
digits: Option<vectorcraft_doc::Digits>,
|
||||
/// The language to set: a tag, or `None` for the default.
|
||||
@@ -471,7 +472,7 @@ impl CharChange {
|
||||
/// `setup` gives the superscript, subscript and small caps proportions.
|
||||
pub(crate) fn parse(p: &Value, cmd: &str, setup: &vectorcraft_doc::DocSetup) -> Result<Self> {
|
||||
check_numbers(p, &["size", "tracking", "baselineShift", "hScale", "vScale", "rotation", "strokeWidth"], cmd)?;
|
||||
check_switches(p, &["underline", "strikethrough", "allCaps", "proportionalMetrics", "tateChuYoko", "noBreak"], cmd)?;
|
||||
check_switches(p, &["underline", "strikethrough", "allCaps", "proportionalMetrics", "tateChuYoko", "standardVerticalRoman", "noBreak"], cmd)?;
|
||||
let num = |k: &str| p.get(k).and_then(Value::as_f64);
|
||||
let (position, small_caps) = super::docsetup::script_params(p, setup, cmd)?;
|
||||
let flag = |k: &str| p.get(k).and_then(Value::as_bool);
|
||||
@@ -517,6 +518,7 @@ impl CharChange {
|
||||
char_direction: char_direction_param(p, cmd)?,
|
||||
proportional_metrics: flag("proportionalMetrics"),
|
||||
tate_chu_yoko: flag("tateChuYoko"),
|
||||
standard_vertical_roman: flag("standardVerticalRoman"),
|
||||
no_break: flag("noBreak"),
|
||||
digits: digits_param(p, cmd)?,
|
||||
language: language_param(p, cmd)?,
|
||||
@@ -563,6 +565,7 @@ impl CharChange {
|
||||
&& self.char_direction.is_none()
|
||||
&& self.proportional_metrics.is_none()
|
||||
&& self.tate_chu_yoko.is_none()
|
||||
&& self.standard_vertical_roman.is_none()
|
||||
&& self.no_break.is_none()
|
||||
&& self.digits.is_none()
|
||||
&& self.language.is_none()
|
||||
@@ -668,6 +671,9 @@ impl CharChange {
|
||||
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;
|
||||
}
|
||||
if let Some(v) = self.no_break {
|
||||
st.no_break = v;
|
||||
}
|
||||
|
||||
@@ -175,6 +175,7 @@ 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)),
|
||||
("standardVerticalRoman", json!(v.standard_vertical_roman)),
|
||||
("noBreak", json!(v.no_break)),
|
||||
("digits", json!(v.digits)),
|
||||
("language", json!(v.language)),
|
||||
|
||||
@@ -1206,6 +1206,44 @@ fn tate_chu_yoko_is_a_character_attribute_saved_only_when_on() {
|
||||
assert!(runs(&s).iter().all(|r| !r.style.tate_chu_yoko));
|
||||
}
|
||||
|
||||
/// 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.
|
||||
#[test]
|
||||
fn standard_vertical_roman_is_a_character_attribute_saved_only_when_on() {
|
||||
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": "ABC年"})).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"),
|
||||
};
|
||||
assert!(!runs(&s)[0].style.standard_vertical_roman);
|
||||
assert!(serde_json::to_value(&runs(&s)[0].style).unwrap().get("standardVerticalRoman").is_none());
|
||||
s.execute("select.set", &json!({"ids": [id]})).unwrap();
|
||||
s.execute("text.setFormat", &json!({"standardVerticalRoman": true})).unwrap();
|
||||
assert!(runs(&s).iter().all(|r| r.style.standard_vertical_roman));
|
||||
assert_eq!(serde_json::to_value(&runs(&s)[0].style).unwrap()["standardVerticalRoman"], true);
|
||||
s.execute("edit.undo", &json!({})).unwrap();
|
||||
assert!(!runs(&s)[0].style.standard_vertical_roman);
|
||||
s.execute("text.setRangeStyle", &json!({"id": id, "start": 0, "end": 3, "standardVerticalRoman": true})).unwrap();
|
||||
let on: Vec<_> = runs(&s).iter().map(|r| (r.text.clone(), r.style.standard_vertical_roman)).collect();
|
||||
assert_eq!(on, [("ABC".to_string(), true), ("年".to_string(), false)]);
|
||||
let old = serde_json::to_value(&runs(&s)[1].style).unwrap();
|
||||
assert!(old.get("standardVerticalRoman").is_none());
|
||||
assert!(!serde_json::from_value::<vectorcraft_doc::CharStyle>(old).unwrap().standard_vertical_roman);
|
||||
// Saved and reopened as it was.
|
||||
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.standard_vertical_roman)).collect();
|
||||
assert_eq!(reopened, on);
|
||||
assert_eq!(super::super::textstyles::char_attrs(&runs(&s)[0].style)["standardVerticalRoman"], true);
|
||||
assert_eq!(super::super::textstyles::char_attrs(&runs(&s)[1].style)["standardVerticalRoman"], false);
|
||||
assert!(s.execute("text.setRangeStyle", &json!({"id": id, "standardVerticalRoman": "true"})).is_err());
|
||||
s.execute("text.setFormat", &json!({"standardVerticalRoman": false})).unwrap();
|
||||
assert!(runs(&s).iter().all(|r| !r.style.standard_vertical_roman));
|
||||
}
|
||||
|
||||
/// No Break is a character attribute, saved only when on.
|
||||
#[test]
|
||||
fn no_break_is_a_character_attribute_saved_only_when_on() {
|
||||
|
||||
Reference in New Issue
Block a user