mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-15 02:32:22 +00:00
File > Open and File > Place read QuickDraw pictures, versions 1 and 2, from .pct, .pic and .pict files, with or without the file's 512 bytes of application data; a version 2 picture also opens by its content. The reader plays the opcodes of Apple's Opcodes in Pictures table on the metafile player: lines, rectangles, rounded rectangles, ovals, arcs, polygons and regions become paths, text becomes point type in the font the picture names, bitmaps, pixel maps and QuickTime JPEG images become images, and the clip region clips what follows it. Place puts the picture in one group clipped to its frame. What isn't read comes back as warnings, and a picture cut short keeps what came before it. PICT is not written. Images whose transform flattens them no longer leave their pixel data in the document. Formats whose signature starts the file are detected before PICT, which is known by bytes 522 or 10. Docs: docs/mcp.md, docs/file-format-parity.md, docs/target-app-parity.md.
136 lines
5.0 KiB
Rust
136 lines
5.0 KiB
Rust
//! Windows metafiles: hand-written EMF and WMF writers and readers.
|
|
//!
|
|
//! [`export`] writes the visible art over one region of a document (an artboard) as a metafile:
|
|
//!
|
|
//! - **EMF**: paths keep their Bézier curves, fills are solid brushes, strokes geometric pens with
|
|
//! their width, caps, joins, miter limit and dashes; strokes a pen can't draw (aligned inside or
|
|
//! outside, width profiles, arrowheads) are their filled outlines and brush strokes their brush
|
|
//! art. Clipping masks clip, type is written as glyph outlines, images keep their pixels (with
|
|
//! their transparency), gradients become images clipped to what they paint and pattern fills
|
|
//! their tiles clipped to the shape. The header's frame is the region in 0.01 mm.
|
|
//! - **WMF**: the same art with curves flattened into polygons and 16-bit coordinates, behind a
|
|
//! placeable header that gives the size; WMF has no clipping paths, transparency, dashes on wide
|
|
//! lines or gradients, so clipped art is written whole, images over white, dashes as separate
|
|
//! lines and gradients and patterns as one colour (each loss comes back as a warning).
|
|
//!
|
|
//! [`import`] reads either format back into a document of one artboard (the picture's frame) and
|
|
//! one layer: paths with fills and strokes, clipping, images and text. Records it doesn't read are
|
|
//! skipped, with one warning. [`import_pict`] reads QuickDraw pictures (PICT, versions 1 and 2)
|
|
//! into the same kind of document.
|
|
//!
|
|
//! - `bytes`: little-endian writing and bounds-checked reading.
|
|
//! - `dib`: device-independent bitmaps, both ways.
|
|
//! - `scene`: the document walk both writers share.
|
|
//! - `emf`, `wmf`: the writers.
|
|
//! - `import`: the readers.
|
|
|
|
mod bytes;
|
|
mod dib;
|
|
mod emf;
|
|
mod import;
|
|
mod scene;
|
|
mod wmf;
|
|
|
|
use vectorcraft_doc::Document;
|
|
use vectorcraft_geom::Rect;
|
|
|
|
/// A Windows metafile format.
|
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
|
pub enum Kind {
|
|
/// Enhanced Metafile (32-bit coordinates, Bézier curves, paths and clipping).
|
|
Emf,
|
|
/// Windows Metafile with a placeable header (16-bit coordinates, polygons).
|
|
Wmf,
|
|
}
|
|
|
|
impl Kind {
|
|
/// The format id (`emf`, `wmf`).
|
|
pub fn id(self) -> &'static str {
|
|
match self {
|
|
Self::Emf => "emf",
|
|
Self::Wmf => "wmf",
|
|
}
|
|
}
|
|
}
|
|
|
|
/// The metafile format `bytes` start like, if any.
|
|
pub fn sniff(bytes: &[u8]) -> Option<Kind> {
|
|
if emf::is_emf(bytes) {
|
|
Some(Kind::Emf)
|
|
} else if wmf::is_wmf(bytes) {
|
|
Some(Kind::Wmf)
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
|
|
/// A written metafile.
|
|
#[derive(Clone, Debug, Default)]
|
|
pub struct Output {
|
|
pub bytes: Vec<u8>,
|
|
/// What was approximated or left out.
|
|
pub warnings: Vec<String>,
|
|
}
|
|
|
|
/// Write the visible art of `doc` over `region` (document space: its top-left corner is the
|
|
/// picture's origin, its size the picture's) as `kind`. Live geometry effects are applied first;
|
|
/// template layers, hidden objects and guides are left out. Raster effects should be turned into
|
|
/// images before (the engine does); the ones left are reported and left out.
|
|
pub fn export(doc: &Document, region: Rect, kind: Kind) -> Result<Output, String> {
|
|
let r = region.abs();
|
|
if ![r.x0, r.y0, r.x1, r.y1].iter().all(|v| v.is_finite()) || r.width() <= 0.0 || r.height() <= 0.0 {
|
|
return Err("the export region is empty".into());
|
|
}
|
|
let baked = vectorcraft_effects::bake_document(doc);
|
|
let doc = baked.as_ref().unwrap_or(doc);
|
|
let (ops, mut warnings) = scene::Scene::new(doc, r, kind == Kind::Emf).run();
|
|
let bytes = match kind {
|
|
Kind::Emf => emf::write(&ops, r, doc.info_title()),
|
|
Kind::Wmf => wmf::write(&ops, r, &mut warnings),
|
|
};
|
|
Ok(Output { bytes, warnings })
|
|
}
|
|
|
|
/// A metafile read into a document.
|
|
#[derive(Clone, Debug)]
|
|
pub struct Imported {
|
|
pub document: Document,
|
|
pub kind: Kind,
|
|
/// What was approximated or skipped.
|
|
pub warnings: Vec<String>,
|
|
}
|
|
|
|
/// Read an EMF or WMF file (with or without a placeable header) into a document whose one
|
|
/// artboard is the picture's frame.
|
|
pub fn import(bytes: &[u8]) -> Result<Imported, String> {
|
|
let mut i = import::import(bytes)?;
|
|
i.document.limit_depth_noting(&mut i.warnings);
|
|
Ok(i)
|
|
}
|
|
|
|
/// A QuickDraw picture read into a document.
|
|
#[derive(Clone, Debug)]
|
|
pub struct Picture {
|
|
pub document: Document,
|
|
/// What was approximated or skipped.
|
|
pub warnings: Vec<String>,
|
|
}
|
|
|
|
/// Do `bytes` hold a version 2 QuickDraw picture, after a PICT file's 512 bytes of application
|
|
/// data or on their own? A version 1 picture is known by its file's name.
|
|
pub fn is_pict(bytes: &[u8]) -> bool {
|
|
import::pict::is_pict(bytes)
|
|
}
|
|
|
|
/// Read a QuickDraw picture (version 1 or 2; a PICT file, or a picture on its own) into a document
|
|
/// whose one artboard is the picture's frame.
|
|
pub fn import_pict(bytes: &[u8]) -> Result<Picture, String> {
|
|
let i = import::pict::play(bytes)?;
|
|
let mut p = Picture { document: i.document, warnings: i.warnings };
|
|
p.document.limit_depth_noting(&mut p.warnings);
|
|
Ok(p)
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests;
|