mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-08 16:30:41 +00:00
Merge pull request #5094 from dotCooCoo/feat/char-style-options-advanced
Character Styles: Style Options sets scale, baseline shift, rotation and language
This commit is contained in:
@@ -475,7 +475,10 @@ fn name_param<'a>(p: &'a Value, kind: Kind, verb: &str) -> Result<&'a str> {
|
||||
str_param(p, "name").ok_or_else(|| bad(&kind.cmd(verb), "missing `name`"))
|
||||
}
|
||||
|
||||
fn attrs_param(p: &Value, kind: Kind, verb: &str) -> Result<Option<Map<String, Value>>> {
|
||||
/// The `attrs` of `p`, validated. `kept` holds the style's current attributes (`charStyle.edit`) or
|
||||
/// the target text's attributes (`charStyle.new`): a language tag that is not in the list, which a
|
||||
/// file can give a style or a run, stays when `attrs` keeps it.
|
||||
fn attrs_param(p: &Value, kind: Kind, verb: &str, kept: Option<&Map<String, Value>>) -> Result<Option<Map<String, Value>>> {
|
||||
match p.get("attrs") {
|
||||
None | Some(Value::Null) => Ok(None),
|
||||
Some(Value::Object(m)) => {
|
||||
@@ -486,7 +489,9 @@ fn attrs_param(p: &Value, kind: Kind, verb: &str) -> Result<Option<Map<String, V
|
||||
Kind::Char => {
|
||||
let mut st = with_attrs(&CharStyle::default(), m, &c)?;
|
||||
// A language of the list, stored as its tag (as text.setFormat takes it).
|
||||
if let Some(l) = &st.language {
|
||||
if let Some(l) = &st.language
|
||||
&& kept.and_then(|k| k.get("language")) != m.get("language")
|
||||
{
|
||||
st.language = vectorcraft_doc::setup::char_language(l).map_err(|e| bad(&c, e))?.map(str::to_string);
|
||||
}
|
||||
char_attrs(&st)
|
||||
@@ -532,9 +537,10 @@ fn list(s: &mut Session, kind: Kind) -> Result<Value> {
|
||||
|
||||
fn new(s: &mut Session, p: &Value, kind: Kind) -> Result<Value> {
|
||||
let c = kind.cmd("new");
|
||||
let attrs = match attrs_param(p, kind, "new")? {
|
||||
let target = selection_attrs(s, p, kind)?;
|
||||
let attrs = match attrs_param(p, kind, "new", target.as_ref())? {
|
||||
Some(a) => a,
|
||||
None => match selection_attrs(s, p, kind)? {
|
||||
None => match target {
|
||||
Some(a) => a,
|
||||
None => {
|
||||
let a = new_style_attrs(s, kind);
|
||||
@@ -660,7 +666,7 @@ fn redefine(s: &mut Session, p: &Value, kind: Kind) -> Result<Value> {
|
||||
|
||||
fn set_attrs(s: &mut Session, p: &Value, kind: Kind) -> Result<Value> {
|
||||
let name = name_param(p, kind, "setAttrs")?.to_string();
|
||||
let attrs = attrs_param(p, kind, "setAttrs")?.ok_or_else(|| bad(&kind.cmd("setAttrs"), "missing `attrs`"))?;
|
||||
let attrs = attrs_param(p, kind, "setAttrs", None)?.ok_or_else(|| bad(&kind.cmd("setAttrs"), "missing `attrs`"))?;
|
||||
redefine_to(s, kind, &name, attrs, "setAttrs")
|
||||
}
|
||||
|
||||
@@ -668,9 +674,9 @@ fn edit_style(s: &mut Session, p: &Value, kind: Kind) -> Result<Value> {
|
||||
let c = kind.cmd("edit");
|
||||
let name = name_param(p, kind, "edit")?.to_string();
|
||||
let to = str_param(p, "to").ok_or_else(|| bad(&c, "missing `to`"))?.trim().to_string();
|
||||
let attrs = attrs_param(p, kind, "edit")?.ok_or_else(|| bad(&c, "missing `attrs`"))?;
|
||||
let d = &s.doc()?.doc;
|
||||
let old = kind.attrs(d, &name).ok_or_else(|| bad(&c, format!("no style `{name}`")))?;
|
||||
let attrs = attrs_param(p, kind, "edit", Some(&old))?.ok_or_else(|| bad(&c, "missing `attrs`"))?;
|
||||
if name == kind.normal() {
|
||||
return Err(bad(&c, format!("`{name}` can't be edited this way")));
|
||||
}
|
||||
|
||||
@@ -205,6 +205,58 @@ fn character_style_options_edit_applies_to_users_and_is_one_saved_undo_step() {
|
||||
assert_eq!(s.doc().unwrap().doc, changed);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn character_style_options_edit_sets_the_advanced_character_formats() {
|
||||
let (mut s, id) = session_with_text("First second");
|
||||
s.execute("charStyle.new", &json!({"name":"Body", "attrs":{"h_scale":100.0,"rotation":0.0}})).unwrap();
|
||||
s.execute("charStyle.apply", &json!({"name":"Body", "id":id})).unwrap();
|
||||
s.execute("text.setRangeStyle", &json!({"id":id,"start":6,"end":12,"rotation":30.0})).unwrap();
|
||||
let before = s.doc().unwrap().doc.clone();
|
||||
let attrs = json!({"h_scale":80.0,"v_scale":120.0,"baseline_shift":-2.0,"rotation":15.0,"language":"German"});
|
||||
s.execute("charStyle.edit", &json!({"name":"Body","to":"Body","attrs":attrs})).unwrap();
|
||||
let changed = s.doc().unwrap().doc.clone();
|
||||
let def = changed.char_styles.iter().find(|d| d.name == "Body").unwrap();
|
||||
assert_eq!(def.attrs["language"], json!("de"));
|
||||
assert_eq!(
|
||||
runs(&s, id)
|
||||
.iter()
|
||||
.map(|r| (r.style.h_scale, r.style.v_scale, r.style.baseline_shift, r.style.rotation, r.style.language.clone()))
|
||||
.collect::<Vec<_>>(),
|
||||
[(80.0, 120.0, -2.0, 15.0, Some("de".to_string())), (80.0, 120.0, -2.0, 30.0, Some("de".to_string()))]
|
||||
);
|
||||
let saved = vectorcraft_format::load(&vectorcraft_format::save(&changed, false)).unwrap();
|
||||
assert_eq!(saved.char_styles.iter().find(|d| d.name == "Body").unwrap().attrs, def.attrs);
|
||||
s.execute("edit.undo", &json!({})).unwrap();
|
||||
assert_eq!(s.doc().unwrap().doc, before);
|
||||
s.execute("edit.redo", &json!({})).unwrap();
|
||||
assert_eq!(s.doc().unwrap().doc, changed);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn character_style_options_edit_keeps_a_language_tag_from_a_file() {
|
||||
let (mut s, _) = session_with_text("Text");
|
||||
s.execute("charStyle.new", &json!({"name":"Body", "attrs":{}})).unwrap();
|
||||
if let Some(def) = std::sync::Arc::make_mut(&mut s.doc_mut().unwrap().doc).char_styles.iter_mut().find(|d| d.name == "Body") {
|
||||
def.attrs.insert("language".into(), json!("x-file"));
|
||||
}
|
||||
s.execute("charStyle.edit", &json!({"name":"Body","to":"Body","attrs":{"size":18.0,"language":"x-file"}})).unwrap();
|
||||
assert_eq!(s.doc().unwrap().doc.char_styles.iter().find(|d| d.name == "Body").unwrap().attrs["language"], json!("x-file"));
|
||||
let error = s.execute("charStyle.edit", &json!({"name":"Body","to":"Body","attrs":{"language":"x-other"}})).unwrap_err().to_string();
|
||||
assert!(error.contains("unknown language"), "{error}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn new_character_style_keeps_a_language_tag_from_the_text() {
|
||||
let (mut s, id) = session_with_text("Text");
|
||||
if let Some(NodeKind::Text(t)) = std::sync::Arc::make_mut(&mut s.doc_mut().unwrap().doc).node_mut(NodeId(id)).map(|n| &mut n.kind) {
|
||||
t.runs[0].style.language = Some("x-file".into());
|
||||
}
|
||||
s.execute("charStyle.new", &json!({"name":"Body","id":id,"attrs":{"size":18.0,"language":"x-file"}})).unwrap();
|
||||
assert_eq!(s.doc().unwrap().doc.char_styles.iter().find(|d| d.name == "Body").unwrap().attrs["language"], json!("x-file"));
|
||||
let error = s.execute("charStyle.new", &json!({"name":"Other","id":id,"attrs":{"language":"x-other"}})).unwrap_err().to_string();
|
||||
assert!(error.contains("unknown language"), "{error}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn editing_character_style_clears_removed_size_without_losing_local_override() {
|
||||
let (mut s, id) = session_with_text("One two");
|
||||
|
||||
@@ -25,6 +25,10 @@ const BASIC: &[&str] = &[
|
||||
const NUMBERS: &[&str] = &["size", "leading", "kerning", "tracking"];
|
||||
/// The on, off or unset checkboxes.
|
||||
const SWITCHES: &[&str] = &["underline", "strikethrough", "standardVerticalRoman"];
|
||||
/// The attributes of Advanced Character Formats, in the order the section shows them.
|
||||
const ADVANCED: &[&str] = &["h_scale", "v_scale", "baseline_shift", "rotation", "language"];
|
||||
/// The number fields of Advanced Character Formats.
|
||||
const ADVANCED_NUMBERS: &[&str] = &["h_scale", "v_scale", "baseline_shift", "rotation"];
|
||||
|
||||
pub(super) const SPEC: DialogSpec = DialogSpec {
|
||||
heading: |_| tl!("Character Style Options").into(),
|
||||
@@ -46,6 +50,11 @@ pub(super) const SPEC: DialogSpec = DialogSpec {
|
||||
"strikethrough",
|
||||
"standardVerticalRoman",
|
||||
"charAlign",
|
||||
"h_scale",
|
||||
"v_scale",
|
||||
"baseline_shift",
|
||||
"rotation",
|
||||
"language",
|
||||
"section",
|
||||
]
|
||||
},
|
||||
@@ -87,9 +96,24 @@ pub(crate) fn fields_from_attrs(attrs: &Map<String, Value>) -> Map<String, Value
|
||||
}
|
||||
fields.insert("case".into(), case_from_attrs(attrs).map_or(Value::Null, Value::from));
|
||||
fields.insert("position".into(), attrs.get("position").and_then(|v| v.get("kind")).cloned().unwrap_or(Value::Null));
|
||||
for key in ADVANCED_NUMBERS {
|
||||
fields.insert((*key).into(), attrs.get(*key).filter(|v| !v.is_null()).cloned().unwrap_or_else(|| json!("")));
|
||||
}
|
||||
fields.insert("language".into(), language_field(attrs.get("language")));
|
||||
fields
|
||||
}
|
||||
|
||||
/// The Language field for a style's `language` attribute: unset for none, the default language's
|
||||
/// name for an explicit default (`null`), a listed tag's name, or a tag not in the list as it is.
|
||||
fn language_field(value: Option<&Value>) -> Value {
|
||||
match value {
|
||||
None => Value::Null,
|
||||
Some(Value::Null) => vectorcraft_doc::setup::char_language_name(None).map_or(Value::Null, Value::from),
|
||||
Some(Value::String(tag)) => json!(vectorcraft_doc::setup::char_language_name(Some(tag)).unwrap_or(tag)),
|
||||
Some(other) => other.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
fn case_from_attrs(attrs: &Map<String, Value>) -> Option<&'static str> {
|
||||
if attrs.get("all_caps") == Some(&json!(true)) {
|
||||
Some("allCaps")
|
||||
@@ -108,6 +132,10 @@ fn optional_number(d: &Dialog, key: &str) -> Result<Option<Value>, String> {
|
||||
"leading" => tl!("Leading"),
|
||||
"kerning" => tl!("Kerning"),
|
||||
"tracking" => tl!("Tracking"),
|
||||
"h_scale" => tl!("Horizontal Scale"),
|
||||
"v_scale" => tl!("Vertical Scale"),
|
||||
"baseline_shift" => tl!("Baseline Shift"),
|
||||
"rotation" => tl!("Character Rotation"),
|
||||
_ => return Err("unknown character style number field".into()),
|
||||
};
|
||||
match d.fields.get(key) {
|
||||
@@ -123,6 +151,17 @@ fn optional_number(d: &Dialog, key: &str) -> Result<Option<Value>, String> {
|
||||
if ["size", "leading"].contains(&key) && n < 0.1 {
|
||||
return Err(format!("{label} must be a number of at least 0.1"));
|
||||
}
|
||||
// The limits the document model keeps scales and baseline shift in.
|
||||
let max = vectorcraft_doc::MAX_COORD;
|
||||
if ["h_scale", "v_scale"].contains(&key) && !(n > 0.0 && n <= max) {
|
||||
return Err(format!("{label} must be a number above 0 and at most {max}"));
|
||||
}
|
||||
let min = -max;
|
||||
if key == "baseline_shift" && !(min..=max).contains(&n) {
|
||||
return Err(format!("{label} must be a number from {min} to {max}"));
|
||||
}
|
||||
// An angle from -180 to 180 degrees, as text.setFormat stores it.
|
||||
let n = if key == "rotation" { (n + 180.0).rem_euclid(360.0) - 180.0 } else { n };
|
||||
Ok(Some(json!(n)))
|
||||
}
|
||||
}
|
||||
@@ -146,6 +185,10 @@ pub(crate) fn edited_attrs(d: &Dialog) -> Result<Map<String, Value>, String> {
|
||||
attrs.remove("fontAxes");
|
||||
}
|
||||
for key in NUMBERS {
|
||||
// An unchanged field keeps the original value below, unchecked (see `advanced_attrs`).
|
||||
if !reset && d.fields.get(*key) == original_fields.get(*key) {
|
||||
continue;
|
||||
}
|
||||
if let Some(v) = optional_number(d, key)? {
|
||||
attrs.insert((*key).into(), v);
|
||||
}
|
||||
@@ -205,9 +248,54 @@ pub(crate) fn edited_attrs(d: &Dialog) -> Result<Map<String, Value>, String> {
|
||||
}
|
||||
}
|
||||
}
|
||||
advanced_attrs(d, &original, &original_fields, &mut attrs)?;
|
||||
Ok(attrs)
|
||||
}
|
||||
|
||||
/// Write the Advanced Character Formats fields of `d` into `attrs`: a filled field sets its
|
||||
/// attribute, an empty one leaves it unset, and a field still as it opened keeps the original
|
||||
/// value, an explicit default or a language tag not in the list included.
|
||||
fn advanced_attrs(
|
||||
d: &Dialog,
|
||||
original: &Map<String, Value>,
|
||||
original_fields: &Map<String, Value>,
|
||||
attrs: &mut Map<String, Value>,
|
||||
) -> Result<(), String> {
|
||||
let reset = d.bool("__resetAdvanced");
|
||||
for key in ADVANCED {
|
||||
attrs.remove(*key);
|
||||
}
|
||||
// A field still as it opened keeps its original value below without a check.
|
||||
let unchanged = |key: &str| !reset && d.fields.get(key) == original_fields.get(key);
|
||||
for key in ADVANCED_NUMBERS {
|
||||
if unchanged(key) {
|
||||
continue;
|
||||
}
|
||||
if let Some(v) = optional_number(d, key)? {
|
||||
attrs.insert((*key).into(), v);
|
||||
}
|
||||
}
|
||||
if !unchanged("language")
|
||||
&& let Some(language) = d.fields.get("language").and_then(Value::as_str).map(str::trim).filter(|v| !v.is_empty())
|
||||
{
|
||||
let value = match vectorcraft_doc::setup::char_language(language) {
|
||||
Ok(tag) => tag.map_or(Value::Null, Value::from),
|
||||
// A tag not in the list goes to the command as it is: charStyle.edit keeps it only
|
||||
// when the style already has it.
|
||||
Err(_) => json!(language),
|
||||
};
|
||||
attrs.insert("language".into(), value);
|
||||
}
|
||||
for key in ADVANCED {
|
||||
if unchanged(key)
|
||||
&& let Some(value) = original.get(*key)
|
||||
{
|
||||
attrs.insert((*key).into(), value.clone());
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn reset_basic(d: &mut Dialog) {
|
||||
d.fields.insert("__resetBasic".into(), json!(true));
|
||||
for key in [
|
||||
@@ -231,16 +319,59 @@ pub(crate) fn reset_basic(d: &mut Dialog) {
|
||||
}
|
||||
}
|
||||
|
||||
/// Reset Panel in Advanced Character Formats: its five attributes become unset.
|
||||
pub(crate) fn reset_advanced(d: &mut Dialog) {
|
||||
d.fields.insert("__resetAdvanced".into(), json!(true));
|
||||
for key in ADVANCED_NUMBERS {
|
||||
d.fields.insert((*key).into(), json!(""));
|
||||
}
|
||||
d.fields.insert("language".into(), Value::Null);
|
||||
}
|
||||
|
||||
/// Whether the section `d` shows has a Reset Panel: Basic or Advanced Character Formats.
|
||||
pub(crate) fn can_reset(d: &Dialog) -> bool {
|
||||
["basic", "advanced"].contains(&d.str("section").as_str())
|
||||
}
|
||||
|
||||
/// Reset Panel: clear the settings of the section `d` shows.
|
||||
pub(crate) fn reset_section(d: &mut Dialog) {
|
||||
match d.str("section").as_str() {
|
||||
"basic" => reset_basic(d),
|
||||
"advanced" => reset_advanced(d),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn reset_panel(app: &mut VectorcraftApp) -> Result<Value, String> {
|
||||
let d = app.ui.dialog.as_mut().ok_or("no dialog open")?;
|
||||
let character_new = d.kind == super::new_text_style::KIND && !d.bool("__para");
|
||||
if (d.kind != KIND && !character_new) || d.str("section") != "basic" {
|
||||
if (d.kind != KIND && !character_new) || !can_reset(d) {
|
||||
return Err("the open dialog has no Reset".into());
|
||||
}
|
||||
reset_basic(d);
|
||||
reset_section(d);
|
||||
Ok(json!({"dialog": d.kind}))
|
||||
}
|
||||
|
||||
/// The section list of Character Style Options and New Character Style.
|
||||
pub(crate) fn section_list(ui: &mut egui::Ui, d: &mut Dialog) {
|
||||
ui.vertical(|ui| {
|
||||
for (id, label) in [("general", tl!("General")), ("basic", tl!("Basic Character Formats")), ("advanced", tl!("Advanced Character Formats"))] {
|
||||
if ui.selectable_label(d.str("section") == id, label).clicked() {
|
||||
d.fields.insert("section".into(), json!(id));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// The Reset Panel button, enabled on Basic and Advanced Character Formats.
|
||||
pub(crate) fn reset_button(ui: &mut egui::Ui, d: &mut Dialog) {
|
||||
ui.add_enabled_ui(can_reset(d), |ui| {
|
||||
if ui.button(tl!("Reset Panel")).clicked() {
|
||||
reset_section(d);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fn choice(ui: &mut egui::Ui, d: &mut Dialog, key: &str, options: &[(&str, &str)]) {
|
||||
let selected = d.fields.get(key).and_then(Value::as_str).unwrap_or("").to_string();
|
||||
let shown = options.iter().find(|(value, _)| *value == selected).map_or("", |(_, label)| *label);
|
||||
@@ -310,19 +441,45 @@ pub(crate) fn basic_body(ui: &mut egui::Ui, d: &mut Dialog) {
|
||||
});
|
||||
}
|
||||
|
||||
/// Advanced Character Formats: Horizontal Scale, Vertical Scale, Baseline Shift, Character Rotation
|
||||
/// and Language, each empty (unset) or set.
|
||||
pub(crate) fn advanced_body(ui: &mut egui::Ui, d: &mut Dialog) {
|
||||
egui::Grid::new("character-style-advanced").num_columns(2).spacing([18.0, 8.0]).show(ui, |ui| {
|
||||
for (key, caption, unit) in [
|
||||
("h_scale", tl!("Horizontal Scale"), "%"),
|
||||
("v_scale", tl!("Vertical Scale"), "%"),
|
||||
("baseline_shift", tl!("Baseline Shift"), "pt"),
|
||||
("rotation", tl!("Character Rotation"), "°"),
|
||||
] {
|
||||
ui.label(caption);
|
||||
if d.fields.get(key).is_some_and(Value::is_null) {
|
||||
d.fields.insert(key.into(), json!(""));
|
||||
}
|
||||
ui.horizontal(|ui| {
|
||||
form::text(ui, d, key, 160.0);
|
||||
ui.label(unit);
|
||||
});
|
||||
ui.end_row();
|
||||
}
|
||||
ui.label(tl!("Language:"));
|
||||
let names: Vec<&str> = std::iter::once(" ").chain(vectorcraft_doc::setup::LANGUAGES.iter().map(|(name, _, _)| *name)).collect();
|
||||
let current = d.fields.get("language").and_then(Value::as_str).unwrap_or("").to_string();
|
||||
if let Some(i) = crate::widgets::dropdown(ui, (KIND, "language"), ¤t, &names, 190.0) {
|
||||
d.fields.insert("language".into(), if i == 0 { Value::Null } else { names.get(i).map_or(Value::Null, |name| json!(name)) });
|
||||
}
|
||||
ui.end_row();
|
||||
});
|
||||
}
|
||||
|
||||
fn body(app: &mut VectorcraftApp, ui: &mut egui::Ui, d: &mut Dialog) -> bool {
|
||||
ui.horizontal(|ui| {
|
||||
ui.vertical(|ui| {
|
||||
for (id, label) in [("general", tl!("General")), ("basic", tl!("Basic Character Formats"))] {
|
||||
if ui.selectable_label(d.str("section") == id, label).clicked() {
|
||||
d.fields.insert("section".into(), json!(id));
|
||||
}
|
||||
}
|
||||
});
|
||||
section_list(ui, d);
|
||||
ui.separator();
|
||||
ui.vertical(|ui| {
|
||||
if d.str("section") == "basic" {
|
||||
basic_body(ui, d);
|
||||
} else if d.str("section") == "advanced" {
|
||||
advanced_body(ui, d);
|
||||
} else {
|
||||
ui.horizontal(|ui| {
|
||||
ui.label(tl!("Style Name:"));
|
||||
@@ -337,11 +494,7 @@ fn body(app: &mut VectorcraftApp, ui: &mut egui::Ui, d: &mut Dialog) -> bool {
|
||||
}
|
||||
});
|
||||
});
|
||||
ui.add_enabled_ui(d.str("section") == "basic", |ui| {
|
||||
if ui.button(tl!("Reset Panel")).clicked() {
|
||||
reset_basic(d);
|
||||
}
|
||||
});
|
||||
reset_button(ui, d);
|
||||
match edited_attrs(d) {
|
||||
Ok(attrs) => {
|
||||
let params = json!({"name": d.str("__style"), "to": d.str("name"), "attrs": attrs});
|
||||
@@ -521,6 +674,173 @@ mod tests {
|
||||
assert!(t.runs.iter().all(|r| r.style.standard_vertical_roman));
|
||||
}
|
||||
|
||||
fn attrs(v: Value) -> Map<String, Value> {
|
||||
v.as_object().cloned().unwrap_or_default()
|
||||
}
|
||||
|
||||
fn dialog_for(original: Value) -> Dialog {
|
||||
let mut d = Dialog::new(KIND, json!({}));
|
||||
d.fields.extend(fields_from_attrs(&attrs(original.clone())));
|
||||
d.fields.insert("__attrs".into(), original);
|
||||
d
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn advanced_fields_show_set_values_and_keep_untouched_ones() {
|
||||
let d = dialog_for(json!({}));
|
||||
for key in ADVANCED_NUMBERS {
|
||||
assert_eq!(d.fields[*key], json!(""), "{key}");
|
||||
}
|
||||
assert!(d.fields["language"].is_null());
|
||||
assert_eq!(edited_attrs(&d).unwrap(), Map::new());
|
||||
for original in [
|
||||
json!({"h_scale": 100.0, "rotation": 0.0, "language": null}),
|
||||
json!({"v_scale": 50.0, "baseline_shift": -3.0, "language": "de"}),
|
||||
json!({"language": "x-file", "size": 12.0}),
|
||||
// Values a style captures from text in a scaled type object, Size and Leading below 0.1 included.
|
||||
json!({"h_scale": 20000.0, "baseline_shift": 3000.0, "size": 0.05, "leading": 0.05}),
|
||||
] {
|
||||
let d = dialog_for(original.clone());
|
||||
assert_eq!(edited_attrs(&d).unwrap(), attrs(original.clone()), "{original}");
|
||||
}
|
||||
let d = dialog_for(json!({"h_scale": 100.0, "rotation": 0.0, "language": null}));
|
||||
assert_eq!((d.fields["h_scale"].clone(), d.fields["v_scale"].clone()), (json!(100.0), json!("")));
|
||||
assert_eq!(d.fields["language"], json!("English: USA"));
|
||||
assert_eq!(dialog_for(json!({"language": "de"})).fields["language"], json!("German"));
|
||||
assert_eq!(dialog_for(json!({"language": "x-file"})).fields["language"], json!("x-file"));
|
||||
|
||||
let mut d = dialog_for(json!({"h_scale": 100.0, "language": "de"}));
|
||||
d.fields.insert("h_scale".into(), json!(""));
|
||||
d.fields.insert("v_scale".into(), json!("150"));
|
||||
d.fields.insert("baseline_shift".into(), json!("-4"));
|
||||
d.fields.insert("rotation".into(), json!("190"));
|
||||
d.fields.insert("language".into(), json!("English: USA"));
|
||||
assert_eq!(edited_attrs(&d).unwrap(), attrs(json!({"v_scale": 150.0, "baseline_shift": -4.0, "rotation": -170.0, "language": null})));
|
||||
d.fields.insert("language".into(), json!("French"));
|
||||
assert_eq!(edited_attrs(&d).unwrap()["language"], json!("fr"));
|
||||
d.fields.insert("language".into(), Value::Null);
|
||||
assert!(!edited_attrs(&d).unwrap().contains_key("language"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn advanced_numbers_outside_their_limits_are_errors() {
|
||||
for (key, value, message) in [
|
||||
("h_scale", "0", "Horizontal Scale must be a number above 0 and at most 4000000"),
|
||||
("h_scale", "-5", "Horizontal Scale must be a number above 0 and at most 4000000"),
|
||||
("v_scale", "4000001", "Vertical Scale must be a number above 0 and at most 4000000"),
|
||||
("baseline_shift", "4000001", "Baseline Shift must be a number from -4000000 to 4000000"),
|
||||
("baseline_shift", "-4000001", "Baseline Shift must be a number from -4000000 to 4000000"),
|
||||
("rotation", "inf", "Character Rotation must be a number"),
|
||||
("h_scale", "wide", "Horizontal Scale must be a number"),
|
||||
] {
|
||||
let mut d = dialog_for(json!({}));
|
||||
d.fields.insert(key.into(), json!(value));
|
||||
assert_eq!(edited_attrs(&d).unwrap_err(), message, "{key} {value}");
|
||||
}
|
||||
let mut d = dialog_for(json!({}));
|
||||
for (key, value) in [("h_scale", "0.5"), ("v_scale", "4000000"), ("baseline_shift", "-4000000"), ("rotation", "-200")] {
|
||||
d.fields.insert(key.into(), json!(value));
|
||||
}
|
||||
assert_eq!(edited_attrs(&d).unwrap(), attrs(json!({"h_scale": 0.5, "v_scale": 4000000.0, "baseline_shift": -4000000.0, "rotation": 160.0})));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn advanced_section_shows_its_five_controls() {
|
||||
let mut d = dialog_for(json!({}));
|
||||
let mut app = app();
|
||||
let shown = crate::tests_labels::painted_text(&mut app, |_, ui| advanced_body(ui, &mut d));
|
||||
for label in ["Horizontal Scale", "Vertical Scale", "Baseline Shift", "Character Rotation", "Language:"] {
|
||||
assert!(shown.contains(label), "{label}: {shown}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn advanced_options_preview_cancel_reset_and_commit() {
|
||||
let mut app = app();
|
||||
app.run("charStyle.new", json!({"name": "Body", "attrs": {"size": 24.0, "h_scale": 90.0, "baseline_shift": 0.0}})).unwrap();
|
||||
let id = app.run("text.create", json!({"x": 10, "y": 40, "text": "Text"})).unwrap()["id"].as_u64().unwrap();
|
||||
app.run("charStyle.apply", json!({"name": "Body", "id": id})).unwrap();
|
||||
app.run("text.setRangeStyle", json!({"id": id, "start": 2, "end": 4, "baselineShift": 5.0})).unwrap();
|
||||
let before = app.session.doc().unwrap().doc.clone();
|
||||
let undo = app.session.doc().unwrap().history.undo.len();
|
||||
open(&mut app, "Body").unwrap();
|
||||
assert_eq!(app.ui.dialog.as_ref().unwrap().fields["h_scale"], json!(90.0));
|
||||
assert_eq!(app.ui.dialog.as_ref().unwrap().str("rotation"), "");
|
||||
{
|
||||
let d = app.ui.dialog.as_mut().unwrap();
|
||||
d.fields.insert("section".into(), json!("advanced"));
|
||||
d.fields.insert("h_scale".into(), json!("80"));
|
||||
d.fields.insert("baseline_shift".into(), json!("2"));
|
||||
d.fields.insert("rotation".into(), json!("45"));
|
||||
d.fields.insert("language".into(), json!("Turkish"));
|
||||
}
|
||||
assert!(crate::menus::enabled(&app, "ui.charStyleOptions.resetPanel"));
|
||||
frame(&mut app);
|
||||
assert_eq!(app.session.doc().unwrap().doc.char_styles[0].attrs["rotation"], json!(45.0));
|
||||
super::super::cancel(&mut app);
|
||||
assert_eq!(app.session.doc().unwrap().doc, before);
|
||||
assert_eq!(app.session.doc().unwrap().history.undo.len(), undo);
|
||||
|
||||
open(&mut app, "Body").unwrap();
|
||||
{
|
||||
let d = app.ui.dialog.as_mut().unwrap();
|
||||
d.fields.insert("section".into(), json!("advanced"));
|
||||
d.fields.insert("size".into(), json!(18));
|
||||
d.fields.insert("v_scale".into(), json!("70"));
|
||||
}
|
||||
app.run("ui.charStyleOptions.resetPanel", json!({})).unwrap();
|
||||
assert_eq!(edited_attrs(app.ui.dialog.as_ref().unwrap()).unwrap(), attrs(json!({"size": 18.0})));
|
||||
{
|
||||
let d = app.ui.dialog.as_mut().unwrap();
|
||||
d.fields.insert("h_scale".into(), json!("80"));
|
||||
d.fields.insert("v_scale".into(), json!("60"));
|
||||
d.fields.insert("baseline_shift".into(), json!("2"));
|
||||
d.fields.insert("rotation".into(), json!("45"));
|
||||
d.fields.insert("language".into(), json!("Turkish"));
|
||||
}
|
||||
super::super::confirm(&mut app).unwrap();
|
||||
let state = app.session.doc().unwrap();
|
||||
assert_eq!(state.history.undo.len(), undo + 1);
|
||||
assert_eq!(
|
||||
state.doc.char_styles[0].attrs,
|
||||
attrs(json!({"size": 18.0, "h_scale": 80.0, "v_scale": 60.0, "baseline_shift": 2.0, "rotation": 45.0, "language": "tr"}))
|
||||
);
|
||||
let vectorcraft_doc::NodeKind::Text(t) = &state.doc.node(vectorcraft_doc::NodeId(id)).unwrap().kind else { panic!("text") };
|
||||
assert_eq!(
|
||||
t.runs
|
||||
.iter()
|
||||
.map(|r| (r.style.h_scale, r.style.v_scale, r.style.baseline_shift, r.style.rotation, r.style.language.clone()))
|
||||
.collect::<Vec<_>>(),
|
||||
[(80.0, 60.0, 2.0, 45.0, Some("tr".to_string())), (80.0, 60.0, 5.0, 45.0, Some("tr".to_string()))]
|
||||
);
|
||||
app.run("edit.undo", json!({})).unwrap();
|
||||
assert_eq!(app.session.doc().unwrap().doc, before);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn new_character_style_sets_and_resets_advanced_formats() {
|
||||
let mut app = app();
|
||||
app.run("ui.newCharStyle", json!({})).unwrap();
|
||||
{
|
||||
let d = app.ui.dialog.as_mut().unwrap();
|
||||
d.fields.insert("section".into(), json!("advanced"));
|
||||
d.fields.insert("v_scale".into(), json!("130"));
|
||||
}
|
||||
app.run("ui.charStyleOptions.resetPanel", json!({})).unwrap();
|
||||
assert_eq!(app.ui.dialog.as_ref().unwrap().str("v_scale"), "");
|
||||
{
|
||||
let d = app.ui.dialog.as_mut().unwrap();
|
||||
d.fields.insert("name".into(), json!("Raised"));
|
||||
d.fields.insert("baseline_shift".into(), json!("3"));
|
||||
d.fields.insert("language".into(), json!("French"));
|
||||
}
|
||||
super::super::confirm(&mut app).unwrap();
|
||||
let doc = &app.session.doc().unwrap().doc;
|
||||
let def = doc.char_styles.iter().find(|def| def.name == "Raised").unwrap();
|
||||
assert_eq!((def.attrs["baseline_shift"].clone(), def.attrs["language"].clone()), (json!(3.0), json!("fr")));
|
||||
assert!(!def.attrs.contains_key("v_scale"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_numbers_name_the_visible_field() {
|
||||
let mut d = Dialog::new(KIND, json!({}));
|
||||
|
||||
@@ -53,16 +53,12 @@ pub fn open(app: &mut VectorcraftApp, para: bool, target: Value) -> Result<Value
|
||||
fn body(_: &mut VectorcraftApp, ui: &mut egui::Ui, d: &mut Dialog) -> bool {
|
||||
if !para(d) {
|
||||
ui.horizontal(|ui| {
|
||||
ui.vertical(|ui| {
|
||||
for (id, title) in [("general", tl!("General")), ("basic", tl!("Basic Character Formats"))] {
|
||||
if ui.selectable_label(d.str("section") == id, title).clicked() {
|
||||
d.fields.insert("section".into(), json!(id));
|
||||
}
|
||||
}
|
||||
});
|
||||
super::char_style_options::section_list(ui, d);
|
||||
ui.separator();
|
||||
if d.str("section") == "basic" {
|
||||
super::char_style_options::basic_body(ui, d);
|
||||
} else if d.str("section") == "advanced" {
|
||||
super::char_style_options::advanced_body(ui, d);
|
||||
} else {
|
||||
ui.vertical(|ui| {
|
||||
grid(ui, |ui| {
|
||||
@@ -78,11 +74,7 @@ fn body(_: &mut VectorcraftApp, ui: &mut egui::Ui, d: &mut Dialog) -> bool {
|
||||
});
|
||||
}
|
||||
});
|
||||
ui.add_enabled_ui(d.str("section") == "basic", |ui| {
|
||||
if ui.button(tl!("Reset Panel")).clicked() {
|
||||
super::char_style_options::reset_basic(d);
|
||||
}
|
||||
});
|
||||
super::char_style_options::reset_button(ui, d);
|
||||
return false;
|
||||
}
|
||||
grid(ui, |ui| {
|
||||
|
||||
@@ -5913,6 +5913,11 @@ anchor Points: Punts:
|
||||
Case Majúscules i minúscules
|
||||
@msg {label} must be a number {label} ha de ser un nombre
|
||||
@msg {label} must be a number of at least 0.1 {label} ha de ser un nombre de com a mínim 0,1
|
||||
Advanced Character Formats Formats avançats de caràcters
|
||||
Horizontal Scale Escala horitzontal
|
||||
Vertical Scale Escala vertical
|
||||
@msg {label} must be a number from {min} to {max} {label} ha de ser un nombre de {min} a {max}
|
||||
@msg {label} must be a number above 0 and at most {max} {label} ha de ser un nombre superior a 0 i com a màxim {max}
|
||||
@msg no character style `{name}` No s'ha trobat l'estil de caràcter `{name}`
|
||||
|
||||
# Color Effects add-on: Duotone (Effect › Color Effects) and Print Ink Fixes (Object › Plug-ins › Color Effects).
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -5925,6 +5925,11 @@ anchor Points: Body:
|
||||
Case Velikost písmen
|
||||
@msg {label} must be a number {label} musí být číslo
|
||||
@msg {label} must be a number of at least 0.1 {label} musí být číslo alespoň 0,1
|
||||
Advanced Character Formats Rozšířené formáty znaků
|
||||
Horizontal Scale Vodorovná změna velikosti
|
||||
Vertical Scale Svislá změna velikosti
|
||||
@msg {label} must be a number from {min} to {max} {label} musí být číslo od {min} do {max}
|
||||
@msg {label} must be a number above 0 and at most {max} {label} musí být číslo větší než 0 a nejvýše {max}
|
||||
@msg no character style `{name}` styl znaků `{name}` neexistuje
|
||||
|
||||
# Color Effects add-on: Duotone (Effect › Color Effects) and Print Ink Fixes (Object › Plug-ins › Color Effects).
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -5510,6 +5510,11 @@ anchor Points: Punkte:
|
||||
Case Großschreibung
|
||||
@msg {label} must be a number {label} muss eine Zahl sein
|
||||
@msg {label} must be a number of at least 0.1 {label} muss eine Zahl von mindestens 0,1 sein
|
||||
Advanced Character Formats Erweiterte Zeichenformate
|
||||
Horizontal Scale Horizontale Skalierung
|
||||
Vertical Scale Vertikale Skalierung
|
||||
@msg {label} must be a number from {min} to {max} {label} muss eine Zahl von {min} bis {max} sein
|
||||
@msg {label} must be a number above 0 and at most {max} {label} muss eine Zahl über 0 und höchstens {max} sein
|
||||
@msg no character style `{name}` Zeichenformat `{name}` nicht gefunden
|
||||
|
||||
# Color Effects add-on: Duotone (Effect › Color Effects) and Print Ink Fixes (Object › Plug-ins › Color Effects).
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -5876,6 +5876,11 @@ anchor Points: Σημεία:
|
||||
Case Πεζά και κεφαλαία
|
||||
@msg {label} must be a number Το {label} πρέπει να είναι αριθμός
|
||||
@msg {label} must be a number of at least 0.1 Το {label} πρέπει να είναι αριθμός τουλάχιστον 0,1
|
||||
Advanced Character Formats Σύνθετες μορφές χαρακτήρων
|
||||
Horizontal Scale Οριζόντια κλίμακα
|
||||
Vertical Scale Κατακόρυφη κλίμακα
|
||||
@msg {label} must be a number from {min} to {max} Το {label} πρέπει να είναι αριθμός από {min} έως {max}
|
||||
@msg {label} must be a number above 0 and at most {max} Το {label} πρέπει να είναι αριθμός μεγαλύτερος από 0 και έως {max}
|
||||
@msg no character style `{name}` Δεν βρέθηκε το στυλ χαρακτήρων `{name}`
|
||||
|
||||
# Color Effects add-on: Duotone (Effect › Color Effects) and Print Ink Fixes (Object › Plug-ins › Color Effects).
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 164 and column 5.
|
@@ -5867,6 +5867,11 @@ anchor Points: Puntos:
|
||||
Case Mayúsculas y minúsculas
|
||||
@msg {label} must be a number {label} debe ser un número
|
||||
@msg {label} must be a number of at least 0.1 {label} debe ser un número de al menos 0,1
|
||||
Advanced Character Formats Formatos avanzados de caracteres
|
||||
Horizontal Scale Escala horizontal
|
||||
Vertical Scale Escala vertical
|
||||
@msg {label} must be a number from {min} to {max} {label} debe ser un número de {min} a {max}
|
||||
@msg {label} must be a number above 0 and at most {max} {label} debe ser un número mayor que 0 y como máximo {max}
|
||||
@msg no character style `{name}` No existe el estilo de carácter `{name}`
|
||||
|
||||
# Color Effects add-on: Duotone (Effect › Color Effects) and Print Ink Fixes (Object › Plug-ins › Color Effects).
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -5955,6 +5955,11 @@ anchor Points: Pisteet:
|
||||
Case Kirjainkoko
|
||||
@msg {label} must be a number {label} on oltava luku
|
||||
@msg {label} must be a number of at least 0.1 {label} on oltava vähintään 0,1
|
||||
Advanced Character Formats Merkkien lisämuotoilut
|
||||
Horizontal Scale Vaakaskaalaus
|
||||
Vertical Scale Pystyskaalaus
|
||||
@msg {label} must be a number from {min} to {max} {label} on oltava luku {min} ja {max} väliltä
|
||||
@msg {label} must be a number above 0 and at most {max} {label} on oltava luku, joka on yli 0 ja enintään {max}
|
||||
@msg no character style `{name}` Merkkityyliä `{name}` ei löytynyt
|
||||
|
||||
# Color Effects add-on: Duotone (Effect › Color Effects) and Print Ink Fixes (Object › Plug-ins › Color Effects).
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -5922,6 +5922,11 @@ anchor Points: Points :
|
||||
Case Casse
|
||||
@msg {label} must be a number {label} doit être un nombre
|
||||
@msg {label} must be a number of at least 0.1 {label} doit être un nombre d’au moins 0,1
|
||||
Advanced Character Formats Formats de caractère avancés
|
||||
Horizontal Scale Mise à l’échelle horizontale
|
||||
Vertical Scale Échelle verticale
|
||||
@msg {label} must be a number from {min} to {max} {label} doit être un nombre compris entre {min} et {max}
|
||||
@msg {label} must be a number above 0 and at most {max} {label} doit être un nombre supérieur à 0 et au plus égal à {max}
|
||||
@msg no character style `{name}` Style de caractère `{name}` introuvable
|
||||
|
||||
# Color Effects add-on: Duotone (Effect › Color Effects) and Print Ink Fixes (Object › Plug-ins › Color Effects).
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -5911,6 +5911,11 @@ anchor Points: Pontok:
|
||||
Case Kis- és nagybetűk
|
||||
@msg {label} must be a number {label}: szám kell
|
||||
@msg {label} must be a number of at least 0.1 {label} legalább 0,1 értékű szám kell legyen
|
||||
Advanced Character Formats Speciális karakterformátumok
|
||||
Horizontal Scale Vízszintes méretezés
|
||||
Vertical Scale Függőleges méretezés
|
||||
@msg {label} must be a number from {min} to {max} {label} {min} és {max} közötti szám kell legyen
|
||||
@msg {label} must be a number above 0 and at most {max} {label} 0-nál nagyobb és legfeljebb {max} értékű szám kell legyen
|
||||
@msg no character style `{name}` A(z) `{name}` karakterstílus nem található
|
||||
|
||||
# Color Effects add-on: Duotone (Effect › Color Effects) and Print Ink Fixes (Object › Plug-ins › Color Effects).
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -5544,6 +5544,11 @@ anchor Points: Punti:
|
||||
Case Maiuscole e minuscole
|
||||
@msg {label} must be a number {label} deve essere un numero
|
||||
@msg {label} must be a number of at least 0.1 {label} deve essere un numero di almeno 0,1
|
||||
Advanced Character Formats Formati carattere avanzati
|
||||
Horizontal Scale Scala orizzontale
|
||||
Vertical Scale Scala verticale
|
||||
@msg {label} must be a number from {min} to {max} {label} deve essere un numero da {min} a {max}
|
||||
@msg {label} must be a number above 0 and at most {max} {label} deve essere un numero maggiore di 0 e al massimo {max}
|
||||
@msg no character style `{name}` Stile di carattere `{name}` non trovato
|
||||
|
||||
# Color Effects add-on: Duotone (Effect › Color Effects) and Print Ink Fixes (Object › Plug-ins › Color Effects).
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -5611,6 +5611,11 @@ anchor Points: ポイント:
|
||||
Case 大文字と小文字
|
||||
@msg {label} must be a number {label} には数値を指定してください
|
||||
@msg {label} must be a number of at least 0.1 {label} には 0.1 以上の数値を指定してください
|
||||
Advanced Character Formats 詳細文字形式
|
||||
Horizontal Scale 水平比率
|
||||
Vertical Scale 垂直比率
|
||||
@msg {label} must be a number from {min} to {max} {label} には {min} から {max} までの数値を指定してください
|
||||
@msg {label} must be a number above 0 and at most {max} {label} には 0 より大きく {max} 以下の数値を指定してください
|
||||
@msg no character style `{name}` 文字スタイル `{name}` が見つかりません
|
||||
|
||||
# Color Effects add-on: Duotone (Effect › Color Effects) and Print Ink Fixes (Object › Plug-ins › Color Effects).
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -5913,6 +5913,11 @@ anchor Points: წერტილები:
|
||||
Case ასოების რეგისტრი
|
||||
@msg {label} must be a number {label} უნდა იყოს რიცხვი
|
||||
@msg {label} must be a number of at least 0.1 {label} უნდა იყოს მინიმუმ 0,1
|
||||
Advanced Character Formats სიმბოლოს დამატებითი ფორმატები
|
||||
Horizontal Scale ჰორიზონტალური მასშტაბი
|
||||
Vertical Scale ვერტიკალური მასშტაბი
|
||||
@msg {label} must be a number from {min} to {max} {label} უნდა იყოს რიცხვი {min}-დან {max}-მდე
|
||||
@msg {label} must be a number above 0 and at most {max} {label} უნდა იყოს 0-ზე მეტი და არაუმეტეს {max}-ისა რიცხვი
|
||||
@msg no character style `{name}` სიმბოლოს სტილი `{name}` ვერ მოიძებნა
|
||||
|
||||
# Color Effects add-on: Duotone (Effect › Color Effects) and Print Ink Fixes (Object › Plug-ins › Color Effects).
|
||||
|
||||
|
Can't render this file because it is too large.
|
@@ -5507,6 +5507,11 @@ anchor Points: 포인트:
|
||||
Case 대소문자
|
||||
@msg {label} must be a number {label}은(는) 숫자여야 합니다
|
||||
@msg {label} must be a number of at least 0.1 {label}은(는) 0.1 이상의 숫자여야 합니다
|
||||
Advanced Character Formats 고급 문자 형식
|
||||
Horizontal Scale 가로 비율
|
||||
Vertical Scale 세로 비율
|
||||
@msg {label} must be a number from {min} to {max} {label}은(는) {min}에서 {max} 사이의 숫자여야 합니다
|
||||
@msg {label} must be a number above 0 and at most {max} {label}은(는) 0 초과 {max} 이하의 숫자여야 합니다
|
||||
@msg no character style `{name}` 문자 스타일 `{name}`을 찾을 수 없습니다
|
||||
|
||||
# Color Effects add-on: Duotone (Effect › Color Effects) and Print Ink Fixes (Object › Plug-ins › Color Effects).
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -5779,6 +5779,11 @@ anchor Points: Punkty:
|
||||
Case Wielkość liter
|
||||
@msg {label} must be a number {label} musi być liczbą
|
||||
@msg {label} must be a number of at least 0.1 {label} musi być liczbą co najmniej 0,1
|
||||
Advanced Character Formats Zaawansowane formaty znaków
|
||||
Horizontal Scale Skala pozioma
|
||||
Vertical Scale Skala pionowa
|
||||
@msg {label} must be a number from {min} to {max} {label} musi być liczbą od {min} do {max}
|
||||
@msg {label} must be a number above 0 and at most {max} {label} musi być liczbą większą od 0 i nie większą niż {max}
|
||||
@msg no character style `{name}` Nie znaleziono stylu znakowego `{name}`
|
||||
|
||||
# Color Effects add-on: Duotone (Effect › Color Effects) and Print Ink Fixes (Object › Plug-ins › Color Effects).
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -5841,6 +5841,11 @@ anchor Points: Pontos:
|
||||
Case Maiúsculas e minúsculas
|
||||
@msg {label} must be a number {label} deve ser um número
|
||||
@msg {label} must be a number of at least 0.1 {label} deve ser um número de pelo menos 0,1
|
||||
Advanced Character Formats Formatos avançados de caracteres
|
||||
Horizontal Scale Escala Horizontal
|
||||
Vertical Scale Escala Vertical
|
||||
@msg {label} must be a number from {min} to {max} {label} deve ser um número de {min} a {max}
|
||||
@msg {label} must be a number above 0 and at most {max} {label} deve ser um número maior que 0 e no máximo {max}
|
||||
@msg no character style `{name}` Estilo de caractere `{name}` não encontrado
|
||||
|
||||
# Color Effects add-on: Duotone (Effect › Color Effects) and Print Ink Fixes (Object › Plug-ins › Color Effects).
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 47.
|
@@ -5902,6 +5902,11 @@ anchor Points: Точки:
|
||||
Case Регистр
|
||||
@msg {label} must be a number {label} должно быть числом
|
||||
@msg {label} must be a number of at least 0.1 {label} должно быть числом не меньше 0,1
|
||||
Advanced Character Formats Дополнительные форматы символов
|
||||
Horizontal Scale Масштаб по горизонтали
|
||||
Vertical Scale Масштаб по вертикали
|
||||
@msg {label} must be a number from {min} to {max} {label} должно быть числом от {min} до {max}
|
||||
@msg {label} must be a number above 0 and at most {max} {label} должно быть числом больше 0 и не более {max}
|
||||
@msg no character style `{name}` Стиль символов `{name}` не найден
|
||||
|
||||
# Color Effects add-on: Duotone (Effect › Color Effects) and Print Ink Fixes (Object › Plug-ins › Color Effects).
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -5977,6 +5977,11 @@ shape Open Öppen
|
||||
Case Skiftläge
|
||||
@msg {label} must be a number {label} måste vara ett tal
|
||||
@msg {label} must be a number of at least 0.1 {label} måste vara ett tal på minst 0,1
|
||||
Advanced Character Formats Avancerade teckenformat
|
||||
Horizontal Scale Horisontell skala
|
||||
Vertical Scale Vertikal skala
|
||||
@msg {label} must be a number from {min} to {max} {label} måste vara ett tal från {min} till {max}
|
||||
@msg {label} must be a number above 0 and at most {max} {label} måste vara ett tal över 0 och högst {max}
|
||||
@msg no character style `{name}` Teckenformatet `{name}` hittades inte
|
||||
|
||||
# Color Effects add-on: Duotone (Effect › Color Effects) and Print Ink Fixes (Object › Plug-ins › Color Effects).
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 5 and column 25.
|
@@ -5882,6 +5882,11 @@ anchor Points: Noktalar:
|
||||
Case Büyük küçük harf
|
||||
@msg {label} must be a number {label} bir sayı olmalıdır
|
||||
@msg {label} must be a number of at least 0.1 {label} en az 0,1 olan bir sayı olmalıdır
|
||||
Advanced Character Formats Gelişmiş karakter biçimleri
|
||||
Horizontal Scale Yatay Ölçek
|
||||
Vertical Scale Dikey Ölçek
|
||||
@msg {label} must be a number from {min} to {max} {label} {min} ile {max} arasında bir sayı olmalıdır
|
||||
@msg {label} must be a number above 0 and at most {max} {label} 0'ın üzerinde ve en fazla {max} olan bir sayı olmalıdır
|
||||
@msg no character style `{name}` `{name}` karakter stili bulunamadı
|
||||
|
||||
# Color Effects add-on: Duotone (Effect › Color Effects) and Print Ink Fixes (Object › Plug-ins › Color Effects).
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 5 and column 25.
|
@@ -5524,6 +5524,11 @@ anchor Points: Точки:
|
||||
Case Регістр
|
||||
@msg {label} must be a number {label} має бути числом
|
||||
@msg {label} must be a number of at least 0.1 {label} має бути числом не менше 0,1
|
||||
Advanced Character Formats Додаткові формати символів
|
||||
Horizontal Scale Масштаб по горизонталі
|
||||
Vertical Scale Масштаб за вертикаллю
|
||||
@msg {label} must be a number from {min} to {max} {label} має бути числом від {min} до {max}
|
||||
@msg {label} must be a number above 0 and at most {max} {label} має бути числом більше 0 і не більше {max}
|
||||
@msg no character style `{name}` Стиль символів `{name}` не знайдено
|
||||
|
||||
# Color Effects add-on: Duotone (Effect › Color Effects) and Print Ink Fixes (Object › Plug-ins › Color Effects).
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 178 and column 5.
|
@@ -5527,6 +5527,11 @@ anchor Points: Điểm:
|
||||
Case Chữ hoa và chữ thường
|
||||
@msg {label} must be a number {label} phải là một số
|
||||
@msg {label} must be a number of at least 0.1 {label} phải là một số ít nhất bằng 0,1
|
||||
Advanced Character Formats Định dạng ký tự nâng cao
|
||||
Horizontal Scale Tỷ lệ ngang
|
||||
Vertical Scale Tỷ lệ dọc
|
||||
@msg {label} must be a number from {min} to {max} {label} phải là một số từ {min} đến {max}
|
||||
@msg {label} must be a number above 0 and at most {max} {label} phải là một số lớn hơn 0 và tối đa {max}
|
||||
@msg no character style `{name}` Không tìm thấy kiểu ký tự `{name}`
|
||||
|
||||
# Color Effects add-on: Duotone (Effect › Color Effects) and Print Ink Fixes (Object › Plug-ins › Color Effects).
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 200 and column 5.
|
@@ -5768,6 +5768,11 @@ anchor Points: 点:
|
||||
Case 大小写
|
||||
@msg {label} must be a number {label} 必须是数字
|
||||
@msg {label} must be a number of at least 0.1 {label} 必须是至少 0.1 的数字
|
||||
Advanced Character Formats 高级字符格式
|
||||
Horizontal Scale 水平缩放
|
||||
Vertical Scale 垂直缩放
|
||||
@msg {label} must be a number from {min} to {max} {label} 必须是 {min} 到 {max} 之间的数字
|
||||
@msg {label} must be a number above 0 and at most {max} {label} 必须是大于 0 且不超过 {max} 的数字
|
||||
@msg no character style `{name}` 找不到字符样式 `{name}`
|
||||
|
||||
# Color Effects add-on: Duotone (Effect › Color Effects) and Print Ink Fixes (Object › Plug-ins › Color Effects).
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -5564,6 +5564,11 @@ anchor Points: 點:
|
||||
Case 大小寫
|
||||
@msg {label} must be a number {label} 必須是數字
|
||||
@msg {label} must be a number of at least 0.1 {label} 必須是至少 0.1 的數字
|
||||
Advanced Character Formats 進階字元格式
|
||||
Horizontal Scale 水平縮放
|
||||
Vertical Scale 垂直縮放
|
||||
@msg {label} must be a number from {min} to {max} {label} 必須是 {min} 到 {max} 之間的數字
|
||||
@msg {label} must be a number above 0 and at most {max} {label} 必須是大於 0 且不超過 {max} 的數字
|
||||
@msg no character style `{name}` 找不到字元樣式 `{name}`
|
||||
|
||||
# Color Effects add-on: Duotone (Effect › Color Effects) and Print Ink Fixes (Object › Plug-ins › Color Effects).
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 47.
|
@@ -229,7 +229,7 @@ pub fn enabled(app: &VectorcraftApp, id: &str) -> bool {
|
||||
"ui.graphicStyleOptions" | "ui.charStyleOptions" | "ui.newCharStyle" | "ui.newParaStyle" => app.session.active().is_some(),
|
||||
"ui.charStyleOptions.resetPanel" => app.ui.dialog.as_ref().is_some_and(|d| {
|
||||
(d.kind == crate::dialogs::char_style_options::KIND || (d.kind == crate::dialogs::new_text_style::KIND && !d.bool("__para")))
|
||||
&& d.str("section") == "basic"
|
||||
&& crate::dialogs::char_style_options::can_reset(d)
|
||||
}),
|
||||
"ui.newArtboard" => app.session.active().is_some(),
|
||||
"ui.sampleScreenColor" => crate::screen_color::available(app),
|
||||
|
||||
@@ -667,7 +667,7 @@ pub const UI_COMMANDS: &[(&str, &str, &str, &str)] = &[
|
||||
"ui.charStyleOptions.resetPanel",
|
||||
"Reset Panel",
|
||||
"",
|
||||
"{} clear the Basic Character Formats settings in the open Character Style Options or New Character Style dialog; available only on that section",
|
||||
"{} clear the settings of the section shown, Basic Character Formats or Advanced Character Formats, in the open Character Style Options or New Character Style dialog; available only on those sections",
|
||||
),
|
||||
(
|
||||
"ui.newCharStyle",
|
||||
|
||||
+10
-4
@@ -5296,16 +5296,22 @@ Character Style Options uses `charStyle.edit {name, to, attrs}` to replace the n
|
||||
attribute map in one undo step. `to` is trimmed and must be unique. The command updates text using the style
|
||||
while preserving local overrides; Undo/Redo restores the definition and its users. The `charStyleOptions`
|
||||
dialog exposes `name`, `font_family`, `font_style`, `size`, `leading`, `kerning`, `tracking`, `case`,
|
||||
`position`, `underline`, `strikethrough`, `charAlign`, `section`, and `preview`. An empty field leaves its
|
||||
`position`, `underline`, `strikethrough`, `charAlign`, `h_scale`, `v_scale`, `baseline_shift`, `rotation`,
|
||||
`language`, `section` (`general`, `basic` or `advanced`), and `preview`. An empty field leaves its
|
||||
attribute unset. `leading` and `kerning` also accept `Auto` to store an explicit automatic value.
|
||||
Advanced Character Formats holds `h_scale` and `v_scale` (percent, above 0 and at most 4000000),
|
||||
`baseline_shift` (points, -4000000 to 4000000), `rotation` (degrees, stored from -180 to 180) and `language` (a name or tag from Document
|
||||
Setup's languages; English: USA stores the explicit default `null`, and a tag not in the list that the style
|
||||
already has is kept). Language sets the language the characters are shaped in; it does not change hyphenation
|
||||
or spelling.
|
||||
The New Character Style dialog accepts the same character fields and starts with the selected text's
|
||||
captured attributes. Without selected text, it starts with the character defaults for the interface language
|
||||
(including `charAlign: "emBoxCenter"` in Japanese); changing a Basic field keeps other seeded attributes.
|
||||
Without selected text, the New Paragraph Style dialog sends no `attrs`, and the engine gives the new style the
|
||||
paragraph defaults for the interface language. Reset Panel in Basic Character
|
||||
Formats removes those fields from the definition.
|
||||
`ui.charStyleOptions.resetPanel {}` performs that reset when Basic Character Formats is selected in
|
||||
either character-style dialog; it is disabled on General.
|
||||
Formats removes those fields from the definition, and Reset Panel in Advanced Character Formats removes its five.
|
||||
`ui.charStyleOptions.resetPanel {}` performs that reset for the section selected in either character-style
|
||||
dialog; it is disabled on General.
|
||||
|
||||
In the native file a text object's `para` holds the first paragraph's attributes and `paras` (left out when every
|
||||
paragraph is alike) one entry per paragraph, so older readers keep the first paragraph's. SVG export anchors each
|
||||
|
||||
@@ -502,6 +502,7 @@ since 2026-10-01.
|
||||
- **OpenType SVG colour fonts:** each glyph's SVG document is read by the SVG importer (registered by the engine) into vector art in the run's colours, then drawn, exported and outlined as COLR glyphs are; PDF real text writes them as text that draws nothing and draws their art where they sit. Measured by `tests_svg_glyphs`, `svg/tests/it/svg_glyphs.rs`, `tests_colourfonts::create_outlines_turns_svg_glyphs_into_groups`, `tests_colourfonts::pdf_real_text_draws_svg_glyphs_as_art_and_keeps_their_characters`. Scores unchanged (estimated).
|
||||
- **Character-style Auto leading:** capture and explicit style attributes retain Auto as a reset, distinct from omitted leading. Redefine and later numeric updates preserve local leading overrides; the Character Styles marker recognizes Auto correctly. Regression assertions cover partial definitions, Undo/Redo and native save/load serialization. Public references: [Character and paragraph styles](https://helpx.adobe.com/illustrator/using/character-paragraph-styles.html) and [Leading](https://helpx.adobe.com/illustrator/using/line-character-spacing.html).
|
||||
- **Character Style Options, General and Basic Character Formats:** the dialog edits a style's name and stored character formats together. Empty fields remain unset, checkboxes show an indeterminate unset state, Basic's Reset Panel clears its explicit settings, and Preview can be canceled without changing history. OK saves one undo step; New Character Style offers the same fields from captured text or the interface language's defaults without a selection. Public reference: [Character and paragraph styles](https://helpx.adobe.com/illustrator/using/character-paragraph-styles.html).
|
||||
- **Character Style Options, Advanced Character Formats:** Character Style Options and New Character Style set a style's Horizontal Scale and Vertical Scale (above 0% and at most 4,000,000%), Baseline Shift (-4,000,000 pt to 4,000,000 pt), Character Rotation (stored from -180° to 180°) and Language; text that uses the style takes them and keeps its local overrides. Empty fields remain unset, an explicit 100%, 0° or English: USA stays distinct from unset, and the section's Reset Panel clears only these five. Language sets the language the characters are shaped in, not hyphenation or spelling. Public reference: [Character and paragraph styles](https://helpx.adobe.com/illustrator/using/character-paragraph-styles.html) ("Adjust the Horizontal and Vertical Scale, Baseline Shift, Character Rotation, and Language options").
|
||||
- **Text styles redefined to the defaults:** New and Redefine Character Style and Paragraph Style capture every attribute of the selected text, including those at their defaults (Auto kerning, Roman baseline alignment, the default Hyphenation options, the Every-line Composer, no tab stops), and `charStyle.setAttrs` and `paraStyle.setAttrs` keep an attribute set to its default value. After a redefinition, text whose value still equals the old definition's takes the new value, a default included; local overrides stay, and text that still has the old value of an attribute the new definition leaves out goes back to the Normal style's value. The style panels show "+" only where the text differs from its style, including type with Auto alignment. Public reference: [Character and paragraph styles](https://web.archive.org/web/20260825113735/https://helpx.adobe.com/illustrator/using/character-paragraph-styles.html).
|
||||
- **Text styles dragged onto the panel's buttons:** a style dragged from the Character Styles or Paragraph Styles panel onto Create New Style is copied (`charStyle.duplicate`, `paraStyle.duplicate`, which name the copy `<name> copy`) and the copy is highlighted; dragged onto Delete it is deleted (`charStyle.delete`, `paraStyle.delete`); the Normal style is not deleted. Each drop is one undo step. Public reference: [Character and paragraph styles](https://web.archive.org/web/20261015081534/https://helpx.adobe.com/illustrator/using/character-paragraph-styles.html).
|
||||
- **Auto Leading outside the layout:** a paragraph's Justification… › Auto Leading (<https://helpx.adobe.com/illustrator/desktop/design-with-text/edit-format-text/adjust-word-and-letterspacing-in-justified-text.html>) also sets the Leading the Character panel shows for Auto type (blank where the selected paragraphs differ), the leading the Alt+↑/↓ nudge (`type.step`) steps from, each paragraph's own, and the lines Fill with Placeholder Text counts in area type. Evidence: `ParaStyle::effective_leading` (`crates/doc/src/text.rs` test `paragraph_effective_leading_follows_auto_leading`), `tests_prefs::auto_leading_steps_and_fills_from_the_paragraph_auto_leading`, `panels::character` test `leading_field_shows_the_paragraph_auto_leading`.
|
||||
|
||||
Reference in New Issue
Block a user