mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-07 13:23:22 +00:00
Part of #394: each run hyphenates in its own language; Default Language sets new type's
Squashed from #2818 onto main.
This commit is contained in:
@@ -143,8 +143,11 @@ pub struct CharStyle {
|
||||
pub no_break: bool,
|
||||
/// Language (Character panel): the BCP 47 tag of one of [`crate::setup::LANGUAGES`] these
|
||||
/// characters are shaped in, so that the font uses that language's forms (OpenType `locl`).
|
||||
/// `None`: the default language, English: USA, shaped without a language. New type takes the
|
||||
/// document's language (Document Setup). Saved only when set.
|
||||
/// `None`: the default language, English: USA, shaped without a language, which is what text
|
||||
/// saved without one reads as. A tag the list doesn't have, from a file, is kept and has no
|
||||
/// hyphenation. New type takes the document's language (Document Setup), else the default
|
||||
/// language of Preferences › Hyphenation. Saved only when set. Each run hyphenates in its own
|
||||
/// language (`vectorcraft_text::hyphen`).
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub language: Option<String>,
|
||||
/// Another glyph of the font for the run's first character (Glyphs panel › Show › Alternates
|
||||
|
||||
@@ -833,7 +833,10 @@ pub(crate) fn new_type_style(s: &Session, p: &Value) -> CharStyle {
|
||||
None if !s.paint.fill.is_none() && s.paint.fill != vectorcraft_color::Paint::solid(vectorcraft_color::Color::WHITE) => s.paint.fill.clone(),
|
||||
None => vectorcraft_color::Paint::solid(vectorcraft_color::Color::BLACK),
|
||||
};
|
||||
// In the document's language (Document Setup), none for the default.
|
||||
style.language = s.doc().ok().and_then(|d| vectorcraft_doc::setup::char_language(&d.doc.setup.language).ok().flatten()).map(str::to_string);
|
||||
// In the language Document Setup chose, else Preferences › Hyphenation › Default Language; none
|
||||
// for the default. Type already in a document keeps its own.
|
||||
let chosen = |name: &str| vectorcraft_doc::setup::char_language(name).ok().flatten();
|
||||
let document = s.doc().ok().and_then(|d| chosen(&d.doc.setup.language));
|
||||
style.language = document.or_else(|| chosen(&s.prefs.hyphenation_language)).map(str::to_string);
|
||||
style
|
||||
}
|
||||
|
||||
@@ -260,14 +260,40 @@ pub const PREF_SPECS: &[PrefSpec] = &[
|
||||
"",
|
||||
"Default Language",
|
||||
choice(&[
|
||||
("English: USA", "English: USA"),
|
||||
("English: UK", "English: UK"),
|
||||
("French", "French"),
|
||||
("German", "German"),
|
||||
("Spanish", "Spanish"),
|
||||
("Italian", "Italian"),
|
||||
("Dutch", "Dutch"),
|
||||
("Portuguese", "Portuguese")
|
||||
("en-US", "English: USA"),
|
||||
("en-GB", "English: UK"),
|
||||
("de", "German"),
|
||||
("de-CH", "German: Swiss"),
|
||||
("fr", "French"),
|
||||
("it", "Italian"),
|
||||
("es", "Spanish"),
|
||||
("ca", "Catalan"),
|
||||
("pt", "Portuguese"),
|
||||
("nl", "Dutch"),
|
||||
("da", "Danish"),
|
||||
("sv", "Swedish"),
|
||||
("fi", "Finnish"),
|
||||
("nb", "Norwegian"),
|
||||
("pl", "Polish"),
|
||||
("cs", "Czech"),
|
||||
("ru", "Russian"),
|
||||
("sr-Cyrl", "Serbian: Cyrillic"),
|
||||
("sr-Latn", "Serbian: Latin"),
|
||||
("hr", "Croatian"),
|
||||
("tr", "Turkish"),
|
||||
("ja", "Japanese"),
|
||||
("zh", "Chinese"),
|
||||
("hi", "Hindi"),
|
||||
("mr", "Marathi"),
|
||||
("gu", "Gujarati"),
|
||||
("bn", "Bengali"),
|
||||
("pa", "Punjabi"),
|
||||
("ta", "Tamil"),
|
||||
("te", "Telugu"),
|
||||
("kn", "Kannada"),
|
||||
("ml", "Malayalam"),
|
||||
("or", "Odia"),
|
||||
("ur", "Urdu")
|
||||
])
|
||||
),
|
||||
p!("hyphenationExceptions", "Hyphenation", "", "Exceptions (comma separated)", text),
|
||||
|
||||
@@ -10,6 +10,14 @@ pub enum DrawMode {
|
||||
Inside,
|
||||
}
|
||||
|
||||
/// A saved Default Language as its tag: older preferences hold the language's name, and one the list
|
||||
/// no longer has reads as the default.
|
||||
fn language_tag<'de, D: serde::Deserializer<'de>>(d: D) -> std::result::Result<String, D::Error> {
|
||||
let saved = <String as serde::Deserialize>::deserialize(d)?;
|
||||
let tag = vectorcraft_doc::setup::char_language(&saved).ok().flatten();
|
||||
Ok(tag.or_else(|| vectorcraft_doc::setup::LANGUAGES.first().map(|(_, t, _)| *t)).unwrap_or_default().to_string())
|
||||
}
|
||||
|
||||
/// Application preferences (Edit → Preferences). Every field is reachable through `prefs.get` /
|
||||
/// `prefs.set {key, value}` (camelCase keys, validated against [`cmd::prefscmds::PREF_SPECS`]).
|
||||
#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
|
||||
@@ -114,6 +122,9 @@ pub struct Prefs {
|
||||
pub show_slice_numbers: bool,
|
||||
pub slice_line_color: String,
|
||||
// Hyphenation
|
||||
/// Default Language: the BCP 47 tag of the language new type takes when its document's language
|
||||
/// is the default ([`vectorcraft_doc::setup::LANGUAGES`]).
|
||||
#[serde(deserialize_with = "language_tag")]
|
||||
pub hyphenation_language: String,
|
||||
pub hyphenation_exceptions: String,
|
||||
/// Type › Options › Additional Fonts Folder: a folder (read with its subfolders) whose fonts
|
||||
@@ -404,7 +415,7 @@ impl Default for Prefs {
|
||||
snap_perpendicular: true,
|
||||
show_slice_numbers: true,
|
||||
slice_line_color: s("#ff3f3f"),
|
||||
hyphenation_language: s("English: USA"),
|
||||
hyphenation_language: s("en-US"),
|
||||
hyphenation_exceptions: String::new(),
|
||||
fonts_folder: String::new(),
|
||||
plugins_folder: String::new(),
|
||||
|
||||
@@ -582,6 +582,60 @@ fn set_pref(s: &mut Session, key: &str, value: Value) {
|
||||
s.execute("prefs.set", &json!({"key": key, "value": value})).unwrap();
|
||||
}
|
||||
|
||||
/// New type with the session's defaults: its id and language.
|
||||
fn new_type(s: &mut Session) -> (NodeId, Option<String>) {
|
||||
let id = NodeId(s.execute("text.create", &json!({"x": 10, "y": 50, "text": "Hello"})).unwrap()["id"].as_u64().unwrap());
|
||||
(id, type_language(s, id))
|
||||
}
|
||||
|
||||
fn type_language(s: &Session, id: NodeId) -> Option<String> {
|
||||
let Some(NodeKind::Text(t)) = s.doc().unwrap().doc.node(id).map(|n| &n.kind) else { panic!("not text") };
|
||||
t.runs[0].style.language.clone()
|
||||
}
|
||||
|
||||
/// Preferences › Hyphenation › Default Language (#394) is the language of new type, stored as its
|
||||
/// tag. Type already there keeps its language, and a language chosen in Document Setup comes first.
|
||||
#[test]
|
||||
fn default_language_is_the_language_of_new_type() {
|
||||
let mut s = Session::new();
|
||||
s.execute("file.new", &json!({"width": 200, "height": 200})).unwrap();
|
||||
let (before, language) = new_type(&mut s);
|
||||
assert_eq!(language, None, "the default language isn't stored");
|
||||
|
||||
set_pref(&mut s, "hyphenationLanguage", json!("German"));
|
||||
assert_eq!(s.prefs.hyphenation_language, "de", "a name is stored as its tag");
|
||||
assert_eq!(new_type(&mut s).1.as_deref(), Some("de"));
|
||||
assert_eq!(type_language(&s, before), None, "type already there stays as it is");
|
||||
|
||||
s.execute("document.setup", &json!({"language": "French"})).unwrap();
|
||||
assert_eq!(new_type(&mut s).1.as_deref(), Some("fr"), "the document's own language comes first");
|
||||
|
||||
set_pref(&mut s, "hyphenationLanguage", json!("en-US"));
|
||||
s.execute("document.setup", &json!({"language": "English: USA"})).unwrap();
|
||||
assert_eq!(new_type(&mut s).1, None);
|
||||
}
|
||||
|
||||
/// Preferences saved before the language was a tag hold its name; a language no longer in the list
|
||||
/// reads as the default.
|
||||
#[test]
|
||||
fn a_saved_default_language_reads_as_its_tag() {
|
||||
let read = |v: &str| serde_json::from_value::<Prefs>(json!({"hyphenationLanguage": v})).unwrap().hyphenation_language;
|
||||
assert_eq!(read("German"), "de");
|
||||
assert_eq!(read("English: USA"), "en-US");
|
||||
assert_eq!(read("de"), "de");
|
||||
assert_eq!(read("Klingon"), "en-US");
|
||||
assert_eq!(Prefs::default().hyphenation_language, "en-US");
|
||||
}
|
||||
|
||||
/// The preference offers every language the Character panel and Document Setup do.
|
||||
#[test]
|
||||
fn default_language_offers_every_language() {
|
||||
let spec = PREF_SPECS.iter().find(|p| p.key == "hyphenationLanguage").unwrap();
|
||||
let crate::cmd::prefscmds::PrefKind::Choice(options) = spec.kind else { panic!("a choice") };
|
||||
let languages: Vec<(&str, &str)> = vectorcraft_doc::setup::LANGUAGES.iter().map(|(name, tag, _)| (*tag, *name)).collect();
|
||||
assert_eq!(options, languages.as_slice());
|
||||
}
|
||||
|
||||
fn gesture(s: &mut Session, v: ViewInfo, events: &[(vectorcraft_tools::PointerKind, f64, f64)]) {
|
||||
for (k, x, y) in events {
|
||||
s.pointer(&vectorcraft_tools::PointerEvent::new(*k, *x, *y), v).unwrap();
|
||||
|
||||
@@ -7,6 +7,11 @@
|
||||
//! Preferences › Hyphenation › Exceptions (comma separated) override the points for named words:
|
||||
//! `typography` never breaks, `ty-pog-ra-phy` breaks only where the hyphens mark. See
|
||||
//! [`set_hyphenation_exceptions`].
|
||||
//!
|
||||
//! Each run hyphenates in its own language ([`vectorcraft_doc::CharStyle::language`]). English is
|
||||
//! the only language with break points so far: text in any other language, or in a language a
|
||||
//! file names that isn't known, doesn't hyphenate, apart from the words in the Exceptions list
|
||||
//! ([`language_has_patterns`]).
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::sync::{Mutex, MutexGuard, OnceLock};
|
||||
@@ -144,6 +149,12 @@ fn onset_pair(a: char, b: char) -> bool {
|
||||
)
|
||||
}
|
||||
|
||||
/// Whether text in `language` (a [`vectorcraft_doc::CharStyle::language`]: a BCP 47 tag, `None` for
|
||||
/// the default, English: USA) has break points of its own.
|
||||
pub fn language_has_patterns(language: Option<&str>) -> bool {
|
||||
language.is_none_or(|l| l.get(..2).is_some_and(|p| p.eq_ignore_ascii_case("en")) && matches!(l.as_bytes().get(2), None | Some(b'-')))
|
||||
}
|
||||
|
||||
/// Allowed hyphenation points in `word`, as char indices (a hyphen goes *before* the char), with
|
||||
/// the default Hyphenation options.
|
||||
pub fn hyphen_points(word: &str) -> Vec<usize> {
|
||||
@@ -157,12 +168,21 @@ pub fn hyphen_points(word: &str) -> Vec<usize> {
|
||||
/// (other than a trailing apostrophe) are not hyphenated. Preferences exceptions override the
|
||||
/// pattern and these options for a matching word.
|
||||
pub fn hyphen_points_with(word: &str, opts: &HyphenationOptions) -> Vec<usize> {
|
||||
hyphen_points_in(word, opts, true)
|
||||
}
|
||||
|
||||
/// [`hyphen_points_with`] for a word in a language with or without break points of its own
|
||||
/// ([`language_has_patterns`]): without them, only a word in the Exceptions list breaks.
|
||||
pub fn hyphen_points_in(word: &str, opts: &HyphenationOptions, patterns: bool) -> Vec<usize> {
|
||||
if let Some(ex) = exception_for(word) {
|
||||
return match ex {
|
||||
Exception::Never => vec![],
|
||||
Exception::Only(pts) => lowercase_points_to_word(word, &pts),
|
||||
};
|
||||
}
|
||||
if !patterns {
|
||||
return vec![];
|
||||
}
|
||||
let c: Vec<char> = word.chars().collect();
|
||||
let n = c.len();
|
||||
let longer_than = usize::try_from(opts.words_longer_than).unwrap_or(usize::MAX);
|
||||
@@ -235,6 +255,31 @@ mod tests {
|
||||
with_hyphenation_exceptions_for_test(source, f);
|
||||
}
|
||||
|
||||
/// #394: English in any region has break points; other languages, and a tag from a file that
|
||||
/// isn't a known language, have none.
|
||||
#[test]
|
||||
fn only_english_has_break_points() {
|
||||
for l in [None, Some("en-US"), Some("en-GB"), Some("EN-gb"), Some("en")] {
|
||||
assert!(language_has_patterns(l), "{l:?}");
|
||||
}
|
||||
for l in [Some("de"), Some("fr"), Some("pt"), Some("eng"), Some("x-unknown"), Some(""), Some("é")] {
|
||||
assert!(!language_has_patterns(l), "{l:?}");
|
||||
}
|
||||
}
|
||||
|
||||
/// #394: a language without break points leaves words whole, but the Exceptions list is the
|
||||
/// user's own and applies in every language.
|
||||
#[test]
|
||||
fn a_language_without_break_points_keeps_only_the_exceptions() {
|
||||
let opts = HyphenationOptions::default();
|
||||
assert_eq!(hyphen_points_in("typography", &opts, true), vec![4, 7]);
|
||||
assert!(hyphen_points_in("typography", &opts, false).is_empty());
|
||||
with_exceptions("ty-pog-ra-phy, happen", || {
|
||||
assert_eq!(hyphen_points_in("typography", &opts, false), vec![2, 5, 7]);
|
||||
assert!(hyphen_points_in("happen", &opts, false).is_empty());
|
||||
});
|
||||
}
|
||||
|
||||
/// #394 Preferences › Hyphenation › Exceptions contract (parse + override).
|
||||
#[test]
|
||||
fn hyphenation_exceptions_override_the_pattern() {
|
||||
|
||||
@@ -14,7 +14,7 @@ use vectorcraft_geom::{ArcPath, PathData};
|
||||
use crate::GlyphBend;
|
||||
use crate::composer::{Breakpoint, Params, compose};
|
||||
use crate::fontdb::FontDb;
|
||||
use crate::hyphen::hyphen_points_with;
|
||||
use crate::hyphen::hyphen_points_in;
|
||||
use crate::justify::{Flex, LineFit, apply_desired, fit_line, is_word_space};
|
||||
use crate::kashida;
|
||||
use crate::shape::{
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use super::region::Region;
|
||||
use super::{
|
||||
Affine, BezPath, Breakpoint, Burasagari, EPS, FirstBaseline, FontDb, HyphenationOptions, Kinsoku, LeadingModel, SGlyph, hyphen_glyph,
|
||||
hyphen_points_with, no_line_end, no_line_start, soft_line_start,
|
||||
hyphen_points_in, no_line_end, no_line_start, soft_line_start,
|
||||
};
|
||||
|
||||
/// Vertical metrics of a line: (ascent, descent, leading, cap height, x height).
|
||||
@@ -518,7 +518,8 @@ fn soft_kinsoku_lets_small_kana_and_the_prolonged_sound_mark_start_a_line() {
|
||||
}
|
||||
}
|
||||
|
||||
/// Glyph indices inside `g[ws..we]` (a word) where a hyphenated break may go under `opts`.
|
||||
/// Glyph indices inside `g[ws..we]` (a word) where a hyphenated break may go under `opts`, in the
|
||||
/// language of the word's first letter.
|
||||
fn hyphen_breaks(text: &str, g: &[SGlyph], ws: usize, we: usize, opts: &HyphenationOptions) -> Vec<usize> {
|
||||
// Strip leading/trailing punctuation (quotes, commas).
|
||||
let (mut a, mut b) = (ws, we);
|
||||
@@ -542,7 +543,7 @@ fn hyphen_breaks(text: &str, g: &[SGlyph], ws: usize, we: usize, opts: &Hyphenat
|
||||
starts.entry(g[k].byte).or_insert(k);
|
||||
}
|
||||
}
|
||||
hyphen_points_with(word, opts).into_iter().filter_map(|ci| starts.get(offs.get(ci)?).copied()).collect()
|
||||
hyphen_points_in(word, opts, g[a].hyphenates).into_iter().filter_map(|ci| starts.get(offs.get(ci)?).copied()).collect()
|
||||
}
|
||||
|
||||
/// Break candidates for the every-line composer: hyphenation points under `hyphenate` (the
|
||||
|
||||
@@ -39,6 +39,7 @@ fn synthetic_glyphs(specs: &[(usize, usize, f64, char)]) -> Vec<SGlyph> {
|
||||
no_break: false,
|
||||
vertical_roman: false,
|
||||
cursive: false,
|
||||
hyphenates: true,
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
@@ -80,6 +80,8 @@ pub(crate) struct SGlyph {
|
||||
/// decided for the whole segment it was shaped in ([`joins`]): on a curved path it is bent
|
||||
/// along the path so its joins meet.
|
||||
pub cursive: bool,
|
||||
/// The glyph's language has hyphenation break points ([`crate::hyphen::language_has_patterns`]).
|
||||
pub hyphenates: bool,
|
||||
}
|
||||
|
||||
/// Does `script` join its letters (a cursive script, whose neighbouring glyphs touch)?
|
||||
@@ -947,6 +949,7 @@ fn inline_glyph(
|
||||
no_break: st.no_break,
|
||||
vertical_roman: false,
|
||||
cursive: false,
|
||||
hyphenates: false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1327,6 +1330,7 @@ fn shape_segment(text: &str, context: &Range<usize>, seg: &Segment, feats: &OtFe
|
||||
no_break: gst.no_break,
|
||||
vertical_roman: gst.standard_vertical_roman,
|
||||
cursive,
|
||||
hyphenates: crate::hyphen::language_has_patterns(gst.language.as_deref()),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1013,6 +1013,33 @@ fn hyphenation_rules() {
|
||||
}
|
||||
}
|
||||
|
||||
/// #394: each run hyphenates in its own language. English breaks words, a language without break
|
||||
/// points doesn't, and text saved without a language breaks as it always did.
|
||||
#[test]
|
||||
fn each_run_hyphenates_in_its_own_language() {
|
||||
let text = "Extraordinarily comprehensive typographical considerations notwithstanding everything.";
|
||||
let frame = Rect::new(0.0, 0.0, 120.0, 600.0);
|
||||
let hyphens = |language: Option<&str>| {
|
||||
let st = CharStyle { language: language.map(str::to_string), ..style(14.0) };
|
||||
let mut t = area(text, st, frame, Justify::Left);
|
||||
t.para.hyphenate = true;
|
||||
layout(db(), &t).glyphs.iter().filter(|g| g.len == 0).count()
|
||||
};
|
||||
let saved_without_one = hyphens(None);
|
||||
assert!(saved_without_one > 0, "some words hyphenated");
|
||||
assert_eq!(hyphens(Some("en-GB")), saved_without_one);
|
||||
assert_eq!(hyphens(Some("de")), 0, "German has no break points yet");
|
||||
assert_eq!(hyphens(Some("x-from-a-file")), 0, "a tag that isn't known has no hyphenation");
|
||||
|
||||
// both in one paragraph: the English run breaks and the German one doesn't
|
||||
let mut t = area(&format!("{text} {text}"), style(14.0), frame, Justify::Left);
|
||||
t.para.hyphenate = true;
|
||||
edit::style_range(&mut t.runs, text.len(), 2 * text.len() + 1, |s| s.language = Some("de".into()));
|
||||
let l = layout(db(), &t);
|
||||
let at: Vec<usize> = l.glyphs.iter().filter(|g| g.len == 0).map(|g| g.byte).collect();
|
||||
assert!(!at.is_empty() && at.iter().all(|b| *b < text.len()), "hyphens only in the English run: {at:?}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hyphenated_layout_adds_hyphens() {
|
||||
let text = "Extraordinarily comprehensive typographical considerations notwithstanding everything.";
|
||||
|
||||
+7
-4
@@ -1,6 +1,6 @@
|
||||
# Gaps
|
||||
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-11-05 · **Change:** minor (document tabs are reordered by dragging them along the tab strip; scores and estimates unchanged) · **Target:** Adobe Illustrator 2026 (30.x)
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-11-05 · **Change:** minor (a slow first frame is waited for instead of restarting without the graphics adapter, and the menu bar no longer waits for the installed fonts' scan; scores and estimates unchanged) · **Target:** Adobe Illustrator 2026 (30.x)
|
||||
|
||||
## Feature submission evidence
|
||||
|
||||
@@ -28,6 +28,7 @@ Artboard Options forwards the active Artboard tool's Scale Artwork with Artboard
|
||||
|
||||
| Date | Change | Summary |
|
||||
|---|---|---|
|
||||
| 2026-11-05 | minor | G12: Hyphenation › Default Language sets the language of new type unless Document Setup chose one, stored as a tag; each run hyphenates in its own language, English only so far, so 2 Preferences options stay unused (#394) |
|
||||
| 2026-11-03 | minor | New gradients use CMYK default stops in CMYK documents, including Gradient tool creation. Scores and estimates unchanged. |
|
||||
| 2026-11-02 | minor | Font-family fields step selected text with the mouse wheel (#5253); no scored gap or estimate reassessed. |
|
||||
| 2026-10-24 | minor | G12: General › Corner Radius is the Rounded Rectangle tool's radius, a change while the tool is active reaches it, and the radius ↑/↓ set while drawing (or a Tool Options value) becomes the preference, within 0–1296 pt (#394, `crates/engine/src/tests/constrain_tools.rs`); no entry closed, estimate unchanged. |
|
||||
@@ -437,9 +438,11 @@ from G14 and the budget run of G15: **~170–270 h** one agent, ~50–80 h wall
|
||||
- **Done:** User Interface › Open Documents As Tabs: with the box cleared, each document opened or created
|
||||
afterwards opens in a floating window of its own (G7's Float in Window); documents already open stay where they
|
||||
are. Estimate unchanged.
|
||||
- **Missing:** 3 Preferences options stored but not acted on (#394), each waiting on a subsystem of its own:
|
||||
Hyphenation › Default Language (hyphenation is English-only, with no per-language
|
||||
dictionaries), Plug-ins & Scratch Disks › Primary and Secondary (documents are kept in memory; nothing pages to
|
||||
- **Done:** Hyphenation › Default Language is the language of new type, unless Document Setup chose one, and each
|
||||
run hyphenates in its own language (Character panel). Only English has break points so far: text in another
|
||||
language stays whole, apart from the Exceptions list (#394).
|
||||
- **Missing:** 2 Preferences options stored but not acted on (#394), each waiting on a subsystem of its own:
|
||||
Plug-ins & Scratch Disks › Primary and Secondary (documents are kept in memory; nothing pages to
|
||||
disk), and Clipboard Handling › Legacy vector clipboard with its Preserve Paths / Preserve Appearance and
|
||||
Overprints choice (Illustrator's own clipboard flavour, which has no public specification). Also Properties
|
||||
panel per context, context menus beyond the canvas, Layers panel row menus and Collect for Export, SVG
|
||||
|
||||
File diff suppressed because one or more lines are too long
+2
-1
@@ -30,6 +30,7 @@ Artboard Options forwards the active Artboard tool's Scale Artwork with Artboard
|
||||
|
||||
| Date | Change | Summary |
|
||||
|---|---|---|
|
||||
| 2026-11-05 | minor | G12: Hyphenation › Default Language sets the language of new type unless Document Setup chose one, stored as a tag; each run hyphenates in its own language, English only so far, so 2 Preferences options stay unused (#394) |
|
||||
| 2026-11-05 | minor | Gradient panel: Delete Stop sits at the right end of the ramp, so it shows whenever the ramp does instead of under the stop fields, where a short panel hid it (#5403); scores and estimates unchanged |
|
||||
| 2026-11-04 | minor | Artboards panel: a Shift- or Ctrl/Cmd-click on a row selects several artboards with any tool, switching to the Artboard tool first, so a preset, orientation, size or Fill set in Properties or the Control bar goes to every selected artboard; Fill used to set the active artboard only (#5698); scores and estimates unchanged |
|
||||
| 2026-11-04 | minor | Isolation mode: once the layer, or a group, around the isolated object is locked or hidden, a click or a marquee with the Selection tool no longer selects the isolated art (part of #5309); scores and estimates unchanged |
|
||||
@@ -367,7 +368,7 @@ next unit in the order a right-click on a ruler lists them, from Feet back to Po
|
||||
| Swatch, graphic style and symbol library panels: each library opens in a panel of its own, several at once, that floats, groups, docks and closes like any panel; opening an open library brings its panel forward; Previous / Next Library change the library in place; the panel menu's Persistent reopens the panel at startup with its view and find field (a library loaded from a file is read again; when its file is missing the panel reopens showing the file's path with Locate Library…, and the status bar names the file) | done | #2603; panel ids `library:<kind>:<id>`, `ui.libraryPanel {panel, library?, view?, showFind?, persistent?}`, `ui.libraryPanel.locate {panel, path?}`; the library panels have no sort option, as before |
|
||||
| DXF export/import preserves intended curve closure | done | #1299; R13–2018 closed SPLINE bit set/read; an open curve ending at its start stays open; knots and control points unchanged; native CIRCLE/ARC export and CNC preset remain pending |
|
||||
| macOS save sheet: choose the format from a menu | done | #866; the save panel is VectorCraft's own `NSSavePanel` with its Format menu set before the sheet opens (`apps/vectorcraft/src/mac_save_format.rs`); #1855 had withdrawn the menu that was added after the sheet began, which made Save As, Save a Copy and the first Save show no sheet |
|
||||
| Preferences: every option acts | partial | 3 stored but unused (#394, audited 2026-10-30): Hyphenation › Default Language, Scratch Disks, Legacy vector clipboard; the rest act (Units › East Asian Type since 2026-10-31; Open Documents As Tabs: cleared, new documents open in floating windows). General › Corner Radius drives the Rounded Rectangle tool and follows its ↑/↓ (2026-10-24) |
|
||||
| Preferences: every option acts | partial | 2 stored but unused (#394, audited 2026-10-30): Scratch Disks, Legacy vector clipboard; the rest act (Units › East Asian Type since 2026-10-31; Open Documents As Tabs: cleared, new documents open in floating windows). General › Corner Radius drives the Rounded Rectangle tool and follows its ↑/↓ (2026-10-24) |
|
||||
| Preferences › General › Constrain Angle | done | arrow keys and Shift drags along the turned axes; the Rectangle, Rounded Rectangle, Ellipse and grid tools, point and area type and the shape size dialogs draw along them with the bounding box turned; Shift-drawn polygons and stars keep the turned base orientation; the Line Segment tool's Shift steps count from it (#394, <https://helpx.adobe.com/illustrator/desktop/manage-objects/arrange-objects/rotate-objects-by-specific-angles.html>); arcs, spirals, flares and Pen paths are not turned |
|
||||
| Tabs panel: a stop drags from where the press landed, a stop just added included | done | #1458 |
|
||||
| Outline view keeps template layers in Preview, dimmed | done | #1486; a picture placed to be traced stays visible under the outlines |
|
||||
|
||||
Reference in New Issue
Block a user