mirror of
https://github.com/storytold/photocraft.git
synced 2026-11-11 00:11:46 +00:00
PDF: validate --pdf-dpi, band the raster conversion, complete the notices
- --pdf-dpi now rejects zero, negative, NaN and infinite values at parse time (unit-tested) instead of silently falling back to 300 DPI. - The RGBA8 raster is converted and written in 256-row bands with a cancellation check per band, so the f32 buffer no longer duplicates a whole page; the pixel-memory estimate charges the retained raster instead of that buffer. - docs/file-format-parity.md: the 2026-11-07 revision row is inside the revision table. - NOTICE now reproduces the Hayro MIT notice, the Foxit/PDFium BSD-3-Clause text, Adobe's CMaps BSD-3-Clause text and the CC0 statement for the ICC profile; package.ps1 ships NOTICE and ATTRIBUTION.md in both installers.
This commit is contained in:
1 parent
c5a9288c4b
commit
d297dd3a2d
5 files changed
+83
-13
No files matched your search
@@ -81,3 +81,40 @@ the standard-14 Foxit fonts (BSD-3-Clause), Adobe CMaps (BSD-3-Clause) and a Com
|
||||
profile by Clinton Ingram (CC0-1.0); per-file sources and hashes are in craft-libs
|
||||
ATTRIBUTION.md. No PDF assets are copied into this repository, and PDFium is no longer used or
|
||||
shipped.
|
||||
|
||||
Licence texts for the optional PDF reader
|
||||
|
||||
Hayro (MIT):
|
||||
Copyright (c) The Hayro Authors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
Standard-14 Foxit fonts (BSD-3-Clause):
|
||||
Copyright 2014 PDFium Authors. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of Google Inc. nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
Adobe CMaps (BSD-3-Clause):
|
||||
Copyright 1990-2023 Adobe. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
||||
|
||||
Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
||||
|
||||
Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
||||
|
||||
Neither the name of Adobe nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
Compact ICC profile: CC0 1.0 Universal (public domain dedication), by Clinton Ingram (saucecontrol/Compact-ICC-Profiles).
|
||||
@@ -114,7 +114,7 @@ pub(crate) struct ConvertArgs {
|
||||
#[arg(long)]
|
||||
pub(crate) approve_high_memory: bool,
|
||||
/// PDF import resolution in DPI (default 300)
|
||||
#[arg(long)]
|
||||
#[arg(long, value_name = "DPI", value_parser = pdf_dpi)]
|
||||
pub(crate) pdf_dpi: Option<f32>,
|
||||
/// PDF pages to import, 1-based: "3" or "2-7" (default all)
|
||||
#[arg(long)]
|
||||
@@ -320,6 +320,8 @@ enum ValueError {
|
||||
JpegFormat,
|
||||
#[error("expected on or off")]
|
||||
OnOff,
|
||||
#[error("expected a positive number")]
|
||||
PdfDpi,
|
||||
/// Why a `--bridge` value, or a PHOTOCRAFT_CONTROL_PORT value for `mcp`, is refused.
|
||||
#[error("expected a port, 0-65535 (--bridge also takes 127.0.0.1:<PORT>)")]
|
||||
Bridge,
|
||||
@@ -388,6 +390,12 @@ fn on_off(s: &str) -> Result<bool, ValueError> {
|
||||
}
|
||||
}
|
||||
|
||||
/// A PDF import DPI: a finite number greater than zero. The importer's fallback to 300 DPI is
|
||||
/// for absent preferences, not for a mistyped flag.
|
||||
fn pdf_dpi(s: &str) -> Result<f32, ValueError> {
|
||||
s.parse::<f32>().ok().filter(|dpi| dpi.is_finite() && *dpi > 0.0).ok_or(ValueError::PdfDpi)
|
||||
}
|
||||
|
||||
fn jpeg_format(s: &str) -> Result<JpegFormat, ValueError> {
|
||||
JpegFormat::parse(s).ok_or(ValueError::JpegFormat)
|
||||
}
|
||||
@@ -538,3 +546,19 @@ fn check_server(sub: &mut clap::Command, m: &ArgMatches, has_transport: bool, tr
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
/// `--pdf-dpi` only takes a positive finite number: a mistyped value is refused at parse time
|
||||
/// instead of silently becoming the importer's 300 DPI fallback.
|
||||
#[test]
|
||||
fn pdf_dpi_rejects_non_positive_and_non_finite_values() {
|
||||
assert!(pdf_dpi("300").is_ok());
|
||||
assert!(pdf_dpi("0.5").is_ok());
|
||||
for bad in ["0", "-1", "nan", "inf", "-inf", "abc"] {
|
||||
assert!(pdf_dpi(bad).is_err(), "{bad} should be refused");
|
||||
}
|
||||
}
|
||||
}
|
||||
+17
-11
@@ -101,10 +101,10 @@ pub fn import(name: &str, bytes: &[u8], opts: &ImportOptions, ctl: &Interrupt) -
|
||||
if doc_width > u64::from(u32::MAX) || doc_height > u64::from(u32::MAX) {
|
||||
return Err(IoError::Unsupported("PDF import is too large at this DPI; lower the import DPI".into()));
|
||||
}
|
||||
// Retained tiles plus the converting page's peak: craft-pdf's RGBA8 raster (4 B/px) and the
|
||||
// f32 document buffer (16 B/px).
|
||||
// Retained tiles plus the converting page's raster (4 B/px); the f32 conversion runs in
|
||||
// 256-row bands, so it adds only a constant-sized buffer rather than a second full page.
|
||||
let max_page = sizes.iter().map(|&(w, h)| u64::from(page_pixels(w, dpi)) * u64::from(page_pixels(h, dpi))).max().unwrap_or(0);
|
||||
let estimate = retained.saturating_add(max_page.saturating_mul(20));
|
||||
let estimate = retained.saturating_add(max_page.saturating_mul(4));
|
||||
let budget = if opts.flat_max_pixel_bytes > 0 { opts.flat_max_pixel_bytes } else { DEFAULT_MAX_PIXEL_BYTES };
|
||||
if estimate > budget {
|
||||
return Err(IoError::Unsupported(format!(
|
||||
@@ -159,17 +159,23 @@ pub fn import(name: &str, bytes: &[u8], opts: &ImportOptions, ctl: &Interrupt) -
|
||||
|
||||
let mut surface = Surface::new(PixelFormat::new(ColorMode::Rgb, SampleType::U8, true));
|
||||
let bytes = rendered.bytes();
|
||||
let mut data = Vec::new();
|
||||
if data.try_reserve_exact(bytes.len()).is_err() {
|
||||
// Convert and write in row bands: the f32 buffer stays bounded instead of a second
|
||||
// 16-bytes-per-pixel copy of the whole page, and cancellation is polled per band.
|
||||
const BAND_ROWS: usize = 256;
|
||||
let row_bytes = width as usize * 4;
|
||||
let band_bytes = row_bytes.saturating_mul(BAND_ROWS);
|
||||
let mut data: Vec<f32> = Vec::new();
|
||||
if data.try_reserve_exact(band_bytes).is_err() {
|
||||
return Err(IoError::Unsupported(format!("PDF page {page_number} needs more memory than is available")));
|
||||
}
|
||||
for chunk in bytes.as_chunks::<4>().0 {
|
||||
for &value in &chunk[..3] {
|
||||
data.push(f32::from(value) / 255.0);
|
||||
}
|
||||
data.push(f32::from(chunk[3]) / 255.0);
|
||||
for (band, rows) in bytes.chunks(band_bytes).enumerate() {
|
||||
ctl.check().map_err(|_| IoError::Cancelled)?;
|
||||
data.clear();
|
||||
data.extend(rows.iter().map(|&value| f32::from(value) / 255.0));
|
||||
let top = (band * BAND_ROWS) as i32;
|
||||
let band_height = (rows.len() / row_bytes) as u32;
|
||||
surface.write_region(Rect::from_xywh(x as i32, top, width, band_height), &data);
|
||||
}
|
||||
surface.write_region(Rect::from_xywh(x as i32, 0, width, height), &data);
|
||||
let rect = Rect::new(x as i32, 0, (x + width) as i32, height as i32);
|
||||
let layer = Layer::new(format!("Page {page_number}"), LayerContent::Raster(surface));
|
||||
let group = Group { children: vec![layer], expanded: true, artboard: Some(Artboard::new(rect)), frame_stroke: None };
|
||||
|
||||
@@ -103,6 +103,7 @@ fn first_artboard(document: &photocraft_doc::Document) -> Rect {
|
||||
group.artboard.as_ref().expect("page artboard").rect
|
||||
}
|
||||
|
||||
/// Each page becomes its own artboard at 72 and 300 DPI, with the print size preserved.
|
||||
#[test]
|
||||
fn imports_every_page_as_an_artboard_with_its_print_size() {
|
||||
let pdf = variant_pdf();
|
||||
@@ -148,6 +149,7 @@ fn imports_every_page_as_an_artboard_with_its_print_size() {
|
||||
}
|
||||
}
|
||||
|
||||
/// A page over the renderer's side limit lowers the effective DPI instead of failing.
|
||||
#[test]
|
||||
fn huge_pages_lower_the_effective_dpi_within_the_renderer_bounds() {
|
||||
// 3000 pt at 300 dpi would be 12500 px wide, over craft-pdf's 8192 px side limit.
|
||||
@@ -162,6 +164,7 @@ fn huge_pages_lower_the_effective_dpi_within_the_renderer_bounds() {
|
||||
assert!((print_width - 3000.0).abs() < 1.0, "print width {print_width:.1} pt should stay 3000 pt");
|
||||
}
|
||||
|
||||
/// Cancelling on the last page check or the public boundary returns Cancelled, never a document.
|
||||
#[test]
|
||||
fn cancelling_on_the_last_checks_is_reported() {
|
||||
let pdf = variant_pdf();
|
||||
|
||||
@@ -145,13 +145,13 @@ remaining gaps in #1606 stay tracked with per-sample corpus ceilings.
|
||||
Embedded SVG and PDF-compatible AI/PDF/EPS payloads can use an explicit VectorCraft editor choice, including nested PSB contents. AI/PDF/EPS working copies retain their format on save-back; a separate source-bound SVG display preview does not replace the native payload. Placement, shared instances, masks and Undo/Redo use existing smart-object transactions, with native bytes and previews persisted through .pcraft and PSD/PSB saves. Unsupported editor features may still change; standalone EPS/DCS import and lossless Adobe round-trip compatibility remain unverified. See [workflow, safety and validation limits](embedded-vector-editing.md). No corpus floor or parity percentage is changed.
|
||||
|
||||
## Revision history
|
||||
| 2026-11-07 | minor | Photoshop PDF read: pages import as raster artboards through the shared craft-pdf (Hayro) renderer, up to 16 pages, 8192 px per side and 64M px per page; editable text/vectors and PDFium are gone. Write is unchanged. |
|
||||
|
||||
The original reference row retains its source date and historical totals; current totals are in the status and Actions row above.
|
||||
The original reference entry retains its source date and historical totals; current totals are in the status above.
|
||||
|
||||
| Date | Change | Summary |
|
||||
|---|---|---|
|
||||
| 2026-11-07 | minor | Photoshop PDF read: pages import as raster artboards through the shared craft-pdf (Hayro) renderer, up to 16 pages, 8192 px per side and 64M px per page; editable text/vectors and PDFium are gone. Write is unchanged. |
|
||||
| 2026-11-06 | minor | Scripted PSD/PSB saves take `maximizeCompatibility` (Save, Save As, Save a Copy, `app.save`, serve `doc.save`, MCP `doc_save`/`doc_export`, Batch, Image Processor, Layers to Files) over Preferences › File Handling for one save, and the CLI `--no-maximize-compatibility` (#8064). Layer channels and the merged image were already RLE (ZIP with prediction at 32-bit), as Photoshop writes them (#8645) |
|
||||
| 2026-11-02 | minor | Actions Shear (`Shr `) loads, plays and saves the measured curve points and edge mode; unsafe subranges and unrepresentable forms remain kept steps (#10605). |
|
||||
| 2026-11-01 | minor | `.atn`: 23 newer measured forms mapped, including explicit Link/Unlink, Smart Object file operations, Make Type to Shape, radius-only Shape Blur, Artboard from Group and the Crop Photos parent; supported in batch 13; Link Layers keeps the measured toggle. Artboard forms with unmeasured settings and generated Crop helper steps remain kept. |
|
||||
|
||||
Reference in new issue
Block a user