mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-14 19:22:03 +00:00
Validate character style numeric dimensions
Squashed from #5540 onto main.
This commit is contained in:
@@ -51,14 +51,14 @@ const fn spec(id: &'static str, label: &'static str, menu: &'static [&'static st
|
||||
}
|
||||
|
||||
macro_rules! style_cmds {
|
||||
($kind:expr, $p:literal, $menu:literal, $what:literal) => {
|
||||
($kind:expr, $p:literal, $menu:literal, $what:literal, $bounds:literal) => {
|
||||
vec![
|
||||
spec(concat!($p, ".list"), concat!($what, " Styles"), &[], "{} → {styles: [{name, attrs, uses}]}", |s, _| list(s, $kind), false),
|
||||
spec(
|
||||
concat!($p, ".new"),
|
||||
concat!("New ", $what, " Style"),
|
||||
&["Window", "Type", $menu],
|
||||
"{name? (trimmed, as rename trims `to`), attrs?: {…} (default: selected text), id?, start?: byte, end?: byte} → {name}; a nonempty character range samples its first selected character; start alone keeps insertion affinity; paragraph styles sample start",
|
||||
concat!("{name? (trimmed, as rename trims `to`), attrs?: {…} (default: selected text), id?, start?: byte, end?: byte} → {name}; a nonempty character range samples its first selected character; start alone keeps insertion affinity; paragraph styles sample start", $bounds),
|
||||
|s, p| new(s, p, $kind),
|
||||
true,
|
||||
),
|
||||
@@ -82,7 +82,7 @@ macro_rules! style_cmds {
|
||||
concat!($p, ".setAttrs"),
|
||||
concat!($what, " Style Options"),
|
||||
&[],
|
||||
"{name, attrs: {…}} replace the style's attributes; users keep their overrides",
|
||||
concat!("{name, attrs: {…}} replace the style's attributes; users keep their overrides", $bounds),
|
||||
|s, p| set_attrs(s, p, $kind),
|
||||
true,
|
||||
),
|
||||
@@ -116,16 +116,22 @@ macro_rules! style_cmds {
|
||||
}
|
||||
|
||||
pub fn specs() -> Vec<CommandSpec> {
|
||||
let mut v = style_cmds!(Kind::Char, "charStyle", "Character Styles", "Character");
|
||||
let mut v = style_cmds!(
|
||||
Kind::Char,
|
||||
"charStyle",
|
||||
"Character Styles",
|
||||
"Character",
|
||||
"; changed numeric attrs: size 0.1..=1296 pt, explicit leading 0.1..=5000 pt, h/v scale 1..=10000%, baseline shift and Tate-chu-yoko offsets -1296..=1296 pt"
|
||||
);
|
||||
v.push(spec(
|
||||
"charStyle.edit",
|
||||
"Edit Character Style",
|
||||
&[],
|
||||
"{name, to, attrs: {…}} replace name and sparse attributes in one undo step; users keep local overrides",
|
||||
"{name, to, attrs: {…}} replace name and sparse attributes in one undo step; users keep local overrides; changed numeric attrs: size 0.1..=1296 pt, explicit leading 0.1..=5000 pt, h/v scale 1..=10000%, baseline shift and Tate-chu-yoko offsets -1296..=1296 pt",
|
||||
|s, p| edit_style(s, p, Kind::Char),
|
||||
true,
|
||||
));
|
||||
v.extend(style_cmds!(Kind::Para, "paraStyle", "Paragraph Styles", "Paragraph"));
|
||||
v.extend(style_cmds!(Kind::Para, "paraStyle", "Paragraph Styles", "Paragraph", ""));
|
||||
v
|
||||
}
|
||||
|
||||
@@ -494,6 +500,22 @@ fn attrs_param(d: &Document, p: &Value, kind: Kind, verb: &str, kept: Option<&Ma
|
||||
Kind::Char => {
|
||||
let mut st = with_attrs(&CharStyle::default(), m, &c)?;
|
||||
let changed = |key: &str| m.contains_key(key) && kept.and_then(|k| k.get(key)) != m.get(key);
|
||||
for (key, value, lo, hi) in
|
||||
[("size", st.size, 0.1, 1296.0), ("h_scale", st.h_scale, 1.0, 10000.0), ("v_scale", st.v_scale, 1.0, 10000.0)]
|
||||
{
|
||||
if changed(key) && !(lo..=hi).contains(&value) {
|
||||
return Err(bad(&c, format!("`{key}` must be from {lo} to {hi}")));
|
||||
}
|
||||
}
|
||||
if changed("leading") && st.leading.is_some_and(|value| !(0.1..=5000.0).contains(&value)) {
|
||||
return Err(bad(&c, "`leading` must be from 0.1 to 5000, or null for Auto"));
|
||||
}
|
||||
let shift_max = 1296.0;
|
||||
for (key, value) in [("baseline_shift", st.baseline_shift), ("tcyUpDown", st.tcy_up_down), ("tcyLeftRight", st.tcy_left_right)] {
|
||||
if changed(key) && !(-shift_max..=shift_max).contains(&value) {
|
||||
return Err(bad(&c, format!("`{key}` must be from {} to {shift_max}", -shift_max)));
|
||||
}
|
||||
}
|
||||
// A language of the list, stored as its tag (as text.setFormat takes it).
|
||||
if let Some(l) = &st.language
|
||||
&& kept.and_then(|k| k.get("language")) != m.get("language")
|
||||
|
||||
@@ -335,6 +335,54 @@ fn character_style_color_and_features_are_checked() {
|
||||
assert_eq!(s.doc().unwrap().history.undo.len(), undo);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn character_style_dimensions_are_checked_before_a_definition_changes() {
|
||||
let (mut s, _) = session_with_text("Text");
|
||||
s.execute("charStyle.new", &json!({"name": "Body", "attrs": {"size": 24.0}})).unwrap();
|
||||
let before = s.doc().unwrap().doc.clone();
|
||||
let undo = s.doc().unwrap().history.undo.len();
|
||||
for attrs in [
|
||||
json!({"size": 0.0}),
|
||||
json!({"size": 1296.1}),
|
||||
json!({"leading": 0.0}),
|
||||
json!({"leading": 5000.1}),
|
||||
json!({"h_scale": 0.0}),
|
||||
json!({"h_scale": 0.9}),
|
||||
json!({"h_scale": 10000.1}),
|
||||
json!({"v_scale": 0.0}),
|
||||
json!({"v_scale": 0.9}),
|
||||
json!({"v_scale": 10000.1}),
|
||||
json!({"baseline_shift": -1296.1}),
|
||||
json!({"baseline_shift": 1296.1}),
|
||||
json!({"tcyUpDown": 1296.1}),
|
||||
json!({"tcyLeftRight": -1296.1}),
|
||||
] {
|
||||
let key = attrs.as_object().unwrap().keys().next().unwrap();
|
||||
for cmd in ["charStyle.edit", "charStyle.setAttrs", "charStyle.new"] {
|
||||
let params = match cmd {
|
||||
"charStyle.edit" => json!({"name": "Body", "to": "Body", "attrs": attrs}),
|
||||
"charStyle.new" => json!({"name": "Other", "attrs": attrs}),
|
||||
_ => json!({"name": "Body", "attrs": attrs}),
|
||||
};
|
||||
let error = s.execute(cmd, ¶ms).unwrap_err().to_string();
|
||||
assert!(error.contains(&format!("`{key}`")), "{cmd} {attrs}: {error}");
|
||||
}
|
||||
}
|
||||
assert_eq!(s.doc().unwrap().doc, before);
|
||||
assert_eq!(s.doc().unwrap().history.undo.len(), undo);
|
||||
|
||||
s.execute(
|
||||
"charStyle.setAttrs",
|
||||
&json!({"name": "Body", "attrs": {
|
||||
"size": 1296.0, "leading": 5000.0,
|
||||
"h_scale": 1.0, "v_scale": 10000.0,
|
||||
"baseline_shift": -1296.0,
|
||||
"tcyUpDown": 1296.0, "tcyLeftRight": -1296.0
|
||||
}}),
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn text_style_rejects_doubly_signed_feature_tags() {
|
||||
let (mut s, id) = session_with_text("Text");
|
||||
|
||||
@@ -264,23 +264,29 @@ fn optional_number(d: &Dialog, key: &str) -> Result<Option<Value>, String> {
|
||||
.or_else(|| v.as_str().and_then(|s| s.trim().parse::<f64>().ok()))
|
||||
.filter(|n| n.is_finite())
|
||||
.ok_or_else(|| format!("{label} must be a number"))?;
|
||||
// Character Style fields use the Character panel's visible ranges.
|
||||
let max = match key {
|
||||
"size" => 1296.0,
|
||||
"leading" => 5000.0,
|
||||
_ => vectorcraft_doc::MAX_COORD,
|
||||
};
|
||||
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}"));
|
||||
if ["size", "leading"].contains(&key) && n > max {
|
||||
return Err(format!("{label} must be a number 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}"));
|
||||
if ["h_scale", "v_scale"].contains(&key) && !(1.0..=10000.0).contains(&n) {
|
||||
return Err(format!("{label} must be a number from 1 to 10000"));
|
||||
}
|
||||
if key == "baseline_shift" && !(-1296.0..=1296.0).contains(&n) {
|
||||
return Err(format!("{label} must be a number from -1296 to 1296"));
|
||||
}
|
||||
// Weight from 0 to the document limit, a tint from 0% to 100%.
|
||||
let (min, max) = match key {
|
||||
"stroke_width" => (0.0, max),
|
||||
"stroke_width" => (0.0, vectorcraft_doc::MAX_COORD),
|
||||
"fill_tint" | "stroke_tint" => (0.0, 100.0),
|
||||
_ => (min, max),
|
||||
_ => (0.0, 0.0),
|
||||
};
|
||||
if ["stroke_width", "fill_tint", "stroke_tint"].contains(&key) && !(min..=max).contains(&n) {
|
||||
return Err(format!("{label} must be a number from {min} to {max}"));
|
||||
@@ -912,14 +918,17 @@ fn body(app: &mut VectorcraftApp, ui: &mut egui::Ui, d: &mut Dialog) -> bool {
|
||||
let params = json!({"name": d.str("__style"), "to": d.str("name"), "attrs": attrs});
|
||||
form::preview(app, ui, d, tl!("Character Style Options"), "charStyle.edit", params);
|
||||
}
|
||||
Err(why) => {
|
||||
ui.add_space(6.0);
|
||||
ui.colored_label(crate::theme::Tokens::get(ui.ctx()).error, crate::i18n::message(crate::i18n::current(), &why));
|
||||
}
|
||||
Err(why) => attrs_error(ui, &why),
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
/// The message `edited_attrs` gave for a field value it does not accept, drawn under the dialog.
|
||||
pub(crate) fn attrs_error(ui: &mut egui::Ui, why: &str) {
|
||||
ui.add_space(6.0);
|
||||
ui.colored_label(crate::theme::Tokens::get(ui.ctx()).error, crate::i18n::message(crate::i18n::current(), why));
|
||||
}
|
||||
|
||||
fn confirm(app: &mut VectorcraftApp, d: &Dialog) -> Result<Value, String> {
|
||||
let attrs = edited_attrs(d)?;
|
||||
form::commit_preview(app, "charStyle.edit", json!({"name": d.str("__style"), "to": d.str("name"), "attrs": attrs}))
|
||||
@@ -1135,13 +1144,15 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn advanced_numbers_outside_their_limits_are_errors() {
|
||||
fn character_style_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"),
|
||||
("size", "1296.1", "Size must be a number at most 1296"),
|
||||
("leading", "5000.1", "Leading must be a number at most 5000"),
|
||||
("h_scale", "0", "Horizontal Scale must be a number from 1 to 10000"),
|
||||
("h_scale", "0.9", "Horizontal Scale must be a number from 1 to 10000"),
|
||||
("v_scale", "10000.1", "Vertical Scale must be a number from 1 to 10000"),
|
||||
("baseline_shift", "1296.1", "Baseline Shift must be a number from -1296 to 1296"),
|
||||
("baseline_shift", "-1296.1", "Baseline Shift must be a number from -1296 to 1296"),
|
||||
("rotation", "inf", "Character Rotation must be a number"),
|
||||
("h_scale", "wide", "Horizontal Scale must be a number"),
|
||||
] {
|
||||
@@ -1150,10 +1161,15 @@ mod tests {
|
||||
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")] {
|
||||
for (key, value) in
|
||||
[("size", "1296"), ("leading", "5000"), ("h_scale", "1"), ("v_scale", "10000"), ("baseline_shift", "-1296"), ("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})));
|
||||
assert_eq!(
|
||||
edited_attrs(&d).unwrap(),
|
||||
attrs(json!({"size": 1296.0, "leading": 5000.0, "h_scale": 1.0, "v_scale": 10000.0, "baseline_shift": -1296.0, "rotation": 160.0}))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -1514,6 +1530,40 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn new_character_style_shows_size_and_leading_errors_before_ok() {
|
||||
use egui_kittest::kittest::Queryable;
|
||||
for (key, value) in [("size", json!(0)), ("leading", json!(0.05))] {
|
||||
let mut app = app();
|
||||
app.run("ui.newCharStyle", json!({})).unwrap();
|
||||
let why = {
|
||||
let d = app.ui.dialog.as_mut().unwrap();
|
||||
d.fields.insert("section".into(), json!("basic"));
|
||||
d.fields.insert(key.into(), value);
|
||||
edited_attrs(d).unwrap_err()
|
||||
};
|
||||
let shown = crate::i18n::message(crate::i18n::current(), &why);
|
||||
let mut fonts = false;
|
||||
let mut h = egui_kittest::Harness::builder().with_size(egui::vec2(1100.0, 900.0)).with_accessibility_check(false).build_ui_state(
|
||||
move |ui, app: &mut VectorcraftApp| {
|
||||
if !std::mem::replace(&mut fonts, true) {
|
||||
crate::theme::install_fonts(ui.ctx());
|
||||
return;
|
||||
}
|
||||
super::super::show(app, ui.ctx());
|
||||
},
|
||||
app,
|
||||
);
|
||||
h.run_steps(4);
|
||||
assert!(h.query_by_label(&shown).is_some(), "{key}: {shown} is not shown");
|
||||
let app = h.state_mut();
|
||||
let styles = app.session.doc().unwrap().doc.char_styles.len();
|
||||
assert!(super::super::confirm(app).is_err(), "{key} confirm");
|
||||
assert!(app.ui.dialog.is_some(), "{key} dialog stays open");
|
||||
assert_eq!(app.session.doc().unwrap().doc.char_styles.len(), styles, "{key} style count");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_number_keeps_last_valid_preview() {
|
||||
let mut app = app();
|
||||
|
||||
@@ -80,6 +80,9 @@ fn body(_: &mut VectorcraftApp, ui: &mut egui::Ui, d: &mut Dialog) -> bool {
|
||||
}
|
||||
});
|
||||
super::char_style_options::reset_button(ui, d);
|
||||
if let Err(why) = super::char_style_options::edited_attrs(d) {
|
||||
super::char_style_options::attrs_error(ui, &why);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
grid(ui, |ui| {
|
||||
|
||||
+5
-2
@@ -5615,8 +5615,8 @@ dialog exposes `name`, `font_family`, `font_style`, `size`, `leading`, `kerning`
|
||||
`ot_figure`, `ot_position`, `ot_liga`, `ot_calt`, `ot_dlig`, `ot_swsh`, `ot_salt`, `ot_titl`, `ot_ordn`, `ot_frac`,
|
||||
`ot_sets`, `section` (`general`, `basic`, `advanced`, `color` or `opentype`), 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
|
||||
Advanced Character Formats holds `h_scale` and `v_scale` (percent, 1 to 10000),
|
||||
`baseline_shift` (points, -1296 to 1296), `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.
|
||||
@@ -5637,6 +5637,9 @@ its features turned off, distinct from unset.
|
||||
`charStyle.new`, `charStyle.setAttrs` and `charStyle.edit` reject a tint outside 0 to 1, a link to a swatch that is
|
||||
not a global swatch of the document, a stroke weight outside 0 to 4000000, more than 256 feature tags and an
|
||||
OpenType feature tag the shaper does not know, unless the style or the text it is made from already has that value.
|
||||
They also reject a size outside 0.1 to 1296, explicit leading outside 0.1 to 5000, horizontal or vertical scale
|
||||
outside 1 to 10000, and baseline shift or Tate-chu-yoko offsets outside -1296 to 1296. `leading: null` keeps
|
||||
Auto leading. An invalid value leaves the style, its text, and Undo history unchanged.
|
||||
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.
|
||||
|
||||
@@ -518,8 +518,9 @@ since 2026-10-01.
|
||||
- **COLR colour fonts:** v0 and v1 glyphs draw as vector layers (solid colours, linear and radial gradients, clips, blend modes, the foreground colour from the run's fill) on the canvas, as paths in SVG, EPS, legacy `.ai` and PDF with outlined text, as Type 3 text in PDF, and as editable groups with Create Outlines; sweep gradients and Porter-Duff compositing draw the glyph as an image. Measured by `tests_colr`, `render::tests_colrglyphs`, `pdf::tests::colr_glyphs_export_as_vector_art`, `svg/tests/it/colour_glyphs.rs`, `eps` `colr_glyphs_*`, `tests_pathops::create_outlines_turns_colr_glyphs_into_layer_groups`. Scores unchanged (estimated).
|
||||
- **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 numeric bounds:** `charStyle.new`, `charStyle.edit`, and `charStyle.setAttrs` accept size from 0.1 to 1296 pt, explicit leading from 0.1 to 5000 pt, horizontal and vertical scale from 1% to 10,000%, and baseline shift and Tate-chu-yoko offsets from -1296 to 1296 pt. Out-of-range input leaves the style and Undo history unchanged; Auto leading remains available. These limits match the Character panel controls.
|
||||
- **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).
|
||||
- **Character Style Options, Advanced Character Formats:** Character Style Options and New Character Style set a style's Horizontal Scale and Vertical Scale (1% to 10,000%), Baseline Shift (-1296 pt to 1296 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).
|
||||
- **Character Style Options, Character Color and OpenType Features:** Character Style Options and New Character Style set a style's fill and stroke (None or a document swatch; a global swatch stays linked, with a Tint from 0% to 100%), stroke Weight, Overprint Fill and Overprint Stroke, and its Figure, Position, Standard and Discretionary Ligatures, Contextual, Stylistic and Titling Alternates, Swash, Ordinals, Fractions and Stylistic Sets 1 to 20; text that uses the style takes them and keeps its local overrides, and editing a global swatch later updates the style and its text. Empty controls remain unset, an explicit None, off, Default Figure or Default Position stays distinct from unset, each stylistic set is on, off or unset, a gradient or pattern paint stays until another paint is chosen, a changed OpenType control keeps the style's other feature tags, and each section's Reset Panel clears only its own attributes. Public references: [Character and paragraph styles](https://helpx.adobe.com/illustrator/using/character-paragraph-styles.html) and [Format text with OpenType features](https://helpx.adobe.com/illustrator/desktop/design-with-text/special-characters-glyphs/format-text-with-opentype-features.html).
|
||||
- **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).
|
||||
|
||||
Reference in New Issue
Block a user