diff --git a/crates/engine/src/cmd/textstyles.rs b/crates/engine/src/cmd/textstyles.rs index 08426e6c6..9b3b14456 100644 --- a/crates/engine/src/cmd/textstyles.rs +++ b/crates/engine/src/cmd/textstyles.rs @@ -366,11 +366,27 @@ pub(crate) fn selected_style_attrs(s: &Session, para: bool) -> Result Option> { + selection_attrs(s, target, if para { Kind::Para } else { Kind::Char }).ok().flatten() +} + /// Whether the document has a character (`para`: paragraph) style `name`, and its attributes. pub(crate) fn style_attrs(d: &Document, para: bool, name: &str) -> Option> { if para { Kind::Para } else { Kind::Char }.attrs(d, name) } +/// The name `charStyle.new` (`para`: `paraStyle.new`) gives a style when it is given none: the +/// first "Character Style N" ("Paragraph Style N") that no character style (paragraph style) of the +/// document has. +pub fn new_style_name(d: &Document, para: bool) -> String { + let kind = if para { Kind::Para } else { Kind::Char }; + let base = if para { "Paragraph Style" } else { "Character Style" }; + (1..).map(|i| format!("{base} {i}")).find(|n| kind.attrs(d, n).is_none()).unwrap_or_else(|| base.to_string()) +} + 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`")) } @@ -436,10 +452,9 @@ fn new(s: &mut Session, p: &Value, kind: Kind) -> Result { }, }; let d = &s.doc()?.doc; - let base = if kind == Kind::Char { "Character Style" } else { "Paragraph Style" }; let name = match str_param(p, "name") { Some(n) => n.trim().to_string(), - None => (1..).map(|i| format!("{base} {i}")).find(|n| kind.attrs(d, n).is_none()).unwrap_or_else(|| base.to_string()), + None => new_style_name(d, kind == Kind::Para), }; if name.is_empty() { return Err(bad(&c, "give the style a name")); diff --git a/crates/ui-egui/src/dialogs.rs b/crates/ui-egui/src/dialogs.rs index 2e6b50cb1..3ca9da699 100644 --- a/crates/ui-egui/src/dialogs.rs +++ b/crates/ui-egui/src/dialogs.rs @@ -62,6 +62,7 @@ pub mod new_brush; pub mod new_color_group; mod new_document; pub mod new_swatch; +pub mod new_text_style; pub mod office_export; pub mod package; pub(crate) mod path_ops; @@ -298,6 +299,7 @@ registry! { GradientStop: ["gradientStop"] => gradient_stop::SPEC, ColorPicker: [color_picker::KIND] => color_picker::SPEC, GraphicStyleOptions: [graphic_style_options::KIND] => graphic_style_options::SPEC, + NewTextStyle: [new_text_style::KIND] => new_text_style::SPEC, EffectExists: [effect::EXISTS] => effect::EXISTS_SPEC, ColorGuideOptions: [color_guide_options::KIND] => color_guide_options::SPEC, ColorBalance: [color_balance::KIND] => color_balance::SPEC, diff --git a/crates/ui-egui/src/dialogs/new_text_style.rs b/crates/ui-egui/src/dialogs/new_text_style.rs new file mode 100644 index 000000000..1949f0054 --- /dev/null +++ b/crates/ui-egui/src/dialogs/new_text_style.rs @@ -0,0 +1,60 @@ +//! New Character Style and New Paragraph Style: the new style's name. OK makes the style from the +//! text that was selected when the dialog opened (`charStyle.new`, `paraStyle.new`). +//! +//! Fields: `name`, `__para` (true for a paragraph style) and `__target` (the `id`, `start` and +//! `end` of the Type tool's selection, empty for the selected objects, and the `attrs` that text had +//! when the dialog opened). + +use serde_json::{Value, json}; + +use super::swatch_options::{grid, label}; +use super::{DialogSpec, form}; +use crate::VectorcraftApp; +use crate::state::Dialog; + +/// The dialog kind of New Character Style and New Paragraph Style. +pub const KIND: &str = "newTextStyle"; + +pub(super) const SPEC: DialogSpec = DialogSpec { heading, body, confirm, min_width: 320.0, ..DialogSpec::FORM }; + +fn para(d: &Dialog) -> bool { + d.fields.get("__para").and_then(Value::as_bool).unwrap_or(false) +} + +fn heading(d: &Dialog) -> String { + if para(d) { tl!("New Paragraph Style") } else { tl!("New Character Style") }.into() +} + +/// Open New Character Style (`para`: New Paragraph Style) with the name `charStyle.new` would give +/// the style, to make it from the text `target` names. +pub fn open(app: &mut VectorcraftApp, para: bool, target: Value) -> Result { + let d = &app.session.active().ok_or("no document open")?.doc; + let name = vectorcraft_engine::cmd::textstyles::new_style_name(d, para); + let mut target = target; + if let Some(attrs) = vectorcraft_engine::cmd::textstyles::target_style_attrs(&app.session, &target, para) { + target["attrs"] = Value::Object(attrs); + } + app.ui.dialog = Some(Dialog::new(KIND, json!({ "name": name, "__para": para, "__target": target }))); + Ok(json!({ "dialog": KIND })) +} + +fn body(_: &mut VectorcraftApp, ui: &mut egui::Ui, d: &mut Dialog) -> bool { + grid(ui, |ui| { + label(ui, tl!("Style Name:")); + form::text(ui, d, "name", 190.0); + ui.end_row(); + }); + false +} + +/// OK: make the style with the name. A name another style has, or an empty one, keeps the dialog +/// open. +fn confirm(app: &mut VectorcraftApp, d: &Dialog) -> Result { + let mut params = d.fields.get("__target").filter(|t| t.is_object()).cloned().unwrap_or_else(|| json!({})); + params["name"] = json!(d.str("name")); + let r = app.run(if para(d) { "paraStyle.new" } else { "charStyle.new" }, params); + if r.is_ok() { + app.ui.dialog = None; + } + r +} diff --git a/crates/ui-egui/src/dialogs/tests.rs b/crates/ui-egui/src/dialogs/tests.rs index d409c360c..849de1326 100644 --- a/crates/ui-egui/src/dialogs/tests.rs +++ b/crates/ui-egui/src/dialogs/tests.rs @@ -393,6 +393,7 @@ fn dialogs_do_not_stretch_to_the_screen() { ("ui.swatchOptions", json!({"name": "White"})), ("ui.colorPicker", json!({})), ("ui.graphicStyleOptions", json!({})), + ("ui.newCharStyle", json!({})), ("ui.newArtboard", json!({})), ("ui.brushOptions", json!({"name": "3 pt. Round"})), ("ui.brushOptions", json!({"name": "Bristle Round"})), diff --git a/crates/ui-egui/src/i18n/bg.tsv b/crates/ui-egui/src/i18n/bg.tsv index 984412702..760e89f77 100644 --- a/crates/ui-egui/src/i18n/bg.tsv +++ b/crates/ui-egui/src/i18n/bg.tsv @@ -1963,6 +1963,7 @@ font-technology Variable Променливи Brush Art Графика на четка New Calligraphic Brush Нова калиграфска четка New Character Style Нов стил на знак + New Character Style… Нов стил на знак… New Color Group Нова цветова група New Color Group… Нова цветова група… New Document Presets Предварителни настройки за нов документ @@ -1978,6 +1979,7 @@ font-technology Variable Променливи New Opacity Masks Are Clipping Новите маски за непрозрачност са изрязващи New Opacity Masks Are Inverted Новите маски за непрозрачност са инвертирани New Paragraph Style Нов стил на абзац + New Paragraph Style… Нов стил на абзац… New Pattern Brush from Selection Нова щампова четка от селекцията New PDF Preset Нова предварителна настройка за PDF New Print Preset Нова предварителна настройка за печат diff --git a/crates/ui-egui/src/i18n/cs.tsv b/crates/ui-egui/src/i18n/cs.tsv index 73c1d312a..7aa201da0 100644 --- a/crates/ui-egui/src/i18n/cs.tsv +++ b/crates/ui-egui/src/i18n/cs.tsv @@ -4038,7 +4038,9 @@ font-technology Variable Variabilní Mojikumi Mojikumi N pt per N pt na New Character Style Nový styl znaků + New Character Style… Nový styl znaků… New Paragraph Style Nový styl odstavce + New Paragraph Style… Nový styl odstavce… Paragraph Style Options Možnosti stylu odstavce Redefine Character Style Předefinovat styl znaků Redefine Paragraph Style Předefinovat styl odstavce diff --git a/crates/ui-egui/src/i18n/da.tsv b/crates/ui-egui/src/i18n/da.tsv index bc891b84b..e0ca1db88 100644 --- a/crates/ui-egui/src/i18n/da.tsv +++ b/crates/ui-egui/src/i18n/da.tsv @@ -3773,7 +3773,9 @@ font-technology Variable Variable Mojikumi Mojikumi N pt per N pt per New Character Style Ny tegnstil + New Character Style… Ny tegnstil… New Paragraph Style Ny afsnitsstil + New Paragraph Style… Ny afsnitsstil… Paragraph Style Options Indstillinger for afsnitsstil Redefine Character Style Omdefinér tegnstil Redefine Paragraph Style Omdefinér afsnitsstil diff --git a/crates/ui-egui/src/i18n/de.tsv b/crates/ui-egui/src/i18n/de.tsv index 6bb0bdda2..54e8c7c93 100644 --- a/crates/ui-egui/src/i18n/de.tsv +++ b/crates/ui-egui/src/i18n/de.tsv @@ -1976,6 +1976,7 @@ file-filter Templates Vorlagen Brush Art Pinselgrafik New Calligraphic Brush Neuer Kalligrafiepinsel New Character Style Neues Zeichenformat + New Character Style… Neues Zeichenformat… New Color Group Neue Farbgruppe New Color Group… Neue Farbgruppe… New Document Presets Vorgaben für neue Dokumente @@ -1991,6 +1992,7 @@ file-filter Templates Vorlagen New Opacity Masks Are Clipping Neue Deckkraftmasken sind Schnittmasken New Opacity Masks Are Inverted Neue Deckkraftmasken sind invertiert New Paragraph Style Neues Absatzformat + New Paragraph Style… Neues Absatzformat… New Pattern Brush from Selection Neuer Musterpinsel aus Auswahl New PDF Preset Neue PDF-Vorgabe New Print Preset Neue Druckvorgabe diff --git a/crates/ui-egui/src/i18n/el.tsv b/crates/ui-egui/src/i18n/el.tsv index 9dff89a69..dbf8ff595 100644 --- a/crates/ui-egui/src/i18n/el.tsv +++ b/crates/ui-egui/src/i18n/el.tsv @@ -2749,6 +2749,7 @@ theme Appearance Εμφάνιση Flat Fan Επίπεδη βεντάλια New Calligraphic Brush Νέο καλλιγραφικό πινέλο New Character Style Νέο στυλ χαρακτήρων + New Character Style… Νέο στυλ χαρακτήρων… New Color Group Νέα ομάδα χρωμάτων New Color Group… Νέα ομάδα χρωμάτων… New Document Presets Νέες προεπιλογές εγγράφου @@ -2763,6 +2764,7 @@ theme Appearance Εμφάνιση New Opacity Masks Are Clipping Οι νέες μάσκες αδιαφάνειας εφαρμόζουν αποκοπή New Opacity Masks Are Inverted Οι νέες μάσκες αδιαφάνειας είναι ανεστραμμένες New Paragraph Style Νέο στυλ παραγράφου + New Paragraph Style… Νέο στυλ παραγράφου… New Pattern Brush from Selection Νέο πινέλο μοτίβου από επιλογή New PDF Preset Νέα προεπιλογή PDF New Print Preset Νέα προεπιλογή εκτύπωσης diff --git a/crates/ui-egui/src/i18n/es.tsv b/crates/ui-egui/src/i18n/es.tsv index 2e4156ba2..b22a812ca 100644 --- a/crates/ui-egui/src/i18n/es.tsv +++ b/crates/ui-egui/src/i18n/es.tsv @@ -2018,6 +2018,7 @@ font-technology Variable Variables Brush Art Ilustración del pincel New Calligraphic Brush Nuevo pincel caligráfico New Character Style Nuevo estilo de carácter + New Character Style… Nuevo estilo de carácter… New Color Group Nuevo grupo de colores New Color Group… Nuevo grupo de colores… New Document Presets Preajustes de documento nuevo @@ -2033,6 +2034,7 @@ font-technology Variable Variables New Opacity Masks Are Clipping Las nuevas máscaras de opacidad recortan New Opacity Masks Are Inverted Las nuevas máscaras de opacidad se invierten New Paragraph Style Nuevo estilo de párrafo + New Paragraph Style… Nuevo estilo de párrafo… New Pattern Brush from Selection Nuevo pincel de motivo a partir de la selección New PDF Preset Nuevo preajuste de PDF New Print Preset Nuevo preajuste de impresión diff --git a/crates/ui-egui/src/i18n/fi.tsv b/crates/ui-egui/src/i18n/fi.tsv index 2abb500b1..4e1981a58 100644 --- a/crates/ui-egui/src/i18n/fi.tsv +++ b/crates/ui-egui/src/i18n/fi.tsv @@ -3958,7 +3958,9 @@ font-technology Variable Muuttuvat Mojikumi Mojikumi N pt per N pt kohden New Character Style Uusi merkkityyli + New Character Style… Uusi merkkityyli… New Paragraph Style Uusi kappaletyyli + New Paragraph Style… Uusi kappaletyyli… Paragraph Style Options Kappaletyylin asetukset Redefine Character Style Määritä merkkityyli uudelleen Redefine Paragraph Style Määritä kappaletyyli uudelleen diff --git a/crates/ui-egui/src/i18n/fr.tsv b/crates/ui-egui/src/i18n/fr.tsv index a6d75df4d..ac49fcf19 100644 --- a/crates/ui-egui/src/i18n/fr.tsv +++ b/crates/ui-egui/src/i18n/fr.tsv @@ -2015,6 +2015,7 @@ font-technology Variable Variables Brush Art Illustration de la forme New Calligraphic Brush Nouvelle forme calligraphique New Character Style Nouveau style de caractère + New Character Style… Nouveau style de caractère… New Color Group Nouveau groupe de couleurs New Color Group… Nouveau groupe de couleurs… New Document Presets Paramètres prédéfinis de nouveau document @@ -2030,6 +2031,7 @@ font-technology Variable Variables New Opacity Masks Are Clipping Les nouveaux masques d’opacité sont des masques d’écrêtage New Opacity Masks Are Inverted Les nouveaux masques d’opacité sont inversés New Paragraph Style Nouveau style de paragraphe + New Paragraph Style… Nouveau style de paragraphe… New Pattern Brush from Selection Nouvelle forme de motif à partir de la sélection New PDF Preset Nouveau paramètre prédéfini PDF New Print Preset Nouveau paramètre prédéfini d’impression diff --git a/crates/ui-egui/src/i18n/hu.tsv b/crates/ui-egui/src/i18n/hu.tsv index 33414fce7..271fd9f0e 100644 --- a/crates/ui-egui/src/i18n/hu.tsv +++ b/crates/ui-egui/src/i18n/hu.tsv @@ -2456,6 +2456,7 @@ file-filter Templates Sablonok New Brush… Új ecset… New Calligraphic Brush Új kalligrafikus ecset New Character Style Új karakterstílus + New Character Style… Új karakterstílus… New Color Group Új színcsoport New Color Group… Új színcsoport… New Data Set Új adatkészlet @@ -2477,6 +2478,7 @@ file-filter Templates Sablonok New Opacity Masks Are Clipping Új átlátszósági maszkok vágnak New Opacity Masks Are Inverted Új átlátszósági maszkok invertáltak New Paragraph Style Új bekezdésstílus + New Paragraph Style… Új bekezdésstílus… New Pattern Brush from Selection Új mintázatecset a kijelölésből New PDF Preset Új PDF-előbeállítás New Print Preset Új nyomtatási előbeállítás diff --git a/crates/ui-egui/src/i18n/id.tsv b/crates/ui-egui/src/i18n/id.tsv index f9bff04fd..148c4ad74 100644 --- a/crates/ui-egui/src/i18n/id.tsv +++ b/crates/ui-egui/src/i18n/id.tsv @@ -3773,7 +3773,9 @@ font-technology Variable Variabel Mojikumi Mojikumi N pt per N pt per New Character Style Gaya karakter baru + New Character Style… Gaya karakter baru… New Paragraph Style Gaya paragraf baru + New Paragraph Style… Gaya paragraf baru… Paragraph Style Options Opsi gaya paragraf Redefine Character Style Definisikan ulang gaya karakter Redefine Paragraph Style Definisikan ulang gaya paragraf diff --git a/crates/ui-egui/src/i18n/it.tsv b/crates/ui-egui/src/i18n/it.tsv index b0e060bbe..489924442 100644 --- a/crates/ui-egui/src/i18n/it.tsv +++ b/crates/ui-egui/src/i18n/it.tsv @@ -2045,6 +2045,7 @@ file-filter Templates Modelli Brush Art Grafica del pennello New Calligraphic Brush Nuovo pennello calligrafico New Character Style Nuovo stile carattere + New Character Style… Nuovo stile carattere… New Color Group Nuovo gruppo colori New Color Group… Nuovo gruppo colori… New Document Presets Preset nuovo documento @@ -2060,6 +2061,7 @@ file-filter Templates Modelli New Opacity Masks Are Clipping Le nuove maschere di opacità ritagliano New Opacity Masks Are Inverted Le nuove maschere di opacità sono invertite New Paragraph Style Nuovo stile paragrafo + New Paragraph Style… Nuovo stile paragrafo… New Pattern Brush from Selection Nuovo pennello motivo dalla selezione New PDF Preset Nuovo preset PDF New Print Preset Nuovo preset di stampa diff --git a/crates/ui-egui/src/i18n/ja.tsv b/crates/ui-egui/src/i18n/ja.tsv index 07b58b7a0..087d51a00 100644 --- a/crates/ui-egui/src/i18n/ja.tsv +++ b/crates/ui-egui/src/i18n/ja.tsv @@ -2179,6 +2179,7 @@ font-technology Variable バリアブル Brush Art ブラシのアート New Calligraphic Brush 新規絵筆ブラシ New Character Style 新規文字スタイル + New Character Style… 新規文字スタイル… New Color Group 新規カラーグループ New Color Group… 新規カラーグループ… New Document Presets 新規ドキュメントプリセット @@ -2192,6 +2193,7 @@ font-technology Variable バリアブル New Opacity Masks Are Inverted 新規不透明マスクを反転に設定 New PDF Preset 新規 PDF プリセット New Paragraph Style 新規段落スタイル + New Paragraph Style… 新規段落スタイル… New Pattern Brush from Selection 選択範囲から新規パターンブラシ New Print Preset 新規プリントプリセット New Scatter Brush from Selection 選択範囲から新規散布ブラシ diff --git a/crates/ui-egui/src/i18n/ka.tsv b/crates/ui-egui/src/i18n/ka.tsv index 0c0e47a53..b5eab9051 100644 --- a/crates/ui-egui/src/i18n/ka.tsv +++ b/crates/ui-egui/src/i18n/ka.tsv @@ -3554,7 +3554,9 @@ font-technology Variable ცვლადი Mojikumi Mojikumi N pt per N pt თითოზე New Character Style სიმბოლოს ახალი სტილი + New Character Style… სიმბოლოს ახალი სტილი… New Paragraph Style აბზაცის ახალი სტილი + New Paragraph Style… აბზაცის ახალი სტილი… Paragraph Style Options აბზაცის სტილის პარამეტრები Redefine Character Style სიმბოლოს სტილის ხელახლა განსაზღვრა Redefine Paragraph Style აბზაცის სტილის ხელახლა განსაზღვრა diff --git a/crates/ui-egui/src/i18n/ko.tsv b/crates/ui-egui/src/i18n/ko.tsv index 67ec8582d..a2c7e0f6a 100644 --- a/crates/ui-egui/src/i18n/ko.tsv +++ b/crates/ui-egui/src/i18n/ko.tsv @@ -2174,6 +2174,7 @@ font-technology Variable 가변 Brush Art 브러시 아트 New Calligraphic Brush 새 캘리그래피 브러시 New Character Style 새 문자 스타일 + New Character Style… 새 문자 스타일… New Color Group 새 색상 그룹 New Color Group… 새 색상 그룹… New Document Presets 새 문서 사전 설정 @@ -2187,6 +2188,7 @@ font-technology Variable 가변 New Opacity Masks Are Inverted 새 불투명 마스크를 반전으로 설정 New PDF Preset 새 PDF 사전 설정 New Paragraph Style 새 단락 스타일 + New Paragraph Style… 새 단락 스타일… New Pattern Brush from Selection 선택 항목으로 새 패턴 브러시 만들기 New Print Preset 새 인쇄 사전 설정 New Scatter Brush from Selection 선택 항목으로 새 흩뿌리기 브러시 만들기 diff --git a/crates/ui-egui/src/i18n/nl.tsv b/crates/ui-egui/src/i18n/nl.tsv index 92f855cb7..bf9c3577d 100644 --- a/crates/ui-egui/src/i18n/nl.tsv +++ b/crates/ui-egui/src/i18n/nl.tsv @@ -3787,7 +3787,9 @@ font-technology Variable Variabel Mojikumi Mojikumi N pt per N pt per New Character Style Nieuwe tekenstijl + New Character Style… Nieuwe tekenstijl… New Paragraph Style Nieuwe alineastijl + New Paragraph Style… Nieuwe alineastijl… Paragraph Style Options Alineastijlopties Redefine Character Style Tekenstijl opnieuw definiëren Redefine Paragraph Style Alineastijl opnieuw definiëren diff --git a/crates/ui-egui/src/i18n/pl.tsv b/crates/ui-egui/src/i18n/pl.tsv index d218fa7c6..3e8fe7459 100644 --- a/crates/ui-egui/src/i18n/pl.tsv +++ b/crates/ui-egui/src/i18n/pl.tsv @@ -3879,7 +3879,9 @@ font-technology Variable Zmienne Mojikumi Mojikumi N pt per N pt na New Character Style Nowy styl znakowy + New Character Style… Nowy styl znakowy… New Paragraph Style Nowy styl akapitowy + New Paragraph Style… Nowy styl akapitowy… Paragraph Style Options Opcje stylu akapitowego Redefine Character Style Zdefiniuj ponownie styl znakowy Redefine Paragraph Style Zdefiniuj ponownie styl akapitowy diff --git a/crates/ui-egui/src/i18n/pt-br.tsv b/crates/ui-egui/src/i18n/pt-br.tsv index 2cfdcfc2f..603df2bde 100644 --- a/crates/ui-egui/src/i18n/pt-br.tsv +++ b/crates/ui-egui/src/i18n/pt-br.tsv @@ -1632,6 +1632,7 @@ Brush Art Arte do Pincel New Calligraphic Brush Novo Pincel Caligráfico New Character Style Novo Estilo de Caractere + New Character Style… Novo Estilo de Caractere… New Color Group Novo Grupo de Cores New Color Group… Novo Grupo de Cores… New Document Presets Novos Predefinidos de Documento @@ -1643,6 +1644,7 @@ New Opacity Masks Are Inverted Novas Máscaras de Opacidade São Invertidas New PDF Preset Novo Predefinido de PDF New Paragraph Style Novo Estilo de Parágrafo + New Paragraph Style… Novo Estilo de Parágrafo… New Pattern Brush from Selection Novo Pincel de Padrão a partir da Seleção New Print Preset Novo Predefinido de Impressão New Scatter Brush from Selection Novo Pincel de Dispersão a partir da Seleção diff --git a/crates/ui-egui/src/i18n/ru.tsv b/crates/ui-egui/src/i18n/ru.tsv index ce5daa355..8ab9549c3 100644 --- a/crates/ui-egui/src/i18n/ru.tsv +++ b/crates/ui-egui/src/i18n/ru.tsv @@ -2041,6 +2041,7 @@ file-filter Templates Шаблоны Brush Art Рисунок кисти New Calligraphic Brush Новая каллиграфическая кисть New Character Style Новый стиль символа + New Character Style… Новый стиль символа… New Color Group Новая цветовая группа New Color Group… Новая цветовая группа… New Document Presets Наборы новых документов @@ -2056,6 +2057,7 @@ file-filter Templates Шаблоны New Opacity Masks Are Clipping Новые непрозрачные маски обрезают New Opacity Masks Are Inverted Новые непрозрачные маски инвертируются New Paragraph Style Новый стиль абзаца + New Paragraph Style… Новый стиль абзаца… New Pattern Brush from Selection Новая узорная кисть из выделенного New PDF Preset Новый набор PDF New Print Preset Новый набор печати diff --git a/crates/ui-egui/src/i18n/sv.tsv b/crates/ui-egui/src/i18n/sv.tsv index 1ef7470b6..99854d41a 100644 --- a/crates/ui-egui/src/i18n/sv.tsv +++ b/crates/ui-egui/src/i18n/sv.tsv @@ -1963,6 +1963,7 @@ font-technology Variable Variabla Brush Art Penselgrafik New Calligraphic Brush Ny kalligrafisk pensel New Character Style Ny teckenstil + New Character Style… Ny teckenstil… New Color Group Ny färggrupp New Color Group… Ny färggrupp… New Document Presets Förinställningar för nytt dokument @@ -1978,6 +1979,7 @@ font-technology Variable Variabla New Opacity Masks Are Clipping Nya opacitetsmasker är klippande New Opacity Masks Are Inverted Nya opacitetsmasker är inverterade New Paragraph Style Ny styckestil + New Paragraph Style… Ny styckestil… New Pattern Brush from Selection Ny mönsterpensel från markeringen New PDF Preset Ny PDF-förinställning New Print Preset Ny utskriftsförinställning diff --git a/crates/ui-egui/src/i18n/tr.tsv b/crates/ui-egui/src/i18n/tr.tsv index 523f3bf3f..6e7a4b46b 100644 --- a/crates/ui-egui/src/i18n/tr.tsv +++ b/crates/ui-egui/src/i18n/tr.tsv @@ -1844,6 +1844,7 @@ font-technology Variable Değişken New Brush… Yeni Fırça… New Calligraphic Brush Yeni Kaligrafi Fırçası New Character Style Yeni Karakter Stili + New Character Style… Yeni Karakter Stili… New Color Group Yeni Renk Grubu New Color Group… Yeni Renk Grubu… New Document Presets Yeni Belge Ön Ayarları @@ -1858,6 +1859,7 @@ font-technology Variable Değişken New Opacity Masks Are Clipping Yeni Opaklık Maskeleri Kırpılıyor New Opacity Masks Are Inverted Yeni Opaklık Maskeleri Ters Çevrildi New Paragraph Style Yeni Paragraf Stili + New Paragraph Style… Yeni Paragraf Stili… New Pattern Brush from Selection Seçimden Yeni Desen Fırçası New PDF Preset Yeni PDF Hazır Ayarı New Print Preset Yeni Yazdırma Ön Ayarı diff --git a/crates/ui-egui/src/i18n/uk.tsv b/crates/ui-egui/src/i18n/uk.tsv index 68b725bf2..c40f4c770 100644 --- a/crates/ui-egui/src/i18n/uk.tsv +++ b/crates/ui-egui/src/i18n/uk.tsv @@ -2037,6 +2037,7 @@ file-filter Templates Шаблони Brush Art Малюнок пензля New Calligraphic Brush Новий каліграфічний пензель New Character Style Новий стиль символу + New Character Style… Новий стиль символу… New Color Group Нова колірна група New Color Group… Нова колірна група… New Document Presets Набір нових документів @@ -2052,6 +2053,7 @@ file-filter Templates Шаблони New Opacity Masks Are Clipping Нові маски непрозорості відсікають New Opacity Masks Are Inverted Нові маски непрозорості інвертуються New Paragraph Style Новий стиль абзацу + New Paragraph Style… Новий стиль абзацу… New Pattern Brush from Selection Новий візерунковий пензель з виділеного New PDF Preset Новий набір PDF New Print Preset Новий набір друку diff --git a/crates/ui-egui/src/i18n/vi.tsv b/crates/ui-egui/src/i18n/vi.tsv index 7570055b2..a75d32b96 100644 --- a/crates/ui-egui/src/i18n/vi.tsv +++ b/crates/ui-egui/src/i18n/vi.tsv @@ -2263,6 +2263,7 @@ file-filter Templates Mẫu New Brush… Cọ mới… New Calligraphic Brush Cọ thư pháp mới New Character Style Kiểu ký tự mới + New Character Style… Kiểu ký tự mới… New Color Group Nhóm màu mới New Color Group… Nhóm màu mới… New Data Set Tập dữ liệu mới @@ -2285,6 +2286,7 @@ file-filter Templates Mẫu New Opacity Masks Are Clipping Mặt nạ độ mờ mới là cắt New Opacity Masks Are Inverted Mặt nạ độ mờ mới bị đảo ngược New Paragraph Style Kiểu đoạn văn mới + New Paragraph Style… Kiểu đoạn văn mới… New Pattern Brush from Selection Cọ họa tiết mới từ vùng chọn New PDF Preset Thiết lập sẵn PDF mới New Print Preset Thiết lập sẵn in mới diff --git a/crates/ui-egui/src/i18n/zh-hans.tsv b/crates/ui-egui/src/i18n/zh-hans.tsv index 71a8fb202..6351f5704 100644 --- a/crates/ui-egui/src/i18n/zh-hans.tsv +++ b/crates/ui-egui/src/i18n/zh-hans.tsv @@ -1918,6 +1918,7 @@ file-filter Templates 模板 Brush Art 画笔图稿 New Calligraphic Brush 新建书法画笔 New Character Style 新建字符样式 + New Character Style… 新建字符样式… New Color Group 新建颜色组 New Color Group… 新建颜色组… New Document Presets 新建文档预设 @@ -1931,6 +1932,7 @@ file-filter Templates 模板 New Opacity Masks Are Clipping 新建不透明度蒙版为剪切 New Opacity Masks Are Inverted 新建不透明度蒙版为反相 New Paragraph Style 新建段落样式 + New Paragraph Style… 新建段落样式… New Pattern Brush from Selection 从选区新建图案画笔 New PDF Preset 新建 PDF 预设 New Print Preset 新建打印预设 diff --git a/crates/ui-egui/src/i18n/zh-hant.tsv b/crates/ui-egui/src/i18n/zh-hant.tsv index 13bf26f1a..1b076e629 100644 --- a/crates/ui-egui/src/i18n/zh-hant.tsv +++ b/crates/ui-egui/src/i18n/zh-hant.tsv @@ -2841,6 +2841,7 @@ file-filter Templates 範本 Brush Art 筆刷圖稿 New Calligraphic Brush 新增書法筆刷 New Character Style 新增字元樣式 + New Character Style… 新增字元樣式… New Color Group 新增顏色群組 New Color Group… 新增顏色群組… New Document Presets 新增文件預設集 @@ -2856,6 +2857,7 @@ file-filter Templates 範本 New Opacity Masks Are Clipping 新的不透明度遮色片為剪裁 New Opacity Masks Are Inverted 新的不透明度遮色片為反轉 New Paragraph Style 新增段落樣式 + New Paragraph Style… 新增段落樣式… New Pattern Brush from Selection 從選取範圍新增圖樣筆刷 New PDF Preset 新增 PDF 預設集 New Print Preset 新增列印預設集 diff --git a/crates/ui-egui/src/menus/run.rs b/crates/ui-egui/src/menus/run.rs index 8dde979a7..22ddb543d 100644 --- a/crates/ui-egui/src/menus/run.rs +++ b/crates/ui-egui/src/menus/run.rs @@ -587,6 +587,8 @@ fn run_ui_part7(app: &mut VectorcraftApp, id: &str, p: &Value) -> Option crate::dialogs::open_color_picker(app, p), "ui.sampleScreenColor" => crate::screen_color::start(app, p), "ui.graphicStyleOptions" => crate::dialogs::graphic_style_options::open(app, s("name").as_deref()), + "ui.newCharStyle" => crate::panels::text_styles::open_new(app, crate::panels::text_styles::Kind::Char), + "ui.newParaStyle" => crate::panels::text_styles::open_new(app, crate::panels::text_styles::Kind::Para), "ui.symbolOptions" => { let name = s("name"); let edit = p.get("edit").and_then(Value::as_bool).unwrap_or(false); diff --git a/crates/ui-egui/src/menus/state.rs b/crates/ui-egui/src/menus/state.rs index 2d89d8b35..4b009fbf4 100644 --- a/crates/ui-egui/src/menus/state.rs +++ b/crates/ui-egui/src/menus/state.rs @@ -221,7 +221,7 @@ pub fn enabled(app: &VectorcraftApp, id: &str) -> bool { "ui.missingFontsDialog" => !cfg!(target_arch = "wasm32") && app.session.active().is_some(), "ui.findFontsInFolder" => crate::picks::can(app, &crate::picks::PickRequest::Folder) && app.session.active().is_some(), "ui.swatchOptions" | "ui.newSwatch" | "ui.newColorGroup" => app.session.active().is_some(), - "ui.graphicStyleOptions" => app.session.active().is_some(), + "ui.graphicStyleOptions" | "ui.newCharStyle" | "ui.newParaStyle" => app.session.active().is_some(), "ui.newArtboard" => app.session.active().is_some(), "ui.sampleScreenColor" => crate::screen_color::available(app), "ui.newBrush" => app.session.active().is_some(), diff --git a/crates/ui-egui/src/menus/ui_commands.rs b/crates/ui-egui/src/menus/ui_commands.rs index c87d53c2e..4e5943a4a 100644 --- a/crates/ui-egui/src/menus/ui_commands.rs +++ b/crates/ui-egui/src/menus/ui_commands.rs @@ -633,6 +633,18 @@ pub const UI_COMMANDS: &[(&str, &str, &str, &str)] = &[ "", "{name?} open Graphic Style Options (dialog `graphicStyleOptions`, field `name`): for style `name` OK renames it (graphicStyle.rename); without, OK makes a new style of that name from the selection (graphicStyle.new)", ), + ( + "ui.newCharStyle", + "New Character Style…", + "", + "{} open New Character Style (dialog `newTextStyle`, field `name`, filled in with the name charStyle.new {} would give the style), what Alt-clicking the Character Styles panel's New button opens; OK runs charStyle.new with the name and the Type tool's selected characters (else the selected type) as they were when the dialog opened; Cancel adds no style", + ), + ( + "ui.newParaStyle", + "New Paragraph Style…", + "", + "{} open New Paragraph Style (dialog `newTextStyle`, field `name`, filled in with the name paraStyle.new {} would give the style), what Alt-clicking the Paragraph Styles panel's New button opens; OK runs paraStyle.new with the name and the paragraph of the Type tool's selection (else the selected type) as it was when the dialog opened; Cancel adds no style", + ), ( "ui.symbolOptions", "Symbol Options…", diff --git a/crates/ui-egui/src/panels/text_styles.rs b/crates/ui-egui/src/panels/text_styles.rs index b322b588a..049ebac48 100644 --- a/crates/ui-egui/src/panels/text_styles.rs +++ b/crates/ui-egui/src/panels/text_styles.rs @@ -2,14 +2,15 @@ //! //! Click a style to apply it to the selected text (or the Type tool's selected characters); //! Alt-click applies it and clears overrides. The style of the selection is highlighted, with a -//! "+" when the text overrides some of its attributes. +//! "+" when the text overrides some of its attributes. New makes a style from the selected text; +//! Alt-click on New first asks for its name (`ui.newCharStyle`, `ui.newParaStyle`). use egui::{Sense, Ui, vec2}; use serde_json::{Map, Value, json}; use vectorcraft_engine::cmd::textstyles; use super::character::{end_typing, text_editing, text_style}; -use super::{pstate, selection_len, set_pstate}; +use super::{alt_held, pstate, selection_len, set_pstate}; use crate::VectorcraftApp; use crate::theme::Tokens; use crate::widgets::{self, menu_item}; @@ -106,6 +107,15 @@ fn target(app: &VectorcraftApp, kind: Kind, mut p: Value) -> Value { p } +/// Open New Character Style (`Kind::Para`: New Paragraph Style) to name a style made from the +/// selected text: `ui.newCharStyle` and `ui.newParaStyle`. +pub(crate) fn open_new(app: &mut VectorcraftApp, kind: Kind) -> Result { + // A later typing preview must not restore the document from before this capture. + end_typing(app); + let t = target(app, kind, json!({})); + crate::dialogs::new_text_style::open(app, kind == Kind::Para, t) +} + pub fn show(app: &mut VectorcraftApp, ui: &mut Ui, kind: Kind) { let t = Tokens::get(ui.ctx()); let all = styles(app, kind); @@ -176,7 +186,9 @@ pub fn show(app: &mut VectorcraftApp, ui: &mut Ui, kind: Kind) { } } else if new.clicked() { end_typing(app); - if let Ok(r) = app.run(&format!("{}.new", kind.prefix()), target(app, kind, json!({}))) { + if alt_held(ui) { + app.run(if kind == Kind::Char { "ui.newCharStyle" } else { "ui.newParaStyle" }, json!({})).ok(); + } else if let Ok(r) = app.run(&format!("{}.new", kind.prefix()), target(app, kind, json!({}))) { highlight_made(ui.ctx(), kind, r["name"].as_str().map(str::to_string)); } } diff --git a/crates/ui-egui/src/panels/text_styles/tests.rs b/crates/ui-egui/src/panels/text_styles/tests.rs index 3430a2cb0..a5e9c31a6 100644 --- a/crates/ui-egui/src/panels/text_styles/tests.rs +++ b/crates/ui-egui/src/panels/text_styles/tests.rs @@ -537,6 +537,129 @@ fn click_new_style_footer_range(ctx: &egui::Context, app: &mut VectorcraftApp, i assert_eq!(text_editing(app), Some((id, start, end)), "footer capture preserves the Type range"); } +/// Alt-click the production bottom bar's New button of the `kind` panel, then draw the dialog +/// it opened: every text the last frame drew. +fn alt_click_new(ctx: &egui::Context, app: &mut VectorcraftApp, kind: Kind) -> Vec { + click_new_with(ctx, app, kind, egui::Modifiers::ALT) +} + +fn click_new(ctx: &egui::Context, app: &mut VectorcraftApp, kind: Kind) { + click_new_with(ctx, app, kind, egui::Modifiers::NONE); +} + +fn click_new_with(ctx: &egui::Context, app: &mut VectorcraftApp, kind: Kind, modifiers: egui::Modifiers) -> Vec { + let pos = std::cell::Cell::new(egui::Pos2::ZERO); + let mut draw = |app: &mut VectorcraftApp, ui: &mut egui::Ui| { + ui.set_width(280.0); + let right = ui.max_rect().right(); + show(app, ui, kind); + pos.set(egui::pos2(right - 40.0, ui.min_rect().bottom() - 12.0)); + }; + frame_events(ctx, app, vec![], &mut draw); + frame_events(ctx, app, vec![], &mut draw); + let at = pos.get(); + let button = |pressed| egui::Event::PointerButton { pos: at, button: egui::PointerButton::Primary, pressed, modifiers }; + for e in [egui::Event::PointerMoved(at), button(true), button(false)] { + frame_events(ctx, app, vec![egui::Event::ModifiersChanged(modifiers), e], &mut draw); + } + // A window draws its content from its second frame. + frame_events(ctx, app, vec![], |app, ui| crate::dialogs::show(app, ui.ctx())); + frame_events(ctx, app, vec![], |app, ui| crate::dialogs::show(app, ui.ctx())).into_iter().map(|(s, _)| s).collect() +} + +fn set_name(app: &mut VectorcraftApp, name: &str) { + app.ui.dialog.as_mut().expect("the dialog is open").fields.insert("name".into(), json!(name)); +} + +/// Alt-click on New Character Style opens New Character Style with the name a plain click's +/// style gets, and adds no style yet. Cancel adds none. OK makes the style with the typed name +/// from the Type tool's selected characters, in one undo step, and a name another style has keeps +/// the dialog open. +#[test] +fn alt_clicking_new_character_style_asks_for_its_name() { + let (mut app, id, ctx) = capture_app(); + select_type_range(&mut app, id, 3, 5); + let names = |app: &VectorcraftApp| app.session.doc().unwrap().doc.char_styles.iter().map(|s| s.name.clone()).collect::>(); + let before = names(&app); + let shown = alt_click_new(&ctx, &mut app, Kind::Char); + let d = app.ui.dialog.as_ref().expect("Alt-click on New opens a dialog"); + assert_eq!((d.kind.as_str(), d.str("name").as_str()), (crate::dialogs::new_text_style::KIND, "Character Style 1")); + assert!(shown.iter().any(|s| s == "New Character Style"), "the dialog's heading: {shown:?}"); + assert_eq!(names(&app), before, "no style is added before OK"); + crate::dialogs::cancel(&mut app); + assert_eq!(names(&app), before, "Cancel adds no style"); + + alt_click_new(&ctx, &mut app, Kind::Char); + set_name(&mut app, "Untouched"); + assert!(crate::dialogs::confirm(&mut app).is_err(), "names are unique"); + assert!(app.ui.dialog.is_some(), "a taken name keeps the dialog open"); + set_name(&mut app, "Heading"); + let undo = app.session.doc().unwrap().history.undo.len(); + crate::dialogs::confirm(&mut app).unwrap(); + assert!(app.ui.dialog.is_none()); + let doc = &app.session.doc().unwrap().doc; + let heading = doc.char_styles.iter().find(|s| s.name == "Heading").expect("OK makes the style"); + assert_eq!(heading.attrs.get("size").and_then(Value::as_f64), Some(34.0), "made from the selected characters"); + assert_eq!(doc.char_styles.len(), before.len() + 1); + assert_eq!(app.session.doc().unwrap().history.undo.len(), undo + 1); + assert_eq!(text_editing(&app), Some((id, 3, 5)), "the Type selection stays"); +} + +/// OK makes the style from the text's attributes as they were when the dialog opened: a later +/// format change of the Type selection, or a change of the selected objects, does not reach it. +#[test] +fn new_style_dialog_keeps_the_attributes_from_when_it_opened() { + let (mut app, id, ctx) = capture_app(); + select_type_range(&mut app, id, 3, 5); + alt_click_new(&ctx, &mut app, Kind::Char); + app.run("text.setRangeStyle", json!({"id": id.0, "start": 2, "end": 6, "size": 50})).unwrap(); + set_name(&mut app, "Range"); + crate::dialogs::confirm(&mut app).unwrap(); + + app.session.select_tool("selection", Default::default()).unwrap(); + app.run("select.set", json!({"ids": [id.0]})).unwrap(); + assert_eq!(text_editing(&app), None); + alt_click_new(&ctx, &mut app, Kind::Char); + let count = app.session.doc().unwrap().doc.char_styles.len(); + app.run("text.setRangeStyle", json!({"id": id.0, "start": 0, "end": 2, "size": 60})).unwrap(); + set_name(&mut app, "Object"); + crate::dialogs::confirm(&mut app).unwrap(); + + let doc = &app.session.doc().unwrap().doc; + assert_eq!(doc.char_styles.len(), count + 1); + let size = |n: &str| doc.char_styles.iter().find(|s| s.name == n).and_then(|s| s.attrs.get("size")).and_then(Value::as_f64); + assert_eq!((size("Range"), size("Object")), (Some(34.0), Some(18.0))); +} + +/// Alt-click on New Paragraph Style opens New Paragraph Style for the selected type's paragraph; +/// a plain click still makes the style at once. +#[test] +fn alt_clicking_new_paragraph_style_asks_for_its_name() { + let mut app = VectorcraftApp::new(vectorcraft_engine::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": "Body"})).unwrap()["id"].clone(); + app.run("select.set", json!({"ids": [id]})).unwrap(); + app.run("text.setFormat", json!({"composer": "singleLine"})).unwrap(); + let ctx = egui::Context::default(); + crate::theme::install_fonts(&ctx); + let shown = alt_click_new(&ctx, &mut app, Kind::Para); + let d = app.ui.dialog.as_ref().expect("Alt-click on New opens a dialog"); + assert_eq!((d.kind.as_str(), d.str("name").as_str()), (crate::dialogs::new_text_style::KIND, "Paragraph Style 1")); + assert!(shown.iter().any(|s| s == "New Paragraph Style"), "the dialog's heading: {shown:?}"); + assert!(app.session.doc().unwrap().doc.para_styles.is_empty(), "no style is added before OK"); + set_name(&mut app, "Body Copy"); + crate::dialogs::confirm(&mut app).unwrap(); + let (_, attrs) = current(&app, Kind::Para).unwrap(); + let doc = &app.session.doc().unwrap().doc; + assert_eq!(doc.para_styles.iter().map(|s| (s.name.as_str(), &s.attrs)).collect::>(), [("Body Copy", &attrs)]); + + // A plain click still makes "Paragraph Style 1" at once, with no dialog. + click_new(&ctx, &mut app, Kind::Para); + assert!(app.ui.dialog.is_none()); + let names: Vec<_> = app.session.doc().unwrap().doc.para_styles.iter().map(|s| s.name.clone()).collect(); + assert_eq!(names, ["Body Copy", "Paragraph Style 1"]); +} + fn create_redefined_texts(app: &mut VectorcraftApp, ctx: &egui::Context, id: NodeId) -> (Option, Option) { let mut follower = None; let mut local_override = None; diff --git a/docs/control-protocol.md b/docs/control-protocol.md index 2cfaff45b..d16d5e3fa 100644 --- a/docs/control-protocol.md +++ b/docs/control-protocol.md @@ -156,6 +156,11 @@ dialog open); without `name` it names a new style made from the selection (`grap `ui.mergeGraphicStyles {names}` opens the same dialog to name the style `ui.dialog.confirm` merges from them (`graphicStyle.merge`). Deleting styles from the panel asks first with a `confirm` dialog (`graphicStyle.delete`). +Text styles: `ui.newCharStyle` and `ui.newParaStyle`, what Alt-click on New in the Character Styles and Paragraph +Styles panels runs, open the `newTextStyle` dialog (field `name`) for a style made from the text selected when it +opened; `ui.dialog.confirm` makes it with `charStyle.new` / `paraStyle.new` (a name another style has returns an error +and keeps the dialog open). + Symbols: `ui.symbolOptions` opens the `symbolOptions` dialog (field `name`) that the Symbols panel's New Symbol asks first; `ui.dialog.confirm` makes the symbol from the selection with that name (`symbol.new`). F8 runs `ui.symbolOptions` without params. diff --git a/docs/mcp.md b/docs/mcp.md index 4ffc83593..6ddf4d843 100644 --- a/docs/mcp.md +++ b/docs/mcp.md @@ -4955,6 +4955,13 @@ inserting `\n`) continues the style of the paragraph it splits; deleting a break `text.setText` gives every paragraph the first one's attributes. Threaded text keeps each paragraph's attributes as the story re-flows (a paragraph split between frames has them in both). +`ui.newCharStyle` and `ui.newParaStyle` (Alt-click on New in the Character Styles or Paragraph Styles panel) open +New Character Style or New Paragraph Style (dialog `newTextStyle`, field `name`) with the name `charStyle.new {}` or +`paraStyle.new {}` would give the style, and add no style yet. OK runs `charStyle.new` or `paraStyle.new` with the +name and the Type tool's selection as it was when the dialog opened (else the selected type); a name another style +has, or an empty one, keeps the dialog open, and Cancel adds no style. A plain click on New still makes the style at +once. + 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 centred or right-aligned line on its own (``), and SVG import gives each line's paragraph its diff --git a/docs/target-app-parity.md b/docs/target-app-parity.md index 84a46130b..ceb4f020d 100644 --- a/docs/target-app-parity.md +++ b/docs/target-app-parity.md @@ -498,6 +498,7 @@ since 2026-10-01. - **Auto Leading outside the layout:** a paragraph's Justification… › Auto Leading () 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`. - **The text style panel menus' Duplicate and Delete:** in the Character Styles and Paragraph Styles panel menus, Duplicate highlights the copy, as Create New Style highlights the new style, and Delete leaves no style highlighted, as the Delete button does, so Duplicate, Clear Overrides, Redefine and Style Options are disabled until another style is clicked. A highlighted style that is renamed in Style Options or deleted by a command is no longer highlighted either. The tests `panels::text_styles::tests::panel_menu_duplicate_and_delete_update_the_highlighted_style` and `a_highlighted_style_that_is_no_longer_listed_is_not_highlighted` cover this. Public reference: [Character and paragraph styles](https://web.archive.org/web/20261015081534/https://helpx.adobe.com/illustrator/using/character-paragraph-styles.html). - **Names of new styles and symbols:** `charStyle.new` and `paraStyle.new` trim the white space around the name they are given, as `charStyle.rename` and `paraStyle.rename` do, and refuse a name that is then empty; Duplicate names its copy from the trimmed name. `symbol.new` and `symbol.duplicate` trim the name as Symbol Options does, and `library.use` adds a library's text style under its trimmed name. Public reference: [Character and paragraph styles](https://web.archive.org/web/20260825113735/https://helpx.adobe.com/illustrator/using/character-paragraph-styles.html). +- **Naming a new text style first:** Alt-click on New in the Character Styles or Paragraph Styles panel opens New Character Style or New Paragraph Style (`ui.newCharStyle`, `ui.newParaStyle`) with the name a plain click's style gets; OK makes the style from the text selected when the dialog opened, and Cancel adds none. Tests `panels::text_styles::tests::alt_clicking_new_character_style_asks_for_its_name` and `alt_clicking_new_paragraph_style_asks_for_its_name`. Public reference: [Default keyboard shortcuts](https://web.archive.org/web/20260726060015/https://helpx.adobe.com/illustrator/using/default-keyboard-shortcuts.html), "Set options (except for Actions, Brushes, Swatches, and Symbols panels)". - **Width Point Edit:** Shift-double-click enables Adjust Adjoining Width Points; plain double-click and direct opening retain the default. Tool dispatch and headless dialog tests cover neighboring widths, Undo/Redo and Cancel. Public reference: [Variable-width strokes](https://helpx.adobe.com/illustrator/using/stroke-object.html). Moved from the ROADMAP's "Shipped so far" on 2026-10-10. Update it when a feature lands.