mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-14 05:24:44 +00:00
EPS: delegate binary preview header, sections, and ASCII85 to craft-eps
This commit is contained in:
Generated
+14
-1
@@ -978,6 +978,11 @@ dependencies = [
|
||||
"ruzstd",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "craft-codepage"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/storytold/craft-libs?rev=6d620e971e820b517b11a99cad040ebac670e1e2#6d620e971e820b517b11a99cad040ebac670e1e2"
|
||||
|
||||
[[package]]
|
||||
name = "craft-docpath"
|
||||
version = "0.1.0"
|
||||
@@ -986,13 +991,20 @@ source = "git+https://github.com/storytold/craft-libs?rev=9be9a63782298ab59cd9b7
|
||||
[[package]]
|
||||
name = "craft-dxf"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/storytold/craft-libs?rev=119415f46f601e20b9bc6b1b4d6979ceda86d88e#119415f46f601e20b9bc6b1b4d6979ceda86d88e"
|
||||
source = "git+https://github.com/storytold/craft-libs?rev=6d620e971e820b517b11a99cad040ebac670e1e2#6d620e971e820b517b11a99cad040ebac670e1e2"
|
||||
dependencies = [
|
||||
"craft-codepage",
|
||||
"encoding_rs",
|
||||
"kurbo 0.13.1",
|
||||
"log",
|
||||
"thiserror 2.0.21",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "craft-eps"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/storytold/craft-libs?rev=6d620e971e820b517b11a99cad040ebac670e1e2#6d620e971e820b517b11a99cad040ebac670e1e2"
|
||||
|
||||
[[package]]
|
||||
name = "craft-fonts-build"
|
||||
version = "0.1.0"
|
||||
@@ -6548,6 +6560,7 @@ dependencies = [
|
||||
name = "vectorcraft-eps"
|
||||
version = "0.24.1"
|
||||
dependencies = [
|
||||
"craft-eps",
|
||||
"craft-pdf",
|
||||
"encoding_rs",
|
||||
"fax",
|
||||
|
||||
@@ -9,6 +9,7 @@ description = "VectorCraft Encapsulated PostScript: a hand-written EPS writer (P
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
craft-eps = { workspace = true }
|
||||
vectorcraft-geom = { workspace = true }
|
||||
vectorcraft-color = { workspace = true }
|
||||
vectorcraft-doc = { workspace = true }
|
||||
|
||||
+5
-46
@@ -352,64 +352,23 @@ fn read_data(ps: &[u8], name: &str) -> Option<Vec<u8>> {
|
||||
None
|
||||
}
|
||||
|
||||
/// The magic number of an EPS file with a binary header (a preview).
|
||||
const DOS_MAGIC: [u8; 4] = [0xC5, 0xD0, 0xD3, 0xC6];
|
||||
/// The binary header's size.
|
||||
const DOS_HEADER: usize = 30;
|
||||
pub use craft_eps::{DOS_HEADER, DOS_MAGIC, Sections, is_eps};
|
||||
|
||||
/// `ps` behind a binary header, followed by the TIFF preview `tiff`.
|
||||
fn dos_eps(ps: &[u8], tiff: &[u8]) -> Result<Vec<u8>, String> {
|
||||
let big = || "the file is too large for an EPS preview header (4 GB): choose no preview".to_string();
|
||||
let ps_len = u32::try_from(ps.len()).map_err(|_| big())?;
|
||||
let tiff_at = u32::try_from(DOS_HEADER + ps.len()).map_err(|_| big())?;
|
||||
let tiff_len = u32::try_from(tiff.len()).map_err(|_| big())?;
|
||||
tiff_at.checked_add(tiff_len).ok_or_else(big)?;
|
||||
let mut out = Vec::with_capacity(DOS_HEADER + ps.len() + tiff.len());
|
||||
out.extend(DOS_MAGIC);
|
||||
out.extend((DOS_HEADER as u32).to_le_bytes());
|
||||
out.extend(ps_len.to_le_bytes());
|
||||
// No metafile preview.
|
||||
out.extend(0u32.to_le_bytes());
|
||||
out.extend(0u32.to_le_bytes());
|
||||
out.extend(tiff_at.to_le_bytes());
|
||||
out.extend(tiff_len.to_le_bytes());
|
||||
// No checksum.
|
||||
out.extend(0xFFFFu16.to_le_bytes());
|
||||
out.extend(ps);
|
||||
out.extend(tiff);
|
||||
Ok(out)
|
||||
craft_eps::wrap(ps, Some(tiff), None).map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
/// The sections of an EPS file: its PostScript and its TIFF preview (binary header), or the whole
|
||||
/// file as PostScript. `None` when a binary header points outside the file.
|
||||
pub fn sections(bytes: &[u8]) -> Option<(&[u8], Option<&[u8]>)> {
|
||||
if !bytes.starts_with(&DOS_MAGIC) {
|
||||
return Some((bytes, None));
|
||||
}
|
||||
let word = |at: usize| bytes.get(at..at + 4).and_then(|b| <[u8; 4]>::try_from(b).ok()).map(|b| u32::from_le_bytes(b) as usize);
|
||||
let part = |at: usize| {
|
||||
let (start, len) = (word(at)?, word(at + 4)?);
|
||||
bytes.get(start..start.checked_add(len)?)
|
||||
};
|
||||
let ps = part(4)?;
|
||||
let tiff = match word(24) {
|
||||
Some(0) | None => None,
|
||||
Some(_) => Some(part(20)?),
|
||||
};
|
||||
Some((ps, tiff))
|
||||
let s = craft_eps::sections(bytes).ok()?;
|
||||
Some((s.postscript, s.tiff))
|
||||
}
|
||||
|
||||
/// The Windows metafile preview behind an EPS file's binary header, if it has one.
|
||||
pub fn metafile_preview(bytes: &[u8]) -> Option<&[u8]> {
|
||||
if !bytes.starts_with(&DOS_MAGIC) {
|
||||
return None;
|
||||
}
|
||||
let word = |at: usize| bytes.get(at..at + 4).and_then(|b| <[u8; 4]>::try_from(b).ok()).map(|b| u32::from_le_bytes(b) as usize);
|
||||
let (start, len) = (word(12)?, word(16)?);
|
||||
if start == 0 || len == 0 {
|
||||
return None;
|
||||
}
|
||||
bytes.get(start..start.checked_add(len)?)
|
||||
craft_eps::sections(bytes).ok()?.metafile
|
||||
}
|
||||
|
||||
/// The native document an EPS file written by [`export`] carries, if any.
|
||||
|
||||
+2
-89
@@ -180,54 +180,7 @@ pub(crate) fn clip_op(rule: FillRule) -> &'static str {
|
||||
|
||||
/// `bytes` in ASCII85 with its `~>` end, in lines of about [`LINE`] characters, none starting
|
||||
/// with `%` (DSC readers would take it for a comment).
|
||||
pub(crate) fn ascii85(bytes: &[u8]) -> String {
|
||||
let mut raw = String::with_capacity(bytes.len() * 5 / 4 + 8);
|
||||
let (chunks, rest) = bytes.as_chunks::<4>();
|
||||
for c in chunks {
|
||||
let v = u32::from_be_bytes(*c);
|
||||
if v == 0 {
|
||||
raw.push('z');
|
||||
} else {
|
||||
push85(&mut raw, v, 5);
|
||||
}
|
||||
}
|
||||
if !rest.is_empty() {
|
||||
let mut c = [0u8; 4];
|
||||
for (d, s) in c.iter_mut().zip(rest) {
|
||||
*d = *s;
|
||||
}
|
||||
push85(&mut raw, u32::from_be_bytes(c), rest.len() + 1);
|
||||
}
|
||||
raw.push_str("~>");
|
||||
let mut out = String::with_capacity(raw.len() + raw.len() / LINE + 2);
|
||||
if raw.starts_with('%') {
|
||||
// Whitespace is ignored by readers, and keeps the first line from starting with `%`.
|
||||
out.push(' ');
|
||||
}
|
||||
let mut col = 0;
|
||||
for ch in raw.chars() {
|
||||
if col >= LINE && ch != '%' {
|
||||
out.push('\n');
|
||||
col = 0;
|
||||
}
|
||||
out.push(ch);
|
||||
col += 1;
|
||||
}
|
||||
out.push('\n');
|
||||
out
|
||||
}
|
||||
|
||||
/// The first `n` base-85 digits of `v`.
|
||||
fn push85(s: &mut String, mut v: u32, n: usize) {
|
||||
let mut d = [0u8; 5];
|
||||
for slot in d.iter_mut().rev() {
|
||||
*slot = (v % 85) as u8 + b'!';
|
||||
v /= 85;
|
||||
}
|
||||
for b in d.iter().take(n) {
|
||||
s.push(char::from(*b));
|
||||
}
|
||||
}
|
||||
pub(crate) use craft_eps::ascii85::encode as ascii85;
|
||||
|
||||
/// Most bytes [`ascii85_decode`] makes: what one filter or data section may hold.
|
||||
pub(crate) const MAX_ASCII85: usize = 1 << 28;
|
||||
@@ -241,47 +194,7 @@ pub(crate) fn ascii85_decode(text: &str) -> Option<Vec<u8>> {
|
||||
/// [`ascii85_decode`] of at most `max` bytes: `z` is four bytes for one, so the length of the
|
||||
/// text doesn't bound what it becomes (#1715).
|
||||
pub(crate) fn ascii85_decode_within(text: &str, max: usize) -> Option<Vec<u8>> {
|
||||
let mut out = Vec::with_capacity((text.len() / 5 * 4).min(max));
|
||||
let mut group = [0u8; 5];
|
||||
let mut n = 0;
|
||||
let mut chars = text.bytes().filter(|b| !b.is_ascii_whitespace()).peekable();
|
||||
while let Some(b) = chars.next() {
|
||||
if out.len() > max {
|
||||
return None;
|
||||
}
|
||||
match b {
|
||||
b'~' => {
|
||||
if chars.next() != Some(b'>') {
|
||||
return None;
|
||||
}
|
||||
if n == 1 {
|
||||
return None;
|
||||
}
|
||||
if n > 1 {
|
||||
let padded: Vec<u8> = group.iter().take(n).copied().chain(std::iter::repeat(b'u')).take(5).collect();
|
||||
let v = value85(&padded)?;
|
||||
out.extend(v.to_be_bytes().iter().take(n - 1));
|
||||
}
|
||||
return (out.len() <= max).then_some(out);
|
||||
}
|
||||
b'z' if n == 0 => out.extend([0; 4]),
|
||||
b'!'..=b'u' => {
|
||||
*group.get_mut(n)? = b;
|
||||
n += 1;
|
||||
if n == 5 {
|
||||
out.extend(value85(&group)?.to_be_bytes());
|
||||
n = 0;
|
||||
}
|
||||
}
|
||||
_ => return None,
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// The value of five base-85 digits; `None` past `u32::MAX`.
|
||||
fn value85(digits: &[u8]) -> Option<u32> {
|
||||
digits.iter().try_fold(0u32, |acc, d| acc.checked_mul(85)?.checked_add(u32::from(d.checked_sub(b'!')?)))
|
||||
craft_eps::ascii85::decode(text.as_bytes(), max)
|
||||
}
|
||||
|
||||
/// Run-length encoding (PackBits: what PostScript's `RunLengthDecode` reads), shared with TIFF.
|
||||
|
||||
Reference in New Issue
Block a user