mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-11 18:54:55 +00:00
#5820: files that record no units open in Preferences › Units › General
A PDF, an EPS or .ai without ruler units, a raster image, a metafile, an Affinity file or an importer plug-in's document opened (and reverted) in points whatever the General unit was. They now open in the General unit, as the reference app opens files that record no unit. Native, SVG and DXF files and a .ai or EPS with %AI5_RulerUnits keep their own units. Only the displayed units change, not the art.
This commit is contained in:
@@ -75,6 +75,7 @@ impl Document {
|
||||
dimensions,
|
||||
retired_artboard_id: _,
|
||||
opened_artboard: _,
|
||||
units_from_file: _,
|
||||
index: _,
|
||||
} = self;
|
||||
*version == other.version
|
||||
@@ -256,6 +257,7 @@ impl Document {
|
||||
dimensions: false,
|
||||
retired_artboard_id: Default::default(),
|
||||
opened_artboard: None,
|
||||
units_from_file: false,
|
||||
index: Default::default(),
|
||||
};
|
||||
let id = d.alloc_id();
|
||||
|
||||
@@ -339,6 +339,11 @@ pub struct Document {
|
||||
/// names it as the file's active artboard. `None`: the first. Native files do not keep it.
|
||||
#[serde(skip)]
|
||||
pub opened_artboard: Option<u32>,
|
||||
/// The file this document was read from recorded its units (a `.ai` file's
|
||||
/// `%AI5_RulerUnits`): it opens in them. A file that records none opens in Preferences ›
|
||||
/// Units › General (#5820). Native files do not keep it: they always keep their units.
|
||||
#[serde(skip)]
|
||||
pub units_from_file: bool,
|
||||
/// Some object is a dimension (the Dimension tool): edits refresh dimensions only while this
|
||||
/// is set, so documents without any pay nothing for them.
|
||||
#[serde(default, skip_serializing_if = "std::ops::Not::not")]
|
||||
|
||||
@@ -43,6 +43,22 @@ impl Loaded {
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
/// The file in the units of Preferences › Units › General when it records none of its own: a
|
||||
/// PDF, an EPS or `.ai` without ruler units, a raster image, a metafile, an Affinity file or
|
||||
/// what an importer plug-in reads, as the reference app opens such files (#5820). A native
|
||||
/// document (also one that an SVG, PDF, `.ai` or EPS file carries, `restored`), an SVG (the
|
||||
/// units of its root's size, pixels when it gives none), a DXF drawing (the unit it was read
|
||||
/// in) and a `.ai` or EPS with ruler units keep theirs. Only the units the document shows
|
||||
/// change, not its art.
|
||||
pub(crate) fn with_unit_prefs(mut self, prefs: &Prefs) -> Self {
|
||||
let records_units =
|
||||
self.restored || self.doc.units_from_file || matches!(self.format.id, "vectorcraft" | "template" | "svg" | "svgz" | "dxf");
|
||||
if !records_units {
|
||||
self.doc.units = crate::units::pref_unit(&prefs.units_general);
|
||||
}
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// An image ready to embed: PNG/JPEG/GIF/WebP keep their bytes, other formats are stored as PNG
|
||||
@@ -610,7 +626,8 @@ pub fn open_template(s: &mut Session, name: &str, bytes: &[u8], path: Option<&st
|
||||
/// Make a loaded file the new active document; `links` is what became of its linked files, and
|
||||
/// `opts` are the options it was read with.
|
||||
fn open_loaded(s: &mut Session, loaded: Loaded, links: Resolved, path: Option<String>, opts: &LoadOptions, as_template: bool) -> Result<Value> {
|
||||
let Loaded { mut doc, format, warnings, restored, converted, native_pdf_compatible, .. } = loaded.with_grid_prefs(&s.prefs);
|
||||
let Loaded { mut doc, format, warnings, restored, converted, native_pdf_compatible, .. } =
|
||||
loaded.with_grid_prefs(&s.prefs).with_unit_prefs(&s.prefs);
|
||||
// EPS now has an ordinary Save path. Its preview, partial-program and damaged-editing
|
||||
// fallbacks must ask before overwriting the richer source they could not fully read.
|
||||
let losses: Vec<String> = warnings
|
||||
|
||||
@@ -1051,7 +1051,7 @@ fn can_revert(s: &Session) -> std::result::Result<(), String> {
|
||||
fn revert(s: &mut Session, _: &Value) -> Result<Value> {
|
||||
let index = s.active_index().ok_or(crate::EngineError::NoDocument)?;
|
||||
let path = s.doc()?.path.clone().ok_or_else(|| bad("file.revert", "the document has never been saved"))?;
|
||||
let Loaded { mut doc, native_pdf_compatible, .. } = load(&path, &read_file(&path)?)?.with_grid_prefs(&s.prefs);
|
||||
let Loaded { mut doc, native_pdf_compatible, .. } = load(&path, &read_file(&path)?)?.with_grid_prefs(&s.prefs).with_unit_prefs(&s.prefs);
|
||||
doc.template = false;
|
||||
// Back to the saved file: its recovery copy has nothing left to recover.
|
||||
let uid = s.doc()?.uid;
|
||||
|
||||
@@ -199,3 +199,78 @@ fn east_asian_type_unit_leaves_the_document_in_points() {
|
||||
assert!((lead.unwrap() - Unit::Ha.to_pt(20.0)).abs() < 1e-9, "{lead:?}");
|
||||
assert!((sz - 17.0 * 72.0 / 101.6).abs() < 1e-9);
|
||||
}
|
||||
|
||||
/// A session whose Preferences › Units › General is `unit`, with no document open.
|
||||
fn general_in(unit: &str) -> Session {
|
||||
let mut s = Session::new();
|
||||
s.execute("prefs.set", &json!({"key": "unitsGeneral", "value": unit})).unwrap();
|
||||
s
|
||||
}
|
||||
|
||||
/// `bytes` opened as `name` in `s` → the opened document's units.
|
||||
fn opened_units(s: &mut Session, name: &str, bytes: &[u8]) -> Unit {
|
||||
s.execute("document.open", &json!({"name": name, "dataBase64": vectorcraft_format::base64_encode(bytes)})).unwrap();
|
||||
units(s)
|
||||
}
|
||||
|
||||
/// The active document of a session in `unit`, exported as `format`.
|
||||
fn exported(unit: &str, format: &str) -> Vec<u8> {
|
||||
let mut s = session();
|
||||
s.execute("document.setUnits", &json!({"units": unit})).unwrap();
|
||||
s.execute("shape.rectangle", &json!({"x": 10, "y": 10, "width": 50, "height": 30})).unwrap();
|
||||
let data = s.execute("document.export", &json!({"format": format})).unwrap()["dataBase64"].clone();
|
||||
vectorcraft_format::base64_decode(data.as_str().unwrap()).unwrap()
|
||||
}
|
||||
|
||||
/// A PostScript `.ai` with one empty layer and `header` among its comments.
|
||||
fn postscript_ai(header: &str) -> Vec<u8> {
|
||||
format!(
|
||||
"%!PS-Adobe-3.0\n%%Creator: test\n%AI5_FileFormat 14.0\n{header}%AI3_Cropmarks: 0 0 200 100\n%%EndComments\n%%BeginProlog\n%%EndProlog\n%%BeginSetup\n%%EndSetup\n%AI5_BeginLayer\n1 1 1 1 0 0 1 0 79 128 255 0 50 0 Lb\n(L) Ln\n0 A\n0 Xw\n\nLB\n%AI5_EndLayer--\n%%PageTrailer\ngsave annotatepage grestore showpage\n%%Trailer\n%%EOF\n"
|
||||
)
|
||||
.into_bytes()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn files_that_record_no_units_open_in_the_general_unit() {
|
||||
let pdf = vectorcraft_testkit::pdf::pdf(&[vectorcraft_testkit::pdf::PdfPage::new(120.0, 60.0, "0 0 0 rg 10 10 50 30 re f")], None);
|
||||
let png = exported("Inches", "png");
|
||||
let ai = postscript_ai("");
|
||||
let mut s = general_in("Millimeters");
|
||||
assert_eq!(opened_units(&mut s, "plain.pdf", &pdf), Unit::Millimeters, "a PDF records no units (#5820)");
|
||||
assert_eq!(opened_units(&mut s, "image.png", &png), Unit::Millimeters, "nor does an image");
|
||||
assert_eq!(opened_units(&mut s, "old.ai", &ai), Unit::Millimeters, "nor a .ai without ruler units");
|
||||
// Only the units change: the page keeps its size in points.
|
||||
s.execute("document.open", &json!({"name": "plain.pdf", "dataBase64": vectorcraft_format::base64_encode(&pdf)})).unwrap();
|
||||
let board = s.doc().unwrap().doc.artboards[0].rect;
|
||||
assert!((board.width() - 120.0).abs() < 1e-6 && (board.height() - 60.0).abs() < 1e-6, "{board:?}");
|
||||
// With the preference left at points, they open in points as before.
|
||||
let mut p = general_in("Points");
|
||||
assert_eq!(opened_units(&mut p, "plain.pdf", &pdf), Unit::Points);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn files_that_record_units_keep_them_whatever_the_general_unit() {
|
||||
let mut s = general_in("Millimeters");
|
||||
assert_eq!(opened_units(&mut s, "inches.ai", &postscript_ai("%AI5_RulerUnits: 0\n")), Unit::Inches, "a .ai's ruler units");
|
||||
assert_eq!(opened_units(&mut s, "points.ai", &postscript_ai("%AI5_RulerUnits: 2\n")), Unit::Points, "ruler units in points are kept too");
|
||||
assert_eq!(opened_units(&mut s, "saved.vectorcraft", &exported("Inches", "vectorcraft")), Unit::Inches, "a native file");
|
||||
let svg = r#"<svg xmlns="http://www.w3.org/2000/svg" width="2in" height="1in" viewBox="0 0 144 72"><rect width="10" height="10"/></svg>"#;
|
||||
assert_eq!(opened_units(&mut s, "inches.svg", svg.as_bytes()), Unit::Inches, "an SVG's root size");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn revert_reopens_a_file_without_units_in_the_general_unit() {
|
||||
let dir = std::env::temp_dir().join(format!("vc-5820-revert-{}", std::process::id()));
|
||||
std::fs::create_dir_all(&dir).unwrap();
|
||||
let path = dir.join("plain.pdf");
|
||||
std::fs::write(&path, vectorcraft_testkit::pdf::pdf(&[vectorcraft_testkit::pdf::PdfPage::new(120.0, 60.0, "0 0 0 rg 10 10 50 30 re f")], None))
|
||||
.unwrap();
|
||||
let mut s = general_in("Millimeters");
|
||||
s.execute("document.open", &json!({"path": path.to_string_lossy()})).unwrap();
|
||||
assert_eq!(units(&s), Unit::Millimeters);
|
||||
s.execute("shape.rectangle", &json!({"x": 1, "y": 1, "width": 5, "height": 5})).unwrap();
|
||||
s.execute("document.setUnits", &json!({"units": "Inches"})).unwrap();
|
||||
s.execute("file.revert", &json!({})).unwrap();
|
||||
assert_eq!(units(&s), Unit::Millimeters, "as the file opens");
|
||||
let _ = std::fs::remove_dir_all(&dir);
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@ impl Measure {
|
||||
}
|
||||
|
||||
/// The unit a Units preference value names (points when it names none).
|
||||
fn pref_unit(value: &str) -> Unit {
|
||||
pub(crate) fn pref_unit(value: &str) -> Unit {
|
||||
Unit::named(value).unwrap_or_default()
|
||||
}
|
||||
|
||||
|
||||
@@ -1253,6 +1253,7 @@ impl<'a> Reader<'a> {
|
||||
}
|
||||
if let Some(u) = self.ruler_units.and_then(ruler_unit) {
|
||||
doc.units = u;
|
||||
doc.units_from_file = true;
|
||||
}
|
||||
// The file's own swatches in place of the defaults, in its order and colour groups.
|
||||
let own_palette = match self.palette_swatches(&doc.patterns) {
|
||||
|
||||
+3
-2
@@ -1,6 +1,6 @@
|
||||
# UI and interaction parity
|
||||
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-11-09 · **Change:** minor (interim macOS cursor delivery, #2292; native validation pending; scores and estimates unchanged; Control bar › Snapping includes the existing Snap to Glyph and Alignment Guides toggles (#6164); scores and estimates unchanged; floating tab groups collapse into the side icon rail and opened rail panels fold back into it, #5925 in part; numeric fields step on wheel hover without taking keyboard focus, #5949; Character Styles panel: a double-click on a style's row opens its Character Style Options; the dialog keeps a fixed width and scrolls its sections, so its buttons stay on screen; Selection QoL: marquee modes and visible feedback, #5313; #3380 deletion-only review follow-up: independent survivors keep gradient placement, owned subpaths avoid discarded copies, and contracts/status/evidence are clarified; endpoint highlighting remains open; scores and estimates unchanged; Drop Shadow and glow dialogs: spinner arrows, #5367, #5368; Stroke Weight Up and Down shortcuts, #5797; Layers: Rename (`ui.rename`) opens the name field of the selected layer, group or object, #5324; Properties in Edit Artboards: Transform section above the Artboards section, #5219; scrubby labels: the Control bar's Stroke: and Opacity: links and the Properties panel's Stroke link scrub their fields, #4088; Properties: Quick Actions has Repeat Options for one selected repeat, #5181; Direct Selection: Shift-click picks several blend spine points, which drag together, #5285; Direct Selection: Cmd/Ctrl held lends the Selection tool and shows its bounding box, #3901; Join tool trims two crossing open paths and joins them at the crossing, #5844; Control bar: Paragraph: link and Align left/center/right while type is selected, #5372; shape tools: a press on the selection's centre point moves it and one in the rotate zone outside a box corner turns it, #5477; scores and estimates unchanged) · **Target:** Adobe Illustrator 2026 (30.x; Units › East Asian Type acts, with Q and Ha units; scores unchanged)
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-11-11 · **Change:** minor (files that record no units open in Preferences › Units › General, #5820; previously minor: interim macOS cursor delivery, #2292; native validation pending; scores and estimates unchanged; Control bar › Snapping includes the existing Snap to Glyph and Alignment Guides toggles (#6164); scores and estimates unchanged; floating tab groups collapse into the side icon rail and opened rail panels fold back into it, #5925 in part; numeric fields step on wheel hover without taking keyboard focus, #5949; Character Styles panel: a double-click on a style's row opens its Character Style Options; the dialog keeps a fixed width and scrolls its sections, so its buttons stay on screen; Selection QoL: marquee modes and visible feedback, #5313; #3380 deletion-only review follow-up: independent survivors keep gradient placement, owned subpaths avoid discarded copies, and contracts/status/evidence are clarified; endpoint highlighting remains open; scores and estimates unchanged; Drop Shadow and glow dialogs: spinner arrows, #5367, #5368; Stroke Weight Up and Down shortcuts, #5797; Layers: Rename (`ui.rename`) opens the name field of the selected layer, group or object, #5324; Properties in Edit Artboards: Transform section above the Artboards section, #5219; scrubby labels: the Control bar's Stroke: and Opacity: links and the Properties panel's Stroke link scrub their fields, #4088; Properties: Quick Actions has Repeat Options for one selected repeat, #5181; Direct Selection: Shift-click picks several blend spine points, which drag together, #5285; Direct Selection: Cmd/Ctrl held lends the Selection tool and shows its bounding box, #3901; Join tool trims two crossing open paths and joins them at the crossing, #5844; Control bar: Paragraph: link and Align left/center/right while type is selected, #5372; shape tools: a press on the selection's centre point moves it and one in the rotate zone outside a box corner turns it, #5477; scores and estimates unchanged) · **Target:** Adobe Illustrator 2026 (30.x; Units › East Asian Type acts, with Q and Ha units; scores unchanged)
|
||||
>
|
||||
> Vector Halftone preview update (2026-11-10): temporary dot selection marks are hidden, and the bounding box stays available (#6369).
|
||||
>
|
||||
@@ -282,7 +282,7 @@ tools, never checked side by side) isn't captured row by row.
|
||||
| Numeric spinner arrows step once on a click and keep repeating while held with the mouse | done | #6307; headless test `a_held_spinner_arrow_repeats` holds past egui's click timeout and checks release. |
|
||||
| Arithmetic in fields (`+ - * /`, and `%` of the value before it: `3cm*50%`, `50pt+25%`) and unit suffixes (`3 mm`, `1in`, `2p6`) | done | `Unit::parse`; mixed units in one expression not verified |
|
||||
| Numeric fields: focus click selects the whole value; dragging selects from the pressed character | partial | #1326; select-all waits for click release; px/pt/plain/optional fields, both directions and replacement text tested; native API input, 17 px partial replacement, Undo/Redo, saved-file round trip and screenshots verified; Tab, double-click, arrows and wheel preserved; short movements still classified as clicks can select all and remain open |
|
||||
| Units: pt, pc, in, mm, cm, px, ft-in, m, yd, ft; General/Stroke/Type units | done | units preference reverting to points on SVG output reported (#864) |
|
||||
| Units: pt, pc, in, mm, cm, px, ft-in, m, yd, ft; General/Stroke/Type units | done | units preference reverting to points on SVG output reported (#864). A file that records no units (PDF, EPS or `.ai` without ruler units, raster images, metafiles, Affinity files) opens and reverts in General units; native, SVG and DXF files and a `.ai` or EPS with ruler units keep theirs, as a [Community Expert's answer](https://community.adobe.com/questions-652/change-units-from-points-to-inches-won-t-stick-765372) describes (#5820) |
|
||||
| Units › East Asian Type (Q and Ha) | done | #394: `unitsAsianType` offers every unit and Q and Ha, a quarter of a millimetre each ([Scripting Guide's Measurement Units](https://ai-scripting.docsforadobe.dev/scripting/measurementUnits/): 0.709 pt per Q; [InDesign's unit notation](https://helpx.adobe.com/in/indesign/desktop/layout-and-grid-tools/rulers-and-measure-tools/change-ruler-measurement-units.html): `q`, `h`); only it lists Q and Ha. The public pages do not say which fields use it, so no field uses it yet and that waits for a source. Tests `q_and_ha_are_a_quarter_millimetre`, `east_asian_type_unit_*` |
|
||||
| Field precision: 3 decimals for steps; values kept as f64 | done | |
|
||||
| Scrubby labels (beyond Illustrator) | done | #400; the labels that are links (the Control bar's Stroke: and Opacity:, the Properties panel's Stroke) scrub too, and a click on them still opens their panel (#4088) |
|
||||
@@ -507,6 +507,7 @@ separate. Scores and hours unchanged. [Behavior and command details](development
|
||||
|
||||
| Date | Change | Summary |
|
||||
|---|---|---|
|
||||
| 2026-11-11 | minor | Files that record no units (PDF, EPS or `.ai` without ruler units, raster images, metafiles, Affinity files, importer plug-ins) open and revert in Preferences › Units › General instead of points; native, SVG and DXF files and `.ai`/EPS ruler units are kept (#5820). Scores and estimates unchanged. |
|
||||
| 2026-11-10 | minor | Held numeric spinner arrows keep stepping past the click timeout and stop on release (#6307). Scores and estimates unchanged. |
|
||||
| 2026-11-09 | minor | Interim macOS canvas cursor delivery (#2292): existing artwork in aligned 1×/2× AppKit representations, visible system fallback and host-owned restoration. Headless routing, bitmap and ownership coverage; native timing, menus, focus/handback, mixed displays, Spotlight and full-screen Spaces remain unverified. Scores and estimates unchanged. |
|
||||
| 2026-11-08 | minor | Floating tab groups collapse into the side icon rail and opened rail panels fold back into it (#5925, in part); tab groups remain intact. Scores and estimates unchanged. |
|
||||
|
||||
Reference in New Issue
Block a user