Merge pull request #4708 from mockydevs/fix/ai-all-caps-text

.ai import: keep All Caps and Small Caps on type from the editing data
This commit is contained in:
BFlat
2026-10-31 00:50:52 +07:00
committed by GitHub
3 changed files with 33 additions and 1 deletions
+7
View File
@@ -296,6 +296,8 @@ struct Style {
fill: Option<Color>,
stroke: Option<Color>,
stroke_width: f64,
/// Key 12, the case: 1 Small Caps, 2 All Caps.
caps: u8,
}
/// The text document of an editing copy.
@@ -476,6 +478,7 @@ impl Texts {
fill: if on("56") { Some(pick("53").and_then(paint).unwrap_or(Color::BLACK)) } else { None },
stroke: if matches!(pick("57"), Some(Val::Bool(true))) { pick("54").and_then(paint) } else { None },
stroke_width: num("63").filter(|v| *v >= 0.0 && *v < 1e4).unwrap_or(1.0),
caps: num("12").filter(|v| *v == 1.0 || *v == 2.0).map_or(0, |v| v as u8),
}
}
}
@@ -824,6 +827,9 @@ fn char_style(s: &Style, scale: f64, leading: Option<f64>) -> CharStyle {
fill: s.fill.map_or(Paint::None, Paint::solid),
stroke: s.stroke.map_or(Paint::None, Paint::solid),
stroke_width: if s.stroke.is_some() { s.stroke_width * scale } else { 0.0 },
all_caps: s.caps == 2,
// At the default Small Caps size of Document Setup: the file's own isn't read.
small_caps: (s.caps == 1).then(|| vectorcraft_doc::DocSetup::default().small_caps_size),
..CharStyle::default()
}
}
@@ -1006,6 +1012,7 @@ mod tests {
fill: Some(Color::BLACK),
stroke: None,
stroke_width: 1.0,
caps: 0,
},
)],
}
+24
View File
@@ -685,6 +685,30 @@ fn type_on_a_hidden_layer_comes_from_the_text_document() {
assert!(!r.warnings.iter().any(|w| w.contains("couldn't be read")), "{:?}", r.warnings);
}
#[test]
fn type_from_the_text_document_keeps_its_case_setting() {
// Key 12 of a character style is its case: 1 Small Caps, 2 All Caps. The characters stay as
// typed; the setting shows them as capitals.
let doc = text_document(&[
("hon. guest\r", 0, (8200.0, 8180.0), (0.0, 0.0), 0.0),
("Book Now\r", 0, (8200.0, 8170.0), (0.0, 0.0), 0.0),
("plain\r", 0, (8200.0, 8160.0), (0.0, 0.0), 0.0),
])
.replacen("/1 14.0 /53", "/1 14.0 /12 2 /53", 1)
.replacen("/1 14.0 /53", "/1 14.0 /12 1 /53", 1);
let spare = text_object(0) + &text_object(1) + &text_object(2);
let art = native_with_text(&(layer("Back", true, &square(10, 10)) + &layer("Spare", false, &spare)), &doc);
let r = import(&eps(&page_square(10, 10), &art)).unwrap();
let texts = text_of(&r.document.layers[1]);
assert_eq!(texts.len(), 3, "{:?}", r.warnings);
let case = |i: usize| texts[i].runs.iter().map(|r| (r.style.all_caps, r.style.small_caps)).collect::<Vec<_>>();
assert_eq!(texts[0].plain_text(), "hon. guest");
assert_eq!(case(0), [(true, None)]);
assert_eq!(texts[1].plain_text(), "Book Now");
assert_eq!(case(1), [(false, Some(r.document.setup.small_caps_size))]);
assert_eq!(case(2), [(false, None)]);
}
#[test]
fn type_with_a_frame_it_cant_read_is_left_out_with_a_note() {
// A story whose frame is of a kind this doesn't know isn't guessed at.
+2 -1
View File
@@ -1,6 +1,6 @@
# File format parity
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-30 · **Change:** minor (`.ai` import: placed files at their box's corner where that draws as the page) · **Target:** Adobe Illustrator 2026 (30.x)
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-30 · **Change:** minor (`.ai` import: All Caps and Small Caps type from the editing data keeps its case setting) · **Target:** Adobe Illustrator 2026 (30.x)
Every format Illustrator opens, places, saves or exports, against VectorCraft's format table (`FORMATS`,
`OPEN_EXTS`, `PLACE_EXTS` in `crates/engine/src/cmd/fileio/formats.rs`). Illustrator's list comes from its public
@@ -132,6 +132,7 @@ the app's own editing copy of a file (an EPS after its `%%EOF`, a `.ai`'s `AIPri
| Date | Change | Summary |
|---|---|---|
| 2026-10-30 | minor | `.ai` import: type read from the editing data's text document keeps its case setting (character attribute 12: 1 Small Caps, at Document Setup's default size, 2 All Caps); it came in as typed, so a heading set in All Caps showed in lower or mixed case where the page draws capitals. Test `import::tests_native::type_from_the_text_document_keeps_its_case_setting`; checked locally against a user's files; scores unchanged |
| 2026-10-30 | minor | PDF import: a patch or triangle mesh shading of up to 16,384 patches comes in as gradient meshes (it was 4,096; Illustrator writes up to 14,355 in files users sent), and a stroke painted with one (how Illustrator writes a freeform gradient on a stroke) is the meshes clipped to the area the stroke paints, with its width, caps, joins and dashes; both came in as a flat grey. In two files of a user's set, the formerly grey area now matches a reference rendering in 99% of its pixels. Own-fixture tests in `tests::import_fidelity`; scores unchanged |
| 2026-10-30 | minor | `.ai`/EPS editing data: gradient meshes the reader marked unread, which made the whole file come in as its page or PDF part, are read: a mesh starts grey and its `/CS` is written only where it changes, before the corner it applies to (within a patch too), so each corner takes the space last set before it; named colours (`[c m y k /DeviceCMYK (name) /Process]` or `/Spot`) paint at the corner's tint and `/NChannel` inks overprint at theirs; meshes up to 128 patches a side and 16,384 patches are read (files users sent had up to 97 × 72). Ten of eleven such files from a user's set now open from their layers; meshes with handle weights other than 1 still don't. Own-fixture tests in `import::ai::mesh`; scores unchanged |
| 2026-10-30 | minor | `.ai` import: a placed (linked) file is put where its matrix takes the canvas point of art space's origin, and, when the layers then don't draw as the page, at the corner of the box it is written with, which many files use; six files of a user's set whose photos landed off their artboards now open from their layers. Own-fixture test in `import::tests_native`; scores unchanged |