Character: Font Size can measure the cap height, x-height or ICF box

Squashed from #5026 onto main.
This commit is contained in:
dotCooCoo
2026-11-01 11:08:55 +07:00
committed by bflatastic
parent af133b1ee4
commit 427bce12b0
54 changed files with 895 additions and 40 deletions
+3 -2
View File
@@ -21,5 +21,6 @@ image = { workspace = true }
[dev-dependencies]
serde_json = { workspace = true }
# The tests lay type out in the bundled fonts (the workspace leaves turning them on to the engine).
vectorcraft-text = { workspace = true, features = ["bundled-fonts"] }
# The tests lay type out in the bundled fonts (the workspace leaves turning them on to the engine)
# and in fonts built in code (`vectorcraft_text::test_fonts`).
vectorcraft-text = { workspace = true, features = ["bundled-fonts", "test-fonts"] }
+4 -1
View File
@@ -1062,7 +1062,8 @@ impl<'a> Scene<'a> {
}
/// Type: glyph outlines (Preserve Appearance, Outline Text, type on a path), else one text
/// entity per run of each line.
/// entity per run of each line, whose height (group 40) is [`CAP_HEIGHT`] times the em size its
/// run is drawn at ([`vectorcraft_text::FontDb::font_heights_resolved`]).
fn text(&mut self, n: &Node, t: &TextObject) {
let outline = self.o.preserve == Preserve::Appearance || self.o.outline_text || matches!(t.kind, TextKind::OnPath { .. });
if outline {
@@ -1074,6 +1075,8 @@ impl<'a> Scene<'a> {
if !n.appearance.items.is_empty() {
self.warn(TYPE_ITEMS);
}
let resolved = vectorcraft_text::FontDb::global().font_heights_resolved(t);
let t = &*resolved;
let layout = vectorcraft_text::layout(vectorcraft_text::FontDb::global(), t);
let plain = t.plain_text();
let xf = self.to_dxf * t.xf;
+17
View File
@@ -906,6 +906,23 @@ fn text_outside_ascii_is_escaped_before_2007() {
assert_eq!(text(DxfVersion::R2018), "Größe");
}
/// Type whose font size measures the cap height is written at the em size it is drawn at: 14 pt
/// measuring a 0.7 em cap height is 20 pt type, whose TEXT height is 14.
#[test]
fn a_font_height_reference_is_written_as_the_size_drawn() {
use vectorcraft_text::test_fonts::{HEIGHT_FAMILY, height_font};
vectorcraft_text::FontDb::global().add_font(height_font(true).unwrap());
let mut d = text_doc("Plan A", 14.0);
let id = d.layers[0].children().and_then(|c| c.first()).map(|n| n.id).unwrap();
if let Some(NodeKind::Text(t)) = d.node_mut(id).map(|n| &mut n.kind) {
t.runs[0].style.font_family = HEIGHT_FAMILY.into();
t.runs[0].style.font_height_reference = vectorcraft_doc::FontHeightReference::CapHeight;
}
let editable = export(&d, &DxfOptions { preserve: Preserve::Editability, ..opts(&d) }).unwrap();
let e = entities(&editable);
assert!((all(&e[0], 40)[0] - 14.0).abs() < 1e-6, "{:?}", all(&e[0], 40));
}
#[test]
fn images_link_png_or_jpeg_files() {
let mut png = vec![];
+1
View File
@@ -106,6 +106,7 @@ pub use selection::{AnchorRef, SegmentRef, Selection};
pub use setup::{Background, DocSetup, ExportText, GridSize, Quotes};
pub use slices::{CellAlign, CellVAlign, Slice, SliceArea, SliceKind, SliceOptions, SliceSource};
pub use style_libs::StyleLibrary;
pub use text::FontHeightReference;
pub use text::{
AreaFit, AreaOptions, Burasagari, CharAlign, CharPosition, CharStyle, Composer, Digits, FirstBaseline, HyphenationOptions, InlineArt,
JustificationOptions, Justify, Kinsoku, LeadingModel, Mojikumi, ParaDirection, ParaStyle, PathAlign, PathEffect, RubyAlign, RubyPosition,
+1 -1
View File
@@ -15,7 +15,7 @@ mod tabs_lists;
pub use area::{AreaFit, AreaOptions, PathAlign, PathEffect, TextFlow, TextWrap, VerticalAlign, WrapShape};
use char_style::yes;
pub use char_style::{CharAlign, CharPosition, CharStyle, Digits, ScriptMetrics};
pub use char_style::{CharAlign, CharPosition, CharStyle, Digits, FontHeightReference, ScriptMetrics};
pub use para_style::{
Burasagari, Composer, FirstBaseline, HyphenationOptions, JustificationOptions, Justify, Kashida, Kinsoku, LeadingModel, Mojikumi, ParaDirection,
ParaStyle, SingleWordJustify,
+20
View File
@@ -116,6 +116,10 @@ pub struct CharStyle {
/// these characters are drawn in, whatever digits were typed.
#[serde(default, skip_serializing_if = "crate::skip::is_default")]
pub digits: Digits,
/// Font height reference (Character panel menu › Show Font Height Options): the height of the
/// font that [`Self::size`] measures, the em box by default.
#[serde(default, rename = "fontHeightReference", skip_serializing_if = "crate::skip::is_default")]
pub font_height_reference: FontHeightReference,
/// No Break (Character panel menu): these characters are not broken across lines, including
/// at a space or a hyphenation point inside the run. A run longer than the line stays together
/// and can overflow it.
@@ -130,6 +134,21 @@ pub struct CharStyle {
pub language: Option<String>,
}
/// What a character's font size measures ([`CharStyle::font_height_reference`]): the em square
/// of the font, the height of its capitals or of its lowercase letters (the cap height and x
/// height of its OS/2 table), or its ideographic character face (ICF, OpenType's `icfb` and
/// `icft` baselines). Layout sets the characters at the em size at which the chosen height equals
/// the font size; with a font that has no such value, the size measures the em box.
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub enum FontHeightReference {
#[default]
EmBox,
CapHeight,
XHeight,
IcfBox,
}
/// The numeral system digits are drawn in ([`CharStyle::digits`]): as typed, or every digit of
/// the Western, Arabic-Indic and Eastern Arabic-Indic sets drawn in one of them or in the digits
/// of an Indic script. The text keeps the digits that were typed; only the glyphs change (like
@@ -380,6 +399,7 @@ impl Default for CharStyle {
standard_vertical_roman: false,
small_caps: None,
digits: Digits::Default,
font_height_reference: FontHeightReference::EmBox,
no_break: false,
language: None,
}
+18
View File
@@ -60,6 +60,24 @@ fn digits_map_between_the_numeral_systems() {
assert_eq!(serde_json::from_value::<CharStyle>(serde_json::to_value(&farsi).unwrap()).unwrap(), farsi);
}
/// The font height reference is the em box by default and is saved only when it is another.
#[test]
fn font_height_reference_is_saved_only_when_not_the_em_box() {
let st = CharStyle::default();
assert_eq!(st.font_height_reference, FontHeightReference::EmBox);
assert!(serde_json::to_value(&st).unwrap().get("fontHeightReference").is_none());
for (r, key) in
[(FontHeightReference::CapHeight, "capHeight"), (FontHeightReference::XHeight, "xHeight"), (FontHeightReference::IcfBox, "icfBox")]
{
let set = CharStyle { font_height_reference: r, ..CharStyle::default() };
assert_eq!(serde_json::to_value(&set).unwrap()["fontHeightReference"], key);
assert_eq!(serde_json::from_value::<CharStyle>(serde_json::to_value(&set).unwrap()).unwrap(), set);
}
// Styles written before the attribute existed read as the em box.
let old = serde_json::to_value(&st).unwrap();
assert_eq!(serde_json::from_value::<CharStyle>(old).unwrap().font_height_reference, FontHeightReference::EmBox);
}
/// Each Indic numeral system draws 0–9 (and the Arabic-Indic sets) as the ten digits of its
/// script's block; every system saves under its id, which reads back, and other names are refused.
#[test]
File diff suppressed because one or more lines are too long
+26 -2
View File
@@ -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), 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)}",
"{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), 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
),
@@ -325,6 +325,7 @@ pub(crate) struct CharChange {
/// The language to set: a tag, or `None` for the default.
language: Option<Option<String>>,
font_axes: Option<AxisChange>,
font_height_reference: Option<vectorcraft_doc::FontHeightReference>,
}
/// `charDirection: "default"|"leftToRight"|"rightToLeft"` → the Character Direction to set (`None`
@@ -452,6 +453,20 @@ pub(crate) fn char_align_param(p: &Value, cmd: &str) -> Result<Option<vectorcraf
}))
}
/// `fontHeightReference: "emBox"|"capHeight"|"xHeight"|"icfBox"` (Character panel › Set Font Height
/// Reference): the height of the font that the font size measures.
pub(crate) fn font_height_reference_param(p: &Value, cmd: &str) -> Result<Option<vectorcraft_doc::FontHeightReference>> {
use vectorcraft_doc::FontHeightReference;
let Some(v) = p.get("fontHeightReference").filter(|v| !v.is_null()) else { return Ok(None) };
Ok(Some(match v.as_str() {
Some("emBox") => FontHeightReference::EmBox,
Some("capHeight") => FontHeightReference::CapHeight,
Some("xHeight") => FontHeightReference::XHeight,
Some("icfBox") => FontHeightReference::IcfBox,
_ => return Err(bad(cmd, "`fontHeightReference` must be \"emBox\", \"capHeight\", \"xHeight\" or \"icfBox\"")),
}))
}
fn paint_param(p: &Value, k: &str, cmd: &str) -> Result<Option<Paint>> {
match p.get(k) {
None | Some(Value::Null) => Ok(None),
@@ -523,6 +538,7 @@ impl CharChange {
digits: digits_param(p, cmd)?,
language: language_param(p, cmd)?,
font_axes: font_axes_param(p, cmd)?,
font_height_reference: font_height_reference_param(p, cmd)?,
};
if c.size.is_some_and(|v| v <= 0.0) {
return Err(bad(cmd, "size must be positive"));
@@ -570,6 +586,7 @@ impl CharChange {
&& self.digits.is_none()
&& self.language.is_none()
&& self.font_axes.is_none()
&& self.font_height_reference.is_none()
}
fn localize(&mut self, t: &TextObject, cmd: &str) -> Result<()> {
@@ -680,6 +697,9 @@ impl CharChange {
if let Some(v) = self.digits {
st.digits = v;
}
if let Some(v) = self.font_height_reference {
st.font_height_reference = v;
}
if let Some(v) = &self.language {
st.language.clone_from(v);
}
@@ -1216,7 +1236,11 @@ fn type_step(s: &mut Session, p: &Value) -> Result<Value> {
match step {
Step::Size => st.size = super::typecmd::local_type_value(t, st.size * points + inc, (0.1, 1296.0), false, C)?,
Step::Leading => {
let leading = t.para_at(paras.get(i).copied().unwrap_or(0)).effective_leading(st);
// With Auto leading the step starts from the leading the lines are set with:
// the paragraph's Auto Leading share of the em size the font height reference
// gives the characters. An inline graphic keeps its size.
let em = if run.inline.is_some() { st.size } else { vectorcraft_text::FontDb::global().em_size(st, t.vertical) };
let leading = t.para_at(paras.get(i).copied().unwrap_or(0)).effective_leading(&CharStyle { size: em, ..st.clone() });
st.leading = Some(super::typecmd::local_type_value(t, leading * points + inc, (0.1, 5000.0), false, C)?);
}
Step::Tracking => st.tracking = (st.tracking + inc).clamp(-1000.0, 10000.0),
+1
View File
@@ -178,6 +178,7 @@ pub fn char_attrs(v: &CharStyle) -> Map<String, Value> {
("standardVerticalRoman", json!(v.standard_vertical_roman)),
("noBreak", json!(v.no_break)),
("digits", json!(v.digits)),
("fontHeightReference", json!(v.font_height_reference)),
("language", json!(v.language)),
] {
m.insert(k.into(), x);
@@ -1306,6 +1306,97 @@ fn digits_are_a_character_attribute_saved_only_when_set() {
assert_eq!(set, [("صفحة ".to_string(), Digits::Default), ("12".to_string(), Digits::Farsi)]);
}
/// Font height reference (Character panel › Set Font Height Reference): a character attribute,
/// saved only when not the em box; the type is laid out again at the em size it gives, so 20 pt
/// type measuring a 0.7 em cap height grows by 1 / 0.7, and a leading step from Auto starts at
/// 120% of that em size.
#[test]
fn font_height_reference_is_a_character_attribute_saved_only_when_not_the_em_box() {
use vectorcraft_doc::FontHeightReference as R;
use vectorcraft_text::test_fonts::{HEIGHT_CAP, HEIGHT_FAMILY, height_font};
vectorcraft_text::FontDb::global().add_font(height_font(true).unwrap());
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": "Hx Hx", "font": HEIGHT_FAMILY, "size": 20})).unwrap()["id"].as_u64().unwrap();
let text = |s: &Session| match &s.doc().unwrap().doc.node(NodeId(id)).unwrap().kind {
NodeKind::Text(t) => t.clone(),
_ => panic!("text"),
};
let height = |s: &Session| text(s).cached_bounds.unwrap().height();
let em_box = height(&s);
assert_eq!(text(&s).runs[0].style.font_height_reference, R::EmBox);
s.execute("select.set", &json!({"ids": [id]})).unwrap();
for (key, want) in [("capHeight", R::CapHeight), ("xHeight", R::XHeight), ("icfBox", R::IcfBox), ("emBox", R::EmBox)] {
s.execute("text.setFormat", &json!({"fontHeightReference": key})).unwrap();
assert!(text(&s).runs.iter().all(|r| r.style.font_height_reference == want && r.style.size == 20.0));
let saved = serde_json::to_value(&text(&s).runs[0].style).unwrap();
assert_eq!(saved.get("fontHeightReference").and_then(|v| v.as_str()), (want != R::EmBox).then_some(key));
}
s.execute("text.setFormat", &json!({"fontHeightReference": "capHeight"})).unwrap();
let grown = height(&s) / em_box;
assert!((grown - 1000.0 / f64::from(HEIGHT_CAP)).abs() < 1e-6, "{grown}");
// A leading step from Auto starts at the leading the lines are set with: 120% of the em size
// (the paragraph's Auto Leading).
s.execute("type.step", &json!({"attribute": "leading"})).unwrap();
let stepped = text(&s).runs[0].style.leading.unwrap();
assert!((stepped - (1.2 * 20.0 * 1000.0 / f64::from(HEIGHT_CAP) + 2.0)).abs() < 1e-9, "{stepped}");
s.execute("edit.undo", &json!({})).unwrap();
s.execute("edit.undo", &json!({})).unwrap();
assert_eq!(text(&s).runs[0].style.font_height_reference, R::EmBox);
assert!((height(&s) - em_box).abs() < 1e-9);
let err = s.execute("text.setFormat", &json!({"fontHeightReference": "baseline"})).unwrap_err().to_string();
assert!(err.contains("`fontHeightReference` must be"), "{err}");
// Characters selected with the Type tool: only the range takes it.
s.execute("text.setRangeStyle", &json!({"id": id, "start": 3, "end": 5, "fontHeightReference": "xHeight"})).unwrap();
let set: Vec<_> = text(&s).runs.iter().map(|r| (r.text.clone(), r.style.font_height_reference)).collect();
assert_eq!(set, [("Hx ".to_string(), R::EmBox), ("Hx".to_string(), R::XHeight)]);
}
/// A leading step from Auto starts at the leading each paragraph's lines are set with, its
/// Auto Leading share of the em size: 20 pt type in paragraphs at 120% and 150% steps from 24 pt
/// and from 30 pt.
#[test]
fn a_leading_step_from_auto_starts_at_each_paragraphs_auto_leading() {
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": "Ab\nCd", "size": 20})).unwrap()["id"].as_u64().unwrap();
s.execute("text.setFormat", &json!({"id": id, "start": 3, "end": 5, "autoLeading": 150})).unwrap();
s.execute("select.set", &json!({"ids": [id]})).unwrap();
s.execute("type.step", &json!({"attribute": "leading"})).unwrap();
let NodeKind::Text(t) = &s.doc().unwrap().doc.node(NodeId(id)).unwrap().kind else {
panic!("text");
};
let leading: Vec<_> = t.runs.iter().map(|r| (r.text.as_str(), r.style.leading)).collect();
assert_eq!(leading, [("Ab\n", Some(26.0)), ("Cd", Some(32.0))]);
}
/// An inline graphic keeps its size whatever the font height reference, so a leading step from
/// Auto starts at 120% of that size: 20 pt Cap Height type steps from 120% of 20 / 0.7 pt and
/// the graphic in it from 24 pt.
#[test]
fn a_leading_step_from_auto_starts_at_an_inline_graphics_own_size() {
use vectorcraft_text::test_fonts::{HEIGHT_CAP, HEIGHT_FAMILY, height_font};
vectorcraft_text::FontDb::global().add_font(height_font(true).unwrap());
let mut s = Session::new();
s.execute("file.new", &json!({"width": 400, "height": 400})).unwrap();
s.execute("shape.ellipse", &json!({"x": 300, "y": 300, "width": 10, "height": 10})).unwrap();
s.execute("symbol.new", &json!({"name": "Dot"})).unwrap();
s.execute("edit.clear", &json!({})).unwrap();
let id = s.execute("text.create", &json!({"x": 10, "y": 50, "text": "Hx", "font": HEIGHT_FAMILY, "size": 20})).unwrap()["id"].as_u64().unwrap();
s.execute("text.insertInline", &json!({"id": id, "at": 2, "symbol": "Dot"})).unwrap();
s.execute("select.set", &json!({"ids": [id]})).unwrap();
s.execute("text.setFormat", &json!({"fontHeightReference": "capHeight"})).unwrap();
s.execute("type.step", &json!({"attribute": "leading"})).unwrap();
let NodeKind::Text(t) = &s.doc().unwrap().doc.node(NodeId(id)).unwrap().kind else {
panic!("text");
};
let leading: Vec<_> = t.runs.iter().map(|r| (r.inline.is_some(), r.style.leading.unwrap())).collect();
let text = 1.2 * 20.0 * 1000.0 / f64::from(HEIGHT_CAP) + 2.0;
assert_eq!(leading.len(), 2, "{leading:?}");
assert!(leading.iter().all(|&(inline, l)| (l - if inline { 26.0 } else { text }).abs() < 1e-9), "{leading:?}");
}
/// The Indic digits (#4942): each set by its id through text.setFormat and text.setRangeStyle,
/// saved under it, kept by save and reopen and by character styles; the text stays as typed.
#[test]
+33
View File
@@ -1017,6 +1017,39 @@ fn variable_font_settings_are_warned_as_left_out() {
assert!(String::from_utf8(out.bytes).unwrap().contains("(H\\351llo \\2005\\r) Tx\n"), "still written as text");
}
/// Type whose font size measures the cap height is written at the em size it is drawn at, with
/// no warning: 14 pt measuring a 0.7 em cap height reads back as 20 pt type.
#[test]
fn a_font_height_reference_is_written_as_the_size_drawn() {
use vectorcraft_text::test_fonts::{HEIGHT_FAMILY, height_font};
vectorcraft_text::FontDb::global().add_font(height_font(true).unwrap());
let mut doc = typed();
let id = texts(&doc)[0].0.id;
if let Some(NodeKind::Text(t)) = doc.node_mut(id).map(|n| &mut n.kind) {
let st = &mut t.runs[0].style;
(st.font_family, st.size) = (HEIGHT_FAMILY.into(), 14.0);
st.font_height_reference = vectorcraft_doc::FontHeightReference::CapHeight;
}
let out = write(&doc);
assert!(!out.warnings.iter().any(|w| w == TYPE_ATTRIBUTES || w == TYPE), "{:?}", out.warnings);
let imported = crate::import(&out.bytes).unwrap();
let size = texts(&imported.document)[0].1.runs[0].style.size;
assert!((size - 20.0).abs() < 1e-6, "{size}");
}
/// Characters with Auto leading are written with their paragraph's Auto Leading share of the
/// size: 18 pt type in paragraphs at 150% is written with 27 pt leading.
#[test]
fn auto_leading_is_written_at_the_paragraphs_share() {
let mut doc = typed();
let id = texts(&doc)[0].0.id;
if let Some(NodeKind::Text(t)) = doc.node_mut(id).map(|n| &mut n.kind) {
t.edit_paras(None, |p| p.justification.auto_leading = 150.0);
}
let text = String::from_utf8(write(&doc).bytes).unwrap();
assert!(text.contains("\n27 0 Tl\n"), "{text}");
}
/// A legacy `.ai` paragraph has no list: type with a bulleted or numbered list is written as its
/// text, with the warning that the bullets and numbers are left out.
#[test]
+7 -2
View File
@@ -719,11 +719,15 @@ impl<'a> Writer<'a> {
// ---------- type ----------
/// Type: a text object when the format can hold it as text, else its outlines.
/// Type: a text object when the format can hold it as text, else its outlines. A run whose
/// font size measures a height other than the em box is written at the em size it is drawn
/// at ([`vectorcraft_text::FontDb::font_heights_resolved`]).
fn text(&mut self, t: &TextObject) {
if !invertible(t.xf) {
return;
}
let resolved = vectorcraft_text::FontDb::global().font_heights_resolved(t);
let t = &*resolved;
if !self.outline_text {
if writable(t) {
return self.text_object(t);
@@ -849,7 +853,8 @@ impl<'a> Writer<'a> {
ps::push_num(&mut self.body, finite(st.tracking, 0.0));
self.line(" Tt");
self.line(if st.kerning.is_none() { "1 TA" } else { "0 TA" });
ps::push_nums(&mut self.body, &[finite(st.effective_leading(), size * 1.2), finite(p.space_before, 0.0)]);
let leading = st.leading.unwrap_or_else(|| p.justification.auto_leading_for(st.size));
ps::push_nums(&mut self.body, &[finite(leading, size * 1.2), finite(p.space_before, 0.0)]);
self.line("Tl");
}
+5 -1
View File
@@ -567,8 +567,12 @@ impl Rules<'_> {
}
}
/// Type: its first character style's font, colour and spacing, its paragraph's alignment.
/// Type: its first character style's font, colour and spacing, its paragraph's alignment. The
/// font size is the em size the characters are drawn at
/// ([`vectorcraft_text::FontDb::font_heights_resolved`]).
fn type_art(&mut self, n: &Node, t: &vectorcraft_doc::TextObject, art: &mut Art) {
let resolved = vectorcraft_text::FontDb::global().font_heights_resolved(t);
let t = &*resolved;
if matches!(t.kind, TextKind::OnPath { .. }) {
art.unsupported("type on a path");
}
+5 -2
View File
@@ -73,11 +73,14 @@ impl Writer<'_> {
/// instances of their symbols (a `<use>` of the symbol's def where it can be shared), all in
/// one group carrying the object's transparency. The live text skips their characters and
/// places the text after each one; type on a path with inline graphics is written as outlines
/// (a `<textPath>` couldn't leave room for the art).
/// (a `<textPath>` couldn't leave room for the art). A run whose font size measures a height
/// other than the em box is written at the em size it is drawn at
/// ([`vectorcraft_text::FontDb::font_heights_resolved`]).
pub(super) fn text_node(&mut self, n: &Node, t: &TextObject) {
let doc = self.doc;
let resolved = doc.inline_resolved(t);
let t = &*resolved;
let heights = vectorcraft_text::FontDb::global().font_heights_resolved(&resolved);
let t = &*heights;
let inline = t.runs.iter().any(|r| r.inline.is_some());
// Ruby is written as outlines with its base text: a reader's <text> can't place it. So are
// small capitals, superscript and subscript and tab leaders, which SVG text doesn't draw (#3869).
+20
View File
@@ -148,6 +148,26 @@ fn ellipses_round_fully_and_options_change_units_and_names() {
assert!(prop(&r, "width").is_none() && prop(&r, "height").is_none());
}
/// Type whose font size measures the cap height gives the em size it is drawn at: 14 pt measuring
/// a 0.7 em cap height is `font-size: 20px`, and its Auto leading is not written.
#[test]
fn type_gives_the_font_size_it_is_drawn_at() {
use vectorcraft_text::test_fonts::{HEIGHT_FAMILY, height_font};
vectorcraft_text::FontDb::global().add_font(height_font(true).unwrap());
let mut d = doc();
let st = CharStyle {
font_family: HEIGHT_FAMILY.into(),
size: 14.0,
font_height_reference: vectorcraft_doc::FontHeightReference::CapHeight,
..CharStyle::default()
};
let n = text(&mut d, "Hello", st);
let id = add(&mut d, n);
let r = one(&d, id, &CssOptions::default());
assert_eq!(prop(&r, "font-size"), Some("20px"));
assert!(prop(&r, "line-height").is_none());
}
#[test]
fn type_gives_font_properties() {
let mut d = doc();
+17
View File
@@ -176,6 +176,23 @@ fn digits_are_written_as_drawn() {
assert!(!text.contains("page 4") && !text.contains("page ٥"), "{svg}");
}
/// A run whose font size measures the cap height is written at the em size it is drawn at: 14 pt
/// measuring a 0.7 em cap height is `font-size="20"`, after a 12 pt run measuring the em box,
/// which takes the `<text>` element's 12.
#[test]
fn font_height_reference_writes_the_size_drawn() {
use vectorcraft_doc::FontHeightReference;
use vectorcraft_text::test_fonts::{HEIGHT_FAMILY, height_font};
vectorcraft_text::FontDb::global().add_font(height_font(true).unwrap());
let em_box = CharStyle { font_family: HEIGHT_FAMILY.into(), ..style(12.0) };
let cap = CharStyle { size: 14.0, font_height_reference: FontHeightReference::CapHeight, ..em_box.clone() };
let d = area_doc(vec![run("Hx ", em_box), run("Hx", cap)], 280.0, Justify::Left);
let svg = export(&d, &ExportOptions::default());
assert!(svg.contains("font-size=\"12\" fill"), "{svg}");
let sizes: Vec<_> = tspans(&svg).iter().map(|t| attr(t, "font-size")).collect();
assert_eq!(sizes, [None, Some(20.0)], "{svg}");
}
#[test]
fn fewer_tspans_writes_one_per_line() {
let bold = CharStyle { font_style: "Bold".into(), ..style(12.0) };
+20
View File
@@ -279,6 +279,26 @@ impl FontFace {
*self.icf_margins.get_or_init(|| self.icf_from_base().or_else(|| self.icf_from_glyphs()).unwrap_or_default())
}
/// The height of the face that a font size measures with `reference`, in ems: 1 for the em
/// box; the cap height or x height of its OS/2 table; the height of its ideographic character
/// face ([`Self::icf_margins`]), or across a `vertical` line its width. None for a face whose
/// font has no such value (an OS/2 table without the heights, no ICF), or one outside 0.1 to
/// 2 ems.
pub fn font_height(&self, reference: vectorcraft_doc::FontHeightReference, vertical: bool) -> Option<f64> {
use vectorcraft_doc::FontHeightReference as R;
let units = |v: Option<f32>| v.map(f64::from).map(|v| v / self.upem);
let h = match reference {
R::EmBox => Some(1.0),
R::CapHeight => units(self.skrifa()?.metrics(Size::unscaled(), self.location()).cap_height),
R::XHeight => units(self.skrifa()?.metrics(Size::unscaled(), self.location()).x_height),
R::IcfBox => {
let m = self.icf_margins();
(m != IcfMargins::default()).then_some(if vertical { 1.0 - m.left - m.right } else { 1.0 - m.top - m.bottom })
}
};
h.filter(|h| (0.1..=2.0).contains(h))
}
/// [`Self::icf_margins`] from the BASE table: the ICF's bottom edge `icfb` (top edge `icft`, else
/// as far below the em box top) against the em box `ideo` .. `idtp` (else the OS/2 typographic
/// descender, and one em above it); across vertical lines, `icfb` and `icft` of the vertical
+31
View File
@@ -247,6 +247,37 @@ impl FontDb {
Some(self.instance(&face, &st.font_axes))
}
/// The em size characters of style `st` are set at, in `vertical` type or not: its size, or
/// with a font height reference other than the em box ([`vectorcraft_doc::FontHeightReference`])
/// the size at which its face ([`Self::style_face`]) has that height
/// ([`FontFace::font_height`]). The size itself when the face has no such height.
pub fn em_size(&self, st: &vectorcraft_doc::CharStyle, vertical: bool) -> f64 {
let reference = st.font_height_reference;
if reference == vectorcraft_doc::FontHeightReference::EmBox {
return st.size;
}
self.style_face(st).and_then(|f| f.font_height(reference, vertical)).map_or(st.size, |h| st.size / h)
}
/// `t` as layout sets it: each run takes its [`Self::em_size`] and the em box as its font
/// height reference; an inline graphic keeps its size. Explicit leading and baseline shift
/// stay in points; Auto leading, tracking and kerning follow the em size. `t` itself when every
/// run measures the em box.
pub fn font_heights_resolved<'a>(&self, t: &'a vectorcraft_doc::TextObject) -> std::borrow::Cow<'a, vectorcraft_doc::TextObject> {
use vectorcraft_doc::FontHeightReference;
if t.runs.iter().all(|r| r.style.font_height_reference == FontHeightReference::EmBox) {
return std::borrow::Cow::Borrowed(t);
}
let mut out = t.clone();
for r in &mut out.runs {
if r.inline.is_none() {
r.style.size = self.em_size(&r.style, t.vertical);
}
r.style.font_height_reference = FontHeightReference::EmBox;
}
std::borrow::Cow::Owned(out)
}
/// `face` with the axis settings `axes` (by tag, in the axes' own units) in place of its own,
/// each clamped to its axis's range: its outlines, metrics and advances taken there, and its
/// file embedded instanced there. Tags the font has no axis for are left out. `face` itself
+4 -1
View File
@@ -106,8 +106,11 @@ pub fn decorations(layout: &TextLayout, db: &FontDb, t: &TextObject) -> Vec<(usi
const SHRINK_PASSES: usize = 11;
/// Lay out a text object with explicit options (area type rows/columns, inset, first baseline,
/// fit, composer, OpenType features).
/// fit, composer, OpenType features). Each run is set at the em size its font height reference
/// gives it ([`FontDb::font_heights_resolved`]).
pub fn layout_with(db: &FontDb, t: &TextObject, opts: &LayoutOptions) -> TextLayout {
let resolved = db.font_heights_resolved(t);
let t = &*resolved;
let first = layout_once(db, t, opts);
match (opts.fit.min_scale(), &t.kind) {
(Some(min), TextKind::Area { .. }) if first.overflow => shrink_to_fit(db, t, opts, min).unwrap_or(first),
+2
View File
@@ -718,6 +718,7 @@ fn shapes_alike(a: &CharStyle, b: &CharStyle) -> bool {
tate_chu_yoko,
standard_vertical_roman: _,
digits,
font_height_reference,
no_break,
language,
} = a;
@@ -735,6 +736,7 @@ fn shapes_alike(a: &CharStyle, b: &CharStyle) -> bool {
&& *proportional_metrics == b.proportional_metrics
&& *tate_chu_yoko == b.tate_chu_yoko
&& *digits == b.digits
&& *font_height_reference == b.font_height_reference
&& *no_break == b.no_break
&& *language == b.language
&& ligatures_suppressed_by(*tracking) == ligatures_suppressed_by(b.tracking)
+80
View File
@@ -254,6 +254,86 @@ pub fn vertical_font(vorg: bool) -> Option<Vec<u8>> {
Some(builder.build())
}
/// The family of [`height_font`] with its heights.
pub const HEIGHT_FAMILY: &str = "Heighttest Sans";
/// The family of [`height_font`] without them.
pub const HEIGHTLESS_FAMILY: &str = "Heightlesstest Sans";
/// The cap height and x height [`height_font`] gives, in font units (1,000 to the em).
pub const HEIGHT_CAP: i16 = 700;
pub const HEIGHT_X: i16 = 500;
/// The height (and the width across a vertical line) of [`height_font`]'s ideographic character
/// face, in ems.
pub const HEIGHT_ICF: f64 = 0.9;
/// The family of [`height_outside_font`].
pub const HEIGHT_OUTSIDE_FAMILY: &str = "Heightoutsidetest Sans";
/// The cap height and x height [`height_outside_font`] gives, in font units (1,000 to the em):
/// 2.5 em and 0.05 em, outside the 0.1 to 2 ems a font size measures.
pub const HEIGHT_OUTSIDE_CAP: i16 = 2500;
pub const HEIGHT_OUTSIDE_X: i16 = 50;
/// The bundled Source Sans 3 Regular as family [`HEIGHT_FAMILY`], with the cap height
/// [`HEIGHT_CAP`] and x height [`HEIGHT_X`] in its OS/2 table and a `BASE` table whose Han
/// baselines put the ideographic character face [`HEIGHT_ICF`] em high inside the em box (`ideo`
/// -120, `idtp` 880, `icfb` -70, `icft` 830). Without `heights`: family [`HEIGHTLESS_FAMILY`],
/// with a version 1 OS/2 table, which holds neither height, and no `BASE` table. `None` when it
/// can't be built.
pub fn height_font(heights: bool) -> Option<Vec<u8>> {
if heights { heights_font(HEIGHT_FAMILY, Some((HEIGHT_X, HEIGHT_CAP)), true) } else { heights_font(HEIGHTLESS_FAMILY, None, false) }
}
/// The bundled Source Sans 3 Regular as family [`HEIGHT_OUTSIDE_FAMILY`], with the cap height
/// [`HEIGHT_OUTSIDE_CAP`] and x height [`HEIGHT_OUTSIDE_X`] in its OS/2 table and no `BASE`
/// table. `None` when it can't be built.
pub fn height_outside_font() -> Option<Vec<u8>> {
heights_font(HEIGHT_OUTSIDE_FAMILY, Some((HEIGHT_OUTSIDE_X, HEIGHT_OUTSIDE_CAP)), false)
}
/// The bundled Source Sans 3 Regular as `family`: with the x height and cap height `heights` in
/// its OS/2 table, else a version 1 OS/2 table, which holds neither; with `base`, the `BASE`
/// table of [`height_font`].
fn heights_font(family: &str, heights: Option<(i16, i16)>, base: bool) -> Option<Vec<u8>> {
use write_fonts::tables::base::{Axis, Base, BaseCoord, BaseScript, BaseScriptList, BaseScriptRecord, BaseTagList, BaseValues};
const BASE: &[u8] = include_bytes!("../../../assets/fonts/SourceSans3-Regular.ttf");
let font = skrifa::FontRef::new(BASE).ok()?;
let full = format!("{family} Regular");
let postscript = format!("{}-Regular", family.replace(' ', ""));
let name = name_table(&[(1, family), (2, "Regular"), (4, &full), (6, &postscript)])?;
let mut builder = FontBuilder::new();
builder.add_raw(Tag::new(b"name"), name);
if base {
let tags = BaseTagList::new([b"icfb", b"icft", b"ideo", b"idtp"].map(Tag::new).to_vec());
let values = BaseValues::new(2, [-70, 830, -120, 880].map(BaseCoord::format_1).to_vec());
let hani = BaseScriptRecord::new(Tag::new(b"hani"), BaseScript::new(Some(values), None, vec![]));
let axis = Axis::new(Some(tags), BaseScriptList::new(vec![hani]));
builder.add_table(&Base::new(Some(axis), None)).ok()?;
}
for r in font.table_directory.table_records() {
let tag = Tag::new(&r.tag().to_be_bytes());
if builder.contains(tag) {
continue;
}
let mut data = font.table_data(r.tag())?.as_bytes().to_vec();
if tag == Tag::new(b"OS/2") {
if let Some((x, cap)) = heights {
// sxHeight and sCapHeight, at bytes 86 and 88 of a version 2 or later table.
data.get_mut(86..90)?.copy_from_slice(&[x.to_be_bytes(), cap.to_be_bytes()].concat());
} else {
// Version 1 ends before sxHeight.
data.truncate(86);
data.get_mut(0..2)?.copy_from_slice(&1_u16.to_be_bytes());
}
}
builder.add_raw(tag, data);
}
Some(builder.build())
}
/// The family of [`vertical_punctuation_font`].
pub const VERTICAL_PUNCTUATION_FAMILY: &str = "Punctuationtest Sans";
+56
View File
@@ -1347,6 +1347,62 @@ fn character_alignment_on_the_icf_lines_small_characters_up_with_the_largest_fac
}
}
/// The font height reference: 20 pt type that measures the cap height is set at the em size at
/// which the font's cap height (OS/2) is 20 pt, and the same for the x height and for the ideographic
/// character face's height (its width in vertical type); the em box keeps 20 pt to the em, and
/// so does every reference in a font without these values. Auto leading follows the em size,
/// and two runs that measure different heights each take their own em size.
#[test]
fn font_size_measures_the_font_height_reference() {
use crate::test_fonts::{HEIGHT_CAP, HEIGHT_FAMILY, HEIGHT_ICF, HEIGHT_X, HEIGHTLESS_FAMILY, height_font};
use vectorcraft_doc::FontHeightReference as R;
let db = FontDb::with_font_dirs(vec![]);
db.add_font(height_font(true).unwrap());
db.add_font(height_font(false).unwrap());
let st = |family: &str, r: R| CharStyle { font_family: family.into(), font_height_reference: r, ..style(20.0) };
// The em size each glyph of `t` is drawn at (its transform's scale times 1,000 units).
let ems = |t: &TextObject| layout(&db, t).glyphs.iter().map(|g| g.xf.determinant().abs().sqrt() * 1000.0).collect::<Vec<_>>();
let near = |a: f64, b: f64| (a - b).abs() < 1e-6;
let (cap, x) = (f64::from(HEIGHT_CAP) / 1000.0, f64::from(HEIGHT_X) / 1000.0);
for vertical_type in [false, true] {
for (r, h) in [(R::EmBox, 1.0), (R::CapHeight, cap), (R::XHeight, x), (R::IcfBox, HEIGHT_ICF)] {
let mut t = point("Hx", st(HEIGHT_FAMILY, r));
t.vertical = vertical_type;
assert!(ems(&t).iter().all(|e| near(*e, 20.0 / h)), "{r:?} vertical {vertical_type}: {:?}", ems(&t));
t.runs[0].style.font_family = HEIGHTLESS_FAMILY.into();
assert!(ems(&t).iter().all(|e| near(*e, 20.0)), "{r:?} without the value, vertical {vertical_type}: {:?}", ems(&t));
}
}
let face = db.face(HEIGHT_FAMILY, "Regular").unwrap();
assert_eq!(face.font_height(R::CapHeight, false), Some(cap));
assert_eq!(db.face(HEIGHTLESS_FAMILY, "Regular").unwrap().font_height(R::XHeight, false), None);
// Auto leading: 120% of the em size the line is set at.
let lines = layout(&db, &point("H\nH", st(HEIGHT_FAMILY, R::CapHeight))).lines;
assert!(near(lines[1].baseline - lines[0].baseline, 1.2 * 20.0 / cap), "{lines:?}");
// One run at the em box, the next at the cap height.
let mut t = point("H", st(HEIGHT_FAMILY, R::EmBox));
t.runs.push(TextRun { text: "H".into(), style: st(HEIGHT_FAMILY, R::CapHeight), inline: None });
let set = ems(&t);
assert!(set.len() == 2 && near(set[0], 20.0) && near(set[1], 20.0 / cap), "{set:?}");
}
/// A font height outside 0.1 to 2 ems is not used: 20 pt type that measures a font's 2.5 em cap
/// height or its 0.05 em x height is set at 20 pt to the em.
#[test]
fn a_font_height_outside_a_tenth_to_two_ems_measures_the_em_box() {
use crate::test_fonts::{HEIGHT_OUTSIDE_FAMILY, height_outside_font};
use vectorcraft_doc::FontHeightReference as R;
let db = FontDb::with_font_dirs(vec![]);
db.add_font(height_outside_font().unwrap());
let face = db.face(HEIGHT_OUTSIDE_FAMILY, "Regular").unwrap();
for r in [R::CapHeight, R::XHeight] {
assert_eq!(face.font_height(r, false), None, "{r:?}");
let st = CharStyle { font_family: HEIGHT_OUTSIDE_FAMILY.into(), font_height_reference: r, ..style(20.0) };
let ems = layout(&db, &point("Hx", st)).glyphs.iter().map(|g| g.xf.determinant().abs().sqrt() * 1000.0).collect::<Vec<_>>();
assert!(!ems.is_empty() && ems.iter().all(|e| (e - 20.0).abs() < 1e-6), "{r:?}: {ems:?}");
}
}
/// Burasagari leaves Latin punctuation alone: in a measure of exactly "abcd", the full stop of
/// "abcd. ef" doesn't hang with Standard or Forced (the line breaks as it does with None), and a
/// Latin line ending in a full stop isn't shortened by Forced.
+6
View File
@@ -4500,6 +4500,12 @@ axis Both И двете
Insert Inline Symbol Вграждане на вграден символ
ICF Top/Right ICF горе/дясно
ICF Bottom/Left ICF долу/ляво
Show Font Height Options Показване на опциите за височина на шрифта
Hide Font Height Options Скриване на опциите за височина на шрифта
Set Font Height Reference Задаване на референтна височина на шрифта
Em Box Квадратна касетка (Em Box)
x-Height Височина на x
ICF Box Касетка ICF
@msg `id` is the step reference {r}, which only `run` and `command_batch` resolve `id` е референцията за стъпка {r}, която се разрешава само от `run` и `command_batch`
@msg `ids` must be an array of object ids, not {v} `ids` трябва да бъде масив от идентификатори на обекти, а не {v}
@msg `corners` must be an array of anchor indices, not {v} `corners` трябва да бъде масив от индекси на опорни точки, а не {v}
Can't render this file because it contains an unexpected character in line 2 and column 73.
+6
View File
@@ -1454,6 +1454,12 @@ digits Malayalam Malajalam
Axis Osa
ICF Top/Right ICF vrh/desno
ICF Bottom/Left ICF dno/lijevo
Show Font Height Options Prikaži opcije visine fonta
Hide Font Height Options Sakrij opcije visine fonta
Set Font Height Reference Postavi referentnu visinu fonta
Em Box Em okvir
x-Height Visina malih slova
ICF Box ICF okvir
Copy Color Value (Hex) Kopiraj vrijednost boje (hex)
Create New Swatch… Kreiraj novi uzorak boje…
Name 40% Naziv 40%
Can't render this file because it contains an unexpected character in line 2 and column 73.
+6
View File
@@ -5735,6 +5735,12 @@ theme Appearance Aparença
Glyph Bounds Límits del glif
Proximity Guides Guies de proximitat
Angular Guides Guies angulars
Show Font Height Options Mostra les opcions d’alçada de la font
Hide Font Height Options Amaga les opcions d’alçada de la font
Set Font Height Reference Estableix la referència d’alçada de la font
Em Box Caixa em
x-Height Alçada de la x
ICF Box Caixa ICF
# Average & Join (Object › Path, Cmd+Alt+Shift+J).
Average & Join… Mitjana i unió…
Average & Join Mitjana i unió
Can't render this file because it contains an unexpected character in line 2 and column 73.
+7
View File
@@ -1478,6 +1478,12 @@ digits Malayalam Malajálamské
Axis Osa
ICF Top/Right ICF horní/pravý
ICF Bottom/Left ICF dolní/levý
Show Font Height Options Zobrazit možnosti výšky písma
Hide Font Height Options Skrýt možnosti výšky písma
Set Font Height Reference Nastavit referenční výšku písma
Em Box Čtverčík
x-Height Výška x
ICF Box Rámeček ICF
Copy Color Value (Hex) Kopírovat hodnotu barvy (hex)
Create New Swatch… Vytvořit novou položku vzorníku…
Name 40% Název 40 %
@@ -4209,6 +4215,7 @@ imageTrace Outline Obrys
@msg the file's layers weren't read ({_1}): it comes in as its page, in one layer vrstvy souboru nebyly načteny ({_1}): soubor se otevře jako jeho stránka v jedné vrstvě
@msg the file's layers weren't read from its editing data ({_1}): they come from its PDF part vrstvy souboru nebyly načteny z jeho dat pro úpravy ({_1}): pocházejí z jeho části PDF
@msg `to` must be "corner" or "smooth" `to` musí být "corner" nebo "smooth"
@msg `fontHeightReference` must be "emBox", "capHeight", "xHeight" or "icfBox" `fontHeightReference` musí být "emBox", "capHeight", "xHeight" nebo "icfBox"
@msg BMP files are RGB or grayscale: colorModel rgb or gray soubory BMP jsou RGB nebo ve stupních šedi: colorModel rgb nebo gray
@msg Blend colors: select at least three objects with filled colours Prolnout barvy: vyberte alespoň tři objekty s barevnou výplní
@msg Combine Slices: the slices have no bounds Spojit řezy: řezy nemají hranice
Can't render this file because it contains an unexpected character in line 2 and column 73.
+6
View File
@@ -1388,6 +1388,12 @@ digits Malayalam Malayalam
Axis Akse
ICF Top/Right ICF øverst/højre
ICF Bottom/Left ICF nederst/venstre
Show Font Height Options Vis indstillinger for skrifthøjde
Hide Font Height Options Skjul indstillinger for skrifthøjde
Set Font Height Reference Angiv reference for skrifthøjde
Em Box Em-felt
x-Height x-højde
ICF Box ICF-felt
Copy Color Value (Hex) Kopiér farveværdi (hex)
Create New Swatch… Opret ny farveprøve…
Name 40% Navn 40%
Can't render this file because it contains an unexpected character in line 2 and column 73.
+8
View File
@@ -3821,6 +3821,7 @@ theme Appearance Erscheinungsbild
@msg the file's layers weren't read ({_1}): it comes in as its page, in one layer die Ebenen der Datei wurden nicht gelesen ({_1}): sie wird als ihre Seite in einer Ebene geöffnet
@msg the file's layers weren't read from its editing data ({_1}): they come from its PDF part die Ebenen der Datei wurden nicht aus ihren Bearbeitungsdaten gelesen ({_1}): sie stammen aus ihrem PDF-Teil
@msg `to` must be "corner" or "smooth" `to` muss "corner" oder "smooth" sein
@msg `fontHeightReference` must be "emBox", "capHeight", "xHeight" or "icfBox" `fontHeightReference` muss "emBox", "capHeight", "xHeight" oder "icfBox" sein
@msg BMP files are RGB or grayscale: colorModel rgb or gray BMP-Dateien sind RGB oder Graustufen: colorModel rgb oder gray
@msg Blend colors: select at least three objects with filled colours Farben angleichen: Wählen Sie mindestens drei Objekte mit Flächenfarbe aus
@msg Combine Slices: the slices have no bounds Slices kombinieren: Die Slices haben keine Begrenzung
@@ -4555,6 +4556,13 @@ theme Appearance Erscheinungsbild
Insert Inline Symbol Inline-Symbol einfügen
ICF Top/Right ICF oben/rechts
ICF Bottom/Left ICF unten/links
Show Font Height Options Optionen für Schrifthöhe einblenden
Hide Font Height Options Optionen für Schrifthöhe ausblenden
Set Font Height Reference Bezugsgröße der Schrifthöhe festlegen
Em Box Geviert
Cap Height Versalhöhe
x-Height x-Höhe
ICF Box ICF-Rahmen
@msg `id` is the step reference {r}, which only `run` and `command_batch` resolve `id` ist der Schrittverweis {r}, den nur `run` und `command_batch` auflösen
@msg `ids` must be an array of object ids, not {v} `ids` muss ein Array von Objekt-IDs sein, nicht {v}
@msg `corners` must be an array of anchor indices, not {v} `corners` muss ein Array von Ankerpunkt-Indizes sein, nicht {v}
Can't render this file because it contains an unexpected character in line 2 and column 73.
+7
View File
@@ -2123,6 +2123,7 @@ axis Both Και τα δύο
@msg the art on them differs from the page's in {_1}% of it: they come from its editing data all the same Τα γραφικά τους διαφέρουν από εκείνα της σελίδας κατά {_1}%, αλλά θα χρησιμοποιηθούν τα δεδομένα επεξεργασίας
@msg `Custom` stands for changed settings: save the preset under another name Το `Custom` αντιστοιχεί σε αλλαγμένες ρυθμίσεις: αποθηκεύστε την προεπιλογή με άλλο όνομα
@msg `to` must be "corner" or "smooth" Το `to` πρέπει να είναι "corner" ή "smooth"
@msg `fontHeightReference` must be "emBox", "capHeight", "xHeight" or "icfBox" Το `fontHeightReference` πρέπει να είναι "emBox", "capHeight", "xHeight" ή "icfBox"
@msg BMP files are RGB or grayscale: colorModel rgb or gray Τα αρχεία BMP πρέπει να είναι RGB ή σε κλίμακα του γκρι: colorModel rgb ή gray
@msg Blend colors: select at least three objects with filled colours Ανάμειξη χρωμάτων: επιλέξτε τουλάχιστον τρία αντικείμενα με χρώμα γεμίσματος
@msg Combine Slices: the slices have no bounds Συνδυασμός τομών: οι τομές δεν έχουν όρια
@@ -4580,6 +4581,12 @@ digits Malayalam Μαλαγιάλαμ
Insert Inline Symbol Εισαγωγή ενσωματωμένου συμβόλου
ICF Top/Right ICF επάνω/δεξιά
ICF Bottom/Left ICF κάτω/αριστερά
Show Font Height Options Εμφάνιση επιλογών ύψους γραμματοσειράς
Hide Font Height Options Απόκρυψη επιλογών ύψους γραμματοσειράς
Set Font Height Reference Ορισμός αναφοράς ύψους γραμματοσειράς
Em Box Τετράγωνο em
x-Height Ύψος x
ICF Box Πλαίσιο ICF
@msg `id` is the step reference {r}, which only `run` and `command_batch` resolve το `id` είναι η αναφορά βήματος {r}, την οποία αναλύουν μόνο τα `run` και `command_batch`
@msg `ids` must be an array of object ids, not {v} το `ids` πρέπει να είναι πίνακας αναγνωριστικών αντικειμένων, όχι {v}
@msg `corners` must be an array of anchor indices, not {v} το `corners` πρέπει να είναι πίνακας δεικτών αγκύρωσης, όχι {v}
Can't render this file because it contains an unexpected character in line 164 and column 5.
+7
View File
@@ -949,6 +949,12 @@ font-technology Variable Variables
Em Box Top/Right Arriba/derecha del cuadro eme
ICF Top/Right Arriba/derecha del ICF
ICF Bottom/Left Abajo/izquierda del ICF
Show Font Height Options Mostrar opciones de altura de fuente
Hide Font Height Options Ocultar opciones de altura de fuente
Set Font Height Reference Definir la referencia de altura de fuente
Em Box Cuadro eme
x-Height Altura de la x
ICF Box Cuadro ICF
Em Dash Raya
Em Space Espacio eme
Embed Incrustar
@@ -3901,6 +3907,7 @@ axis Both Ambos
@msg the file's layers weren't read ({_1}): it comes in as its page, in one layer no se leyeron las capas del archivo ({_1}): se abre como su página, en una sola capa
@msg the file's layers weren't read from its editing data ({_1}): they come from its PDF part no se leyeron las capas del archivo desde sus datos de edición ({_1}): proceden de su parte PDF
@msg `to` must be "corner" or "smooth" `to` debe ser "corner" o "smooth"
@msg `fontHeightReference` must be "emBox", "capHeight", "xHeight" or "icfBox" `fontHeightReference` debe ser "emBox", "capHeight", "xHeight" o "icfBox"
@msg BMP files are RGB or grayscale: colorModel rgb or gray los archivos BMP son RGB o en escala de grises: colorModel rgb o gray
@msg Blend colors: select at least three objects with filled colours Fusionar colores: selecciona al menos tres objetos con relleno de color
@msg Combine Slices: the slices have no bounds Combinar sectores: los sectores no tienen límites
Can't render this file because it contains an unexpected character in line 2 and column 73.
+7
View File
@@ -1451,6 +1451,12 @@ digits Malayalam Malajalam
Axis Akseli
ICF Top/Right ICF ylä/oikea
ICF Bottom/Left ICF ala/vasen
Show Font Height Options Näytä fontin korkeuden asetukset
Hide Font Height Options Piilota fontin korkeuden asetukset
Set Font Height Reference Aseta fontin korkeuden viite
Em Box Em-laatikko
x-Height x-korkeus
ICF Box ICF-laatikko
Copy Color Value (Hex) Kopioi väriarvo (heksa)
Create New Swatch… Luo uusi värimalli…
Name 40% Nimi 40%
@@ -4123,6 +4129,7 @@ imageTrace Outline Ääriviiva
@msg the art on them differs from the page's in {_1}% of it: they come from its editing data all the same niiden kuvitus eroaa sivun kuvituksesta {_1} %:n osalta: ne tuodaan silti sen muokkaustiedoista
@msg `Custom` stands for changed settings: save the preset under another name `Custom` tarkoittaa muutettuja asetuksia: tallenna esiasetus toisella nimellä
@msg `to` must be "corner" or "smooth" `to` on oltava "corner" tai "smooth"
@msg `fontHeightReference` must be "emBox", "capHeight", "xHeight" or "icfBox" `fontHeightReference` on oltava "emBox", "capHeight", "xHeight" tai "icfBox"
@msg BMP files are RGB or grayscale: colorModel rgb or gray BMP-tiedostot ovat RGB- tai harmaasävykuvia: colorModel rgb tai gray
@msg Blend colors: select at least three objects with filled colours Sulauta värit: valitse vähintään kolme objektia, joilla on väritäyttö
@msg Combine Slices: the slices have no bounds Yhdistä viipaleet: viipaleilla ei ole rajoja
Can't render this file because it contains an unexpected character in line 2 and column 73.
+7
View File
@@ -3897,6 +3897,7 @@ axis Both Les deux
@msg the file's layers weren't read ({_1}): it comes in as its page, in one layer les calques du fichier n’ont pas été lus ({_1}) : il est ouvert tel que sa page, sur un seul calque
@msg the file's layers weren't read from its editing data ({_1}): they come from its PDF part les calques du fichier n’ont pas été lus depuis ses données de modification ({_1}) : ils proviennent de sa partie PDF
@msg `to` must be "corner" or "smooth" `to` doit être "corner" ou "smooth"
@msg `fontHeightReference` must be "emBox", "capHeight", "xHeight" or "icfBox" `fontHeightReference` doit être "emBox", "capHeight", "xHeight" ou "icfBox"
@msg BMP files are RGB or grayscale: colorModel rgb or gray Les fichiers BMP sont en RVB ou en niveaux de gris : colorModel rgb ou gray
@msg Blend colors: select at least three objects with filled colours Fusion des couleurs : sélectionnez au moins trois objets dotés d’un fond de couleur
@msg Combine Slices: the slices have no bounds Combiner les tranches : les tranches n’ont pas de limites
@@ -4628,6 +4629,12 @@ axis Both Les deux
Free Transform Transformation manuelle
Perspective Distort Distorsion de perspective
Insert Inline Symbol Insérer un symbole dans le texte
Show Font Height Options Afficher les options de hauteur de police
Hide Font Height Options Masquer les options de hauteur de police
Set Font Height Reference Définir la référence de hauteur de police
Em Box Cadratin
x-Height Hauteur d’x
ICF Box Cadre ICF
ICF Top/Right ICF haut/droite
ICF Bottom/Left ICF bas/gauche
@msg `id` is the step reference {r}, which only `run` and `command_batch` resolve `id` est la référence d’étape {r}, que seuls `run` et `command_batch` résolvent
Can't render this file because it contains an unexpected character in line 2 and column 73.
+7
View File
@@ -1937,6 +1937,12 @@ file-filter Templates Sablonok
Hyphenate Capitalized Words Nagybetűs szavak elválasztása
ICC Profile ICC-profil
ICF Bottom/Left ICF alja/bal oldala
Show Font Height Options Betűmagasság-beállítások megjelenítése
Hide Font Height Options Betűmagasság-beállítások elrejtése
Set Font Height Reference Betűmagasság viszonyítási alapjának beállítása
Em Box Kvirtmező
x-Height x-magasság
ICF Box ICF-mező
ICF Top/Right ICF teteje/jobb oldala
Identify Objects By Objektumok azonosítása
Ignore Kihagy
@@ -4518,6 +4524,7 @@ imageTrace Outline Körvonal
@msg `stops` must be an array `stops`: tömb kell
@msg `svg` must be an object of SVG options (see document.formats) `svg`: SVG-beállításokat tartalmazó objektum kell (lásd: document.formats)
@msg `to` must be "corner" or "smooth" `to`: "corner" vagy "smooth" kell
@msg `fontHeightReference` must be "emBox", "capHeight", "xHeight" or "icfBox" `fontHeightReference`: "emBox", "capHeight", "xHeight" vagy "icfBox" kell
@msg `{_1}` is no save command nem mentési parancs: `{_1}`
@msg `{area}` is no recovery area name nem helyreállítási terület neve: `{area}`
@msg `{color}` is not a colour (#rrggbb or a preset name) nem szín: `{color}` (#rrggbb vagy előbeállítás neve)
Can't render this file because it contains an unexpected character in line 2 and column 73.
+6
View File
@@ -1388,6 +1388,12 @@ digits Malayalam Malayalam
Axis Sumbu
ICF Top/Right ICF atas/kanan
ICF Bottom/Left ICF bawah/kiri
Show Font Height Options Tampilkan opsi tinggi font
Hide Font Height Options Sembunyikan opsi tinggi font
Set Font Height Reference Atur acuan tinggi font
Em Box Em Box
x-Height Tinggi x
ICF Box Kotak ICF
Copy Color Value (Hex) Salin nilai warna (Hex)
Create New Swatch… Buat swatch baru…
Name 40% Nama 40%
1 # Indonesian (id) catalog. Same format as zh-hant.tsv: context <TAB> source <TAB> translation
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
+8
View File
@@ -949,6 +949,13 @@ font-technology Variable Variabili
Em Box Top/Right Riquadro em in alto/a destra
ICF Top/Right ICF in alto/a destra
ICF Bottom/Left ICF in basso/a sinistra
Show Font Height Options Mostra opzioni altezza font
Hide Font Height Options Nascondi opzioni altezza font
Set Font Height Reference Imposta riferimento altezza font
Em Box Riquadro em
Cap Height Altezza maiuscole
x-Height Altezza x
ICF Box Riquadro ICF
Em Dash Lineetta
Em Space Spazio em
Embed Incorpora
@@ -3941,6 +3948,7 @@ axis Both Entrambi
@msg the file's layers weren't read ({_1}): it comes in as its page, in one layer i livelli del file non sono stati letti ({_1}): si apre come la sua pagina, in un solo livello
@msg the file's layers weren't read from its editing data ({_1}): they come from its PDF part i livelli del file non sono stati letti dai suoi dati di modifica ({_1}): provengono dalla sua parte PDF
@msg `to` must be "corner" or "smooth" `to` deve essere "corner" o "smooth"
@msg `fontHeightReference` must be "emBox", "capHeight", "xHeight" or "icfBox" `fontHeightReference` deve essere "emBox", "capHeight", "xHeight" o "icfBox"
@msg BMP files are RGB or grayscale: colorModel rgb or gray I file BMP sono RGB o in scala di grigi: colorModel rgb o gray
@msg Blend colors: select at least three objects with filled colours Fondi colori: seleziona almeno tre oggetti con riempimento colore
@msg Combine Slices: the slices have no bounds Combina sezioni: le sezioni non hanno limiti
Can't render this file because it contains an unexpected character in line 2 and column 73.
+8
View File
@@ -42,6 +42,13 @@ font-technology Variable バリアブル
Em Box Top/Right 仮想ボディの上 / 右
ICF Top/Right 平均字面の上 / 右
ICF Bottom/Left 平均字面の下 / 左
Show Font Height Options フォントの高さオプションを表示
Hide Font Height Options フォントの高さオプションを隠す
Set Font Height Reference フォントの高さの基準を設定
Em Box 仮想ボディ
Cap Height キャップハイト
x-Height x ハイト
ICF Box 平均字面
Proportional Metrics プロポーショナルメトリクス
Character Direction 文字の方向
Digits 数字
@@ -3983,6 +3990,7 @@ swatch Bright Violet ビビッドバイオレット
@msg Saving {_1} {_1} を保存中
@msg Sent to {_1} {_1} に送信しました
@msg `to` must be "corner" or "smooth" `to` には "corner" または "smooth" を指定してください
@msg `fontHeightReference` must be "emBox", "capHeight", "xHeight" or "icfBox" `fontHeightReference` には "emBox"、"capHeight"、"xHeight"、"icfBox" のいずれかを指定してください
@msg a layer couldn't be read 読み込めなかったレイヤーがあります
@msg it has more than one artboard アートボードが複数あります
@msg its editing data has no layers 編集データにレイヤーがありません
Can't render this file because it contains an unexpected character in line 2 and column 73.
+7
View File
@@ -1305,6 +1305,12 @@ digits Malayalam მალაიალამური
Axis ღერძი
ICF Top/Right ICF ზედა/მარჯვენა
ICF Bottom/Left ICF ქვედა/მარცხენა
Show Font Height Options შრიფტის სიმაღლის პარამეტრების ჩვენება
Hide Font Height Options შრიფტის სიმაღლის პარამეტრების დამალვა
Set Font Height Reference შრიფტის სიმაღლის საზომის დაყენება
Em Box Em-ბლოკი
x-Height x-სიმაღლე
ICF Box ICF-ბლოკი
Copy Color Value (Hex) ფერის მნიშვნელობის კოპირება (Hex)
Create New Swatch… ახალი ფერის ნიმუშის შექმნა…
Name 40% სახელი 40%
@@ -3692,6 +3698,7 @@ font-technology Variable ცვლადი
@msg the art on them differs from the page's in {_1}% of it მათზე არსებული ნახატი გვერდისას {_1}%-ით განსხვავდება
@msg `Custom` stands for changed settings: save the preset under another name `Custom` შეცვლილ პარამეტრებს ნიშნავს: შეინახეთ პრესეტი სხვა სახელით
@msg `to` must be "corner" or "smooth" `to` უნდა იყოს "corner" ან "smooth"
@msg `fontHeightReference` must be "emBox", "capHeight", "xHeight" or "icfBox" `fontHeightReference` უნდა იყოს "emBox", "capHeight", "xHeight" ან "icfBox"
@msg BMP files are RGB or grayscale: colorModel rgb or gray BMP ფაილები RGB-ა ან ნაცრისფერი: colorModel rgb ან gray
@msg Blend colors: select at least three objects with filled colours ფერების შერევა: მონიშნეთ მინიმუმ სამი ობიექტი ფერადი შევსებით
@msg Combine Slices: the slices have no bounds ნაჭრების გაერთიანება: ნაჭრებს საზღვრები არ აქვს
Can't render this file because it is too large.
+8
View File
@@ -36,6 +36,13 @@ font-technology Variable 가변
Em Box Top/Right 엠 박스 위/오른쪽
ICF Top/Right ICF 위/오른쪽
ICF Bottom/Left ICF 아래/왼쪽
Show Font Height Options 글꼴 높이 옵션 표시
Hide Font Height Options 글꼴 높이 옵션 숨기기
Set Font Height Reference 글꼴 높이 기준 설정
Em Box 엠 박스
Cap Height 대문자 높이
x-Height x 높이
ICF Box ICF 박스
Proportional Metrics 비례 메트릭
Character Direction 문자 방향
Digits 숫자
@@ -3910,6 +3917,7 @@ theme Appearance 외관
@msg Saving {_1} {_1} 저장 중
@msg Sent to {_1} {_1}(으)로 보냈습니다
@msg `to` must be "corner" or "smooth" `to`는 "corner" 또는 "smooth"여야 합니다
@msg `fontHeightReference` must be "emBox", "capHeight", "xHeight" or "icfBox" `fontHeightReference`는 "emBox", "capHeight", "xHeight" 또는 "icfBox"여야 합니다
@msg a layer couldn't be read 레이어를 읽을 수 없습니다
@msg it has more than one artboard 아트보드가 둘 이상입니다
@msg its editing data has no layers 편집 데이터에 레이어가 없습니다
Can't render this file because it contains an unexpected character in line 2 and column 73.
+6
View File
@@ -1402,6 +1402,12 @@ digits Malayalam Malayalam
Axis As
ICF Top/Right ICF boven/rechts
ICF Bottom/Left ICF onder/links
Show Font Height Options Opties voor letterhoogte tonen
Hide Font Height Options Opties voor letterhoogte verbergen
Set Font Height Reference Referentie voor letterhoogte instellen
Em Box Em-vak
x-Height x-hoogte
ICF Box ICF-vak
Copy Color Value (Hex) Kleurwaarde kopiëren (Hex)
Create New Swatch… Nieuw staal maken…
Name 40% Naam 40%
Can't render this file because it contains an unexpected character in line 2 and column 73.
+7
View File
@@ -1391,6 +1391,12 @@ digits Malayalam Malajalam
Axis Oś
ICF Top/Right ICF góra/prawo
ICF Bottom/Left ICF dół/lewo
Show Font Height Options Pokaż opcje wysokości czcionki
Hide Font Height Options Ukryj opcje wysokości czcionki
Set Font Height Reference Ustaw odniesienie wysokości czcionki
Em Box Ramka firetowa
x-Height Wysokość x
ICF Box Ramka ICF
Copy Color Value (Hex) Kopiuj wartość koloru (hex)
Create New Swatch… Utwórz nową próbkę…
Name 40% Nazwa 40%
@@ -4032,6 +4038,7 @@ imageTrace Outline Kontur
@msg the file's layers weren't read ({_1}): it comes in as its page, in one layer nie odczytano warstw pliku ({_1}): plik jest wczytywany jako jego strona, na jednej warstwie
@msg the file's layers weren't read from its editing data ({_1}): they come from its PDF part nie odczytano warstw pliku z jego danych edycyjnych ({_1}): pochodzą z jego części PDF
@msg `to` must be "corner" or "smooth" `to` musi mieć wartość "corner" lub "smooth"
@msg `fontHeightReference` must be "emBox", "capHeight", "xHeight" or "icfBox" `fontHeightReference` musi mieć wartość "emBox", "capHeight", "xHeight" lub "icfBox"
@msg BMP files are RGB or grayscale: colorModel rgb or gray Pliki BMP są w RGB lub skali szarości: colorModel rgb lub gray
@msg Blend colors: select at least three objects with filled colours Przejście kolorów: zaznacz co najmniej trzy obiekty z wypełnieniem kolorem
@msg Combine Slices: the slices have no bounds Połącz plasterki: plasterki nie mają granic
Can't render this file because it contains an unexpected character in line 2 and column 73.
+7
View File
@@ -840,6 +840,12 @@
Em Box Top/Right Topo/Direita da Caixa Eme
ICF Top/Right Topo/Direita do ICF
ICF Bottom/Left Base/Esquerda do ICF
Show Font Height Options Mostrar Opções de Altura da Fonte
Hide Font Height Options Ocultar Opções de Altura da Fonte
Set Font Height Reference Definir Referência de Altura da Fonte
Em Box Caixa Eme
x-Height Altura do x
ICF Box Caixa ICF
Em Dash Travessão
Em Space Espaço Em
Embed Incorporar
@@ -3485,6 +3491,7 @@ axis Both Ambos
@msg the file's layers weren't read ({_1}): it comes in as its page, in one layer as camadas do arquivo não foram lidas ({_1}): ele entra como sua página, em uma só camada
@msg the file's layers weren't read from its editing data ({_1}): they come from its PDF part as camadas do arquivo não foram lidas dos seus dados de edição ({_1}): elas vêm da sua parte PDF
@msg `to` must be "corner" or "smooth" `to` deve ser "corner" ou "smooth"
@msg `fontHeightReference` must be "emBox", "capHeight", "xHeight" or "icfBox" `fontHeightReference` deve ser "emBox", "capHeight", "xHeight" ou "icfBox"
@msg BMP files are RGB or grayscale: colorModel rgb or gray arquivos BMP são RGB ou em tons de cinza: colorModel rgb ou gray
@msg Blend colors: select at least three objects with filled colours Mesclar cores: selecione pelo menos três objetos com preenchimento de cor
@msg Combine Slices: the slices have no bounds Combinar fatias: as fatias não têm limites
Can't render this file because it contains an unexpected character in line 6 and column 47.
+7
View File
@@ -947,6 +947,12 @@ font-technology Variable Вариативные
Em Box Top/Right Верх/право кегельной площадки
ICF Top/Right Верх/право ICF
ICF Bottom/Left Низ/лево ICF
Show Font Height Options Показать параметры высоты шрифта
Hide Font Height Options Скрыть параметры высоты шрифта
Set Font Height Reference Задать опорную высоту шрифта
Em Box Кегельная площадка
x-Height Высота строчных
ICF Box Рамка ICF
Em Dash Длинное тире
Em Space Круглая шпация
Embed Внедрить
@@ -3937,6 +3943,7 @@ axis Both Оба
@msg the file's layers weren't read ({_1}): it comes in as its page, in one layer слои файла не прочитаны ({_1}): он открывается как его страница, на одном слое
@msg the file's layers weren't read from its editing data ({_1}): they come from its PDF part слои файла не прочитаны из его данных для редактирования ({_1}): они взяты из его PDF-части
@msg `to` must be "corner" or "smooth" `to` должно быть "corner" или "smooth"
@msg `fontHeightReference` must be "emBox", "capHeight", "xHeight" or "icfBox" `fontHeightReference` должно быть "emBox", "capHeight", "xHeight" или "icfBox"
@msg BMP files are RGB or grayscale: colorModel rgb or gray файлы BMP бывают RGB или в оттенках серого: colorModel rgb или gray
@msg Blend colors: select at least three objects with filled colours Смешивание цветов: выберите не менее трёх объектов с цветными заливками
@msg Combine Slices: the slices have no bounds Объединение фрагментов: у фрагментов нет границ
Can't render this file because it contains an unexpected character in line 2 and column 73.
+7
View File
@@ -3818,6 +3818,7 @@ axis Both Båda
@msg the art on them differs from the page's in {_1}% of it: they come from its editing data all the same grafiken på dem skiljer sig från sidans i {_1} % av ytan: de hämtas ändå från dess redigeringsdata
@msg `Custom` stands for changed settings: save the preset under another name `Custom` står för ändrade inställningar: spara förinställningen med ett annat namn
@msg `to` must be "corner" or "smooth" `to` måste vara "corner" eller "smooth"
@msg `fontHeightReference` must be "emBox", "capHeight", "xHeight" or "icfBox" `fontHeightReference` måste vara "emBox", "capHeight", "xHeight" eller "icfBox"
@msg BMP files are RGB or grayscale: colorModel rgb or gray BMP-filer är RGB eller gråskala: colorModel rgb eller gray
@msg Blend colors: select at least three objects with filled colours Blanda färger: markera minst tre objekt med färgfyllning
@msg Combine Slices: the slices have no bounds Kombinera skivor: skivorna har inga gränser
@@ -4537,6 +4538,12 @@ axis Both Båda
Insert Inline Symbol Infoga symbol i texten
ICF Top/Right ICF överkant/höger
ICF Bottom/Left ICF nederkant/vänster
Show Font Height Options Visa alternativ för teckenhöjd
Hide Font Height Options Dölj alternativ för teckenhöjd
Set Font Height Reference Ange referens för teckenhöjd
Em Box Em-ruta
x-Height x-höjd
ICF Box ICF-ruta
@msg `id` is the step reference {r}, which only `run` and `command_batch` resolve `id` är stegreferensen {r}, som bara `run` och `command_batch` löser upp
@msg `ids` must be an array of object ids, not {v} `ids` måste vara en array med objekt-id:n, inte {v}
@msg `corners` must be an array of anchor indices, not {v} `corners` måste vara en array med index för ankarpunkter, inte {v}
Can't render this file because it contains an unexpected character in line 5 and column 25.
+11
View File
@@ -144,6 +144,17 @@ fn catalan_reads_as_catalan() {
assert_eq!(message(ca, nested), "l’ordre `object.group` no està disponible ara mateix: no hi ha res seleccionat");
}
#[test]
fn font_height_reference_labels_are_in_complete_catalogs() {
for lang in LANGUAGES.iter().filter(|lang| lang.complete_menus) {
for label in
["Show Font Height Options", "Hide Font Height Options", "Set Font Height Reference", "Em Box", "Cap Height", "x-Height", "ICF Box"]
{
assert!(lang.catalog().plain(label).is_some(), "{} is missing {label}", lang.code);
}
}
}
/// Each language counts with its own plural rule: the form a count picks (`one`, then `few`, then
/// `other`/`many`).
#[test]
+7
View File
@@ -3703,6 +3703,7 @@ axis Both Her İkisi
@msg the file's layers weren't read ({_1}): it comes in as its page, in one layer dosyanın katmanları okunmadı ({_1}): tek katmanda kendi sayfası olarak geliyor
@msg the file's layers weren't read from its editing data ({_1}): they come from its PDF part dosyanın katmanları düzenleme verilerinden okunmadı ({_1}): PDF kısmından geliyorlar
@msg `to` must be "corner" or "smooth" "to" "köşe" veya "pürüzsüz" olmalıdır
@msg `fontHeightReference` must be "emBox", "capHeight", "xHeight" or "icfBox" `fontHeightReference` "emBox", "capHeight", "xHeight" veya "icfBox" olmalıdır
@msg BMP files are RGB or grayscale: colorModel rgb or gray BMP dosyaları RGB veya gri tonlamalıdır: colorModel rgb veya gri
@msg Blend colors: select at least three objects with filled colours Renkleri karıştır: dolgulu renklere sahip en az üç nesne seçin
@msg Combine Slices: the slices have no bounds Dilimleri Birleştir: dilimlerin sınırları yoktur
@@ -4380,6 +4381,12 @@ axis Both Her İkisi
Insert Inline Symbol Satır İçi Sembol Ekle
ICF Top/Right ICF Üst/Sağ
ICF Bottom/Left ICF Alt/Sol
Show Font Height Options Yazı Tipi Yüksekliği Seçeneklerini Göster
Hide Font Height Options Yazı Tipi Yüksekliği Seçeneklerini Gizle
Set Font Height Reference Yazı Tipi Yüksekliği Referansını Ayarla
Em Box Em Kutusu
x-Height x-Yüksekliği
ICF Box ICF Kutusu
@msg `id` is the step reference {r}, which only `run` and `command_batch` resolve "id", yalnızca "run" ve "command_batch" çözümleyen {r} adım referansıdır
@msg `ids` must be an array of object ids, not {v} "kimlikler" {v} değil, bir nesne kimlikleri dizisi olmalıdır
@msg `corners` must be an array of anchor indices, not {v} "köşeler" {v} değil, bir bağlantı indeksleri dizisi olmalıdır
Can't render this file because it contains an unexpected character in line 5 and column 25.
+8
View File
@@ -943,6 +943,13 @@ font-technology Variable Варіативні
Em Box Top/Right Верх/право кегельного майданчика
ICF Top/Right Верх/право ICF
ICF Bottom/Left Нижній/лівий край ICF
Show Font Height Options Показати параметри висоти шрифту
Hide Font Height Options Приховати параметри висоти шрифту
Set Font Height Reference Задати опорну висоту шрифту
Em Box Кегельний майданчик
Cap Height Висота великих літер
x-Height Висота малих літер
ICF Box Рамка ICF
Em Dash Довге тире
Em Space Кругла шпація
Embed Вбудувати
@@ -3925,6 +3932,7 @@ axis Both Обидві
@msg the file's layers weren't read ({_1}): it comes in as its page, in one layer шари файлу не прочитано ({_1}): він відкривається як його сторінка, на одному шарі
@msg the file's layers weren't read from its editing data ({_1}): they come from its PDF part шари файлу не прочитано з його даних для редагування ({_1}): їх взято з його PDF-частини
@msg `to` must be "corner" or "smooth" `to` має бути "corner" або "smooth"
@msg `fontHeightReference` must be "emBox", "capHeight", "xHeight" or "icfBox" `fontHeightReference` має бути "emBox", "capHeight", "xHeight" або "icfBox"
@msg BMP files are RGB or grayscale: colorModel rgb or gray Файли BMP бувають RGB або у відтінках сірого: colorModel rgb або gray
@msg Blend colors: select at least three objects with filled colours Змішування кольорів: виберіть не менше трьох об’єктів із кольоровими заливками
@msg Combine Slices: the slices have no bounds Об’єднання фрагментів: фрагменти не мають кордонів
Can't render this file because it contains an unexpected character in line 178 and column 5.
+8
View File
@@ -1781,6 +1781,13 @@ digits Hindi Hindi
Hyphenate Capitalized Words Gạch nối từ viết hoa chữ đầu
ICC Profile Hồ sơ ICC
ICF Bottom/Left ICF Dưới/Trái
Show Font Height Options Hiện tùy chọn chiều cao phông chữ
Hide Font Height Options Ẩn tùy chọn chiều cao phông chữ
Set Font Height Reference Đặt tham chiếu chiều cao phông chữ
Em Box Ô em
Cap Height Chiều cao chữ hoa
x-Height Chiều cao chữ x
ICF Box Ô ICF
ICF Top/Right ICF Trên/Phải
Identify Objects By Nhận diện đối tượng theo
Ignore Bỏ qua
@@ -4169,6 +4176,7 @@ theme Appearance Giao diện
@msg `stops` must be an array `stops` phải là một mảng
@msg `svg` must be an object of SVG options (see document.formats) `svg` phải là đối tượng tùy chọn SVG (xem document.formats)
@msg `to` must be "corner" or "smooth" `to` phải là "corner" hoặc "smooth"
@msg `fontHeightReference` must be "emBox", "capHeight", "xHeight" or "icfBox" `fontHeightReference` phải là "emBox", "capHeight", "xHeight" hoặc "icfBox"
@msg `{_1}` is no save command `{_1}` không phải là lệnh lưu
@msg `{area}` is no recovery area name `{area}` không phải là tên vùng phục hồi
@msg `{color}` is not a colour (#rrggbb or a preset name) `{color}` không phải là màu (#rrggbb hoặc tên thiết lập sẵn)
Can't render this file because it contains an unexpected character in line 200 and column 5.
+8
View File
@@ -889,6 +889,13 @@
Em Box Top/Right 全角字框上 / 右
ICF Top/Right ICF 上 / 右
ICF Bottom/Left ICF 下 / 左
Show Font Height Options 显示字体高度选项
Hide Font Height Options 隐藏字体高度选项
Set Font Height Reference 设置字体高度参考
Em Box 全角字框
Cap Height 大写字母高度
x-Height x 字高
ICF Box ICF 框
Em Dash 长破折号
Em Space 全角空格
Embed 嵌入
@@ -3720,6 +3727,7 @@ imageTrace Outline 轮廓
@msg Saving {_1} 正在保存 {_1}
@msg Sent to {_1} 已发送到 {_1}
@msg `to` must be "corner" or "smooth" `to` 必须是 "corner" 或 "smooth"
@msg `fontHeightReference` must be "emBox", "capHeight", "xHeight" or "icfBox" `fontHeightReference` 必须是 "emBox"、"capHeight"、"xHeight" 或 "icfBox"
@msg a layer couldn't be read 无法读取某个图层
@msg it has more than one artboard 它有多个画板
@msg its editing data has no layers 其编辑数据中没有图层
Can't render this file because it contains an unexpected character in line 2 and column 73.
+8
View File
@@ -911,6 +911,13 @@ font-technology Variable 可變
Em Box Top/Right 全形字框上 / 右
ICF Top/Right ICF 上 / 右
ICF Bottom/Left ICF 下 / 左
Show Font Height Options 顯示字體高度選項
Hide Font Height Options 隱藏字體高度選項
Set Font Height Reference 設定字體高度參考
Em Box 全形字框
Cap Height 大寫字母高度
x-Height x 字高
ICF Box ICF 框
Em Dash 長破折號
Em Space 全形空格
Embed 嵌入
@@ -1645,6 +1652,7 @@ file-filter Templates 範本
@msg Saving {_1} 正在儲存 {_1}
@msg Sent to {_1} 已傳送至 {_1}
@msg `to` must be "corner" or "smooth" `to` 必須是 "corner" 或 "smooth"
@msg `fontHeightReference` must be "emBox", "capHeight", "xHeight" or "icfBox" `fontHeightReference` 必須是 "emBox"、"capHeight"、"xHeight" 或 "icfBox"
@msg a layer couldn't be read 有一個圖層無法讀取
@msg it has more than one artboard 它有多個工作區域
@msg its editing data has no layers 其編輯資料中沒有圖層
Can't render this file because it contains an unexpected character in line 6 and column 47.
+66 -22
View File
@@ -91,20 +91,27 @@ pub(crate) fn shared_text_flag(app: &VectorcraftApp, f: impl Fn(&vectorcraft_doc
/// caret's style at a caret), else every run of the selected type, groups' included. `None` (a
/// blank field) where they differ, or with no type.
pub(crate) fn shared<T: PartialEq>(app: &VectorcraftApp, f: impl Fn(&CharStyle) -> T) -> Option<T> {
shared_styles(app, |_, _, style| Some(f(style)))
shared_styles(app, |_, _, style, _| Some(f(style)))
}
/// Dimensional controls use document units and tolerate arithmetic roundoff when comparing
/// compensated runs. Keep raw styles available to font/paragraph controls and style definitions,
/// even if a collapsed affine prevents editing dimensions.
fn shared_effective(app: &VectorcraftApp, f: impl Fn(&CharStyle) -> f64) -> Option<f64> {
shared_styles(app, |t, _, style| t.effective_char_style(style).map(|s| Metric(f(&s)))).map(|v| v.0)
shared_styles(app, |t, _, style, _| t.effective_char_style(style).map(|s| Metric(f(&s)))).map(|v| v.0)
}
/// The Leading shown: each character's own, or for Auto leading the share of its size its
/// paragraph's Justification › Auto Leading sets, as the layout uses.
/// The Leading shown: each character's own, or for Auto leading the share of the em size their
/// font height reference gives them ([`vectorcraft_text::FontDb::em_size`]) that their paragraph's
/// Justification › Auto Leading sets, as the layout uses. An inline graphic keeps its size.
fn shared_leading(app: &VectorcraftApp) -> Option<f64> {
shared_styles(app, |t, para, style| t.effective_char_style(style).map(|s| Metric(para.effective_leading(&s)))).map(|v| v.0)
let db = vectorcraft_text::FontDb::global();
shared_styles(app, |t, para, style, inline| {
let s = t.effective_char_style(style)?;
let em = if inline { s.size } else { db.em_size(&s, t.vertical) };
Some(Metric(para.effective_leading(&CharStyle { size: em, ..s })))
})
.map(|v| v.0)
}
pub(crate) struct Metric(pub(crate) f64);
@@ -115,11 +122,12 @@ impl PartialEq for Metric {
}
}
/// `f` of each character style in the selection, with its text object and the paragraph the
/// characters are in (a run that spans paragraphs counts once for each).
/// `f` of each character style in the selection, with its text object, the paragraph the
/// characters are in (a run that spans paragraphs counts once for each) and whether the run is an
/// inline graphic.
fn shared_styles<T: PartialEq>(
app: &VectorcraftApp,
f: impl Fn(&vectorcraft_doc::TextObject, &vectorcraft_doc::ParaStyle, &CharStyle) -> Option<T>,
f: impl Fn(&vectorcraft_doc::TextObject, &vectorcraft_doc::ParaStyle, &CharStyle, bool) -> Option<T>,
) -> Option<T> {
let st = app.session.active()?;
let text = |id: NodeId| match st.doc.node(id).map(|n| &n.kind) {
@@ -131,14 +139,14 @@ fn shared_styles<T: PartialEq>(
let t = text(id)?;
if b <= a {
let para = t.plain_text().get(..a).map_or(0, |s| s.bytes().filter(|&c| c == b'\n').count());
return f(t, t.para_at(para), &vectorcraft_text::edit::insertion_style(&t.runs, a, b));
return f(t, t.para_at(para), &vectorcraft_text::edit::insertion_style(&t.runs, a, b), false);
}
each_paragraph_run(t, Some((a, b)), |para, style| acc.add(f(t, para, style)));
each_paragraph_run(t, Some((a, b)), |para, style, inline| acc.add(f(t, para, style, inline)));
} else {
for n in st.selection.objects.iter().filter_map(|id| st.doc.node(*id)) {
n.walk(&mut |c| {
if let NodeKind::Text(t) = &c.kind {
each_paragraph_run(t, None, |para, style| acc.add(f(t, para, style)));
each_paragraph_run(t, None, |para, style, inline| acc.add(f(t, para, style, inline)));
}
});
}
@@ -147,13 +155,14 @@ fn shared_styles<T: PartialEq>(
}
/// `f` of the paragraph and style of each piece of `t`'s runs that lies in one paragraph (a
/// paragraph's closing line break belongs to it): the pieces within bytes `a..b`, or with no
/// range every run's, empty ones included. With no range, a final empty paragraph (the text ends
/// in a line break) counts too, with the style of the run before it, as the caret there types.
/// paragraph's closing line break belongs to it), and whether the run is an inline graphic: the
/// pieces within bytes `a..b`, or with no range every run's, empty ones included. With no range, a
/// final empty paragraph (the text ends in a line break) counts too, with the style of the run
/// before it, as the caret there types.
fn each_paragraph_run<'a>(
t: &'a vectorcraft_doc::TextObject,
range: Option<(usize, usize)>,
mut f: impl FnMut(&'a vectorcraft_doc::ParaStyle, &'a CharStyle),
mut f: impl FnMut(&'a vectorcraft_doc::ParaStyle, &'a CharStyle, bool),
) {
let (mut end, mut para) = (0, 0);
// The style of the last character and whether it is a line break: the paragraph after it has
@@ -162,7 +171,7 @@ fn each_paragraph_run<'a>(
for r in &t.runs {
if r.text.is_empty() {
if range.is_none_or(|(a, b)| a < end && end < b) {
f(t.para_at(para), &r.style);
f(t.para_at(para), &r.style, r.inline.is_some());
}
continue;
}
@@ -170,7 +179,7 @@ fn each_paragraph_run<'a>(
let start = end;
end += piece.len();
if range.is_none_or(|(a, b)| start < b && end > a) {
f(t.para_at(para), &r.style);
f(t.para_at(para), &r.style, r.inline.is_some());
}
para += usize::from(piece.ends_with('\n'));
last = Some((&r.style, piece.ends_with('\n')));
@@ -179,7 +188,7 @@ fn each_paragraph_run<'a>(
if range.is_none()
&& let Some((style, true)) = last
{
f(t.para_at(para), style);
f(t.para_at(para), style, false);
}
}
@@ -657,7 +666,8 @@ fn cell(ui: &mut Ui, label: &str, tip: &str, add: impl FnOnce(&mut Ui)) {
/// The Character panel's grid of numeric fields, `w` wide: Font Size, Leading, Kerning and
/// Tracking, then with `more` the scales, Baseline Shift and Character Rotation (`s`: the style
/// shown). The Properties panel shows its first rows.
/// shown). With Show Font Height Options on, a row under Font Size holds the Set Font Height
/// Reference dropdown. The Properties panel shows its first rows.
pub(crate) fn metrics_grid(app: &mut VectorcraftApp, ui: &mut Ui, id: &str, s: &CharStyle, w: f32, more: bool) {
let fw = ((w - 66.0) / 2.0).clamp(60.0, 100.0);
let unit = app.session.type_unit();
@@ -674,6 +684,10 @@ pub(crate) fn metrics_grid(app: &mut VectorcraftApp, ui: &mut Ui, id: &str, s: &
}
});
ui.end_row();
if pstate::<bool>(ui.ctx(), FONT_HEIGHT_OPTIONS) {
cell(ui, "T↕", tl!("Set Font Height Reference"), |ui| font_height_reference(app, ui, fw));
ui.end_row();
}
cell(ui, "VA", tl!("Kerning"), |ui| {
let mode = shared(app, |s| (s.optical_kerning, s.kerning));
let caret = text_editing(app).is_some_and(|(_, a, b)| a == b);
@@ -741,6 +755,30 @@ pub(crate) fn size_field(app: &mut VectorcraftApp, ui: &mut Ui, id: &str, width:
}
}
/// The panel state of the Character panel menu's Show Font Height Options.
const FONT_HEIGHT_OPTIONS: &str = "ch-font-height-options";
/// The font height references the Set Font Height Reference dropdown lists, with their
/// `text.setFormat` values.
const FONT_HEIGHT_REFERENCES: [(vectorcraft_doc::FontHeightReference, &str); 4] = [
(vectorcraft_doc::FontHeightReference::EmBox, "emBox"),
(vectorcraft_doc::FontHeightReference::CapHeight, "capHeight"),
(vectorcraft_doc::FontHeightReference::XHeight, "xHeight"),
(vectorcraft_doc::FontHeightReference::IcfBox, "icfBox"),
];
/// The Set Font Height Reference dropdown, `width` wide: the height of the font that the Font Size
/// measures, blank where the characters the panel acts on differ.
fn font_height_reference(app: &mut VectorcraftApp, ui: &mut Ui, width: f32) {
// The labels of [`FONT_HEIGHT_REFERENCES`], in its order.
let labels = [tl!("Em Box"), tl!("Cap Height"), tl!("x-Height"), tl!("ICF Box")];
let current = shared(app, |s| s.font_height_reference);
let shown = FONT_HEIGHT_REFERENCES.iter().zip(labels).find(|(r, _)| Some(r.0) == current).map_or("", |(_, l)| l);
if let Some((_, key)) = widgets::dropdown_names(ui, "ch-font-height", shown, &labels, width).and_then(|i| FONT_HEIGHT_REFERENCES.get(i)) {
format(app, json!({"fontHeightReference": key}));
}
}
/// The font family menu and the font style dropdown of style `s` (Character and Properties panels,
/// Control bar), with their `ids` and `widths`.
pub(crate) fn font_pickers(app: &mut VectorcraftApp, ui: &mut Ui, s: &CharStyle, ids: (&str, &str), widths: (f32, f32)) {
@@ -1065,6 +1103,11 @@ pub fn menu(app: &mut VectorcraftApp, ui: &mut Ui) {
if menu_item(ui, if glyph { tl!("Hide Snap to Glyph Options") } else { tl!("Show Snap to Glyph Options") }, true, false) {
set_pstate(ui.ctx(), SNAP_TO_GLYPH_OPTIONS, !glyph);
}
// Show Font Height Options shows the Set Font Height Reference dropdown under Font Size.
let heights: bool = pstate(ui.ctx(), FONT_HEIGHT_OPTIONS);
if menu_item(ui, if heights { tl!("Hide Font Height Options") } else { tl!("Show Font Height Options") }, true, false) {
set_pstate(ui.ctx(), FONT_HEIGHT_OPTIONS, !heights);
}
ui.separator();
let s = st.map(|x| x.0);
for (label, key, on) in [
@@ -1191,8 +1234,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 and the Default digits, whether or not Show East
/// Asian Options and Show Indic Options are on.
/// 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.
fn reset_panel(app: &mut VectorcraftApp) {
let d = CharStyle::default();
style(app, json!({"tracking": 0, "leading": "auto"}));
@@ -1203,7 +1247,7 @@ fn reset_panel(app: &mut VectorcraftApp) {
"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,
"standardVerticalRoman": d.standard_vertical_roman, "noBreak": d.no_break, "charDirection": "default",
"digits": d.digits
"digits": d.digits, "fontHeightReference": d.font_height_reference
}),
);
}
+144 -5
View File
@@ -89,6 +89,102 @@ fn scaled_character_fields_show_document_sizes_and_mixed_group_values() {
assert_eq!(shared_effective(&app, |s| s.h_scale), Some(100.0));
}
/// The Leading field shows the Auto leading the characters are set with, their paragraph's Auto
/// Leading share (120% by default) of their em size: 20 pt type measuring a 0.7 em cap height
/// is set at 20 / 0.7 pt. Paragraphs whose Auto leading differs show a blank field, and an
/// explicit leading shows as it is.
#[test]
fn leading_shows_the_auto_leading_of_the_em_size() {
use vectorcraft_text::test_fonts::{HEIGHT_CAP, HEIGHT_FAMILY, height_font};
vectorcraft_text::FontDb::global().add_font(height_font(true).unwrap());
let mut app = VectorcraftApp::new(Session::new(), Default::default());
app.run("file.new", json!({"width": 300, "height": 200})).unwrap();
let id = app.run("text.create", json!({"x": 20, "y": 50, "text": "Hx", "size": 20, "font": HEIGHT_FAMILY})).unwrap()["id"].clone();
app.run("select.set", json!({"ids": [id]})).unwrap();
let near = |l: Option<f64>, want: f64| l.is_some_and(|l| (l - want).abs() < 1e-9);
assert!(near(shared_leading(&app), 24.0), "{:?}", shared_leading(&app));
app.run("text.setFormat", json!({"fontHeightReference": "capHeight"})).unwrap();
let cap = f64::from(HEIGHT_CAP) / 1000.0;
assert!(near(shared_leading(&app), 1.2 * 20.0 / cap), "{:?}", shared_leading(&app));
app.run("text.setFormat", json!({"autoLeading": 150})).unwrap();
assert!(near(shared_leading(&app), 1.5 * 20.0 / cap), "{:?}", shared_leading(&app));
// The second paragraph ends in a two-byte character.
let two = app.run("text.create", json!({"x": 20, "y": 120, "text": "Hx\nHé", "size": 20, "font": HEIGHT_FAMILY})).unwrap()["id"].clone();
app.run("select.set", json!({"ids": [two]})).unwrap();
assert!(near(shared_leading(&app), 24.0), "{:?}", shared_leading(&app));
app.run("text.setFormat", json!({"id": two, "start": 3, "end": 6, "autoLeading": 150})).unwrap();
assert_eq!(shared_leading(&app), None, "the second paragraph's Auto leading is 30 pt");
app.run("select.set", json!({"ids": [id]})).unwrap();
app.run("text.setStyle", json!({"leading": 30})).unwrap();
assert!(near(shared_leading(&app), 30.0), "{:?}", shared_leading(&app));
}
/// An inline graphic keeps its size whatever the font height reference, so the Leading field
/// shows 120% of that size for it: blank for 20 pt Cap Height type with a graphic in it, and
/// 24 pt for the graphic alone.
#[test]
fn leading_of_an_inline_graphic_follows_its_own_size() {
use vectorcraft_text::test_fonts::{HEIGHT_FAMILY, height_font};
vectorcraft_text::FontDb::global().add_font(height_font(true).unwrap());
let mut app = VectorcraftApp::new(Session::new(), Default::default());
app.run("file.new", json!({"width": 300, "height": 200})).unwrap();
app.run("shape.ellipse", json!({"x": 200, "y": 150, "width": 10, "height": 10})).unwrap();
app.run("symbol.new", json!({"name": "Dot"})).unwrap();
app.run("edit.clear", json!({})).unwrap();
app.session.select_tool("type", Default::default()).unwrap();
let id = app.run("text.create", json!({"x": 20, "y": 50, "text": "Hx", "size": 20, "font": HEIGHT_FAMILY})).unwrap()["id"].clone();
app.session.apply_actions(vec![vectorcraft_tools::Action::Notify("text.editNew".into())]).unwrap();
app.run("text.insertInline", json!({"id": id, "at": 2, "symbol": "Dot"})).unwrap();
app.run("text.setRangeStyle", json!({"id": id, "start": 0, "end": 5, "fontHeightReference": "capHeight"})).unwrap();
app.session.set_tool_option("select", &json!({"start": 0, "end": 5}));
assert_eq!(shared_leading(&app), None, "the type is set with 34.3 pt Auto leading and the graphic with 24 pt");
app.session.set_tool_option("select", &json!({"start": 2, "end": 5}));
assert_eq!(text_editing(&app).map(|(_, a, b)| (a, b)), Some((2, 5)));
let shown = shared_leading(&app);
assert!(shown.is_some_and(|l| (l - 24.0).abs() < 1e-9), "{shown:?}");
}
/// A selected text object that ends in a line break has an empty last paragraph, which the
/// Leading field takes in; a Type tool selection of all the text does not.
#[test]
fn leading_of_a_whole_object_takes_in_the_empty_last_paragraph() {
let mut app = VectorcraftApp::new(Session::new(), Default::default());
app.run("file.new", json!({"width": 300, "height": 200})).unwrap();
let id = app.run("text.create", json!({"x": 20, "y": 50, "text": "Ab\n", "size": 20})).unwrap()["id"].clone();
app.run("select.set", json!({"ids": [id]})).unwrap();
let near = |l: Option<f64>, want: f64| l.is_some_and(|l| (l - want).abs() < 1e-9);
assert!(near(shared_leading(&app), 24.0), "{:?}", shared_leading(&app));
app.run("text.setFormat", json!({"id": id, "start": 3, "end": 3, "autoLeading": 150})).unwrap();
assert_eq!(shared_leading(&app), None, "the empty last paragraph's Auto leading is 30 pt");
app.session.select_tool("type", Default::default()).unwrap();
app.session.apply_actions(vec![vectorcraft_tools::Action::Notify("text.editNew".into())]).unwrap();
app.session.set_tool_option("select", &json!({"start": 0, "end": 3}));
assert_eq!(text_editing(&app).map(|(_, a, b)| (a, b)), Some((0, 3)));
assert!(near(shared_leading(&app), 24.0), "a line break belongs to the paragraph it ends: {:?}", shared_leading(&app));
}
/// With the Type tool, the Leading field shows the Auto leading of the paragraphs the selection
/// covers (a line break belongs to the paragraph it ends), and at a caret that of the caret's
/// paragraph.
#[test]
fn leading_follows_the_paragraphs_the_type_tool_selection_covers() {
let mut app = VectorcraftApp::new(Session::new(), Default::default());
app.run("file.new", json!({"width": 300, "height": 200})).unwrap();
app.session.select_tool("type", Default::default()).unwrap();
// é is two bytes, 4..6.
let id = app.run("text.create", json!({"x": 20, "y": 50, "text": "Ab\nCé", "size": 20})).unwrap()["id"].clone();
app.session.apply_actions(vec![vectorcraft_tools::Action::Notify("text.editNew".into())]).unwrap();
app.run("text.setFormat", json!({"id": id, "start": 3, "end": 6, "autoLeading": 150})).unwrap();
let ranges = [((0, 2), Some(24.0)), ((0, 3), Some(24.0)), ((3, 6), Some(30.0)), ((1, 4), None), ((1, 6), None), ((4, 4), Some(30.0))];
for ((start, end), want) in ranges {
app.session.set_tool_option("select", &json!({"start": start, "end": end}));
assert_eq!(text_editing(&app).map(|(_, a, b)| (a, b)), Some((start, end)));
let shown = shared_leading(&app);
assert_eq!(shown.is_some(), want.is_some(), "{start}..{end}: {shown:?}");
assert!(shown.zip(want).is_none_or(|(l, w)| (l - w).abs() < 1e-9), "{start}..{end}: {shown:?}");
}
}
#[test]
fn scaled_range_fields_follow_the_type_tool_selection() {
let mut app = VectorcraftApp::new(Session::new(), Default::default());
@@ -498,6 +594,48 @@ fn character_alignment_shows_with_the_east_asian_options() {
assert_eq!(shown(&mut app), (true, true));
}
/// The panel menu's Show Font Height Options shows the Set Font Height Reference dropdown,
/// which shows the selected type's reference and sets the one picked (one undo step); Hide
/// Font Height Options hides it again.
#[test]
fn show_font_height_options_shows_the_font_height_reference() {
use super::super::tests_appearance::{click, frame_events, text_rect};
use vectorcraft_doc::FontHeightReference as R;
let mut app = VectorcraftApp::new(Session::new(), Default::default());
app.run("file.new", json!({"width": 300, "height": 200})).unwrap();
let id = app.run("text.create", json!({"x": 20, "y": 50, "text": "Hx", "size": 12})).unwrap()["id"].as_u64().unwrap();
app.run("select.set", json!({"ids": [id]})).unwrap();
let reference = |app: &VectorcraftApp| match &app.session.active().unwrap().doc.node(NodeId(id)).unwrap().kind {
NodeKind::Text(t) => t.runs.iter().map(|r| r.style.font_height_reference).collect::<Vec<_>>(),
_ => panic!("text"),
};
let ctx = egui::Context::default();
crate::theme::install_fonts(&ctx);
let texts = |ctx: &egui::Context, app: &mut VectorcraftApp, f: fn(&mut VectorcraftApp, &mut Ui)| {
frame_events(ctx, app, vec![], f);
frame_events(ctx, app, vec![], f)
};
// A menu item's label is drawn after its check column (a check mark or spaces).
let item = |texts: &[(String, egui::Rect)], s: &str| texts.iter().find(|(t, _)| t.trim_start_matches(['✓', ' ']) == s).map(|(_, r)| r.center());
assert!(item(&texts(&ctx, &mut app, show), "Em Box").is_none());
let at = item(&texts(&ctx, &mut app, menu), "Show Font Height Options").unwrap();
click(&ctx, &mut app, at, menu);
assert!(item(&texts(&ctx, &mut app, menu), "Hide Font Height Options").is_some());
let panel = texts(&ctx, &mut app, show);
assert!(item(&panel, "Em Box").is_some(), "{panel:?}");
// Pick Cap Height from the dropdown.
click(&ctx, &mut app, text_rect(&panel, "Em Box").center(), show);
let undo = app.session.active().unwrap().history.undo.len();
let open = texts(&ctx, &mut app, show);
click(&ctx, &mut app, text_rect(&open, "Cap Height").center(), show);
assert_eq!(reference(&app), [R::CapHeight]);
assert_eq!(app.session.active().unwrap().history.undo.len(), undo + 1);
assert!(item(&texts(&ctx, &mut app, show), "Cap Height").is_some());
let at = item(&texts(&ctx, &mut app, menu), "Hide Font Height Options").unwrap();
click(&ctx, &mut app, at, menu);
assert!(item(&texts(&ctx, &mut app, show), "Cap Height").is_none());
}
/// Tate-chu-yoko is in the panel menu with the East Asian options, and choosing it marks the
/// selected characters.
#[test]
@@ -723,8 +861,8 @@ 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 and Digits included), in the selected
/// type and in the text the Type tool has selected while it edits.
/// Metrics, Tate-chu-yoko, 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() {
let mut app = VectorcraftApp::new(Session::new(), Default::default());
@@ -755,6 +893,7 @@ fn reset_panel_resets_every_attribute_but_the_font() {
s.no_break != d.no_break,
s.char_direction != d.char_direction,
s.digits != d.digits,
s.font_height_reference != d.font_height_reference,
]
};
let custom = |app: &mut VectorcraftApp| {
@@ -764,16 +903,16 @@ fn reset_panel_resets_every_attribute_but_the_font() {
"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"
"noBreak": true, "charDirection": "rightToLeft", "digits": "hindi", "fontHeightReference": "capHeight"
}),
)
.unwrap();
};
let plain = runs(&app)[0].1.clone();
assert_eq!(differs(&plain, &CharStyle::default()), [false; 19]);
assert_eq!(differs(&plain, &CharStyle::default()), [false; 20]);
custom(&mut app);
let styled = runs(&app)[0].1.clone();
assert_eq!(differs(&styled, &CharStyle::default()), [true; 19]);
assert_eq!(differs(&styled, &CharStyle::default()), [true; 20]);
app.run("select.set", json!({"ids": [id]})).unwrap();
reset_panel(&mut app);
assert_eq!(runs(&app), [("One 12".to_string(), plain.clone())]);
+21
View File
@@ -5119,6 +5119,27 @@ A digit the font lacks comes from a fallback font. Default and saved only when s
{"name":"run_command","arguments":{"command":"text.setFormat","params":{"digits":"hindi"}}}
```
## Font height reference
`text.setFormat {fontHeightReference: "emBox"|"capHeight"|"xHeight"|"icfBox"}` (the selected type, or `ids`) and
`text.setRangeStyle {id, start, end, fontHeightReference}` (a range) set the height of the font that the characters' font
size measures, as the Character panel's Set Font Height Reference dropdown does (panel menu › Show Font Height Options).
With `emBox`, the default, the font size is the size of the em square. With `capHeight`, `xHeight` or `icfBox`, the
characters are set at the em size at which the font's cap height or x height (OS/2 `sCapHeight`, `sxHeight`), or the
height of its ideographic character face (the BASE table's `icfb` and `icft`, else the ink of some ideographs and kana;
across a vertical line, its width), equals the font size: 20 pt Cap Height type in a font whose capitals are 0.7 em
tall is set at about 28.6 pt, and its font size stays 20 pt. With a font that has no such value, or one outside 0.1 to 2
em, the size measures the em box. Auto leading, tracking and kerning follow the em size the characters are set at (the
Character panel's Leading field shows that Auto leading, and `type.step {attribute: "leading"}` starts from it);
explicit leading and baseline shift stay in points, and an inline graphic keeps its size.
SVG, PDF and legacy `.ai` export and the CSS Properties rules write the em size. DXF export writes 0.7 times the em
size as each TEXT entity's height (group 40, the height of its capitals).
Saved only when not `emBox`. One undo step.
```json
{"name":"run_command","arguments":{"command":"text.setFormat","params":{"fontHeightReference":"capHeight"}}}
```
## Language
`text.setFormat {language}` (the selected type, or `ids`) and `text.setRangeStyle {id, start, end, language}` (a range)
File diff suppressed because one or more lines are too long