mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-01 13:47:21 -06:00
Character: Font Size can measure the cap height, x-height or ICF box
Squashed from #5026 onto main.
This commit is contained in:
@@ -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"] }
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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![];
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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
@@ -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),
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
@@ -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).
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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) };
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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";
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
@@ -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.
|
@@ -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.
|
@@ -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.
|
@@ -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.
|
@@ -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.
|
@@ -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.
|
@@ -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.
|
@@ -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.
|
@@ -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.
|
@@ -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.
|
@@ -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%
|
||||
|
||||
|
@@ -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.
|
@@ -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.
|
@@ -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.
|
@@ -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.
|
@@ -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.
|
@@ -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.
|
@@ -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.
|
@@ -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.
|
@@ -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.
|
@@ -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]
|
||||
|
||||
@@ -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.
|
@@ -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.
|
@@ -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.
|
@@ -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.
|
@@ -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.
|
@@ -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
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
Reference in New Issue
Block a user