mirror of
https://github.com/storytold/effectcraft.git
synced 2026-10-31 21:48:20 -06:00
Import: Composition - Retain Layer Sizes for PDF / Illustrator files (#1536)
The PDF / Illustrator import dialog now offers Composition - Retain Layer Sizes. Each file layer's footage is cropped to its artwork bounds (fills, stroke outlines, images, clips) and positioned to keep its place on the page. Decoding, continuous rasterisation, shape conversion and reload share one crop (effectcraft_pdf::layer_doc_with_sizes), so they agree. Also collapses a match guard in the STL reader for current stable clippy. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
2c08f675c2
commit
2d216151c6
@@ -391,7 +391,7 @@ fn prepare_import(s: &mut Session, p: &Value) -> Result<PendingImport> {
|
||||
Ok(b) if effectcraft_psd::is_psd(&b) => b,
|
||||
Ok(b) if effectcraft_pdf::sniff(&b).is_some() && !path.to_ascii_lowercase().ends_with(".svg") => {
|
||||
// PDF / Illustrator / EPS: one layer per file layer.
|
||||
match import_vector_comp(s, &path, &b, page) {
|
||||
match import_vector_comp(s, &path, &b, page, retain) {
|
||||
Ok((comp, made)) => {
|
||||
comps.push(comp);
|
||||
items.extend(made);
|
||||
@@ -540,7 +540,7 @@ pub(crate) fn import_psd_comp(s: &mut Session, path: &str, bytes: Vec<u8>, retai
|
||||
|
||||
/// Import page `page` of a PDF / Illustrator / EPS file as a composition (one undo step).
|
||||
/// Returns (comp, items).
|
||||
fn import_vector_comp(s: &mut Session, path: &str, bytes: &[u8], page: u32) -> Result<(u64, Vec<u64>)> {
|
||||
fn import_vector_comp(s: &mut Session, path: &str, bytes: &[u8], page: u32, retain: bool) -> Result<(u64, Vec<u64>)> {
|
||||
let mut name = std::path::Path::new(path).file_stem().map(|n| n.to_string_lossy().to_string()).unwrap_or_else(|| "Vector".into());
|
||||
if page > 0 {
|
||||
name = format!("{name} (Page {})", page + 1);
|
||||
@@ -549,7 +549,7 @@ fn import_vector_comp(s: &mut Session, path: &str, bytes: &[u8], page: u32) -> R
|
||||
let secs = if s.prefs.import.still_footage == "seconds" { s.prefs.import.still_seconds } else { 10.0 };
|
||||
let duration = rate.snap_nearest(effectcraft_time::Tick::from_seconds_f64(secs));
|
||||
let (comp, folder, items) = s.edit("Import", None, |proj, st| {
|
||||
let r = crate::vector::import_vector_comp(proj, path, bytes, &name, page, rate, duration).map_err(EngineError::Other)?;
|
||||
let r = crate::vector::import_vector_comp(proj, path, bytes, &name, page, retain, (rate, duration)).map_err(EngineError::Other)?;
|
||||
st.project_selection = vec![r.0];
|
||||
Ok(r)
|
||||
})?;
|
||||
|
||||
@@ -58,6 +58,12 @@ fn psd_layer(s: &Session, path: &str, l: &SourceLayer) -> Option<(u32, Option<(u
|
||||
Some((u32::try_from(found.index).ok()?, size))
|
||||
}
|
||||
|
||||
fn vector_size(s: &Session, f: &Footage) -> Option<(u32, u32)> {
|
||||
let bytes = s.services.read_file(&f.path).ok()?;
|
||||
let doc = crate::vector::vector_doc(&f.path, &bytes, f.layer.as_ref(), f.page)?.ok()?;
|
||||
(!doc.root.children.is_empty()).then(|| doc.pixel_size())
|
||||
}
|
||||
|
||||
/// Footage `f` as its file is now, given the importer's fresh look at the file (`probed`).
|
||||
fn refreshed(s: &Session, f: &Footage, probed: Footage) -> Footage {
|
||||
let mut nf = f.clone();
|
||||
@@ -89,6 +95,7 @@ fn refreshed(s: &Session, f: &Footage, probed: Footage) -> Footage {
|
||||
match &f.layer {
|
||||
// A smart object's size is its embedded file's, recorded at import.
|
||||
Some(l) if l.embedded.is_some() => (w, h) = (f.width, f.height),
|
||||
Some(_) if crate::vector::is_vector(f) => (w, h) = vector_size(s, f).unwrap_or((f.width, f.height)),
|
||||
Some(l) => match psd_layer(s, &f.path, l) {
|
||||
Some((index, size)) => {
|
||||
if let Some(nl) = nf.layer.as_mut() {
|
||||
|
||||
@@ -271,7 +271,7 @@ pub fn vector_doc(path: &str, bytes: &[u8], layer: Option<&effectcraft_project::
|
||||
Some(
|
||||
effectcraft_pdf::parse_page(bytes, page as usize)
|
||||
.map(|d| match layer {
|
||||
Some(l) => effectcraft_pdf::layer_doc(&d, l.index as usize),
|
||||
Some(l) => effectcraft_pdf::layer_doc_with_sizes(&d, l.index as usize, l.layer_size).0,
|
||||
None => d,
|
||||
})
|
||||
.map_err(|e| format!("{path}: {e}")),
|
||||
@@ -279,7 +279,7 @@ pub fn vector_doc(path: &str, bytes: &[u8], layer: Option<&effectcraft_project::
|
||||
}
|
||||
|
||||
/// Import page `page` of a PDF / Illustrator / EPS document as a composition the size of the
|
||||
/// page: one footage item per file layer (each showing only that layer, continuously
|
||||
/// page: one footage item per file layer (optionally cropped to its artwork, continuously
|
||||
/// rasterisable), top layer first. Returns (comp, folder, items).
|
||||
pub fn import_vector_comp(
|
||||
proj: &mut Project,
|
||||
@@ -287,11 +287,12 @@ pub fn import_vector_comp(
|
||||
bytes: &[u8],
|
||||
name: &str,
|
||||
page: u32,
|
||||
rate: effectcraft_time::FrameRate,
|
||||
duration: effectcraft_time::Tick,
|
||||
retain: bool,
|
||||
timing: (effectcraft_time::FrameRate, effectcraft_time::Tick),
|
||||
) -> Result<(ItemId, ItemId, Vec<ItemId>), String> {
|
||||
use effectcraft_color::Label;
|
||||
use effectcraft_project::{AlphaMode, Footage, FootageKind, ItemKind, SourceLayer};
|
||||
let (rate, duration) = timing;
|
||||
let doc = effectcraft_pdf::parse_page(bytes, page as usize).map_err(|e| format!("{path}: {e}"))?;
|
||||
let codec = effectcraft_pdf::codec(path, bytes).unwrap_or("PDF");
|
||||
let (w, h) = doc.pixel_size();
|
||||
@@ -301,24 +302,33 @@ pub fn import_vector_comp(
|
||||
let mut items = vec![];
|
||||
let names = effectcraft_pdf::layer_names(&doc);
|
||||
for (i, lname) in names.iter().enumerate() {
|
||||
let (size, offset) = if retain {
|
||||
let (layer, offset) = effectcraft_pdf::layer_doc_with_sizes(&doc, i, true);
|
||||
(layer.pixel_size(), offset)
|
||||
} else {
|
||||
((w, h), [0.0, 0.0])
|
||||
};
|
||||
let f = Footage {
|
||||
path: path.to_string(),
|
||||
kind: FootageKind::Still,
|
||||
width: w,
|
||||
height: h,
|
||||
width: size.0,
|
||||
height: size.1,
|
||||
pixel_aspect: 1.0,
|
||||
frame_rate: rate,
|
||||
has_video: true,
|
||||
alpha: AlphaMode::Straight,
|
||||
loop_count: 1,
|
||||
codec: codec.into(),
|
||||
layer: Some(SourceLayer { index: i as u32, name: lname.clone(), layer_size: false, ..Default::default() }),
|
||||
layer: Some(SourceLayer { index: i as u32, name: lname.clone(), layer_size: retain, ..Default::default() }),
|
||||
page,
|
||||
..Default::default()
|
||||
};
|
||||
let id = proj.add_item(&format!("{lname}/{file}"), Label::Lavender, Some(folder), ItemKind::Footage(f));
|
||||
items.push(id);
|
||||
let mut l = build::layer(proj, &comp, lname, LayerSource::Footage { item: id }, (w, h), None);
|
||||
let mut l = build::layer(proj, &comp, lname, LayerSource::Footage { item: id }, size, None);
|
||||
if retain && let Some(position) = l.props.prop_mut("transform/position") {
|
||||
position.value = Value::Vec3([offset[0] + f64::from(size.0) / 2.0, offset[1] + f64::from(size.1) / 2.0, 0.0]);
|
||||
}
|
||||
l.name = comp.unique_layer_name(lname);
|
||||
comp.layers.insert(0, l);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user