Files
vectorcraft/crates/eps/src/import/data.rs
T
bflatasticandClaude Opus 5.5 64891c1a2e Fix #234: EPS files from the reference app open
EPS files the reference app saves (its procsets in the prolog, a binary header with a TIFF
preview) now open as editable art instead of failing with "undefinedresource in Generic". When
their PostScript still can't be read, the palette preview they carry is placed with the usual
warning instead of the file being refused.

Root cause: five interpreter gaps and one preview format. The Generic and Category resource
categories didn't exist, so categories couldn't be made from Generic (now Category holds the
standard categories, defineresource in Category makes new ones, undefineresource is added);
clipsave/cliprestore were unknown (now a clip stack in the graphics state); systemdict's
languagelevel was the operator, not the integer 3; getinterval copied arrays and strings
instead of sharing their storage, so astore into a subarray was lost (arrays and strings are
now views into shared storage, and readstring/readline return the part they fill); and
resourceforall was unknown (now enumerates matching names with * ? \ templates). The preview
is an 8-bit palette TIFF with an alpha sample, which the image crate can't decode: a small
bounds-checked reader handles that layout (either byte order, any strips).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 09:34:27 +07:00

706 lines
28 KiB
Rust

//! Data the program reads: from itself (`currentfile`) through decoding filters, and images
//! sampled from it, from strings or from procedures.
use std::cell::RefCell;
use std::collections::HashMap;
use std::rc::Rc;
use vectorcraft_color::{Color, Paint};
use vectorcraft_doc::{ImageBlob, ImageObject, Node, NodeId, NodeKind};
use vectorcraft_geom::Affine;
use super::graphics::{Space, process};
use super::interp::{Interp, matrix_of};
use super::lex::{find, hex_decode};
use super::obj::{DictRef, Key, Obj, Op, PsError, Res, Shared, ps_err};
use crate::ps;
/// Most bytes one filter or image reads.
const MAX_DATA: usize = 1 << 28;
/// Most pixels an image may have.
const MAX_PIXELS: usize = 1 << 26;
/// Most distinct colours an image converts through a tint transform.
const MAX_TINTS: usize = 1 << 16;
/// A file object: the program itself, or data a filter decoded.
#[derive(Debug)]
pub(crate) enum Stream {
Current,
/// Filters over the program's own data, decoded when first read.
Pending(Vec<Filter>),
Buf {
data: Vec<u8>,
pos: usize,
/// The samples are 8-bit RGB (or grey) whatever the image's colour space: a decoded JPEG.
rgb: Option<u8>,
},
}
fn io<T>(at: &str) -> Res<T> {
ps_err("ioerror", at)
}
/// The bytes `RunLengthDecode` makes of `raw`, and how many it read.
fn run_length(raw: &[u8]) -> Res<(Vec<u8>, usize)> {
let mut out = vec![];
let mut i = 0;
while let Some(&n) = raw.get(i) {
i += 1;
match n {
128 => break,
0..=127 => {
let len = usize::from(n) + 1;
out.extend_from_slice(raw.get(i..i + len).ok_or(PsError::Ps("ioerror", "RunLengthDecode".into()))?);
i += len;
}
_ => {
let b = *raw.get(i).ok_or(PsError::Ps("ioerror", "RunLengthDecode".into()))?;
i += 1;
out.extend(std::iter::repeat_n(b, 257 - usize::from(n)));
}
}
if out.len() > MAX_DATA {
return Err(PsError::Limit("decoded data is too large"));
}
}
Ok((out, i))
}
/// zlib data inflated, and how many bytes of `raw` it took.
fn flate(raw: &[u8]) -> Res<(Vec<u8>, usize)> {
let mut z = flate2::Decompress::new(true);
let mut out = Vec::with_capacity(raw.len().saturating_mul(3).min(MAX_DATA));
loop {
let before = (z.total_in(), z.total_out());
if out.capacity() - out.len() < 64 * 1024 {
out.reserve(256 * 1024);
}
let input = raw.get(z.total_in() as usize..).unwrap_or_default();
let status = z.decompress_vec(input, &mut out, flate2::FlushDecompress::None).map_err(|_| PsError::Ps("ioerror", "FlateDecode".into()))?;
if out.len() > MAX_DATA {
return Err(PsError::Limit("decoded data is too large"));
}
match status {
flate2::Status::StreamEnd => break,
_ if (z.total_in(), z.total_out()) == before => break,
_ => {}
}
}
Ok((out, z.total_in() as usize))
}
/// LZW data (`EarlyChange` 1) decoded, and how many bytes of `raw` it took.
fn lzw(raw: &[u8]) -> Res<(Vec<u8>, usize)> {
let mut table: Vec<Vec<u8>> = (0..=255u8).map(|b| vec![b]).collect();
table.push(vec![]);
table.push(vec![]);
let (mut out, mut prev): (Vec<u8>, Option<Vec<u8>>) = (vec![], None);
let (mut acc, mut bits, mut width, mut i) = (0u32, 0u32, 9u32, 0usize);
loop {
while bits < width {
let Some(&b) = raw.get(i) else { return Ok((out, i)) };
i += 1;
acc = (acc << 8) | u32::from(b);
bits += 8;
}
let code = ((acc >> (bits - width)) & ((1 << width) - 1)) as usize;
bits -= width;
match code {
256 => {
table.truncate(258);
width = 9;
prev = None;
}
257 => return Ok((out, i)),
_ => {
let entry = match (table.get(code), &prev) {
(Some(e), _) if !e.is_empty() => e.clone(),
(_, Some(p)) if code == table.len() => {
let mut e = p.clone();
e.extend(p.first());
e
}
_ => return io("LZWDecode"),
};
out.extend_from_slice(&entry);
if let Some(mut p) = prev.take()
&& table.len() < 4096
{
p.extend(entry.first());
table.push(p);
}
width = match table.len() + 1 {
n if n >= 2048 => 12,
n if n >= 1024 => 11,
n if n >= 512 => 10,
_ => 9,
};
prev = Some(entry);
if out.len() > MAX_DATA {
return Err(PsError::Limit("decoded data is too large"));
}
}
}
}
}
/// A JPEG's pixels (grey or RGB, 8 bits) with its channel count, and how many bytes of `raw` it
/// took.
fn dct(raw: &[u8]) -> Res<(Vec<u8>, usize, u8)> {
let end = find(raw, &[0xFF, 0xD9]).map_or(raw.len(), |e| e + 2);
let img = image::load_from_memory_with_format(raw.get(..end).unwrap_or_default(), image::ImageFormat::Jpeg)
.map_err(|_| PsError::Ps("ioerror", "DCTDecode".into()))?;
if img.color().channel_count() == 1 { Ok((img.to_luma8().into_raw(), end, 1)) } else { Ok((img.to_rgb8().into_raw(), end, 3)) }
}
/// A decoding filter and its parameters.
#[derive(Clone, Debug)]
pub(crate) struct Filter {
name: Rc<str>,
/// `SubFileDecode`'s count and end-of-data string.
sub: (usize, Vec<u8>),
}
/// The bytes `f` decodes from `raw`, how many it read, and the channels of a decoded JPEG.
fn decode(f: &Filter, raw: &[u8]) -> Res<(Vec<u8>, usize, Option<u8>)> {
let (data, used) = match &*f.name {
"ASCIIHexDecode" => {
let end = raw.iter().position(|b| *b == b'>');
let body = raw.get(..end.unwrap_or(raw.len())).unwrap_or_default();
(hex_decode(body).ok_or(PsError::Ps("ioerror", "ASCIIHexDecode".into()))?, end.map_or(raw.len(), |e| e + 1))
}
"ASCII85Decode" => {
let end = find(raw, b"~>");
let mut text = String::from_utf8_lossy(raw.get(..end.unwrap_or(raw.len())).unwrap_or_default()).into_owned();
text.push_str("~>");
(ps::ascii85_decode(&text).ok_or(PsError::Ps("ioerror", "ASCII85Decode".into()))?, end.map_or(raw.len(), |e| e + 2))
}
"RunLengthDecode" => run_length(raw)?,
"FlateDecode" => flate(raw)?,
"LZWDecode" => lzw(raw)?,
"DCTDecode" => {
let (d, used, channels) = dct(raw)?;
return Ok((d, used, Some(channels)));
}
"SubFileDecode" => {
let (count, eod) = &f.sub;
if eod.is_empty() {
let n = (*count).min(raw.len());
(raw.get(..n).unwrap_or_default().to_vec(), n)
} else {
let (mut at, mut seen) = (0, 0);
loop {
match find(raw.get(at..).unwrap_or_default(), eod) {
Some(i) if seen < *count => {
seen += 1;
at += i + eod.len();
}
Some(i) => break (raw.get(..at + i).unwrap_or_default().to_vec(), at + i + eod.len()),
None => break (raw.to_vec(), raw.len()),
}
}
}
}
"NullEncode" => (raw.to_vec(), raw.len()),
other => return ps_err("undefined", other),
};
Ok((data, used, None))
}
/// What a source of image data gives.
enum Source {
File(Rc<RefCell<Stream>>),
Str(Vec<u8>),
Proc(Obj),
}
impl Interp<'_> {
pub fn data_op(&mut self, op: Op) -> Res {
use Op::*;
match op {
CurrentFile => self.push(Obj::File(Rc::new(RefCell::new(Stream::Current))))?,
Filter => self.filter()?,
ReadHexString | ReadString => {
let s = self.pop_str()?;
let Obj::File(f) = self.pop()? else { return ps_err("typecheck", "") };
let n = s.len();
let got = if op == ReadString { self.read(&f, n)? } else { self.read_hex(&f, n)? };
self.fill(s, &got)?;
self.push(Obj::Bool(got.len() == n))?;
}
ReadLine => {
let s = self.pop_str()?;
let Obj::File(f) = self.pop()? else { return ps_err("typecheck", "") };
let n = s.len();
let mut line = vec![];
let mut ended = false;
while line.len() < n {
match self.read(&f, 1)?.first() {
Some(b'\n') | None => {
ended = true;
break;
}
Some(b'\r') => {
ended = true;
break;
}
Some(b) => line.push(*b),
}
}
self.fill(s, &line)?;
self.push(Obj::Bool(ended))?;
}
FlushFile | CloseFile => {
self.pop()?;
}
File => {
self.pop()?;
self.pop()?;
return ps_err("invalidfileaccess", "file");
}
Eexec => self.eexec()?,
Image | ImageMask | ColorImage => self.image(op)?,
_ => self.text_op(op)?,
}
Ok(())
}
/// Up to `n` bytes of file `f`.
fn read(&mut self, f: &Rc<RefCell<Stream>>, n: usize) -> Res<Vec<u8>> {
self.materialize(f)?;
Ok(match &mut *f.borrow_mut() {
Stream::Current => {
let rest = self.lex.data();
let k = n.min(rest.len());
let v = rest.get(..k).unwrap_or_default().to_vec();
self.lex.advance(k);
v
}
Stream::Buf { data, pos, .. } => {
let start = (*pos).min(data.len());
let end = start.saturating_add(n).min(data.len());
*pos = end;
data.get(start..end).unwrap_or_default().to_vec()
}
Stream::Pending(_) => vec![],
})
}
/// Up to `n` bytes read as hexadecimal digits from `f` (other characters skipped).
fn read_hex(&mut self, f: &Rc<RefCell<Stream>>, n: usize) -> Res<Vec<u8>> {
let mut out = Vec::with_capacity(n);
let mut high: Option<u8> = None;
while out.len() < n {
let Some(&b) = self.read(f, 1)?.first() else { break };
let Some(v) = char::from(b).to_digit(16) else { continue };
match high.take() {
Some(h) => out.push(h << 4 | v as u8),
None => high = Some(v as u8),
}
}
Ok(out)
}
/// Read bytes `got` into string `s` and push the part of `s` they fill.
fn fill(&mut self, s: Shared<u8>, got: &[u8]) -> Res {
if !s.write(0, got) {
return ps_err("rangecheck", "");
}
self.push_interval(Obj::Str(s), 0, got.len())
}
/// `filter`: a file that decodes its source. Over the program's own data it decodes when
/// first read (the data follows the operator that reads it); over other data, at once.
fn filter(&mut self) -> Res {
let name = self.pop()?.text().ok_or(PsError::Ps("typecheck", "filter".into()))?;
let sub = if &*name == "SubFileDecode" {
let eod = self.pop_str()?.to_vec();
let count = self.pop_count()?;
(count, eod)
} else {
(0, vec![])
};
if let Some(Obj::Dict(d)) = self.stack.last().cloned() {
self.pop()?;
let predictor = d.borrow().get(&Key::name("Predictor")).and_then(Obj::as_num).unwrap_or(1.0);
if predictor > 1.0 {
return ps_err("undefined", "a filter predictor");
}
}
let spec = Filter { name, sub };
let stream = match self.pop()? {
Obj::File(f) => {
if let Stream::Buf { data, pos, .. } = &*f.borrow() {
self.alloc(data.len().saturating_sub(*pos))?;
}
let chain = match &*f.borrow() {
Stream::Current => Some(vec![]),
Stream::Pending(c) => Some(c.clone()),
Stream::Buf { .. } => None,
};
match chain {
Some(mut c) => {
c.push(spec);
Stream::Pending(c)
}
None => {
let mut b = f.borrow_mut();
let Stream::Buf { data, pos, .. } = &mut *b else { return io("filter") };
let (out, used, rgb) = decode(&spec, data.get(*pos..).unwrap_or_default())?;
*pos = pos.saturating_add(used);
Stream::Buf { data: out, pos: 0, rgb }
}
}
}
Obj::Str(s) => {
self.alloc(s.borrow().len())?;
let (data, _, rgb) = decode(&spec, &s.borrow())?;
Stream::Buf { data, pos: 0, rgb }
}
p @ Obj::Array { .. } => {
let raw = self.gather(&p, MAX_DATA)?;
let (data, _, rgb) = decode(&spec, &raw)?;
Stream::Buf { data, pos: 0, rgb }
}
_ => return ps_err("typecheck", "filter"),
};
self.push(Obj::File(Rc::new(RefCell::new(stream))))
}
/// Decode the filters a file waits to run over the program's data, from where it is now.
fn materialize(&mut self, f: &Rc<RefCell<Stream>>) -> Res {
let chain = match &*f.borrow() {
Stream::Pending(c) => c.clone(),
_ => return Ok(()),
};
let mut buf: Option<(Vec<u8>, Option<u8>)> = None;
for spec in &chain {
buf = Some(match buf.take() {
None => {
let raw = self.lex.data();
let (data, used, rgb) = decode(spec, raw)?;
self.alloc(data.len())?;
self.lex.advance(used);
(data, rgb)
}
Some((prev, was)) => {
let (data, _, rgb) = decode(spec, &prev)?;
self.alloc(data.len())?;
(data, rgb.or(was))
}
});
}
let (data, rgb) = buf.unwrap_or_default();
*f.borrow_mut() = Stream::Buf { data, pos: 0, rgb };
Ok(())
}
/// The bytes a data procedure gives, called until it gives an empty string or `n` bytes.
fn gather(&mut self, p: &Obj, n: usize) -> Res<Vec<u8>> {
let mut out = vec![];
while out.len() < n {
self.call(p.clone())?;
let s = self.pop_str()?;
let s = s.borrow();
if s.is_empty() {
break;
}
self.alloc(s.len())?;
out.extend_from_slice(&s);
}
out.truncate(n);
Ok(out)
}
/// `n` bytes of image data from `src` (zeros past its end).
fn source_bytes(&mut self, src: &Source, n: usize) -> Res<Vec<u8>> {
let mut v = match src {
Source::File(f) => self.read(f, n)?,
Source::Str(s) if !s.is_empty() => s.iter().copied().cycle().take(n).collect(),
Source::Str(_) => vec![],
Source::Proc(p) => self.gather(p, n)?,
};
v.resize(n, 0);
Ok(v)
}
/// `eexec`: an encrypted font program follows. It is skipped (type is drawn in the app's
/// fonts, by name): the font dictionary it would complete is defined as it is.
fn eexec(&mut self) -> Res {
self.pop()?;
if let Some(Obj::Dict(d)) = self.stack.last().cloned() {
let name = d.borrow().get(&Key::name("FontName")).cloned();
if let Some(k) = name.and_then(|n| n.key()) {
self.pop()?;
self.fonts.borrow_mut().insert(k, Obj::Dict(d));
}
}
let rest = self.lex.data();
let at = find(rest, b"cleartomark").ok_or(PsError::Ps("ioerror", "eexec".into()))?;
self.lex.advance(at);
self.push(Obj::Mark)
}
// ---------- images ----------
fn image(&mut self, op: Op) -> Res {
let mask = op == Op::ImageMask;
let spec = match self.stack.last() {
Some(Obj::Dict(_)) if op != Op::ColorImage => {
let d = self.pop_dict()?;
self.image_dict(&d, mask)?
}
_ => self.image_operands(op)?,
};
let Some(img) = self.decode_image(spec)? else { return Ok(()) };
self.place_image(img)
}
/// The dictionary form of `image` and `imagemask`.
fn image_dict(&mut self, d: &DictRef, mask: bool) -> Res<Spec> {
let get = |k: &str| d.borrow().get(&Key::name(k)).cloned();
let num = |k: &str| get(k).and_then(|o| o.as_num());
let kind = num("ImageType").unwrap_or(1.0);
if kind != 1.0 && kind != 4.0 {
return ps_err("rangecheck", "ImageType");
}
let size = |k: &str| num(k).filter(|v| *v >= 1.0 && *v <= 65_535.0).map(|v| v as usize).ok_or(PsError::Ps("rangecheck", k.to_string()));
let (w, h) = (size("Width")?, size("Height")?);
let bpc = if mask { 1 } else { num("BitsPerComponent").unwrap_or(8.0) as u8 };
let matrix =
get("ImageMatrix").and_then(|o| o.items().and_then(|i| matrix_of(&i.borrow()))).ok_or(PsError::Ps("typecheck", "ImageMatrix".into()))?;
let decode: Vec<f64> = get("Decode").and_then(|o| o.items().map(|i| i.borrow().iter().filter_map(Obj::as_num).collect())).unwrap_or_default();
let space = if mask { Space::Gray } else { (*self.g.space).clone() };
let multi = get("MultipleDataSources").is_some_and(|o| matches!(o, Obj::Bool(true)));
let src = get("DataSource").ok_or(PsError::Ps("undefined", "DataSource".into()))?;
let sources = match (&src, multi) {
(Obj::Array { items, exec: false }, true) => items.borrow().iter().map(source).collect::<Res<Vec<_>>>()?,
_ => vec![source(&src)?],
};
let mask_color: Option<Vec<u32>> = (kind == 4.0)
.then(|| {
get("MaskColor").and_then(|o| o.items().map(|i| i.borrow().iter().filter_map(|v| v.as_num().map(|v| v.max(0.0) as u32)).collect()))
})
.flatten();
// An image mask paints its 0 bits with Decode [0 1], its 1 bits with [1 0].
let stencil = mask.then(|| decode.first().is_some_and(|v| *v > 0.5));
Ok(Spec { w, h, bpc, space, decode, matrix, sources, mask_color, stencil })
}
/// The operand forms: `w h bpc matrix src image`, `w h polarity matrix src imagemask`, and
/// `w h bpc matrix src… multi ncomp colorimage`.
fn image_operands(&mut self, op: Op) -> Res<Spec> {
let (n, multi) = if op == Op::ColorImage {
let n = self.pop_count()?;
(n, self.pop_bool()?)
} else {
(1, false)
};
let space = match n {
1 => Space::Gray,
3 => Space::Rgb,
4 => Space::Cmyk,
_ => return ps_err("rangecheck", "colorimage"),
};
let mut sources = vec![];
for _ in 0..if multi { n } else { 1 } {
let o = self.pop()?;
sources.push(source(&o)?);
}
sources.reverse();
let matrix = self.pop_matrix()?;
let (bpc, stencil) = if op == Op::ImageMask { (1, Some(self.pop_bool()?)) } else { (self.pop_int()?.clamp(1, 16) as u8, None) };
let size = |v: i64| usize::try_from(v).ok().filter(|v| (1..=65_535).contains(v)).ok_or(PsError::Ps("rangecheck", "image".into()));
let h = size(self.pop_int()?)?;
let w = size(self.pop_int()?)?;
Ok(Spec { w, h, bpc, space, decode: vec![], matrix, sources, mask_color: None, stencil })
}
/// The image's pixels, straight RGBA, rows from the first one read.
fn decode_image(&mut self, s: Spec) -> Res<Option<Pixels>> {
if !matches!(s.bpc, 1 | 2 | 4 | 8 | 12 | 16) {
return ps_err("rangecheck", "BitsPerComponent");
}
if s.w.saturating_mul(s.h) > MAX_PIXELS {
self.out.warn("images too large to read were left out");
return Ok(None);
}
self.alloc(s.w * s.h * 4)?;
let n = if s.stencil.is_some() { 1 } else { s.space.n() };
let bpc = usize::from(s.bpc);
for src in &s.sources {
if let Source::File(f) = src {
self.materialize(f)?;
}
}
// A decoded JPEG gives 8-bit grey or RGB samples whatever the colour space says.
let jpeg = s.sources.iter().find_map(|src| match src {
Source::File(f) => match &*f.borrow() {
Stream::Buf { rgb, .. } => *rgb,
_ => None,
},
_ => None,
});
let (n, bpc, space) = match jpeg {
Some(3) => (3, 8, Space::Rgb),
Some(_) => (1, 8, Space::Gray),
None => (n, bpc, s.space.clone()),
};
let multi = s.sources.len() > 1;
let row_bits = if multi { s.w * bpc } else { s.w * n * bpc };
let row = row_bits.div_ceil(8);
let planes: Vec<Vec<u8>> = if multi {
let mut v = vec![];
for src in &s.sources {
v.push(self.source_bytes(src, row * s.h)?);
}
v
} else {
let src = s.sources.first().ok_or(PsError::Ps("undefined", "DataSource".into()))?;
vec![self.source_bytes(src, row * s.h)?]
};
let max = ((1u32 << bpc.min(16)) - 1) as f64;
let sample = |plane: &[u8], y: usize, i: usize| -> u32 {
let bit = y * row * 8 + i * bpc;
let byte = bit / 8;
match bpc {
8 => u32::from(plane.get(byte).copied().unwrap_or(0)),
16 => u32::from(plane.get(byte).copied().unwrap_or(0)) << 8 | u32::from(plane.get(byte + 1).copied().unwrap_or(0)),
12 => {
let v = u32::from(plane.get(byte).copied().unwrap_or(0)) << 8 | u32::from(plane.get(byte + 1).copied().unwrap_or(0));
if bit.is_multiple_of(8) { v >> 4 } else { v & 0xfff }
}
_ => (u32::from(plane.get(byte).copied().unwrap_or(0)) >> (8 - bpc - bit % 8)) & ((1 << bpc) - 1),
}
};
let default_decode: Vec<f64> = match &space {
Space::Indexed { .. } => vec![0.0, max],
_ => (0..n).flat_map(|_| [0.0, 1.0]).collect(),
};
let decode = if s.decode.len() >= 2 * n && jpeg.is_none() { s.decode.clone() } else { default_decode };
let paint = self.g.paint.color().map(|c| c.to_rgba8(1.0));
let mut rgba = Vec::with_capacity(s.w * s.h * 4);
let mut cache: HashMap<Vec<u32>, [u8; 3]> = HashMap::new();
let mut raw = vec![0u32; n];
let mut comps = vec![0f64; n];
for y in 0..s.h {
for x in 0..s.w {
for (c, slot) in raw.iter_mut().enumerate() {
*slot = match planes.as_slice() {
[one] => sample(one, y, x * n + c),
many => many.get(c).map_or(0, |p| sample(p, y, x)),
};
}
if let Some(paint_ones) = s.stencil {
let on = (raw.first().copied().unwrap_or(0) == 1) == paint_ones;
let [r, g, b, _] = paint.unwrap_or([0, 0, 0, 255]);
rgba.extend([r, g, b, if on { 255 } else { 0 }]);
continue;
}
let masked = s.mask_color.as_ref().is_some_and(|m| masked(m, &raw));
for (c, slot) in comps.iter_mut().enumerate() {
let (d0, d1) = (decode.get(2 * c).copied().unwrap_or(0.0), decode.get(2 * c + 1).copied().unwrap_or(1.0));
*slot = d0 + f64::from(*raw.get(c).unwrap_or(&0)) * (d1 - d0) / max;
}
let rgb = match (&space, raw.as_slice()) {
(Space::Gray, [v]) if bpc == 8 && decode == [0.0, 1.0] => [*v as u8; 3],
(Space::Rgb, [r, g, b]) if bpc == 8 && decode == [0.0, 1.0, 0.0, 1.0, 0.0, 1.0] => [*r as u8, *g as u8, *b as u8],
_ => match cache.get(&raw) {
Some(c) => *c,
None => {
let c = self.sample_rgb(&space, &comps)?;
if cache.len() < MAX_TINTS {
cache.insert(raw.clone(), c);
}
c
}
},
};
rgba.extend([rgb[0], rgb[1], rgb[2], if masked { 0 } else { 255 }]);
}
}
Ok(Some(Pixels { w: s.w as u32, h: s.h as u32, rgba, matrix: s.matrix }))
}
/// One pixel's colour in `space` as display RGB.
fn sample_rgb(&mut self, space: &Space, comps: &[f64]) -> Res<[u8; 3]> {
let color = match space {
Space::Gray | Space::Rgb | Space::Cmyk => process(space, comps),
_ => match self.paint_of(space, comps)? {
Paint::Solid { color, .. } => Some(color),
_ => None,
},
};
let [r, g, b, _] = color.unwrap_or(Color::BLACK).to_rgba8(1.0);
Ok([r, g, b])
}
/// Place decoded pixels in user space (its unit square through the image matrix).
fn place_image(&mut self, img: Pixels) -> Res {
let m = img.matrix;
if m.determinant().abs() < 1e-12 {
return Ok(());
}
let xf = self.xf() * m.inverse();
if !xf.as_coeffs().iter().all(|v| v.is_finite()) || xf.determinant().abs() < 1e-12 {
return Ok(());
}
let Some(rgba) = image::RgbaImage::from_raw(img.w, img.h, img.rgba) else { return Ok(()) };
let mut png = vec![];
if rgba.write_to(&mut std::io::Cursor::new(&mut png), image::ImageFormat::Png).is_err() {
self.out.warn("an image that couldn't be read was left out");
return Ok(());
}
let blob = ImageBlob::new("image/png", png);
let key = blob.content_key();
self.out.doc.images.entry(key.clone()).or_insert(blob);
let im = ImageObject { key, width: img.w, height: img.h, xf, link: None, placement: Default::default() };
let clips = self.g.clips.clone();
self.out.push(Node::new(NodeId(0), NodeKind::Image(im)), &clips);
Ok(())
}
}
/// An image to decode.
struct Spec {
w: usize,
h: usize,
bpc: u8,
space: Space,
decode: Vec<f64>,
/// User space → image space.
matrix: Affine,
/// One source, or one per component.
sources: Vec<Source>,
/// `MaskColor` (ImageType 4): samples that are transparent.
mask_color: Option<Vec<u32>>,
/// An image mask: whether its 1 bits paint.
stencil: Option<bool>,
}
/// Decoded pixels.
struct Pixels {
w: u32,
h: u32,
rgba: Vec<u8>,
matrix: Affine,
}
fn source(o: &Obj) -> Res<Source> {
Ok(match o {
Obj::File(f) => Source::File(f.clone()),
Obj::Str(s) => Source::Str(s.to_vec()),
Obj::Array { items, .. } => Source::Proc(Obj::Array { items: items.clone(), exec: true }),
_ => return ps_err("typecheck", "DataSource"),
})
}
/// Do samples `raw` match `MaskColor` `m` (one value per component, or a range)?
fn masked(m: &[u32], raw: &[u32]) -> bool {
if m.len() == raw.len() {
return m.iter().zip(raw).all(|(a, b)| a == b);
}
m.len() == raw.len() * 2
&& raw.iter().enumerate().all(|(i, v)| (m.get(2 * i).copied().unwrap_or(0)..=m.get(2 * i + 1).copied().unwrap_or(0)).contains(v))
}