From 3074560b07e752234f612ec93fa71c4347e36972 Mon Sep 17 00:00:00 2001 From: dotCooCoo Date: Wed, 11 Nov 2026 10:17:24 -0800 Subject: [PATCH] PDF: an option writes pages longer than 200 in at full size in points The Save PDF dialog's General section gets "Write pages longer than 200 in at full size in points" (longPagesInPoints in document.exportPdf, document.export, presets and document.pdfSettings), off by default. When it is on, a page with a side longer than 14,400 pt is written with its media box in points and no /UserUnit at every PDF version, and a PDF 1.5 file stays PDF 1.5. The export's warning for such a page then says it is written in points. With the option off, and for pages of 14,400 pt or less, the file is the same as before. Docs: docs/mcp.md, docs/target-app-parity.md. The new label has a row in every catalog that lists the dialog's other options. --- crates/engine/src/cmd/fileio/pdf.rs | 8 ++- crates/engine/src/tests/pdfpresets.rs | 40 ++++++++++++++ crates/pdf/src/export/entry.rs | 11 ++-- crates/pdf/src/settings.rs | 4 ++ crates/pdf/src/tests/marks.rs | 73 ++++++++++++++++++++++++++ crates/ui-egui/src/dialogs/save_pdf.rs | 4 ++ crates/ui-egui/src/i18n/bg.tsv | 1 + crates/ui-egui/src/i18n/bs.tsv | 1 + crates/ui-egui/src/i18n/ca.tsv | 1 + crates/ui-egui/src/i18n/cs.tsv | 1 + crates/ui-egui/src/i18n/da.tsv | 1 + crates/ui-egui/src/i18n/de.tsv | 1 + crates/ui-egui/src/i18n/el.tsv | 1 + crates/ui-egui/src/i18n/es.tsv | 1 + crates/ui-egui/src/i18n/fi.tsv | 1 + crates/ui-egui/src/i18n/fr.tsv | 1 + crates/ui-egui/src/i18n/hi.tsv | 1 + crates/ui-egui/src/i18n/hu.tsv | 1 + crates/ui-egui/src/i18n/id.tsv | 1 + crates/ui-egui/src/i18n/it.tsv | 1 + crates/ui-egui/src/i18n/ja.tsv | 1 + crates/ui-egui/src/i18n/ka.tsv | 1 + crates/ui-egui/src/i18n/ko.tsv | 1 + crates/ui-egui/src/i18n/nl.tsv | 1 + crates/ui-egui/src/i18n/pl.tsv | 1 + crates/ui-egui/src/i18n/pt-br.tsv | 1 + crates/ui-egui/src/i18n/ru.tsv | 1 + crates/ui-egui/src/i18n/sv.tsv | 1 + crates/ui-egui/src/i18n/tr.tsv | 1 + crates/ui-egui/src/i18n/uk.tsv | 1 + crates/ui-egui/src/i18n/vi.tsv | 1 + crates/ui-egui/src/i18n/zh-hans.tsv | 1 + crates/ui-egui/src/i18n/zh-hant.tsv | 1 + docs/mcp.md | 4 +- docs/target-app-parity.md | 2 +- 35 files changed, 165 insertions(+), 8 deletions(-) diff --git a/crates/engine/src/cmd/fileio/pdf.rs b/crates/engine/src/cmd/fileio/pdf.rs index ccafc96e5..365edbedb 100644 --- a/crates/engine/src/cmd/fileio/pdf.rs +++ b/crates/engine/src/cmd/fileio/pdf.rs @@ -95,6 +95,12 @@ pub(crate) const OPTIONS: &[FormatOption] = &[ default: "false", description: "keep the layers whose Print option is off (left out otherwise, unless createLayers)", }, + FormatOption { + name: "longPagesInPoints", + ty: "boolean", + default: "false", + description: "a page with a side longer than 14,400 pt (200 in) is written at its size in points, without a UserUnit, at every PDF version", + }, FormatOption { name: "createLayers", ty: "boolean", @@ -122,7 +128,7 @@ pub fn specs() -> Vec { "Export PDF", [], None, - "{path?, preset?: \"VectorCraft Default\" (built-in or saved: pdf.preset.list), artboard? | artboards?: [i…] | range?: \"1-3, 5\" (1-based; default all, one page each), standard?: none|pdfA2b|pdfX1a (CMYK, grey and spot colours only: colours and images converted to output.destination or the CMYK output intent, untagged; transparency flattened with flattenerPreset; PDF 1.3)|pdfX3 (colours ICC-tagged; transparency flattened; PDF 1.3)|pdfX4 (transparency and layers kept; colours ICC-tagged; PDF 1.6 at most) (PDF/X: a /GTS_PDFX output intent, by default the CMYK profile in effect, embedded; TrimBox, Trapped, GTS_PDFXVersion, title and dates; fonts embedded or outlined), compatibility?: 1.3 (no transparency: it is flattened with flattenerPreset, without the layers the file leaves out; images keep no see-through pixels)|1.4|1.5|1.6|1.7 (default)|2.0 (choosing a standard sets its version: 1.7 for pdfA2b, 1.6 for pdfX4, 1.3 for pdfX1a and pdfX3, which write 1.4 as 1.3 too; pdfA2b refuses 1.3; a page with a side longer than 14,400 pt keeps its size with a UserUnit from 1.6 on: 1.5 is then written as 1.6, with 1.5's encryption; PDF/X files, 1.3 and 1.4 write it in points, and 1.3 and 1.4 (pdfX1a and pdfX3 too) refuse a page past 32,767 pt whose size or art needs a real number past 32,767; a page whose short side is 3 pt or longer but would be under 3 units at that UserUnit gets a smaller one that keeps the short side at 3 units; a warning gives each such page's size), preserveEditing? (on in VectorCraft Default: the native document as an embedded file, which document.open restores; choosing a standard turns it off, a standard refuses it), thumbnails? (each page drawn as a /Thumb image, 106 px on its long side), fastWebView? (a linearised file: the first page shows while the rest downloads; it stays linearised when encrypted), viewAfterSaving? (the app opens the written file), createLayers? (each layer and sublayer but template layers as a PDF layer, an optional content group listed under its layer's: hidden layers off, non-printing ones /PrintState /OFF, locked ones locked; needs 1.5 or later; they reopen as layers; choosing pdfX1a or pdfX3 turns it off; they refuse it), includeNonPrinting? (keep layers whose Print option is off; left out by default unless createLayers), flattenerPreset?: \"\" (High Resolution) | high | medium | low | a saved preset (flattener.presets.list): what PDF 1.3 files flatten transparency with, flattener?: {object.flattenTransparency options over it}, compression?: {color?, gray?: {downsample: none|average|subsample|bicubic, ppi: 300, abovePpi: 450, compression: none|zip|jpeg|jpeg2000|auto, quality: minimum|low|medium|high|maximum}, mono?: {downsample, ppi: 1200, abovePpi: 1800, compression: none|ccittG3|ccittG4|zip|runLength} (black-and-white images), compressText?: true} (images above abovePpi are resampled to ppi; auto keeps JPEGs JPEG, the others lossless; JPEG needs opaque images; none, jpeg2000, CCITT and runLength are written as ZIP with a warning), marks?: {trim, registration, colorBars, pageInfo, kind: roman|japanese, weight: 0.25 (trim marks and targets), offset: 6 (pt from the artboard, at least the bleed)} (printer's marks in [Registration], every plate: trim marks, registration targets mid-side, CMYK/spot/black-tint colour bars on top, page information (title, artboard, date UTC) below), bleed?: {useDocument (the document's bleed, document.setup), top, bottom, left, right} (pt; art in the bleed is kept; a PDF/X file with no bleed on a side where the document's bleed has one comes back with a warning). Each page: TrimBox = artboard, BleedBox = artboard + bleed, MediaBox = that + the marks' room (art clipped to the BleedBox), output?: {conversion: none (default)|destination (every colour into the destination profile's model, CMYK or RGB, also colours of that model in another profile)|preserveNumbers (only colours of the other model and Lab; those in the destination's model keep their numbers) (grey stays grey; images are converted too, printer's marks are not), destination: profile name (edit.colorSettings lists them; default: the document's profile for its colour mode), profiles: none (device colours)|all|destination|taggedSource (documents assigned a profile: edit.assignProfile) (ICC-based colours: CMYK with the destination or document CMYK profile, RGB as sRGB, grey with the sRGB tone curve; PDF/A, pdfX3 and pdfX4 always, whatever profiles says), outputIntent: profile name (embedded as the catalog's /GTS_PDFX output intent; a name that isn't a profile is only named, which pdfX1a and pdfX3 allow only with a registry and pdfX4 refuses; pdfX1a needs a CMYK one), outputCondition, outputConditionId (default: outputIntent), registry, trapped (/Trapped /True; /False when an output intent is written)} (PDF/A files keep their own output intent), advanced?: {fontSubsetPercent: 100 (below 100 is accepted with a warning: fonts are always embedded as subsets), outlineText: false (type as real, selectable and searchable text in embedded subset fonts with a ToUnicode map; fonts whose licence forbids embedding, or allows only whole fonts, stay outlines with a warning; true: every glyph as an outline), overprint: preserve (overprinting fills and strokes set /OP /op true, /OPM 1; white ones knock out with document.setup discardWhiteOverprint)|discard (they knock out)}, security?: {openPassword, permissionsPassword, printing: none|low|high, changes: none|pages|forms|comments|any, copy, screenReader, plaintextMetadata}, acknowledgeLoss?: bool (write over the file the document was read from although reading it left things out), format?: \"pdf\"} → {path, bytes, warnings}; no path → {dataBase64, bytes, warnings}. The options apply over the preset (null keeps its value); options accepted but not applied yet come back as warnings; a standard with a PDF version it doesn't allow and a password with a standard are refused, and so is a PDF/X file that would still break its standard (transparency the flattener left, RGB in pdfX1a, a font not embedded) or a PDF 1.3 file with transparency left. document.export {format: pdf} takes the same options. A key PDF doesn't take, and an unknown key inside one of the option objects, are refused", + "{path?, preset?: \"VectorCraft Default\" (built-in or saved: pdf.preset.list), artboard? | artboards?: [i…] | range?: \"1-3, 5\" (1-based; default all, one page each), standard?: none|pdfA2b|pdfX1a (CMYK, grey and spot colours only: colours and images converted to output.destination or the CMYK output intent, untagged; transparency flattened with flattenerPreset; PDF 1.3)|pdfX3 (colours ICC-tagged; transparency flattened; PDF 1.3)|pdfX4 (transparency and layers kept; colours ICC-tagged; PDF 1.6 at most) (PDF/X: a /GTS_PDFX output intent, by default the CMYK profile in effect, embedded; TrimBox, Trapped, GTS_PDFXVersion, title and dates; fonts embedded or outlined), compatibility?: 1.3 (no transparency: it is flattened with flattenerPreset, without the layers the file leaves out; images keep no see-through pixels)|1.4|1.5|1.6|1.7 (default)|2.0 (choosing a standard sets its version: 1.7 for pdfA2b, 1.6 for pdfX4, 1.3 for pdfX1a and pdfX3, which write 1.4 as 1.3 too; pdfA2b refuses 1.3; a page with a side longer than 14,400 pt keeps its size with a UserUnit from 1.6 on: 1.5 is then written as 1.6, with 1.5's encryption; PDF/X files, 1.3 and 1.4 write it in points, and 1.3 and 1.4 (pdfX1a and pdfX3 too) refuse a page past 32,767 pt whose size or art needs a real number past 32,767; a page whose short side is 3 pt or longer but would be under 3 units at that UserUnit gets a smaller one that keeps the short side at 3 units; a warning gives each such page's size), longPagesInPoints?: false (true: such a page is written at its size in points, without a UserUnit, at every version, and 1.5 stays 1.5; apps that keep to the limit cut it to 200 in), preserveEditing? (on in VectorCraft Default: the native document as an embedded file, which document.open restores; choosing a standard turns it off, a standard refuses it), thumbnails? (each page drawn as a /Thumb image, 106 px on its long side), fastWebView? (a linearised file: the first page shows while the rest downloads; it stays linearised when encrypted), viewAfterSaving? (the app opens the written file), createLayers? (each layer and sublayer but template layers as a PDF layer, an optional content group listed under its layer's: hidden layers off, non-printing ones /PrintState /OFF, locked ones locked; needs 1.5 or later; they reopen as layers; choosing pdfX1a or pdfX3 turns it off; they refuse it), includeNonPrinting? (keep layers whose Print option is off; left out by default unless createLayers), flattenerPreset?: \"\" (High Resolution) | high | medium | low | a saved preset (flattener.presets.list): what PDF 1.3 files flatten transparency with, flattener?: {object.flattenTransparency options over it}, compression?: {color?, gray?: {downsample: none|average|subsample|bicubic, ppi: 300, abovePpi: 450, compression: none|zip|jpeg|jpeg2000|auto, quality: minimum|low|medium|high|maximum}, mono?: {downsample, ppi: 1200, abovePpi: 1800, compression: none|ccittG3|ccittG4|zip|runLength} (black-and-white images), compressText?: true} (images above abovePpi are resampled to ppi; auto keeps JPEGs JPEG, the others lossless; JPEG needs opaque images; none, jpeg2000, CCITT and runLength are written as ZIP with a warning), marks?: {trim, registration, colorBars, pageInfo, kind: roman|japanese, weight: 0.25 (trim marks and targets), offset: 6 (pt from the artboard, at least the bleed)} (printer's marks in [Registration], every plate: trim marks, registration targets mid-side, CMYK/spot/black-tint colour bars on top, page information (title, artboard, date UTC) below), bleed?: {useDocument (the document's bleed, document.setup), top, bottom, left, right} (pt; art in the bleed is kept; a PDF/X file with no bleed on a side where the document's bleed has one comes back with a warning). Each page: TrimBox = artboard, BleedBox = artboard + bleed, MediaBox = that + the marks' room (art clipped to the BleedBox), output?: {conversion: none (default)|destination (every colour into the destination profile's model, CMYK or RGB, also colours of that model in another profile)|preserveNumbers (only colours of the other model and Lab; those in the destination's model keep their numbers) (grey stays grey; images are converted too, printer's marks are not), destination: profile name (edit.colorSettings lists them; default: the document's profile for its colour mode), profiles: none (device colours)|all|destination|taggedSource (documents assigned a profile: edit.assignProfile) (ICC-based colours: CMYK with the destination or document CMYK profile, RGB as sRGB, grey with the sRGB tone curve; PDF/A, pdfX3 and pdfX4 always, whatever profiles says), outputIntent: profile name (embedded as the catalog's /GTS_PDFX output intent; a name that isn't a profile is only named, which pdfX1a and pdfX3 allow only with a registry and pdfX4 refuses; pdfX1a needs a CMYK one), outputCondition, outputConditionId (default: outputIntent), registry, trapped (/Trapped /True; /False when an output intent is written)} (PDF/A files keep their own output intent), advanced?: {fontSubsetPercent: 100 (below 100 is accepted with a warning: fonts are always embedded as subsets), outlineText: false (type as real, selectable and searchable text in embedded subset fonts with a ToUnicode map; fonts whose licence forbids embedding, or allows only whole fonts, stay outlines with a warning; true: every glyph as an outline), overprint: preserve (overprinting fills and strokes set /OP /op true, /OPM 1; white ones knock out with document.setup discardWhiteOverprint)|discard (they knock out)}, security?: {openPassword, permissionsPassword, printing: none|low|high, changes: none|pages|forms|comments|any, copy, screenReader, plaintextMetadata}, acknowledgeLoss?: bool (write over the file the document was read from although reading it left things out), format?: \"pdf\"} → {path, bytes, warnings}; no path → {dataBase64, bytes, warnings}. The options apply over the preset (null keeps its value); options accepted but not applied yet come back as warnings; a standard with a PDF version it doesn't allow and a password with a standard are refused, and so is a PDF/X file that would still break its standard (transparency the flattener left, RGB in pdfX1a, a font not embedded) or a PDF 1.3 file with transparency left. document.export {format: pdf} takes the same options. A key PDF doesn't take, and an unknown key inside one of the option objects, are refused", has_doc, export_pdf ), diff --git a/crates/engine/src/tests/pdfpresets.rs b/crates/engine/src/tests/pdfpresets.rs index eee88c5d1..7de08463f 100644 --- a/crates/engine/src/tests/pdfpresets.rs +++ b/crates/engine/src/tests/pdfpresets.rs @@ -123,6 +123,46 @@ fn presets_export_and_import_round_trip() { assert!(is_bad_params(Session::new().execute("pdf.preset.export", &json!({}))), "nothing saved to export"); } +/// `longPagesInPoints` is saved with a preset, comes back through a presets file and the +/// preferences, shows in `document.pdfSettings`, and a 10 m page exported with it reopens at 10 m. +/// A value that isn't a boolean is refused. +#[test] +fn long_pages_in_points_round_trips_through_presets_and_reopens_at_full_size() { + let mut s = Session::new(); + s.execute("file.new", &json!({"width": 28_346.456_692_913_386, "height": 2_834.645_669_291_338_5})).unwrap(); + s.execute("pdf.preset.save", &json!({"name": "Banner", "longPagesInPoints": true, "preserveEditing": false})).unwrap(); + assert!(s.prefs.pdf_presets[0].settings.long_pages_in_points); + let back: crate::Prefs = serde_json::from_value(s.prefs.to_json()).unwrap(); + assert_eq!(back.pdf_presets, s.prefs.pdf_presets); + let text = s.execute("pdf.preset.export", &json!({"names": ["Banner"]})).unwrap()["data"].as_str().unwrap().to_string(); + let mut t = session(); + t.execute("pdf.preset.import", &json!({"data": text})).unwrap(); + assert_eq!(t.prefs.pdf_presets, s.prefs.pdf_presets); + let v = s.execute("document.pdfSettings", &json!({"longPagesInPoints": true})).unwrap(); + assert_eq!((&v["settings"]["longPagesInPoints"], &v["changed"]), (&json!(true), &json!([{"option": "longPagesInPoints", "value": true}])), "{v}"); + assert_eq!(s.execute("document.pdfSettings", &json!({})).unwrap()["settings"]["longPagesInPoints"], json!(false)); + // The preset writes the page in points; the file reopens at 10 m. + let v = s.execute("document.exportPdf", &json!({"preset": "Banner"})).unwrap(); + let bytes = pdf(&v); + assert!(!String::from_utf8_lossy(&bytes).contains("/UserUnit")); + assert!( + v["warnings"] + .as_array() + .unwrap() + .iter() + .any(|w| w.as_str().unwrap().ends_with("written without a UserUnit, in points, and apps that keep to the limit cut the page to 200 in")), + "{v}" + ); + let mut o = Session::new(); + o.execute("document.open", &json!({"name": "banner.pdf", "dataBase64": vectorcraft_format::base64_encode(&bytes)})).unwrap(); + let rect = o.doc().unwrap().doc.artboards[0].rect; + assert!((rect.width() - 28_346.46).abs() < 0.01 && (rect.height() - 2_834.65).abs() < 0.01, "{rect:?}"); + for bad in [json!("yes"), json!(1)] { + assert!(is_bad_params(s.execute("document.exportPdf", &json!({"longPagesInPoints": bad}))), "{bad}"); + assert!(is_bad_params(s.execute("pdf.preset.save", &json!({"name": "Bad", "longPagesInPoints": bad}))), "{bad}"); + } +} + #[test] fn saved_presets_persist_with_the_preferences() { let mut s = session(); diff --git a/crates/pdf/src/export/entry.rs b/crates/pdf/src/export/entry.rs index 75b87209c..8d99c1c85 100644 --- a/crates/pdf/src/export/entry.rs +++ b/crates/pdf/src/export/entry.rs @@ -192,9 +192,10 @@ const MIN_PAGE_SIDE: f64 = 3.0; const MAX_PDF14_REAL: f64 = 32_767.0; /// Pages with a side longer than [`MAX_PAGE_SIDE`] are written with a `UserUnit`: in PDF 1.6 and -/// later files that aren't PDF/X files. Other files have every page in points. +/// later files that aren't PDF/X files, unless `long_pages_in_points` is on. Other files have +/// every page in points. fn writes_user_units(set: &PdfSettings) -> bool { - set.compatibility.has_user_unit() && !set.standard.is_pdfx() + set.compatibility.has_user_unit() && !set.standard.is_pdfx() && !set.long_pages_in_points } /// The `UserUnit` (PDF 1.6) of a page `w` × `h` points large: 1 when both sides are at most @@ -220,15 +221,15 @@ pub fn check_page_sizes(doc: &Document, opts: &PdfOptions) -> Result<(), PdfErro /// The settings `set` is written with when one of `pages` has a side longer than /// [`MAX_PAGE_SIDE`]: a PDF 1.5 file that isn't a PDF/X file is written as PDF 1.6, which has a -/// page's `UserUnit` (`None`: `set` as it is). Other files without a `UserUnit` have the page in -/// points. The writer puts a whole number as an integer and fails a PDF 1.3 or 1.4 file that has +/// page's `UserUnit`, unless `long_pages_in_points` is on (`None`: `set` as it is). Other files +/// without a `UserUnit` have the page in points. The writer puts a whole number as an integer and fails a PDF 1.3 or 1.4 file that has /// a real number past [`MAX_PDF14_REAL`]: a page whose side is past it and not a whole number of /// points is refused here, before anything is drawn ([`page_too_long`]). fn for_long_pages(set: &PdfSettings, pages: &[(usize, PageBoxes)]) -> Result, PdfError> { if !pages.iter().any(|(_, b)| b.media.width().max(b.media.height()) > MAX_PAGE_SIDE) { return Ok(None); } - let raised = (set.compatibility == Compatibility::Pdf15 && !set.standard.is_pdfx()) + let raised = (set.compatibility == Compatibility::Pdf15 && !set.standard.is_pdfx() && !set.long_pages_in_points) .then(|| PdfSettings { compatibility: Compatibility::Pdf16, ..set.clone() }); // The writer's test for a whole number, on the `f32` it writes. let real = |side: f64| side > MAX_PDF14_REAL && (side as f32) as i32 as f32 != side as f32; diff --git a/crates/pdf/src/settings.rs b/crates/pdf/src/settings.rs index 17f37f90b..b4b25baeb 100644 --- a/crates/pdf/src/settings.rs +++ b/crates/pdf/src/settings.rs @@ -236,6 +236,10 @@ pub struct PdfSettings { /// Keep the layers whose Print option is off (they are left out otherwise, unless /// `create_layers` is on). pub include_non_printing: bool, + /// Write a page with a side longer than 14,400 pt (200 in) at its size in points, without a + /// `UserUnit`, at every PDF version (off: PDF 1.6 and later files that aren't PDF/X files + /// write it with a `UserUnit`, and PDF 1.5 files are written as PDF 1.6 for it). + pub long_pages_in_points: bool, pub compression: CompressionSettings, pub marks: MarkSettings, pub bleed: BleedSettings, diff --git a/crates/pdf/src/tests/marks.rs b/crates/pdf/src/tests/marks.rs index 19031505d..c829ed8d5 100644 --- a/crates/pdf/src/tests/marks.rs +++ b/crates/pdf/src/tests/marks.rs @@ -418,6 +418,79 @@ fn a_page_within_14400_pt_is_written_as_before_at_every_version() { assert!(r.bytes.starts_with(b"%PDF-1.5") && has(b"/CFM/V2") && !has(b"AESV2")); } +/// 10 m in points. +const TEN_M: f64 = 10.0 / 0.0254 * 72.0; + +/// With `long_pages_in_points`, a 10 m page at PDF 1.7 is written at its size in points, with no +/// `UserUnit`, the warning says so, and the page reads back at 10 m. Without it the page keeps +/// its `UserUnit`. +#[test] +fn long_pages_in_points_writes_a_10_m_page_in_points() { + let d = sized(TEN_M, TEN_M / 10.0); + let r = report(&d, PdfSettings { long_pages_in_points: true, ..PdfSettings::default() }).unwrap(); + assert!(r.bytes.starts_with(b"%PDF-1.7")); + assert_eq!(raw_numbers(&r.bytes, "/UserUnit", 1), Vec::::new()); + let media = raw_numbers(&r.bytes, "/MediaBox", 4); + assert!((media[2] - media[0] - 28_346.46).abs() < 0.01 && (media[3] - media[1] - 2_834.65).abs() < 0.01, "{media:?}"); + assert_eq!( + long_page_warnings(&r), + [ + "the page of artboard 1 is 28347 × 2835 pt (10.00 × 1.00 m), longer than PDF's page limit of 14,400 pt (200 in): PDF 1.7 pages are written without a UserUnit, in points, and apps that keep to the limit cut the page to 200 in" + ], + "{:?}", + r.warnings + ); + let page = &info(&r.bytes, None).unwrap().pages[0]; + assert!((page.width - TEN_M).abs() < 0.01 && (page.height - TEN_M / 10.0).abs() < 0.01, "{page:?}"); + let back = import(&r.bytes).unwrap(); + let rect = back.artboards[0].rect; + assert!((rect.width() - TEN_M).abs() < 0.01 && (rect.height() - TEN_M / 10.0).abs() < 0.01, "{rect:?}"); + // Off (the default), the page has a `UserUnit` of 28,346.46 / 14,400, rounded up. + let off = report(&d, PdfSettings::default()).unwrap(); + assert_eq!(raw_numbers(&off.bytes, "/UserUnit", 1), [1.969]); + assert!( + long_page_warnings(&off)[0].ends_with("it is written with a UserUnit of 1.969, so apps that ignore UserUnit show it that many times smaller") + ); + assert!(!settings(json!({})).long_pages_in_points, "off by default"); +} + +/// With `long_pages_in_points`, every PDF version writes a 10 m page in points: PDF 1.5 stays +/// PDF 1.5, and versions that write it in points anyway (PDF 1.3 and 1.4, PDF/X-4) give the same +/// file as without it. +#[test] +fn long_pages_in_points_keeps_every_version_and_changes_nothing_where_pages_are_in_points_already() { + let d = sized(TEN_M, TEN_M / 10.0); + let with = |set: &PdfSettings| PdfSettings { long_pages_in_points: true, ..set.clone() }; + for c in Compatibility::ALL.iter().copied() { + let set = PdfSettings { compatibility: c, ..PdfSettings::default() }; + let r = report(&d, with(&set)).unwrap(); + assert!(r.bytes.starts_with(format!("%PDF-{}", c.id()).as_bytes()), "{}", c.label()); + assert_eq!(raw_numbers(&r.bytes, "/UserUnit", 1), Vec::::new(), "{}", c.label()); + assert!((raw_meters(&r.bytes).0 - 10.0).abs() < 0.001, "{}", c.label()); + assert!(!r.warnings.iter().any(|w| w.starts_with("PDF 1.5 has no UserUnit")), "{}: {:?}", c.label(), r.warnings); + assert_eq!(long_page_warnings(&r).len(), 1, "{}: {:?}", c.label(), r.warnings); + if matches!(c, Compatibility::Pdf13 | Compatibility::Pdf14) { + assert_eq!(r.bytes, report(&d, set).unwrap().bytes, "{}", c.label()); + } + } + let x4 = crate::builtin_presets().into_iter().find(|p| p.name == "PDF/X-4:2010").unwrap().settings; + assert_eq!(report(&d, with(&x4)).unwrap().bytes, report(&d, x4).unwrap().bytes); +} + +/// A page of 14,400 pt or less gives the same file with `long_pages_in_points` on or off, at every +/// PDF version. +#[test] +fn long_pages_in_points_leaves_a_page_within_14400_pt_as_it_is() { + let d = sized(14_400.0, 300.0); + for c in Compatibility::ALL.iter().copied() { + let off = PdfSettings { compatibility: c, ..PdfSettings::default() }; + let on = PdfSettings { long_pages_in_points: true, ..off.clone() }; + let (off, on) = (report(&d, off).unwrap(), report(&d, on).unwrap()); + assert_eq!(on.bytes, off.bytes, "{}", c.label()); + assert_eq!(on.warnings, off.warnings, "{}", c.label()); + } +} + #[test] fn art_in_the_bleed_is_kept_and_clipped_to_the_bleed_box() { let d = doc(); diff --git a/crates/ui-egui/src/dialogs/save_pdf.rs b/crates/ui-egui/src/dialogs/save_pdf.rs index ee68105a6..ba6d20607 100644 --- a/crates/ui-egui/src/dialogs/save_pdf.rs +++ b/crates/ui-egui/src/dialogs/save_pdf.rs @@ -478,6 +478,10 @@ fn general(ui: &mut egui::Ui, d: &mut Dialog, editor: bool) { let layers = standard.allows_layers() && choice::(d, "compatibility").unwrap_or_default().has_layers(); flag(ui, d, "createLayers", tl!("Create PDF layers from layers and sublayers"), layers); flag(ui, d, "includeNonPrinting", tl!("Include non-printing layers"), true); + // PDF/X files and PDF 1.3 and 1.4 files always write long pages in points. + let compatibility = choice::(d, "compatibility").unwrap_or_default(); + let scales = !standard.is_pdfx() && !matches!(compatibility, Compatibility::Pdf13 | Compatibility::Pdf14); + flag(ui, d, "longPagesInPoints", tl!("Write pages longer than 200 in at full size in points"), scales); if editor { heading(ui, tl!("Description")); if let Some(text) = widgets::text_field_named(ui, "pdf-preset-description", Some(tl!("Description")), Some(&d.str("description")), 460.0, 3) { diff --git a/crates/ui-egui/src/i18n/bg.tsv b/crates/ui-egui/src/i18n/bg.tsv index e69dc7b97..88f1a8431 100644 --- a/crates/ui-egui/src/i18n/bg.tsv +++ b/crates/ui-egui/src/i18n/bg.tsv @@ -1604,6 +1604,7 @@ font-technology Variable Променливи Include Linked Files Включване на свързаните файлове Include Metadata Включване на метаданни Include non-printing layers Включване на слоеве, които не се печат + Write pages longer than 200 in at full size in points Записване на страници, по-дълги от 200 in, в пълен размер в точки Include SVG Code Включване на SVG код Include Swatches for Tints Включване на образци за тонове Inflate… Издуване… diff --git a/crates/ui-egui/src/i18n/bs.tsv b/crates/ui-egui/src/i18n/bs.tsv index fc4610e0d..c6a4d0777 100644 --- a/crates/ui-egui/src/i18n/bs.tsv +++ b/crates/ui-egui/src/i18n/bs.tsv @@ -2878,6 +2878,7 @@ antiAlias Sharp Oštro General › Thumbnails Općenito › Sličice Grayscale images Slike u nijansama sive Include non-printing layers Uključi slojeve koji se ne štampaju + Write pages longer than 200 in at full size in points Upiši stranice duže od 200 in u punoj veličini, u tačkama Left: Lijevo: Mark as trapped Označi kao zarobljeno Marks and Bleeds Oznake i napusti diff --git a/crates/ui-egui/src/i18n/ca.tsv b/crates/ui-egui/src/i18n/ca.tsv index 44562ac0f..556302f9f 100644 --- a/crates/ui-egui/src/i18n/ca.tsv +++ b/crates/ui-egui/src/i18n/ca.tsv @@ -2045,6 +2045,7 @@ digits Malayalam Malaiàlam Include Linked Files Inclou els fitxers enllaçats Include Metadata Inclou les metadades Include non-printing layers Inclou les capes no imprimibles + Write pages longer than 200 in at full size in points Escriu les pàgines de més de 200 in a mida real, en punts Include Objects On Canvas Inclou els objectes del llenç Include SVG Code Inclou codi SVG Include Swatches for Tints Inclou mostres per als tons diff --git a/crates/ui-egui/src/i18n/cs.tsv b/crates/ui-egui/src/i18n/cs.tsv index e42f9bd21..1bee701c2 100644 --- a/crates/ui-egui/src/i18n/cs.tsv +++ b/crates/ui-egui/src/i18n/cs.tsv @@ -2908,6 +2908,7 @@ antiAlias Sharp Ostré General › Thumbnails Obecné › Miniatury Grayscale images Obrazy ve stupních šedi Include non-printing layers Zahrnout netisknutelné vrstvy + Write pages longer than 200 in at full size in points Zapsat stránky delší než 200 in v plné velikosti v bodech Left: Vlevo: Mark as trapped Označit jako s přesahy barev Marks and Bleeds Značky a spadávky diff --git a/crates/ui-egui/src/i18n/da.tsv b/crates/ui-egui/src/i18n/da.tsv index cf7aa67b3..40ff520ca 100644 --- a/crates/ui-egui/src/i18n/da.tsv +++ b/crates/ui-egui/src/i18n/da.tsv @@ -2698,6 +2698,7 @@ antiAlias Sharp Skarp General › Thumbnails Generelt › Miniaturer Grayscale images Gråtonebilleder Include non-printing layers Medtag lag, der ikke udskrives + Write pages longer than 200 in at full size in points Skriv sider længere end 200 in i fuld størrelse i punkter Left: Venstre: Mark as trapped Markér som trappet Marks and Bleeds Mærker og udløb diff --git a/crates/ui-egui/src/i18n/de.tsv b/crates/ui-egui/src/i18n/de.tsv index b339033e6..2606803c4 100644 --- a/crates/ui-egui/src/i18n/de.tsv +++ b/crates/ui-egui/src/i18n/de.tsv @@ -1655,6 +1655,7 @@ file-filter Templates Vorlagen Include Linked Files Verknüpfte Dateien einschließen Include Metadata Metadaten einschließen Include non-printing layers Nicht druckende Ebenen einschließen + Write pages longer than 200 in at full size in points Seiten über 200 in in voller Größe in Punkt schreiben Include SVG Code SVG-Code einschließen Include Swatches for Tints Farbfelder für Tonwerte einschließen Inflate… Aufblasen… diff --git a/crates/ui-egui/src/i18n/el.tsv b/crates/ui-egui/src/i18n/el.tsv index 114ed5a52..e892cc94f 100644 --- a/crates/ui-egui/src/i18n/el.tsv +++ b/crates/ui-egui/src/i18n/el.tsv @@ -1578,6 +1578,7 @@ font-technology Variable Μεταβλητές Include Linked Files Συμπερίληψη συνδεδεμένων αρχείων Include Metadata Συμπερίληψη μεταδεδομένων Include non-printing layers Συμπερίληψη μη εκτυπώσιμων επιπέδων + Write pages longer than 200 in at full size in points Εγγραφή σελίδων μεγαλύτερων από 200 in σε πλήρες μέγεθος, σε στιγμές Include SVG Code Συμπερίληψη κώδικα SVG Include Swatches for Tints Συμπερίληψη δειγμάτων για αποχρώσεις Inflate… Φούσκωμα… diff --git a/crates/ui-egui/src/i18n/es.tsv b/crates/ui-egui/src/i18n/es.tsv index c0e50d82e..f35dcd347 100644 --- a/crates/ui-egui/src/i18n/es.tsv +++ b/crates/ui-egui/src/i18n/es.tsv @@ -1659,6 +1659,7 @@ font-technology Variable Variables Include Linked Files Incluir archivos vinculados Include Metadata Incluir metadatos Include non-printing layers Incluir capas no imprimibles + Write pages longer than 200 in at full size in points Escribir las páginas de más de 200 in a tamaño real, en puntos Include SVG Code Incluir código SVG Include Swatches for Tints Incluir muestras para matices Inflate… Inflar… diff --git a/crates/ui-egui/src/i18n/fi.tsv b/crates/ui-egui/src/i18n/fi.tsv index cdbd87576..0951c9bd5 100644 --- a/crates/ui-egui/src/i18n/fi.tsv +++ b/crates/ui-egui/src/i18n/fi.tsv @@ -2852,6 +2852,7 @@ antiAlias Sharp Terävä General › Thumbnails Yleiset › Pikkukuvat Grayscale images Harmaasävykuvat Include non-printing layers Sisällytä tulostumattomat tasot + Write pages longer than 200 in at full size in points Kirjoita yli 200 in pitkät sivut täysikokoisina pisteinä Left: Vasen: Mark as trapped Merkitse ylitäytetyksi Marks and Bleeds Merkit ja leikkuuvarat diff --git a/crates/ui-egui/src/i18n/fr.tsv b/crates/ui-egui/src/i18n/fr.tsv index cb81c4902..322d70125 100644 --- a/crates/ui-egui/src/i18n/fr.tsv +++ b/crates/ui-egui/src/i18n/fr.tsv @@ -1653,6 +1653,7 @@ font-technology Variable Variables Include Linked Files Inclure les fichiers liés Include Metadata Inclure les métadonnées Include non-printing layers Inclure les calques non imprimables + Write pages longer than 200 in at full size in points Écrire les pages de plus de 200 in en taille réelle, en points Include SVG Code Inclure le code SVG Include Swatches for Tints Inclure les nuances des teintes Inflate… Gonfler… diff --git a/crates/ui-egui/src/i18n/hi.tsv b/crates/ui-egui/src/i18n/hi.tsv index 296f18e80..662ae0175 100644 --- a/crates/ui-egui/src/i18n/hi.tsv +++ b/crates/ui-egui/src/i18n/hi.tsv @@ -2257,6 +2257,7 @@ antiAlias Sharp शार्प Include Linked Files लिंक्ड फ़ाइलें शामिल करें Include Metadata मेटाडाटा शामिल Include non-printing layers गैर-प्रिंटिंग लेयर्स को शामिल करें + Write pages longer than 200 in at full size in points 200 in से लंबे पेज पूरे आकार में, पॉइंट में लिखें Include Objects On Canvas कैनवास पर ऑब्जेक्ट्स शामिल करें Include SVG Code SVG कोड शामिल करें Include Swatches for Tints टिंट्स के लिए स्वॉचेस शामिल करें diff --git a/crates/ui-egui/src/i18n/hu.tsv b/crates/ui-egui/src/i18n/hu.tsv index 34b6be0ab..8d602c5a6 100644 --- a/crates/ui-egui/src/i18n/hu.tsv +++ b/crates/ui-egui/src/i18n/hu.tsv @@ -2069,6 +2069,7 @@ file-filter Templates Sablonok Include Linked Files Csatolt fájlok hozzáadása Include Metadata Metaadatok hozzáadása Include non-printing layers Nem nyomtatandó rétegek hozzáadása + Write pages longer than 200 in at full size in points 200 in-nél hosszabb oldalak írása teljes méretben, pontban Include SVG Code SVG-kód hozzáadása Include Swatches for Tints Tónusok színmintáinak felvétele Include tagged source profiles Címkézett forrásprofilok beágyazása diff --git a/crates/ui-egui/src/i18n/id.tsv b/crates/ui-egui/src/i18n/id.tsv index 16854b5bb..499f0efd9 100644 --- a/crates/ui-egui/src/i18n/id.tsv +++ b/crates/ui-egui/src/i18n/id.tsv @@ -2698,6 +2698,7 @@ antiAlias Sharp Tajam General › Thumbnails Umum › Miniatur Grayscale images Gambar skala abu-abu Include non-printing layers Sertakan lapisan non-cetak + Write pages longer than 200 in at full size in points Tulis halaman yang lebih panjang dari 200 in dalam ukuran penuh, dalam poin Left: Kiri: Mark as trapped Tandai sebagai ter-trap Marks and Bleeds Tanda dan bleed diff --git a/crates/ui-egui/src/i18n/it.tsv b/crates/ui-egui/src/i18n/it.tsv index 6a8f3d725..926ba5269 100644 --- a/crates/ui-egui/src/i18n/it.tsv +++ b/crates/ui-egui/src/i18n/it.tsv @@ -1686,6 +1686,7 @@ file-filter Templates Modelli Include Linked Files Includi file collegati Include Metadata Includi metadati Include non-printing layers Includi livelli non stampabili + Write pages longer than 200 in at full size in points Scrivi le pagine più lunghe di 200 in a grandezza reale, in punti Include SVG Code Includi codice SVG Include Swatches for Tints Includi campioni per tinte Inflate… Gonfia… diff --git a/crates/ui-egui/src/i18n/ja.tsv b/crates/ui-egui/src/i18n/ja.tsv index aabe534b6..4907b20b1 100644 --- a/crates/ui-egui/src/i18n/ja.tsv +++ b/crates/ui-egui/src/i18n/ja.tsv @@ -2002,6 +2002,7 @@ antiAlias Sharp シャープ Include SVG Code SVG コードを含める Include Swatches for Tints ティント用のスウォッチを含める Include non-printing layers 印刷しないレイヤーを含む + Write pages longer than 200 in at full size in points 200 in より長いページを実寸のポイントで書き出す Ink インキ Insert Character 文字を挿入 Inspect Document ドキュメントを調査 diff --git a/crates/ui-egui/src/i18n/ka.tsv b/crates/ui-egui/src/i18n/ka.tsv index bb6a5da7d..f559729aa 100644 --- a/crates/ui-egui/src/i18n/ka.tsv +++ b/crates/ui-egui/src/i18n/ka.tsv @@ -2572,6 +2572,7 @@ antiAlias Sharp მკვეთრი General › Thumbnails ზოგადი › ესკიზები Grayscale images ნაცრისფერი გამოსახულებები Include non-printing layers არაბეჭდვადი შრეების ჩართვა + Write pages longer than 200 in at full size in points 200 in-ზე გრძელი გვერდების სრული ზომით, პუნქტებში ჩაწერა Left: მარცხნივ: Mark as trapped ტრაპინგის მარკირება Marks and Bleeds ნიშნები და ჭრისპირა არეები diff --git a/crates/ui-egui/src/i18n/ko.tsv b/crates/ui-egui/src/i18n/ko.tsv index b7f582463..bda354574 100644 --- a/crates/ui-egui/src/i18n/ko.tsv +++ b/crates/ui-egui/src/i18n/ko.tsv @@ -2031,6 +2031,7 @@ antiAlias Sharp 날카롭게 Include SVG Code SVG 코드 포함 Include Swatches for Tints 농도별 견본 포함 Include non-printing layers 인쇄되지 않는 레이어 포함 + Write pages longer than 200 in at full size in points 200 in보다 긴 페이지를 실제 크기의 포인트로 기록 Ink 잉크 Insert Character 문자 삽입 Inspect Document 문서 검사 diff --git a/crates/ui-egui/src/i18n/nl.tsv b/crates/ui-egui/src/i18n/nl.tsv index 32db6785b..c1c164520 100644 --- a/crates/ui-egui/src/i18n/nl.tsv +++ b/crates/ui-egui/src/i18n/nl.tsv @@ -2712,6 +2712,7 @@ antiAlias Sharp Scherp General › Thumbnails Algemeen › Miniaturen Grayscale images Grijswaardenafbeeldingen Include non-printing layers Niet-afdrukbare lagen opnemen + Write pages longer than 200 in at full size in points Pagina's langer dan 200 in op ware grootte in punten schrijven Left: Links: Mark as trapped Markeren als getrapt Marks and Bleeds Steker en afloop diff --git a/crates/ui-egui/src/i18n/pl.tsv b/crates/ui-egui/src/i18n/pl.tsv index 398adf5f5..0603c6ac1 100644 --- a/crates/ui-egui/src/i18n/pl.tsv +++ b/crates/ui-egui/src/i18n/pl.tsv @@ -2798,6 +2798,7 @@ antiAlias Sharp Ostre General › Thumbnails Ogólne › Miniatury Grayscale images Obrazy w skali szarości Include non-printing layers Uwzględnij warstwy niedrukowane + Write pages longer than 200 in at full size in points Zapisuj strony dłuższe niż 200 in w pełnym rozmiarze, w punktach Left: Lewy: Mark as trapped Oznacz jako z zalewkami Marks and Bleeds Znaczniki i spady diff --git a/crates/ui-egui/src/i18n/pt-br.tsv b/crates/ui-egui/src/i18n/pt-br.tsv index 9ede5521a..e5a83ab2c 100644 --- a/crates/ui-egui/src/i18n/pt-br.tsv +++ b/crates/ui-egui/src/i18n/pt-br.tsv @@ -1456,6 +1456,7 @@ antiAlias Sharp Nítido Include SVG Code Incluir Código SVG Include Swatches for Tints Incluir Amostras para Tons Include non-printing layers Incluir camadas de não impressão + Write pages longer than 200 in at full size in points Gravar páginas com mais de 200 in em tamanho real, em pontos Inflate… Inflar… Info Informações Ink Tinta diff --git a/crates/ui-egui/src/i18n/ru.tsv b/crates/ui-egui/src/i18n/ru.tsv index 50eb9e969..268e4439b 100644 --- a/crates/ui-egui/src/i18n/ru.tsv +++ b/crates/ui-egui/src/i18n/ru.tsv @@ -1683,6 +1683,7 @@ file-filter Templates Шаблоны Include Linked Files Включать связанные файлы Include Metadata Включать метаданные Include non-printing layers Включать непечатаемые слои + Write pages longer than 200 in at full size in points Записывать страницы длиннее 200 in в полном размере, в пунктах Include SVG Code Включать код SVG Include Swatches for Tints Включать образцы для оттенков Inflate… Надувание… diff --git a/crates/ui-egui/src/i18n/sv.tsv b/crates/ui-egui/src/i18n/sv.tsv index 60324bb91..74d20983b 100644 --- a/crates/ui-egui/src/i18n/sv.tsv +++ b/crates/ui-egui/src/i18n/sv.tsv @@ -1617,6 +1617,7 @@ font-technology Variable Variabla Include Linked Files Inkludera länkade filer Include Metadata Inkludera metadata Include non-printing layers Inkludera lager som inte skrivs ut + Write pages longer than 200 in at full size in points Skriv sidor längre än 200 in i full storlek i punkter Include SVG Code Inkludera SVG-kod Include Swatches for Tints Inkludera färgrutor för nyanser Inflate… Blås upp… diff --git a/crates/ui-egui/src/i18n/tr.tsv b/crates/ui-egui/src/i18n/tr.tsv index 496dba8cf..0b7fc1b20 100644 --- a/crates/ui-egui/src/i18n/tr.tsv +++ b/crates/ui-egui/src/i18n/tr.tsv @@ -1497,6 +1497,7 @@ font-technology Variable Değişken Include Linked Files Bağlantılı Dosyaları Ekle Include Metadata Meta Verileri Ekle Include non-printing layers Yazdırılmayan katmanları dahil et + Write pages longer than 200 in at full size in points 200 in'den uzun sayfaları tam boyutta, punto olarak yaz Include SVG Code SVG Kodunu Ekle Include Swatches for Tints Renk Tonları için Renk Örneklerini Ekle Inflate… Şişir… diff --git a/crates/ui-egui/src/i18n/uk.tsv b/crates/ui-egui/src/i18n/uk.tsv index c1b1c3bda..c758a4051 100644 --- a/crates/ui-egui/src/i18n/uk.tsv +++ b/crates/ui-egui/src/i18n/uk.tsv @@ -1680,6 +1680,7 @@ file-filter Templates Шаблони Include Linked Files Включати пов’язані файли Include Metadata Включати метадані Include non-printing layers Включати недруковані шари + Write pages longer than 200 in at full size in points Записувати сторінки, довші за 200 in, у повному розмірі, у пунктах Include SVG Code Вмикати код SVG Include Swatches for Tints Включати зразки для відтінків Inflate… Надування… diff --git a/crates/ui-egui/src/i18n/vi.tsv b/crates/ui-egui/src/i18n/vi.tsv index d458c348e..2eb4cd329 100644 --- a/crates/ui-egui/src/i18n/vi.tsv +++ b/crates/ui-egui/src/i18n/vi.tsv @@ -1950,6 +1950,7 @@ digits Hindi Hindi Include Linked Files Bao gồm tệp được liên kết Include Metadata Bao gồm siêu dữ liệu Include non-printing layers Bao gồm các lớp không in + Write pages longer than 200 in at full size in points Ghi các trang dài hơn 200 in ở kích thước đầy đủ, theo điểm Include SVG Code Bao gồm mã SVG Include Swatches for Tints Bao gồm mẫu màu cho sắc pha Increase Font Size Tăng cỡ chữ diff --git a/crates/ui-egui/src/i18n/zh-hans.tsv b/crates/ui-egui/src/i18n/zh-hans.tsv index 028bd6057..d68ce3070 100644 --- a/crates/ui-egui/src/i18n/zh-hans.tsv +++ b/crates/ui-egui/src/i18n/zh-hans.tsv @@ -1613,6 +1613,7 @@ file-filter Templates 模板 Include Linked Files 包含链接文件 Include Metadata 包含元数据 Include non-printing layers 包含非打印图层 + Write pages longer than 200 in at full size in points 以实际尺寸(点)写入长于 200 in 的页面 Include SVG Code 包含 SVG 代码 Include Swatches for Tints 包含淡色色板 Inflate… 膨胀… diff --git a/crates/ui-egui/src/i18n/zh-hant.tsv b/crates/ui-egui/src/i18n/zh-hant.tsv index 7cb218f99..b203d3057 100644 --- a/crates/ui-egui/src/i18n/zh-hant.tsv +++ b/crates/ui-egui/src/i18n/zh-hant.tsv @@ -1651,6 +1651,7 @@ file-filter Templates 範本 Include Linked Files 包含連結檔案 Include Metadata 包含中繼資料 Include non-printing layers 包含不列印的圖層 + Write pages longer than 200 in at full size in points 以實際尺寸(點)寫入長於 200 in 的頁面 Include SVG Code 包含 SVG 程式碼 Include Swatches for Tints 包含色調的色票 Inflate… 膨脹… diff --git a/docs/mcp.md b/docs/mcp.md index f799c98c5..6287a3c73 100644 --- a/docs/mcp.md +++ b/docs/mcp.md @@ -862,7 +862,9 @@ A page with a side longer than 14,400 pt is written with a `UserUnit` (PDF 1.6): rounded up to the next thousandth, with the page's sizes and coordinates in those units, so that no side is longer than 14,400 units (ISO 32000-1, Annex C.2). A PDF 1.5 file with such a page is written as PDF 1.6, with a warning, and keeps PDF 1.5's encryption (128-bit RC4). PDF/X files and PDF 1.3 and 1.4 files write such a page in points, and apps -that keep to the limit cut it to 200 in. In PDF 1.3 and 1.4 files, PDF/X-1a and PDF/X-3 among them, a real number can +that keep to the limit cut it to 200 in. `longPagesInPoints: true` (off by default; "Write pages longer than 200 in at +full size in points" in the Save PDF dialog's General section) writes such a page in points at every PDF version, without +a `UserUnit`, and a PDF 1.5 file stays PDF 1.5; the warning then says the page is written in points. In PDF 1.3 and 1.4 files, PDF/X-1a and PDF/X-3 among them, a real number can be at most 32,767, and whole numbers are written as integers: a page longer than 32,767 pt whose sides aren't whole numbers of points fails the export with an error naming the page before the thumbnails are drawn and the transparency is flattened, and one whose art needs a real number past 32,767 fails with the same error when the file is written. A page whose short side is 3 pt or longer but would be under 3 units at that diff --git a/docs/target-app-parity.md b/docs/target-app-parity.md index c6a1d02a5..1997fb5b1 100644 --- a/docs/target-app-parity.md +++ b/docs/target-app-parity.md @@ -1435,7 +1435,7 @@ Moved from the ROADMAP's "Shipped so far" on 2026-10-10. Update it when a featur In a `.ai` opened from its editing data, an envelope made with a warp, a mesh or a top object opens as a live envelope of its contents, with its warp style, bend and distortions, its mesh points and handles or its top object's outline, its Envelope Options, its Fidelity and whether its contents are being edited. When Distort Appearance is on and the size the file saves for the contents matches their bounds with strokes, the contents map from that stroked box, until an edit in Edit Contents changes how far the strokes reach; VectorCraft's new envelopes map from the geometric box, as do contents with no width or no height, such as a single straight line. An envelope that can't be read, or that VectorCraft draws differently from the file's drawn art, keeps that drawn art, with a note. An envelope of type opens live, its type laid out from the text document as its contents, where VectorCraft's own envelope of that type draws as the drawn art the file saves (compared as for the other envelopes, seen on white and on black, so white type counts, and only where the layers with it are compared with the page and draw as it, the envelope weighed as the art it draws); elsewhere, and where the type's font isn't available, a text or visibility variable binds the type, or the envelope is in a symbol's or a pattern's art, it keeps its drawn art, with its type hidden and a note that says the type isn't editable (#3179; `crates/eps/src/import/ai/tests.rs`, `crates/eps/src/import/tests_native.rs`). A warp of type whose own warp, VectorCraft's, doesn't draw as its drawn art is tried again as a mesh envelope on the grid the file saves for it: its type stays editable and keeps the file's shape, but the warp's settings (style, bend) are lost, as the document has no place for them on a mesh. Type with strokes in an envelope with Distort Appearance on is also tried mapped from its glyphs grown by as far as its strokes paint along a side (half the weight of a centred stroke), without what miter joins add at corners, where the size the file saves for it doesn't already give that box; the drawn-art check decides which one opens. An envelope's grid reads its way round from all its rows and columns, so a mesh bent far round reads the way it runs. While an envelope's contents are edited, its mesh is kept in its dictionary, and in some files that mesh lies away from the drawn art by a move, the contents with the drawn art (as if the mesh hadn't followed the envelope when it moved): a mesh or top object saved in Edit Contents whose mesh draws the contents as large as the drawn art but elsewhere is tried again, after every other way, with its mesh moved onto that art, and opens there where it then draws as it. In a `.ai` opened from its editing data, Convert to Shape on a whole path or type object opens as a live Rectangle, Rounded Rectangle or Ellipse effect with its Absolute or Relative size, its corner radius, its place among the object's effects and whether it is hidden. A relative size in the file is added to each side. A relative Ellipse, or a setting VectorCraft doesn't read, keeps the drawn art (`crates/eps/src/import/ai/style/tests.rs`, `crates/eps/src/import/ai/tests.rs`, `crates/eps/src/import/tests_native.rs`). In a `.ai` opened from its editing data, Bas Relief in an art style takes its Detail, Smoothness and Light direction from the style's settings block, and an object other than type with it opens as itself with the live effect. A settings block that VectorCraft can't read (malformed, another effect's, a value out of range or a key it doesn't know) keeps the object's drawn look (part of #637; [raster effects reference](https://helpx.adobe.com/illustrator/desktop/special-effects-styles/apply-filter-effects/raster-effects-reference.html), [Photoshop File Formats Specification](https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/), [scripting the filter gallery](https://community.adobe.com/questions-712/scripting-support-for-the-filter-gallery-1064646); `crates/eps/src/import/ai/style/tests.rs`, `crates/eps/src/import/ai/tests.rs`). - PDF export and `.ai` saves write a page with a side longer than 14,400 pt with a `UserUnit` (PDF 1.6), its longer side divided by 14,400 and rounded up to the next thousandth, so that no side is longer than 14,400 units (ISO 32000-1, Annex C.2); a PDF 1.5 file with such a page is written as PDF 1.6 with PDF 1.5's encryption, PDF/X files and PDF 1.3 and 1.4 files write it in points (in PDF 1.3 and 1.4, a page past 32,767 pt that needs a real number past 32,767 is refused with an error naming it), a page whose short side is 3 pt or longer but would be under 3 units at that `UserUnit` gets a smaller one that keeps the short side at 3 units, and the export's warnings give each such page's size and how it is written (#6382). + PDF export and `.ai` saves write a page with a side longer than 14,400 pt with a `UserUnit` (PDF 1.6), its longer side divided by 14,400 and rounded up to the next thousandth, so that no side is longer than 14,400 units (ISO 32000-1, Annex C.2); a PDF 1.5 file with such a page is written as PDF 1.6 with PDF 1.5's encryption, PDF/X files and PDF 1.3 and 1.4 files write it in points (in PDF 1.3 and 1.4, a page past 32,767 pt that needs a real number past 32,767 is refused with an error naming it), a page whose short side is 3 pt or longer but would be under 3 units at that `UserUnit` gets a smaller one that keeps the short side at 3 units, and the export's warnings give each such page's size and how it is written (#6382). The Save PDF dialog's "Write pages longer than 200 in at full size in points" (`longPagesInPoints`, off by default) writes such pages in points without a `UserUnit` at every PDF version. PDF export warns when a PDF/X file has no bleed on a side where the document's bleed has one, and writes the file as asked. PDF/X-1a, PDF/X-3 and PDF 1.3 files keep the art under the hidden part of a picture, gradient, pattern, mesh or raster effect that a clipping mask or clipping layer crops: flattening rasterizes only the part of such art that its clipping path shows, on its own page and on the next one. Layers the file leaves out are left out of the flattening, and transparent art that a clipping path hides entirely is removed (`crates/engine/src/cmd/fileio/tests_pdfx.rs`). Placed (linked) files in a `.ai`'s editing data come in as linked images in their place, each showing the low-resolution preview the file keeps of it until its file is found or relinked in the Links panel (a file found next to the document, at its path, or in the folder the editing data names relative to the document's, such as `src/`, is read as the document opens, and its link takes that file's size, modification time and hash). A file with them no longer falls back to its PDF part, so its guides, sublayers and hidden objects open (#1264; `crates/eps/src/import/ai/tests.rs`, `crates/engine/src/tests/links.rs`). In a file opened that way, a gradient with stops in the middle runs its whole ramp across its shape and a gradient object no longer passes its fill to the next path (#1390); text objects with an appearance keep their own text and frame, type of hidden layers stays hidden, and type on later artboards lands on its text objects (#1391); area type whose frame is too short for a line of the fallback font shows its first line, the frame made taller with a warning in the open result (#1392).