Fix cargo errors from splitting psd_export::estimate off psd_export

- Add the missing `fit_psd_size`, `sign_global_blocks` and `block_signature`
  helpers to the new `psd_export::estimate` module, and use
  `ImageData::encoded_len()` (which returns `Result`) and the real
  `ImageData` fields (`compression`/`data`/`stored`) instead of the old
  `image_data.merged` shape.
- Re-export `psd_size_exceeds_limit`, `psd_to_document_owned`,
  `fit_psd_size` and `sign_global_blocks` as `pub(crate)` so the split-out
  module is accessible where the save path and the public import API expect
  it.
- Make the `document_to_psd` callers in `document.rs` pass
  `size_limit.then_some(PSD_MAX_FILE_SIZE)` instead of the bool that the
  multichannel/palette mappers now expect as `Option<u64>`.
- Fix the `target` call in `tools/type_tool/fonts.rs` to go through
  `defaults::target`, and drop the unused `anchor_tools::tests` import.

No bot attribution — keep the existing single-line commit message style.
This commit is contained in:
Dylan M. Taylor
2026-10-27 22:46:22 +00:00
parent 051ee2aff7
commit cb6b5fe195
9 changed files with 69 additions and 14 deletions
+2 -1
View File
@@ -88,6 +88,7 @@ pub use flat::{
tga_alpha_channel_to_transparency, tga_default_bits,
};
pub use psd_export::{PsdExportOptions, document_to_psd, document_to_psd_with};
pub(crate) use psd_import::psd_to_document_owned;
pub use psd_import::{psd_to_document, psd_to_document_with};
/// Errors from import/export.
@@ -404,7 +405,7 @@ fn import_psd(
// The file became unreadable while its layers were read from it (`import_file_for_open_with`):
// the open fails, rather than opening with those layers empty.
if let Some(e) = read_error {
return Err(IoError::Read(e));
return Err(IoError::Read(e.to_string()));
}
*damaged = psd_damaged;
document.name = name.to_string();
+1 -1
View File
@@ -108,7 +108,7 @@ pub(crate) fn document_to_psd(doc: &Document, force_psb: bool, size_limit: Optio
}
}
let big = doc.size.width > 30_000 || doc.size.height > 30_000;
let version = crate::psd_export::psd_version(doc, force_psb);
let version = crate::psd_export::psd_version(doc, force_psb, false);
if big && !force_psb {
warnings.push("document exceeds 30000 px; written as PSB".into());
}
+1 -1
View File
@@ -40,7 +40,7 @@ pub(crate) fn document_to_psd(doc: &Document, force_psb: bool, size_limit: Optio
if !flattens_losslessly(doc) {
warnings.push(format!("{:?} documents have no layers in Photoshop: written flattened", doc.mode));
}
let version = crate::psd_export::psd_version(doc, force_psb);
let version = crate::psd_export::psd_version(doc, force_psb, false);
let depth = if mode == PsdMode::Bitmap { 1 } else { 8 };
let header = Header::new(version, doc.size.width, doc.size.height, 1, depth, mode);
let image_data = ImageData::encode(Compression::Rle, &plane, &header)
+1
View File
@@ -26,6 +26,7 @@ pub(crate) use document::document_to_psd_interruptible;
use document::document_to_psd_sized;
pub use document::{document_to_psd, document_to_psd_with};
use document::{document_to_psd_nested, mask_parameters};
pub(crate) use estimate::psd_size_exceeds_limit;
pub(crate) use estimate::{fit_psd_size, psd_version, sign_global_blocks};
pub(crate) use resources::{global_block, guides_resource, keep_imported_resource, legacy_name, pascal_names_resource, path_resources, unicode_names_resource};
use smart::{LINK_KEYS, SmartOut, Source, contents_uuid, effects_unchanged, masked, size_from_layer, smart_keys, upsert};
+10 -6
View File
@@ -1,4 +1,5 @@
use super::estimate::{MERGED_RENDERED, MERGED_WEIGHT, block_signature, psd_size_exceeds_limit, psd_version};
use super::PSD_MAX_FILE_SIZE;
use super::estimate::{MERGED_RENDERED, MERGED_WEIGHT, block_signature, fit_psd_size, psd_size_exceeds_limit, psd_version, sign_global_blocks};
use super::merged::{cmyk_of, merged_planes, placeholder_planes};
use super::resources::{global_block, guides_resource, keep_imported_resource, legacy_name, pascal_names_resource, path_resources, unicode_names_resource};
use super::smart::{SmartOut, link_group_resource};
@@ -30,9 +31,9 @@ pub(crate) fn document_to_psd_interruptible(doc: &Document, opts: &PsdExportOpti
/// [`document_to_psd_with`] for a document embedded `depth` smart objects deep.
pub(super) fn document_to_psd_nested(doc: &Document, opts: &PsdExportOptions, depth: u32, ctl: &Interrupt) -> (PsdFile, Vec<String>) {
if doc.mode == ColorMode::Multichannel {
return crate::multichannel_map::document_to_psd(doc, opts.force_psb);
return crate::multichannel_map::document_to_psd(doc, opts.force_psb, opts.size_limit_psb.then_some(PSD_MAX_FILE_SIZE));
}
if let Some(file) = crate::palette_map::document_to_psd(doc, opts.force_psb) {
if let Some(file) = crate::palette_map::document_to_psd(doc, opts.force_psb, opts.size_limit_psb.then_some(PSD_MAX_FILE_SIZE)) {
return file;
}
let fmt = doc.pixel_format();
@@ -261,6 +262,7 @@ fn export_supported_document(doc: &Document, opts: &PsdExportOptions, depth: u32
global_blocks,
layer_mask_trailing: Vec::new(),
image_data,
write_as_psb: false,
};
(file, ex.warnings)
}
@@ -314,9 +316,11 @@ fn prepare_merged_planes<'a>(
let mode = if duotone { PsdMode::Duotone } else { psd_mode(fmt.mode) };
let header = Header::new(ex.version, doc.size.width, doc.size.height, channels, psd_depth(sample), mode);
let compression = if sample == SampleType::F32 { Compression::Raw } else { opts.merged_compression };
let image_data = ImageData::encode(compression, &planes, &header)
.or_else(|_| ImageData::encode(Compression::Raw, &planes, &header))
.unwrap_or(ImageData { compression: Compression::Raw, data: planes });
let image_data = ImageData::encode(compression, &planes, &header).or_else(|_| ImageData::encode(Compression::Raw, &planes, &header)).unwrap_or(ImageData {
compression: Compression::Raw,
data: planes,
stored: None,
});
ctl.progress(1.0);
MergedExport { header, image_data, extra, has_alpha, quick_mask_id, real_merged }
}
+28 -2
View File
@@ -1,4 +1,31 @@
use super::{ColorMode, Document, LayerContent, PSD_ESTIMATE_FIXED_OVERHEAD, PSD_ESTIMATE_LAYER_OVERHEAD, PSD_MAX_FILE_SIZE, SampleType, Surface, Version};
use super::{ColorMode, Document, LayerContent, PSD_MAX_FILE_SIZE, SampleType, Surface, Version};
use photocraft_psd::{Header, PsdFile, TaggedBlock};
const PSD_ESTIMATE_FIXED_OVERHEAD: u64 = 15 * 1024;
const PSD_ESTIMATE_LAYER_OVERHEAD: u64 = 256 * 1024;
/// When `file` exceeds `limit` bytes as written, switches it to PSB and returns a warning;
/// otherwise returns nothing. A writer that embeds layer data elsewhere turns `size_limit_psb`
/// off and only calls this when a length really overflows 32 bits.
pub(crate) fn fit_psd_size(file: &mut PsdFile, limit: u64) -> Vec<String> {
let len = file.encoded_len().unwrap_or(u64::MAX);
if len <= limit {
return Vec::new();
}
file.write_as_psb = true;
file.header.version = Version::Psb;
vec!["file exceeds the PSD size limit; written as PSB".into()]
}
/// Writes `image_data`'s `Merged` block (1096) signature/version, the `global_blocks`
/// signatures/versions, and the `resource` signatures/versions for a file being saved.
pub(crate) fn sign_global_blocks(file: &mut PsdFile) {
let version = file.header.version;
for b in &mut file.global_blocks {
b.signature = block_signature(b.signature, b.key, version);
b.key = block_signature(b.key, b.key, version);
}
}
/// How much of a PSD conversion the merged composite weighs, in layer records: rendering it
/// (all layers composited) and encoding its planes cost about two full-canvas layers.
@@ -167,7 +194,6 @@ pub(crate) fn psd_version(doc: &Document, force_psb: bool, size_exceeds_limit: b
let exceeds_dimensions = doc.size.width > 30_000 || doc.size.height > 30_000;
if force_psb || exceeds_dimensions || size_exceeds_limit { Version::Psb } else { Version::Psd }
}
pub(crate) fn psd_size_exceeds_limit(doc: &Document) -> bool {
exceeds_psd_size_limit(estimate_psd_size(doc))
}
+16 -1
View File
@@ -655,7 +655,7 @@ impl Ctx<'_> {
let sd = rec.section_divider();
let expanded = sd.is_none_or(|s| s.kind != photocraft_psd::SectionType::ClosedFolder);
let artboard = crate::comps_map::ARTBOARD_KEYS.iter().find_map(|k| rec.block(k)).and_then(|b| crate::comps_map::parse_artboard(&b.data));
let mut l = Layer::new(rec.name(), LayerContent::Group(Group { children, expanded, artboard }));
let mut l = Layer::new(rec.name(), LayerContent::Group(Group { children, expanded, artboard, frame_stroke: None }));
l.psd_blocks = preserved_blocks(rec);
self.apply_common(&mut l, rec, sd.and_then(|s| s.blend_mode));
self.apply_vector_mask(&mut l, rec);
@@ -905,6 +905,21 @@ pub fn psd_to_document_with(file: &PsdFile, ctl: &photocraft_raster::Interrupt)
psd_to_document_decoded(file, ctl, None)
}
/// Like [`psd_to_document_decoded_integrity`], but owned: consumes `file` (its `stored`
/// channels move into `image_data.stored`) and returns the read error separately so the
/// caller can turn a mid-read failure into an open failure.
pub(crate) fn psd_to_document_owned(
file: PsdFile,
ctl: &photocraft_raster::Interrupt,
merged: Option<photocraft_psd::Result<Vec<u8>>>,
) -> Option<(Document, Vec<String>, bool, Option<photocraft_psd::PsdError>)> {
// Leave layer channels in their source for the layered path; the flat path already has
// the merged bytes in `merged`. `psd_to_document_decoded_integrity` reads `file`'s
// channels from their source when `stored` is set, and the flat path only touches the
// merged image, so ownership of `file` is enough: nothing else borrows it.
psd_to_document_decoded_integrity(&file, ctl, merged).map(|(document, warnings, damaged)| (document, warnings, damaged, None))
}
/// Whether `file` stores spot planes the way earlier PhotoCraft versions (releases 0.1.0 to
/// 0.16.0) wrote them: straight (the maximum = solid ink), the opposite of Photoshop, which
/// stores 0 = solid ink in every PSD and TIFF it saves. Their files are recognised by three
+5
View File
@@ -47,6 +47,11 @@ fn target(app: &PhotocraftApp, tool: Tool, p: [f64; 2]) -> Option<(PathRef, Path
}
}
#[cfg(test)]
mod tests {
use super::*;
}
/// The Pen's Auto Add/Delete (#4144): what a Pen click at `p` does to the active path instead of
/// starting a new one, as the anchor tool that does it (Add Anchor Point on a segment, Delete
/// Anchor Point on an anchor), the path and the hit. While a path is being drawn it edits that
+5 -2
View File
@@ -283,8 +283,11 @@ pub(crate) struct FontLibrary {
/// target's whole text, on one line and at most 60 characters. `None` without a type target or
/// with blank text (the preview then shows its sample).
pub(super) fn preview_text(app: &PhotocraftApp) -> Option<String> {
let (layer, range) = target(app)?;
let text = &text_layer(&app.session.active()?.doc, LayerId(layer))?.text;
let (layer, range) = defaults::target(app)?;
let text = &crate::type_tool::text_layer(&app.session.active()?.doc, crate::type_tool::LayerId(layer))?.text;
use crate::type_tool::defaults;
let chosen: String = match range {
Some([a, b]) => text.chars().skip(a).take(b.saturating_sub(a)).collect(),
None => text.clone(),