diff --git a/crates/eps/src/import/data.rs b/crates/eps/src/import/data.rs index 4db791972..53848af89 100644 --- a/crates/eps/src/import/data.rs +++ b/crates/eps/src/import/data.rs @@ -17,1336 +17,8 @@ use super::lex::{Lexer, 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; -/// Why an image is missing. -const TOO_LARGE: &str = "images too large to read were left out"; -/// Most distinct colours an image converts through a tint transform. -const MAX_TINTS: usize = 1 << 16; +mod filters; +mod ops; +mod pixels; -/// 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), - Buf { - data: Vec, - pos: usize, - /// The samples are 8-bit gray, RGB or CMYK (1, 3 or 4 channels) whatever the image's - /// color space: a decoded JPEG. - rgb: Option, - }, -} - -fn io(at: &str) -> Res { - ps_err("ioerror", at) -} - -/// The bytes `RunLengthDecode` makes of `raw`, and how many it read. -fn run_length(raw: &[u8]) -> Res<(Vec, 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, 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, usize)> { - let mut table: Vec> = (0..=255u8).map(|b| vec![b]).collect(); - table.push(vec![]); - table.push(vec![]); - let (mut out, mut prev): (Vec, Option>) = (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 (gray, RGB or CMYK, 8 bits) with its channel count, and how many bytes of -/// `raw` it took. A CMYK JPEG gives its samples as stored, for the image's Decode array to read -/// (Adobe's CMYK JPEGs store them inverted, which a Decode array of `[1 0 1 0 1 0 1 0]` undoes). -fn dct(raw: &[u8]) -> Res<(Vec, usize, u8)> { - let end = find(raw, &[0xFF, 0xD9]).map_or(raw.len(), |e| e + 2); - let jpeg = raw.get(..end).unwrap_or_default(); - // `cmyk` gives the ink amounts of inverted samples: inverted back, they are the samples. - if jpeg_components(jpeg) == Some(4) - && let Some(inks) = ImageBlob::new("image/jpeg", jpeg.to_vec()).cmyk() - { - return Ok((inks.data.into_iter().map(|v| 255 - v).collect(), end, 4)); - } - let img = image::load_from_memory_with_format(jpeg, 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)) } -} - -/// CCITT fax data (ITU-T T.4 Group 3, one- or two-dimensional, and T.6 Group 4) decoded to rows -/// of 1-bit samples, each padded to a whole byte, and how many bytes of `raw` it took. With -/// `EndOfBlock` the rows end at the end-of-block code (EOFB, or RTC in Group 3) regardless of -/// `Rows`; without it they end after `Rows` rows, or at the end of the data when `Rows` is 0. -/// Damaged data ends the rows: those decoded before it are kept, and damage in the first row is an -/// `ioerror`. Rows that would take more than `MAX_DATA` bytes are refused. -fn fax(raw: &[u8], settings: DecodeSettings) -> Res<(Vec, usize)> { - let row = (settings.columns as usize).div_ceil(8).max(1); - let most = u32::try_from(MAX_DATA / row).unwrap_or(u32::MAX); - if !settings.end_of_block && settings.rows > most { - return Err(PsError::Limit("decoded data is too large")); - } - // Without a row count that ends the data, a row past `most` shows that the data is too large. - let rows = if settings.end_of_block || settings.rows == 0 { most.saturating_add(1) } else { settings.rows }; - // The decoder reads the RTC of Group 3 two-dimensional data as rows: it gets the data before it. - let rtc = match settings.encoding { - EncodingMode::Group3_2D { .. } if settings.end_of_block => rtc(raw), - _ => None, - }; - let data = rtc.and_then(|(cut, _)| raw.get(..cut)).unwrap_or(raw); - let mut out = FaxRows::default(); - let read = hayro_ccitt::decode(data, &mut out, &mut DecoderContext::new(DecodeSettings { rows, ..settings })); - if out.rows > most { - return Err(PsError::Limit("decoded data is too large")); - } - let used = match read { - Ok(used) => used, - Err(_) if out.rows > 0 => { - out.data.truncate(out.rows as usize * row); - // The whole rows decoded again, for how many bytes they took. - let used = hayro_ccitt::decode(data, &mut Dropped, &mut DecoderContext::new(DecodeSettings { rows: out.rows, ..settings })); - used.unwrap_or(data.len()) - } - Err(_) => return io("CCITTFaxDecode"), - }; - Ok((out.data, rtc.map_or(used, |(_, end)| end))) -} - -/// The RTC that ends Group 3 two-dimensional data (six EOLs, each followed by a 1 tag bit, ITU-T -/// T.4 §4.2.4): the byte that holds the 1 bit of its first EOL, and the byte after its last tag -/// bit. The first EOL with a 1 tag bit that another EOL follows starts it. -fn rtc(raw: &[u8]) -> Option<(usize, usize)> { - let bits = raw.len().saturating_mul(8); - let bit = |i: usize| raw.get(i / 8).is_some_and(|b| b & (0x80 >> (i % 8)) != 0); - let zeros = |i: usize| (i..bits).take_while(|j| !bit(*j)).count(); - // An EOL (its fill bits first) and a 1 tag bit from `i`: the EOL's 1 bit and where they end. - let eol = |i: usize| { - let one = i + zeros(i); - (one - i >= 11 && bit(one) && bit(one + 1)).then_some((one, one + 2)) - }; - let mut i = 0; - while i < bits { - if let Some((one, next)) = eol(i) - && let Some((_, mut end)) = eol(next) - { - for _ in 2..6 { - match eol(end) { - Some((_, e)) => end = e, - None => break, - } - } - return Some((one / 8, end.div_ceil(8))); - } - i += zeros(i) + 1; - } - None -} - -/// Rows of 1-bit samples from a CCITT decoder, each padded to a whole byte: 1 is white, or black -/// with `BlackIs1`. -#[derive(Default)] -struct FaxRows { - data: Vec, - /// Samples in the row being decoded. - x: u32, - /// Whole rows decoded. - rows: u32, -} - -impl Decoder for FaxRows { - fn push_pixel(&mut self, white: bool) { - if self.x.is_multiple_of(8) { - self.data.push(0); - } - if white && let Some(b) = self.data.last_mut() { - *b |= 0x80 >> (self.x % 8); - } - self.x = self.x.saturating_add(1); - } - - fn push_pixel_chunk(&mut self, white: bool, chunk_count: u32) { - self.data.extend(std::iter::repeat_n(if white { 0xff } else { 0 }, chunk_count as usize)); - self.x = self.x.saturating_add(chunk_count.saturating_mul(8)); - } - - fn next_line(&mut self) { - self.x = 0; - self.rows = self.rows.saturating_add(1); - } -} - -/// A CCITT decoder that drops what it decodes. -struct Dropped; - -impl Decoder for Dropped { - fn push_pixel(&mut self, _: bool) {} - - fn push_pixel_chunk(&mut self, _: bool, _: u32) {} - - fn next_line(&mut self) {} -} - -/// A decoding filter and its parameters. -#[derive(Clone, Debug)] -pub(crate) struct Filter { - name: Rc, - /// `SubFileDecode`'s count and end-of-data string. - sub: (usize, Vec), - /// `FlateDecode`'s and `LZWDecode`'s `Predictor`: what it undoes. - predictor: Option, - /// `CCITTFaxDecode`'s parameters. - fax: Option, -} - -/// A `Predictor` (2: TIFF, 10–15: PNG) with its `Colors`, `BitsPerComponent` and `Columns`. -#[derive(Clone, Copy, Debug)] -struct Predictor { - kind: u8, - colors: usize, - bpc: usize, - columns: usize, -} - -impl Predictor { - /// The predictor of a filter's parameters, if it has one. - fn of(d: &DictRef) -> Res> { - let num = |k: &str, default: f64| d.borrow().get(&Key::name(k)).and_then(Obj::as_num).unwrap_or(default); - let kind = num("Predictor", 1.0); - if kind <= 1.0 { - return Ok(None); - } - let (colors, bpc, columns) = (num("Colors", 1.0), num("BitsPerComponent", 8.0), num("Columns", 1.0)); - if !(kind == 2.0 || (10.0..=15.0).contains(&kind)) - || !(1.0..=32.0).contains(&colors) - || ![1.0, 2.0, 4.0, 8.0, 16.0].contains(&bpc) - || !(1.0..=1e6).contains(&columns) - { - return ps_err("rangecheck", "Predictor"); - } - Ok(Some(Self { kind: kind as u8, colors: colors as usize, bpc: bpc as usize, columns: columns as usize })) - } - - /// The data the predictor made `data` from. - fn undo(self, data: &[u8]) -> Vec { - // A row longer than the data is the data. - let row = (self.colors * self.bpc * self.columns).div_ceil(8).min(data.len().max(1)); - // Bytes per pixel (at least one) is how far back the left neighbour is. - let bpp = (self.colors * self.bpc).div_ceil(8).max(1); - if self.kind == 2 { - // Horizontal differencing (ISO 32000-1, §7.4.4.4): each component is stored as its - // difference from the same component of the pixel to its left, wrapping within its bit - // width. Each row starts again, and the padding bits at a row's end stay as they are. - let mut out = data.to_vec(); - for line in out.chunks_mut(row.max(1)) { - match self.bpc { - 8 => { - for i in bpp..line.len() { - let left = line.get(i - bpp).copied().unwrap_or(0); - if let Some(b) = line.get_mut(i) { - *b = b.wrapping_add(left); - } - } - } - // 16-bit components are big-endian and add as 16-bit numbers. - 16 => { - let byte = |line: &[u8], i: usize| line.get(i).copied().unwrap_or(0); - for i in (bpp..line.len().saturating_sub(1)).step_by(2) { - let left = u16::from_be_bytes([byte(line, i - bpp), byte(line, i - bpp + 1)]); - let sum = u16::from_be_bytes([byte(line, i), byte(line, i + 1)]).wrapping_add(left); - if let Some(w) = line.get_mut(i..i + 2) { - w.copy_from_slice(&sum.to_be_bytes()); - } - } - } - // 1, 2 or 4 bits: components are packed from each byte's high bit. - bits @ (1 | 2 | 4) => { - let mask = (1u8 << bits) - 1; - let shift = |k: usize| 8 - bits - k * bits % 8; - let sample = |line: &[u8], k: usize| line.get(k * bits / 8).map_or(0, |b| (b >> shift(k)) & mask); - let samples = (line.len() * 8 / bits).min(self.colors * self.columns); - for k in self.colors..samples { - let sum = sample(line, k).wrapping_add(sample(line, k - self.colors)) & mask; - if let Some(b) = line.get_mut(k * bits / 8) { - *b = (*b & !(mask << shift(k))) | (sum << shift(k)); - } - } - } - // `Predictor::of` accepts no other depth. - _ => {} - } - } - return out; - } - // PNG: each row starts with its filter type. - let mut out: Vec = Vec::with_capacity(data.len()); - let mut prev = vec![0u8; row]; - for chunk in data.chunks(row + 1) { - let Some((&kind, line)) = chunk.split_first() else { break }; - let mut cur = line.to_vec(); - cur.resize(row, 0); - for i in 0..row { - let a = if i >= bpp { cur.get(i - bpp).copied().unwrap_or(0) } else { 0 }; - let b = prev.get(i).copied().unwrap_or(0); - let c = if i >= bpp { prev.get(i - bpp).copied().unwrap_or(0) } else { 0 }; - let add = match kind { - 1 => a, - 2 => b, - 3 => ((u16::from(a) + u16::from(b)) / 2) as u8, - 4 => paeth(a, b, c), - _ => 0, - }; - if let Some(v) = cur.get_mut(i) { - *v = v.wrapping_add(add); - } - } - out.extend_from_slice(cur.get(..line.len().min(row)).unwrap_or_default()); - prev = cur; - } - out - } -} - -/// The PNG Paeth predictor. -fn paeth(a: u8, b: u8, c: u8) -> u8 { - let (ia, ib, ic) = (i16::from(a), i16::from(b), i16::from(c)); - let p = ia + ib - ic; - let (pa, pb, pc) = ((p - ia).abs(), (p - ib).abs(), (p - ic).abs()); - if pa <= pb && pa <= pc { - a - } else if pb <= pc { - b - } else { - c - } -} - -/// `CCITTFaxDecode`'s parameters (PLRM 3rd ed., §3.13, `CCITTFaxDecode` parameters; ISO 32000-2, -/// §7.4.6): `K` (below 0 Group 4, 0 Group 3 one-dimensional, above 0 Group 3 mixed), `Columns`, -/// `Rows`, `EndOfBlock`, `EndOfLine`, `EncodedByteAlign` and `BlackIs1`, with their defaults where -/// `d` leaves them out. `Columns` outside 1 to 65,535 and `Rows` below 0 are a `rangecheck`. -fn fax_settings(d: Option<&DictRef>) -> Res { - let get = |k: &str| d.and_then(|d| d.borrow().get(&Key::name(k)).cloned()); - let num = |k: &str, default: f64| get(k).and_then(|o| o.as_num()).unwrap_or(default); - let flag = |k: &str, default: bool| match get(k) { - Some(Obj::Bool(b)) => b, - _ => default, - }; - let (k, columns, rows) = (num("K", 0.0), num("Columns", 1728.0), num("Rows", 0.0)); - if !k.is_finite() || !(1.0..=65_535.0).contains(&columns) || !(0.0..=f64::from(u32::MAX)).contains(&rows) { - return ps_err("rangecheck", "CCITTFaxDecode"); - } - let encoding = if k < 0.0 { - EncodingMode::Group4 - } else if k == 0.0 { - EncodingMode::Group3_1D - } else { - EncodingMode::Group3_2D { k: k.min(f64::from(u32::MAX)) as u32 } - }; - Ok(DecodeSettings { - columns: columns as u32, - rows: rows as u32, - end_of_block: flag("EndOfBlock", true), - end_of_line: flag("EndOfLine", false), - rows_are_byte_aligned: flag("EncodedByteAlign", false), - encoding, - invert_black: flag("BlackIs1", false), - }) -} - -/// 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, usize, Option)> { - 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" | "LZWDecode" => { - let (data, used) = if &*f.name == "FlateDecode" { flate(raw)? } else { lzw(raw)? }; - match f.predictor { - Some(p) => (p.undo(&data), used), - None => (data, used), - } - } - "DCTDecode" => { - let (d, used, channels) = dct(raw)?; - return Ok((d, used, Some(channels))); - } - "CCITTFaxDecode" => match f.fax { - Some(settings) => fax(raw, settings)?, - None => return io("CCITTFaxDecode"), - }, - "SubFileDecode" => { - let (count, eod) = &f.sub; - if eod.is_empty() { - // A count of 0 without an end string sets no end: the data passes through whole. - let n = if *count == 0 { raw.len() } else { (*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()), - } - } - } - } - // Reads all of its source (up to the end of the filters below it) and keeps it. - "NullEncode" | "ReusableStreamDecode" => (raw.to_vec(), raw.len()), - other => return ps_err("undefined", other), - }; - Ok((data, used, None)) -} - -/// What a source of image data gives. -#[derive(Clone)] -enum Source { - File(Rc>), - Str(Vec), - Proc(Obj), -} - -impl Interp<'_> { - pub fn data_op(&mut self, op: Op) -> Res { - use Op::*; - match op { - CurrentFile => self.push(Obj::File { stream: Rc::new(RefCell::new(Stream::Current)), exec: false })?, - Filter => self.filter()?, - ReadHexString | ReadString => { - let s = self.pop_str()?; - let Obj::File { stream: 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 { stream: 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') => { - self.skip_lf(&f); - ended = true; - break; - } - Some(b) => line.push(*b), - } - } - self.fill(s, &line)?; - self.push(Obj::Bool(ended))?; - } - FlushFile => { - // An input file is read to its end: a filter over the program's data skips it - // (metadata read with `flushfile`). The program's own file goes on being run. - if let Obj::File { stream, .. } = self.pop()? { - self.materialize(&stream)?; - } - } - CloseFile => { - self.pop()?; - } - File => { - self.pop()?; - let name = self.pop()?.text().unwrap_or_else(|| Rc::from("")); - // The standard files take what programs print (and give nothing); there is no - // file system to open others in. - if !matches!(&*name, "%stdout" | "%stderr" | "%stdin" | "%lineedit" | "%statementedit") { - return ps_err("invalidfileaccess", "file"); - } - self.push(Obj::File { stream: Rc::new(RefCell::new(Stream::Buf { data: vec![], pos: 0, rgb: None })), exec: false })?; - } - Status => match self.pop()? { - Obj::File { .. } => self.push(Obj::Bool(true))?, - // A file name: there are no files. - Obj::Str(_) => self.push(Obj::Bool(false))?, - _ => return ps_err("typecheck", ""), - }, - BytesAvailable => { - let Obj::File { stream: f, .. } = self.pop()? else { return ps_err("typecheck", "") }; - self.materialize(&f)?; - let n = match &*f.borrow() { - Stream::Current => self.lex.rest().len(), - Stream::Buf { data, pos, .. } => data.len().saturating_sub(*pos), - Stream::Pending(_) => 0, - }; - self.push(Obj::Int(n as i64))?; - } - ResetFile => { - // A decoded (reusable) stream reads again from its start. - if let Obj::File { stream: f, .. } = self.pop()? - && let Stream::Buf { pos, .. } = &mut *f.borrow_mut() - { - *pos = 0; - } - } - Write | WriteString | WriteHexString => { - self.pop()?; - self.pop()?; - } - Read => { - let Obj::File { stream: f, .. } = self.pop()? else { return ps_err("typecheck", "") }; - match self.read(&f, 1)?.first() { - Some(b) => { - self.push(Obj::Int(i64::from(*b)))?; - self.push(Obj::Bool(true))?; - } - None => self.push(Obj::Bool(false))?, - } - } - FilePosition => { - let Obj::File { stream: f, .. } = self.pop()? else { return ps_err("typecheck", "") }; - let Stream::Buf { pos, .. } = &*f.borrow() else { return ps_err("ioerror", "") }; - self.push(Obj::Int(*pos as i64))?; - } - SetFilePosition => { - let at = self.pop_count()?; - let Obj::File { stream: f, .. } = self.pop()? else { return ps_err("typecheck", "") }; - let Stream::Buf { data, pos, .. } = &mut *f.borrow_mut() else { return ps_err("ioerror", "") }; - *pos = at.min(data.len()); - } - // There are no files to delete, rename or run. - DeleteFile | Run => { - self.pop_str()?; - return ps_err("undefinedfilename", ""); - } - RenameFile => { - self.pop_str()?; - self.pop_str()?; - return ps_err("undefinedfilename", ""); - } - Token => self.token()?, - Eexec => self.eexec()?, - Image | ImageMask | ColorImage => self.image(op)?, - _ => self.text_op(op)?, - } - Ok(()) - } - - /// `token`: the next object read from a string (and the rest of it) or a file. - fn token(&mut self) -> Res { - match self.pop()? { - Obj::Str(s) => { - let bytes = s.to_vec(); - let mut lex = Lexer::new(&bytes); - let Some(o) = lex.next()? else { return self.push(Obj::Bool(false)) }; - let o = self.scanned(o, lex.immediate)?; - // The whitespace after the token is read with it. - let used = bytes.len() - lex.data().len(); - self.push_interval(Obj::Str(s), used, bytes.len() - used)?; - self.push(o)?; - } - Obj::File { stream: f, .. } => { - self.materialize(&f)?; - let (o, immediate) = match &mut *f.borrow_mut() { - Stream::Current => (self.lex.next()?, self.lex.immediate), - Stream::Buf { data, pos, .. } => { - let rest = data.get(*pos..).unwrap_or_default(); - let mut lex = Lexer::new(rest); - let o = lex.next()?; - *pos += rest.len() - lex.data().len(); - (o, lex.immediate) - } - Stream::Pending(_) => (None, false), - }; - let Some(o) = o else { return self.push(Obj::Bool(false)) }; - let o = self.scanned(o, immediate)?; - self.push(o)?; - } - _ => return ps_err("typecheck", "token"), - } - self.push(Obj::Bool(true)) - } - - /// Up to `n` bytes of file `f`. - fn read(&mut self, f: &Rc>, n: usize) -> Res> { - 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![], - }) - } - - /// After `readline` consumes CR, consume only an immediately following LF. PostScript - /// treats CR/LF as one line ending (PLRM §3.8); raw reads must keep every other byte. - /// The preceding `read` has already materialized any pending filter. - fn skip_lf(&mut self, f: &Rc>) { - match &mut *f.borrow_mut() { - Stream::Current => { - if self.lex.rest().first() == Some(&b'\n') { - self.lex.advance(1); - } - } - Stream::Buf { data, pos, .. } => { - if data.get(*pos) == Some(&b'\n') { - *pos += 1; - } - } - Stream::Pending(_) => {} - } - } - - /// Up to `n` bytes read as hexadecimal digits from `f` (other characters skipped). - fn read_hex(&mut self, f: &Rc>, n: usize) -> Res> { - let mut out = Vec::with_capacity(n); - let mut high: Option = 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, got: &[u8]) -> Res { - if !s.write(0, got) { - return ps_err("rangecheck", ""); - } - self.push_interval(Obj::Str(s), 0, got.len()) - } - - /// Count `n` decoded bytes against the memory budget, and the time to decode them. - fn charge_decoded(&mut self, n: usize) -> Res { - self.alloc(n)?; - self.spend((n / 4096) as u64) - } - - /// `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![]) - }; - let (mut predictor, mut params) = (None, None); - if let Some(Obj::Dict(d)) = self.stack.last().cloned() { - self.pop()?; - predictor = Predictor::of(&d)?; - params = Some(d); - } - let fax = if &*name == "CCITTFaxDecode" { Some(fax_settings(params.as_ref())?) } else { None }; - let spec_name = name.clone(); - let spec = Filter { name, sub, predictor, fax }; - let stream = match self.pop()? { - Obj::File { stream: 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) => { - // The new filter reads a pending one's data: it has none left of its own - // (`flushfile` on it skips nothing more). - if !c.is_empty() { - *f.borrow_mut() = Stream::Buf { data: vec![], pos: 0, rgb: None }; - } - 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())?; - self.charge_decoded(out.len())?; - *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())?; - self.charge_decoded(data.len())?; - Stream::Buf { data, pos: 0, rgb } - } - p @ Obj::Array { .. } => { - let raw = self.gather(&p, MAX_DATA)?; - let (data, _, rgb) = decode(&spec, &raw)?; - self.charge_decoded(data.len())?; - Stream::Buf { data, pos: 0, rgb } - } - _ => return ps_err("typecheck", "filter"), - }; - let stream = Rc::new(RefCell::new(stream)); - // A reusable stream reads its source when it is made: the data follows the operator. - if &*spec_name == "ReusableStreamDecode" { - self.materialize(&stream)?; - } - self.push(Obj::File { stream, exec: false }) - } - - /// The data an executable file runs as a program: what is left of it. `None` for the - /// program's own file, which is being run already. - pub(super) fn program_of(&mut self, f: &Rc>) -> Res>> { - if matches!(&*f.borrow(), Stream::Current) { - return Ok(None); - } - self.read(f, MAX_DATA).map(Some) - } - - /// The decoded data left in file `f`, without reading it (a shading's or function's data). - pub(super) fn peek_all(&mut self, f: &Rc>) -> Res> { - self.materialize(f)?; - Ok(match &*f.borrow() { - Stream::Buf { data, pos, .. } => data.get(*pos..).unwrap_or_default().to_vec(), - _ => vec![], - }) - } - - /// Decode the filters a file waits to run over the program's data, from where it is now. - fn materialize(&mut self, f: &Rc>) -> Res { - let chain = match &*f.borrow() { - Stream::Pending(c) => c.clone(), - _ => return Ok(()), - }; - let mut buf: Option<(Vec, Option)> = None; - for spec in &chain { - buf = Some(match buf.take() { - None => { - let raw = self.lex.data(); - let (data, used, rgb) = decode(spec, raw)?; - self.charge_decoded(data.len())?; - self.lex.advance(used); - (data, rgb) - } - Some((prev, was)) => { - let (data, _, rgb) = decode(spec, &prev)?; - self.charge_decoded(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> { - Ok(self.read_sources(&[Source::Proc(p.clone())], n, n)?.pop().unwrap_or_default()) - } - - /// `n` bytes of image data from `src` (zeros past its end). - fn source_bytes(&mut self, src: &Source, n: usize) -> Res> { - let mut v = self.read_sources(std::slice::from_ref(src), n, n)?.pop().unwrap_or_default(); - v.resize(n, 0); - Ok(v) - } - - /// Up to `n` bytes from each of `sources`, read in turn as an image reads several data sources - /// (PLRM 3rd ed., §4.10 "Images" and `image`/`colorimage` in chapter 8: the sources are read - /// in turn, the same amount from each): each procedure called once a round (its string, - /// whatever its length), each file and string giving `step` bytes a round, until each has - /// given `n` bytes or ended. Procedures that read one file get their own rows that way. - fn read_sources(&mut self, sources: &[Source], n: usize, step: usize) -> Res>> { - let mut out: Vec> = vec![vec![]; sources.len()]; - let mut ended = vec![false; sources.len()]; - while out.iter().zip(&ended).any(|(o, e)| o.len() < n && !e) { - for ((src, o), e) in sources.iter().zip(out.iter_mut()).zip(ended.iter_mut()) { - if *e || o.len() >= n { - continue; - } - let want = step.min(n - o.len()); - let chunk = match src { - Source::File(f) => self.read(f, want)?, - // A string gives its bytes over and over. - Source::Str(s) => { - let at = o.len() % s.len().max(1); - s.get(at..).unwrap_or_default().iter().chain(s.iter().cycle()).take(want).copied().collect() - } - Source::Proc(p) => { - self.call(p.clone())?; - let s = self.pop_str()?.to_vec(); - self.alloc(s.len())?; - s - } - }; - *e = chunk.is_empty(); - o.extend_from_slice(chunk.get(..chunk.len().min(n - o.len())).unwrap_or_default()); - } - } - Ok(out) - } - - /// `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 img = match self.stack.last() { - Some(Obj::Dict(_)) if op != Op::ColorImage => { - let d = self.pop_dict()?; - let kind = d.borrow().get(&Key::name("ImageType")).and_then(Obj::as_num); - if kind == Some(3.0) && !mask { - self.masked_image(&d)? - } else { - let spec = self.image_dict(&d, mask)?; - self.decode_image(spec)? - } - } - _ => { - let spec = self.image_operands(op)?; - self.decode_image(spec)? - } - }; - let Some(img) = img else { return Ok(()) }; - self.place_image(img) - } - - /// ImageType 3: an image and the mask that says where it paints (where the mask's samples - /// read below one half through its `Decode`, as an image mask paints its 0 bits), in separate - /// sources (`InterleaveType` 3) or interleaved with the image's by row (2) or by sample (1). - fn masked_image(&mut self, d: &DictRef) -> Res> { - let get = |k: &str| d.borrow().get(&Key::name(k)).cloned(); - let (Some(Obj::Dict(data)), Some(Obj::Dict(mask))) = (get("DataDict"), get("MaskDict")) else { - return ps_err("typecheck", "ImageType 3"); - }; - let interleave = get("InterleaveType").and_then(|o| o.as_num()).unwrap_or(1.0); - let mut image = self.image_dict(&data, false)?; - let mut stencil = self.image_dict(&mask, false)?; - stencil.space = Space::Gray; - let n = image.space.n(); - if image.w.saturating_mul(image.h) > MAX_PIXELS || stencil.w.saturating_mul(stencil.h) > MAX_PIXELS { - self.out.warn(TOO_LARGE); - return Ok(None); - } - if interleave != 3.0 { - let src = image.sources.first().cloned().ok_or(PsError::Ps("undefined", "DataSource".into()))?; - let (pixels, samples) = if interleave == 2.0 { - let (mrow, drow) = (stencil.row_bytes(1), image.row_bytes(n)); - let total = mrow.saturating_mul(stencil.h).saturating_add(drow.saturating_mul(image.h)); - self.alloc(total)?; - let raw = self.source_bytes(&src, total.min(MAX_DATA))?; - by_rows(&raw, (mrow, stencil.h), (drow, image.h)) - } else { - // Each sample: the mask's value, then the colour's, as many bits each. - stencil.bpc = image.bpc; - (stencil.w, stencil.h) = (image.w, image.h); - let row = (image.w * (n + 1) * usize::from(image.bpc)).div_ceil(8); - self.alloc(row.saturating_mul(image.h))?; - let raw = self.source_bytes(&src, row.saturating_mul(image.h).min(MAX_DATA))?; - by_samples(&raw, image.w, image.h, n, image.bpc) - }; - stencil.sources = vec![Source::Str(pixels)]; - image.sources = vec![Source::Str(samples)]; - } - let Some(mut img) = self.decode_image(image)? else { return Ok(None) }; - let Some(m) = self.decode_image(stencil)? else { return Ok(Some(img)) }; - let (w, h, mw, mh) = (img.w as usize, img.h as usize, m.w as usize, m.h as usize); - for (i, px) in img.rgba.as_chunks_mut::<4>().0.iter_mut().enumerate() { - let (x, y) = (i % w, i / w); - // The mask's pixel over this one (in 64 bits: rows times rows overflow 32). - let at = (y as u64 * mh as u64 / h.max(1) as u64) * mw as u64 + x as u64 * mw as u64 / w.max(1) as u64; - if usize::try_from(at * 4).ok().and_then(|i| m.rgba.get(i)).is_none_or(|v| *v >= 128) - && let Some(a) = px.get_mut(3) - { - *a = 0; - } - } - Ok(Some(img)) - } - - /// The dictionary form of `image` and `imagemask`. - fn image_dict(&mut self, d: &DictRef, mask: bool) -> Res { - 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 = 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 sources = match (get("DataSource"), multi) { - (None, _) => vec![], - (Some(Obj::Array { items, exec: false }), true) => items.borrow().iter().map(source).collect::>>()?, - (Some(src), _) => vec![source(&src)?], - }; - let mask_color: Option> = (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 { - 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, and a CMYK image's ink - /// amounts. - fn decode_image(&mut self, s: Spec) -> Res> { - 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(TOO_LARGE); - 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 gray, RGB or CMYK samples whatever the color 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(4) => (4, 8, Space::Cmyk), - 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); - // Several sources are read in turn, a row at a time each (zeros past their ends). - let sources = if multi { &s.sources[..] } else { s.sources.get(..1).ok_or(PsError::Ps("undefined", "DataSource".into()))? }; - if sources.len() > n { - return ps_err("rangecheck", "DataSource"); - } - let plane = row.checked_mul(s.h).filter(|p| *p <= MAX_DATA).ok_or(PsError::Limit("decoded data is too large"))?; - self.alloc(plane.saturating_mul(sources.len()))?; - let mut planes = self.read_sources(sources, row * s.h, if multi { row } else { row * s.h })?; - for p in &mut planes { - p.resize(row * s.h, 0); - } - 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 = match &space { - Space::Indexed { .. } => vec![0.0, max], - _ => (0..n).flat_map(|_| [0.0, 1.0]).collect(), - }; - // The Decode array applies to a CMYK JPEG's samples as stored; gray and RGB JPEGs are read - // with the default Decode array. - let decode = if s.decode.len() >= 2 * n && jpeg.is_none_or(|c| c == 4) { 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); - // CMYK samples keep their ink amounts, as the Decode array reads them. - let mut inks = if matches!(space, Space::Cmyk) && s.stencil.is_none() { - self.alloc(s.w * s.h * 4)?; - Some(Vec::with_capacity(s.w * s.h * 4)) - } else { - None - }; - let mut cache: HashMap, [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; - } - if let Some(inks) = inks.as_mut() { - inks.extend(comps.iter().map(|v| (v.clamp(0.0, 1.0) * 255.0).round() as u8)); - } - 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, inks, 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(()); - } - if self.g.null { - return Ok(()); - } - // CMYK samples keep their ink amounts as a CMYK TIFF, as the PDF importer keeps an 8- or - // 16-bit CMYK image's samples, and transparent pixels (`MaskColor`, an ImageType 3 mask) - // become its opacity mask. - if let Some(blob) = img.inks.and_then(|i| Inks::new(img.w, img.h, i)).and_then(|i| ImageBlob::cmyk_tiff(&i)) { - let alpha: Vec = img.rgba.iter().skip(3).step_by(4).copied().collect(); - let mask = self.alpha_mask(&alpha, img.w, img.h, xf); - return self.add_image(blob, img.w, img.h, xf, mask); - } - 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(()); - } - self.add_image(ImageBlob::new("image/png", png), img.w, img.h, xf, None) - } - - /// Add image `blob` (`w` × `h` pixels, mapped into user space by `xf`) with `mask` as its - /// opacity mask. - fn add_image(&mut self, blob: ImageBlob, w: u32, h: u32, xf: Affine, mask: Option) -> Res { - let key = blob.content_key(); - self.out.doc.images.entry(key.clone()).or_insert(blob); - let im = ImageObject { key, width: w, height: h, xf, link: None, placement: Default::default(), pixels: None }; - let clips = self.g.clips.clone(); - self.out.push(Node::new(NodeId(0), NodeKind::Image(Box::new(im))).with_mask(mask.map(Box::new)), &clips); - Ok(()) - } - - /// An image's alpha (`w` × `h` bytes) as a grayscale image over the image (`xf`): its opacity - /// mask, as in PDF import. `None` when it hides nothing. - fn alpha_mask(&mut self, alpha: &[u8], w: u32, h: u32, xf: Affine) -> Option { - if alpha.iter().all(|v| *v == 255) { - return None; - } - let mut png = Vec::new(); - image::GrayImage::from_raw(w, h, alpha.to_vec())?.write_to(&mut std::io::Cursor::new(&mut png), image::ImageFormat::Png).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: w, height: h, xf, link: None, placement: Default::default(), pixels: None }; - Some(OpacityMask::new(Node::new(self.out.doc.alloc_id(), NodeKind::Image(Box::new(im))), true)) - } -} - -/// An image to decode. -struct Spec { - w: usize, - h: usize, - bpc: u8, - space: Space, - decode: Vec, - /// User space → image space. - matrix: Affine, - /// One source, or one per component. - sources: Vec, - /// `MaskColor` (ImageType 4): samples that are transparent. - mask_color: Option>, - /// An image mask: whether its 1 bits paint. - stencil: Option, -} - -impl Spec { - /// Bytes a row of `n` components takes. - fn row_bytes(&self, n: usize) -> usize { - (self.w * n * usize::from(self.bpc)).div_ceil(8) - } -} - -/// Data of an ImageType 3 interleaved by row, split into the mask's and the image's: the one -/// with fewer rows gives one row before each run of the other's (`(row bytes, rows)` each). -fn by_rows(raw: &[u8], (mrow, mh): (usize, usize), (drow, dh): (usize, usize)) -> (Vec, Vec) { - let room = |row: usize, rows: usize| row.saturating_mul(rows).min(raw.len()); - let (mut mask, mut data) = (Vec::with_capacity(room(mrow, mh)), Vec::with_capacity(room(drow, dh))); - let mut at = 0; - let mut take = |into: &mut Vec, n: usize| { - into.extend_from_slice(raw.get(at..at + n).unwrap_or_default()); - at += n; - }; - let (blocks, masks, rows) = if mh >= dh { (dh, mh / dh.max(1), 1) } else { (mh, 1, dh / mh.max(1)) }; - for _ in 0..blocks { - for _ in 0..masks { - take(&mut mask, mrow); - } - for _ in 0..rows { - take(&mut data, drow); - } - } - (mask, data) -} - -/// Data of an ImageType 3 interleaved by sample (the mask's value first, then `n` colour -/// components, `bpc` bits each, rows from whole bytes), split into the mask's and the image's. -fn by_samples(raw: &[u8], w: usize, h: usize, n: usize, bpc: u8) -> (Vec, Vec) { - let bpc = usize::from(bpc); - let bit = |at: usize| raw.get(at / 8).is_some_and(|b| b & (0x80 >> (at % 8)) != 0); - let (mrow, drow, row) = ((w * bpc).div_ceil(8), (w * n * bpc).div_ceil(8), (w * (n + 1) * bpc).div_ceil(8)); - let (mut mask, mut data) = (vec![0u8; mrow * h], vec![0u8; drow * h]); - let put = |into: &mut [u8], at: usize| { - if let Some(b) = into.get_mut(at / 8) { - *b |= 0x80 >> (at % 8); - } - }; - for y in 0..h { - for x in 0..w { - let from = y * row * 8 + x * (n + 1) * bpc; - for k in 0..bpc { - if bit(from + k) { - put(&mut mask, y * mrow * 8 + x * bpc + k); - } - } - for k in 0..n * bpc { - if bit(from + bpc + k) { - put(&mut data, y * drow * 8 + x * n * bpc + k); - } - } - } - } - (mask, data) -} - -/// Decoded pixels. -struct Pixels { - w: u32, - h: u32, - rgba: Vec, - /// A CMYK image's ink amounts: C, M, Y and K bytes a pixel, 0 is no ink. - inks: Option>, - matrix: Affine, -} - -fn source(o: &Obj) -> Res { - Ok(match o { - Obj::File { stream: 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)) -} +pub(crate) use filters::Stream; diff --git a/crates/eps/src/import/data/filters.rs b/crates/eps/src/import/data/filters.rs new file mode 100644 index 000000000..088dd1b3a --- /dev/null +++ b/crates/eps/src/import/data/filters.rs @@ -0,0 +1,477 @@ +use super::ops::MAX_DATA; +use super::*; + +/// 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), + Buf { + data: Vec, + pos: usize, + /// The samples are 8-bit gray, RGB or CMYK (1, 3 or 4 channels) whatever the image's + /// color space: a decoded JPEG. + rgb: Option, + }, +} + +pub(super) fn io(at: &str) -> Res { + ps_err("ioerror", at) +} + +/// The bytes `RunLengthDecode` makes of `raw`, and how many it read. +fn run_length(raw: &[u8]) -> Res<(Vec, 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, 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, usize)> { + let mut table: Vec> = (0..=255u8).map(|b| vec![b]).collect(); + table.push(vec![]); + table.push(vec![]); + let (mut out, mut prev): (Vec, Option>) = (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 (gray, RGB or CMYK, 8 bits) with its channel count, and how many bytes of +/// `raw` it took. A CMYK JPEG gives its samples as stored, for the image's Decode array to read +/// (Adobe's CMYK JPEGs store them inverted, which a Decode array of `[1 0 1 0 1 0 1 0]` undoes). +fn dct(raw: &[u8]) -> Res<(Vec, usize, u8)> { + let end = find(raw, &[0xFF, 0xD9]).map_or(raw.len(), |e| e + 2); + let jpeg = raw.get(..end).unwrap_or_default(); + // `cmyk` gives the ink amounts of inverted samples: inverted back, they are the samples. + if jpeg_components(jpeg) == Some(4) + && let Some(inks) = ImageBlob::new("image/jpeg", jpeg.to_vec()).cmyk() + { + return Ok((inks.data.into_iter().map(|v| 255 - v).collect(), end, 4)); + } + let img = image::load_from_memory_with_format(jpeg, 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)) } +} + +/// CCITT fax data (ITU-T T.4 Group 3, one- or two-dimensional, and T.6 Group 4) decoded to rows +/// of 1-bit samples, each padded to a whole byte, and how many bytes of `raw` it took. With +/// `EndOfBlock` the rows end at the end-of-block code (EOFB, or RTC in Group 3) regardless of +/// `Rows`; without it they end after `Rows` rows, or at the end of the data when `Rows` is 0. +/// Damaged data ends the rows: those decoded before it are kept, and damage in the first row is an +/// `ioerror`. Rows that would take more than `MAX_DATA` bytes are refused. +fn fax(raw: &[u8], settings: DecodeSettings) -> Res<(Vec, usize)> { + let row = (settings.columns as usize).div_ceil(8).max(1); + let most = u32::try_from(MAX_DATA / row).unwrap_or(u32::MAX); + if !settings.end_of_block && settings.rows > most { + return Err(PsError::Limit("decoded data is too large")); + } + // Without a row count that ends the data, a row past `most` shows that the data is too large. + let rows = if settings.end_of_block || settings.rows == 0 { most.saturating_add(1) } else { settings.rows }; + // The decoder reads the RTC of Group 3 two-dimensional data as rows: it gets the data before it. + let rtc = match settings.encoding { + EncodingMode::Group3_2D { .. } if settings.end_of_block => rtc(raw), + _ => None, + }; + let data = rtc.and_then(|(cut, _)| raw.get(..cut)).unwrap_or(raw); + let mut out = FaxRows::default(); + let read = hayro_ccitt::decode(data, &mut out, &mut DecoderContext::new(DecodeSettings { rows, ..settings })); + if out.rows > most { + return Err(PsError::Limit("decoded data is too large")); + } + let used = match read { + Ok(used) => used, + Err(_) if out.rows > 0 => { + out.data.truncate(out.rows as usize * row); + // The whole rows decoded again, for how many bytes they took. + let used = hayro_ccitt::decode(data, &mut Dropped, &mut DecoderContext::new(DecodeSettings { rows: out.rows, ..settings })); + used.unwrap_or(data.len()) + } + Err(_) => return io("CCITTFaxDecode"), + }; + Ok((out.data, rtc.map_or(used, |(_, end)| end))) +} + +/// The RTC that ends Group 3 two-dimensional data (six EOLs, each followed by a 1 tag bit, ITU-T +/// T.4 §4.2.4): the byte that holds the 1 bit of its first EOL, and the byte after its last tag +/// bit. The first EOL with a 1 tag bit that another EOL follows starts it. +fn rtc(raw: &[u8]) -> Option<(usize, usize)> { + let bits = raw.len().saturating_mul(8); + let bit = |i: usize| raw.get(i / 8).is_some_and(|b| b & (0x80 >> (i % 8)) != 0); + let zeros = |i: usize| (i..bits).take_while(|j| !bit(*j)).count(); + // An EOL (its fill bits first) and a 1 tag bit from `i`: the EOL's 1 bit and where they end. + let eol = |i: usize| { + let one = i + zeros(i); + (one - i >= 11 && bit(one) && bit(one + 1)).then_some((one, one + 2)) + }; + let mut i = 0; + while i < bits { + if let Some((one, next)) = eol(i) + && let Some((_, mut end)) = eol(next) + { + for _ in 2..6 { + match eol(end) { + Some((_, e)) => end = e, + None => break, + } + } + return Some((one / 8, end.div_ceil(8))); + } + i += zeros(i) + 1; + } + None +} + +/// Rows of 1-bit samples from a CCITT decoder, each padded to a whole byte: 1 is white, or black +/// with `BlackIs1`. +#[derive(Default)] +struct FaxRows { + data: Vec, + /// Samples in the row being decoded. + x: u32, + /// Whole rows decoded. + rows: u32, +} + +impl Decoder for FaxRows { + fn push_pixel(&mut self, white: bool) { + if self.x.is_multiple_of(8) { + self.data.push(0); + } + if white && let Some(b) = self.data.last_mut() { + *b |= 0x80 >> (self.x % 8); + } + self.x = self.x.saturating_add(1); + } + + fn push_pixel_chunk(&mut self, white: bool, chunk_count: u32) { + self.data.extend(std::iter::repeat_n(if white { 0xff } else { 0 }, chunk_count as usize)); + self.x = self.x.saturating_add(chunk_count.saturating_mul(8)); + } + + fn next_line(&mut self) { + self.x = 0; + self.rows = self.rows.saturating_add(1); + } +} + +/// A CCITT decoder that drops what it decodes. +struct Dropped; + +impl Decoder for Dropped { + fn push_pixel(&mut self, _: bool) {} + + fn push_pixel_chunk(&mut self, _: bool, _: u32) {} + + fn next_line(&mut self) {} +} + +/// A decoding filter and its parameters. +#[derive(Clone, Debug)] +pub(crate) struct Filter { + pub(super) name: Rc, + /// `SubFileDecode`'s count and end-of-data string. + pub(super) sub: (usize, Vec), + /// `FlateDecode`'s and `LZWDecode`'s `Predictor`: what it undoes. + pub(super) predictor: Option, + /// `CCITTFaxDecode`'s parameters. + pub(super) fax: Option, +} + +/// A `Predictor` (2: TIFF, 10–15: PNG) with its `Colors`, `BitsPerComponent` and `Columns`. +#[derive(Clone, Copy, Debug)] +pub(super) struct Predictor { + kind: u8, + colors: usize, + bpc: usize, + columns: usize, +} + +impl Predictor { + /// The predictor of a filter's parameters, if it has one. + pub(super) fn of(d: &DictRef) -> Res> { + let num = |k: &str, default: f64| d.borrow().get(&Key::name(k)).and_then(Obj::as_num).unwrap_or(default); + let kind = num("Predictor", 1.0); + if kind <= 1.0 { + return Ok(None); + } + let (colors, bpc, columns) = (num("Colors", 1.0), num("BitsPerComponent", 8.0), num("Columns", 1.0)); + if !(kind == 2.0 || (10.0..=15.0).contains(&kind)) + || !(1.0..=32.0).contains(&colors) + || ![1.0, 2.0, 4.0, 8.0, 16.0].contains(&bpc) + || !(1.0..=1e6).contains(&columns) + { + return ps_err("rangecheck", "Predictor"); + } + Ok(Some(Self { kind: kind as u8, colors: colors as usize, bpc: bpc as usize, columns: columns as usize })) + } + + /// The data the predictor made `data` from. + fn undo(self, data: &[u8]) -> Vec { + // A row longer than the data is the data. + let row = (self.colors * self.bpc * self.columns).div_ceil(8).min(data.len().max(1)); + // Bytes per pixel (at least one) is how far back the left neighbour is. + let bpp = (self.colors * self.bpc).div_ceil(8).max(1); + if self.kind == 2 { + // Horizontal differencing (ISO 32000-1, §7.4.4.4): each component is stored as its + // difference from the same component of the pixel to its left, wrapping within its bit + // width. Each row starts again, and the padding bits at a row's end stay as they are. + let mut out = data.to_vec(); + for line in out.chunks_mut(row.max(1)) { + match self.bpc { + 8 => { + for i in bpp..line.len() { + let left = line.get(i - bpp).copied().unwrap_or(0); + if let Some(b) = line.get_mut(i) { + *b = b.wrapping_add(left); + } + } + } + // 16-bit components are big-endian and add as 16-bit numbers. + 16 => { + let byte = |line: &[u8], i: usize| line.get(i).copied().unwrap_or(0); + for i in (bpp..line.len().saturating_sub(1)).step_by(2) { + let left = u16::from_be_bytes([byte(line, i - bpp), byte(line, i - bpp + 1)]); + let sum = u16::from_be_bytes([byte(line, i), byte(line, i + 1)]).wrapping_add(left); + if let Some(w) = line.get_mut(i..i + 2) { + w.copy_from_slice(&sum.to_be_bytes()); + } + } + } + // 1, 2 or 4 bits: components are packed from each byte's high bit. + bits @ (1 | 2 | 4) => { + let mask = (1u8 << bits) - 1; + let shift = |k: usize| 8 - bits - k * bits % 8; + let sample = |line: &[u8], k: usize| line.get(k * bits / 8).map_or(0, |b| (b >> shift(k)) & mask); + let samples = (line.len() * 8 / bits).min(self.colors * self.columns); + for k in self.colors..samples { + let sum = sample(line, k).wrapping_add(sample(line, k - self.colors)) & mask; + if let Some(b) = line.get_mut(k * bits / 8) { + *b = (*b & !(mask << shift(k))) | (sum << shift(k)); + } + } + } + // `Predictor::of` accepts no other depth. + _ => {} + } + } + return out; + } + // PNG: each row starts with its filter type. + let mut out: Vec = Vec::with_capacity(data.len()); + let mut prev = vec![0u8; row]; + for chunk in data.chunks(row + 1) { + let Some((&kind, line)) = chunk.split_first() else { break }; + let mut cur = line.to_vec(); + cur.resize(row, 0); + for i in 0..row { + let a = if i >= bpp { cur.get(i - bpp).copied().unwrap_or(0) } else { 0 }; + let b = prev.get(i).copied().unwrap_or(0); + let c = if i >= bpp { prev.get(i - bpp).copied().unwrap_or(0) } else { 0 }; + let add = match kind { + 1 => a, + 2 => b, + 3 => ((u16::from(a) + u16::from(b)) / 2) as u8, + 4 => paeth(a, b, c), + _ => 0, + }; + if let Some(v) = cur.get_mut(i) { + *v = v.wrapping_add(add); + } + } + out.extend_from_slice(cur.get(..line.len().min(row)).unwrap_or_default()); + prev = cur; + } + out + } +} + +/// The PNG Paeth predictor. +fn paeth(a: u8, b: u8, c: u8) -> u8 { + let (ia, ib, ic) = (i16::from(a), i16::from(b), i16::from(c)); + let p = ia + ib - ic; + let (pa, pb, pc) = ((p - ia).abs(), (p - ib).abs(), (p - ic).abs()); + if pa <= pb && pa <= pc { + a + } else if pb <= pc { + b + } else { + c + } +} + +/// `CCITTFaxDecode`'s parameters (PLRM 3rd ed., §3.13, `CCITTFaxDecode` parameters; ISO 32000-2, +/// §7.4.6): `K` (below 0 Group 4, 0 Group 3 one-dimensional, above 0 Group 3 mixed), `Columns`, +/// `Rows`, `EndOfBlock`, `EndOfLine`, `EncodedByteAlign` and `BlackIs1`, with their defaults where +/// `d` leaves them out. `Columns` outside 1 to 65,535 and `Rows` below 0 are a `rangecheck`. +pub(super) fn fax_settings(d: Option<&DictRef>) -> Res { + let get = |k: &str| d.and_then(|d| d.borrow().get(&Key::name(k)).cloned()); + let num = |k: &str, default: f64| get(k).and_then(|o| o.as_num()).unwrap_or(default); + let flag = |k: &str, default: bool| match get(k) { + Some(Obj::Bool(b)) => b, + _ => default, + }; + let (k, columns, rows) = (num("K", 0.0), num("Columns", 1728.0), num("Rows", 0.0)); + if !k.is_finite() || !(1.0..=65_535.0).contains(&columns) || !(0.0..=f64::from(u32::MAX)).contains(&rows) { + return ps_err("rangecheck", "CCITTFaxDecode"); + } + let encoding = if k < 0.0 { + EncodingMode::Group4 + } else if k == 0.0 { + EncodingMode::Group3_1D + } else { + EncodingMode::Group3_2D { k: k.min(f64::from(u32::MAX)) as u32 } + }; + Ok(DecodeSettings { + columns: columns as u32, + rows: rows as u32, + end_of_block: flag("EndOfBlock", true), + end_of_line: flag("EndOfLine", false), + rows_are_byte_aligned: flag("EncodedByteAlign", false), + encoding, + invert_black: flag("BlackIs1", false), + }) +} + +/// The bytes `f` decodes from `raw`, how many it read, and the channels of a decoded JPEG. +pub(super) fn decode(f: &Filter, raw: &[u8]) -> Res<(Vec, usize, Option)> { + 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" | "LZWDecode" => { + let (data, used) = if &*f.name == "FlateDecode" { flate(raw)? } else { lzw(raw)? }; + match f.predictor { + Some(p) => (p.undo(&data), used), + None => (data, used), + } + } + "DCTDecode" => { + let (d, used, channels) = dct(raw)?; + return Ok((d, used, Some(channels))); + } + "CCITTFaxDecode" => match f.fax { + Some(settings) => fax(raw, settings)?, + None => return io("CCITTFaxDecode"), + }, + "SubFileDecode" => { + let (count, eod) = &f.sub; + if eod.is_empty() { + // A count of 0 without an end string sets no end: the data passes through whole. + let n = if *count == 0 { raw.len() } else { (*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()), + } + } + } + } + // Reads all of its source (up to the end of the filters below it) and keeps it. + "NullEncode" | "ReusableStreamDecode" => (raw.to_vec(), raw.len()), + other => return ps_err("undefined", other), + }; + Ok((data, used, None)) +} diff --git a/crates/eps/src/import/data/ops.rs b/crates/eps/src/import/data/ops.rs new file mode 100644 index 000000000..63db3611f --- /dev/null +++ b/crates/eps/src/import/data/ops.rs @@ -0,0 +1,761 @@ +use super::filters::{Filter, Predictor, Stream, decode, fax_settings, io}; +use super::pixels::{Pixels, Spec, by_rows, by_samples, masked, source}; +use super::*; + +/// Most bytes one filter or image reads. +pub(super) const MAX_DATA: usize = 1 << 28; + +/// Most pixels an image may have. +const MAX_PIXELS: usize = 1 << 26; + +/// Why an image is missing. +const TOO_LARGE: &str = "images too large to read were left out"; + +/// Most distinct colours an image converts through a tint transform. +const MAX_TINTS: usize = 1 << 16; + +/// What a source of image data gives. +#[derive(Clone)] +pub(super) enum Source { + File(Rc>), + Str(Vec), + Proc(Obj), +} + +impl Interp<'_> { + pub fn data_op(&mut self, op: Op) -> Res { + use Op::*; + match op { + CurrentFile => self.push(Obj::File { stream: Rc::new(RefCell::new(Stream::Current)), exec: false })?, + Filter => self.filter()?, + ReadHexString | ReadString => { + let s = self.pop_str()?; + let Obj::File { stream: 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 { stream: 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') => { + self.skip_lf(&f); + ended = true; + break; + } + Some(b) => line.push(*b), + } + } + self.fill(s, &line)?; + self.push(Obj::Bool(ended))?; + } + FlushFile => { + // An input file is read to its end: a filter over the program's data skips it + // (metadata read with `flushfile`). The program's own file goes on being run. + if let Obj::File { stream, .. } = self.pop()? { + self.materialize(&stream)?; + } + } + CloseFile => { + self.pop()?; + } + File => { + self.pop()?; + let name = self.pop()?.text().unwrap_or_else(|| Rc::from("")); + // The standard files take what programs print (and give nothing); there is no + // file system to open others in. + if !matches!(&*name, "%stdout" | "%stderr" | "%stdin" | "%lineedit" | "%statementedit") { + return ps_err("invalidfileaccess", "file"); + } + self.push(Obj::File { stream: Rc::new(RefCell::new(Stream::Buf { data: vec![], pos: 0, rgb: None })), exec: false })?; + } + Status => match self.pop()? { + Obj::File { .. } => self.push(Obj::Bool(true))?, + // A file name: there are no files. + Obj::Str(_) => self.push(Obj::Bool(false))?, + _ => return ps_err("typecheck", ""), + }, + BytesAvailable => { + let Obj::File { stream: f, .. } = self.pop()? else { return ps_err("typecheck", "") }; + self.materialize(&f)?; + let n = match &*f.borrow() { + Stream::Current => self.lex.rest().len(), + Stream::Buf { data, pos, .. } => data.len().saturating_sub(*pos), + Stream::Pending(_) => 0, + }; + self.push(Obj::Int(n as i64))?; + } + ResetFile => { + // A decoded (reusable) stream reads again from its start. + if let Obj::File { stream: f, .. } = self.pop()? + && let Stream::Buf { pos, .. } = &mut *f.borrow_mut() + { + *pos = 0; + } + } + Write | WriteString | WriteHexString => { + self.pop()?; + self.pop()?; + } + Read => { + let Obj::File { stream: f, .. } = self.pop()? else { return ps_err("typecheck", "") }; + match self.read(&f, 1)?.first() { + Some(b) => { + self.push(Obj::Int(i64::from(*b)))?; + self.push(Obj::Bool(true))?; + } + None => self.push(Obj::Bool(false))?, + } + } + FilePosition => { + let Obj::File { stream: f, .. } = self.pop()? else { return ps_err("typecheck", "") }; + let Stream::Buf { pos, .. } = &*f.borrow() else { return ps_err("ioerror", "") }; + self.push(Obj::Int(*pos as i64))?; + } + SetFilePosition => { + let at = self.pop_count()?; + let Obj::File { stream: f, .. } = self.pop()? else { return ps_err("typecheck", "") }; + let Stream::Buf { data, pos, .. } = &mut *f.borrow_mut() else { return ps_err("ioerror", "") }; + *pos = at.min(data.len()); + } + // There are no files to delete, rename or run. + DeleteFile | Run => { + self.pop_str()?; + return ps_err("undefinedfilename", ""); + } + RenameFile => { + self.pop_str()?; + self.pop_str()?; + return ps_err("undefinedfilename", ""); + } + Token => self.token()?, + Eexec => self.eexec()?, + Image | ImageMask | ColorImage => self.image(op)?, + _ => self.text_op(op)?, + } + Ok(()) + } + + /// `token`: the next object read from a string (and the rest of it) or a file. + fn token(&mut self) -> Res { + match self.pop()? { + Obj::Str(s) => { + let bytes = s.to_vec(); + let mut lex = Lexer::new(&bytes); + let Some(o) = lex.next()? else { return self.push(Obj::Bool(false)) }; + let o = self.scanned(o, lex.immediate)?; + // The whitespace after the token is read with it. + let used = bytes.len() - lex.data().len(); + self.push_interval(Obj::Str(s), used, bytes.len() - used)?; + self.push(o)?; + } + Obj::File { stream: f, .. } => { + self.materialize(&f)?; + let (o, immediate) = match &mut *f.borrow_mut() { + Stream::Current => (self.lex.next()?, self.lex.immediate), + Stream::Buf { data, pos, .. } => { + let rest = data.get(*pos..).unwrap_or_default(); + let mut lex = Lexer::new(rest); + let o = lex.next()?; + *pos += rest.len() - lex.data().len(); + (o, lex.immediate) + } + Stream::Pending(_) => (None, false), + }; + let Some(o) = o else { return self.push(Obj::Bool(false)) }; + let o = self.scanned(o, immediate)?; + self.push(o)?; + } + _ => return ps_err("typecheck", "token"), + } + self.push(Obj::Bool(true)) + } + + /// Up to `n` bytes of file `f`. + fn read(&mut self, f: &Rc>, n: usize) -> Res> { + 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![], + }) + } + + /// After `readline` consumes CR, consume only an immediately following LF. PostScript + /// treats CR/LF as one line ending (PLRM §3.8); raw reads must keep every other byte. + /// The preceding `read` has already materialized any pending filter. + fn skip_lf(&mut self, f: &Rc>) { + match &mut *f.borrow_mut() { + Stream::Current => { + if self.lex.rest().first() == Some(&b'\n') { + self.lex.advance(1); + } + } + Stream::Buf { data, pos, .. } => { + if data.get(*pos) == Some(&b'\n') { + *pos += 1; + } + } + Stream::Pending(_) => {} + } + } + + /// Up to `n` bytes read as hexadecimal digits from `f` (other characters skipped). + fn read_hex(&mut self, f: &Rc>, n: usize) -> Res> { + let mut out = Vec::with_capacity(n); + let mut high: Option = 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, got: &[u8]) -> Res { + if !s.write(0, got) { + return ps_err("rangecheck", ""); + } + self.push_interval(Obj::Str(s), 0, got.len()) + } + + /// Count `n` decoded bytes against the memory budget, and the time to decode them. + fn charge_decoded(&mut self, n: usize) -> Res { + self.alloc(n)?; + self.spend((n / 4096) as u64) + } + + /// `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![]) + }; + let (mut predictor, mut params) = (None, None); + if let Some(Obj::Dict(d)) = self.stack.last().cloned() { + self.pop()?; + predictor = Predictor::of(&d)?; + params = Some(d); + } + let fax = if &*name == "CCITTFaxDecode" { Some(fax_settings(params.as_ref())?) } else { None }; + let spec_name = name.clone(); + let spec = Filter { name, sub, predictor, fax }; + let stream = match self.pop()? { + Obj::File { stream: 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) => { + // The new filter reads a pending one's data: it has none left of its own + // (`flushfile` on it skips nothing more). + if !c.is_empty() { + *f.borrow_mut() = Stream::Buf { data: vec![], pos: 0, rgb: None }; + } + 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())?; + self.charge_decoded(out.len())?; + *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())?; + self.charge_decoded(data.len())?; + Stream::Buf { data, pos: 0, rgb } + } + p @ Obj::Array { .. } => { + let raw = self.gather(&p, MAX_DATA)?; + let (data, _, rgb) = decode(&spec, &raw)?; + self.charge_decoded(data.len())?; + Stream::Buf { data, pos: 0, rgb } + } + _ => return ps_err("typecheck", "filter"), + }; + let stream = Rc::new(RefCell::new(stream)); + // A reusable stream reads its source when it is made: the data follows the operator. + if &*spec_name == "ReusableStreamDecode" { + self.materialize(&stream)?; + } + self.push(Obj::File { stream, exec: false }) + } + + /// The data an executable file runs as a program: what is left of it. `None` for the + /// program's own file, which is being run already. + pub(in super::super) fn program_of(&mut self, f: &Rc>) -> Res>> { + if matches!(&*f.borrow(), Stream::Current) { + return Ok(None); + } + self.read(f, MAX_DATA).map(Some) + } + + /// The decoded data left in file `f`, without reading it (a shading's or function's data). + pub(in super::super) fn peek_all(&mut self, f: &Rc>) -> Res> { + self.materialize(f)?; + Ok(match &*f.borrow() { + Stream::Buf { data, pos, .. } => data.get(*pos..).unwrap_or_default().to_vec(), + _ => vec![], + }) + } + + /// Decode the filters a file waits to run over the program's data, from where it is now. + fn materialize(&mut self, f: &Rc>) -> Res { + let chain = match &*f.borrow() { + Stream::Pending(c) => c.clone(), + _ => return Ok(()), + }; + let mut buf: Option<(Vec, Option)> = None; + for spec in &chain { + buf = Some(match buf.take() { + None => { + let raw = self.lex.data(); + let (data, used, rgb) = decode(spec, raw)?; + self.charge_decoded(data.len())?; + self.lex.advance(used); + (data, rgb) + } + Some((prev, was)) => { + let (data, _, rgb) = decode(spec, &prev)?; + self.charge_decoded(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> { + Ok(self.read_sources(&[Source::Proc(p.clone())], n, n)?.pop().unwrap_or_default()) + } + + /// `n` bytes of image data from `src` (zeros past its end). + fn source_bytes(&mut self, src: &Source, n: usize) -> Res> { + let mut v = self.read_sources(std::slice::from_ref(src), n, n)?.pop().unwrap_or_default(); + v.resize(n, 0); + Ok(v) + } + + /// Up to `n` bytes from each of `sources`, read in turn as an image reads several data sources + /// (PLRM 3rd ed., §4.10 "Images" and `image`/`colorimage` in chapter 8: the sources are read + /// in turn, the same amount from each): each procedure called once a round (its string, + /// whatever its length), each file and string giving `step` bytes a round, until each has + /// given `n` bytes or ended. Procedures that read one file get their own rows that way. + fn read_sources(&mut self, sources: &[Source], n: usize, step: usize) -> Res>> { + let mut out: Vec> = vec![vec![]; sources.len()]; + let mut ended = vec![false; sources.len()]; + while out.iter().zip(&ended).any(|(o, e)| o.len() < n && !e) { + for ((src, o), e) in sources.iter().zip(out.iter_mut()).zip(ended.iter_mut()) { + if *e || o.len() >= n { + continue; + } + let want = step.min(n - o.len()); + let chunk = match src { + Source::File(f) => self.read(f, want)?, + // A string gives its bytes over and over. + Source::Str(s) => { + let at = o.len() % s.len().max(1); + s.get(at..).unwrap_or_default().iter().chain(s.iter().cycle()).take(want).copied().collect() + } + Source::Proc(p) => { + self.call(p.clone())?; + let s = self.pop_str()?.to_vec(); + self.alloc(s.len())?; + s + } + }; + *e = chunk.is_empty(); + o.extend_from_slice(chunk.get(..chunk.len().min(n - o.len())).unwrap_or_default()); + } + } + Ok(out) + } + + /// `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 img = match self.stack.last() { + Some(Obj::Dict(_)) if op != Op::ColorImage => { + let d = self.pop_dict()?; + let kind = d.borrow().get(&Key::name("ImageType")).and_then(Obj::as_num); + if kind == Some(3.0) && !mask { + self.masked_image(&d)? + } else { + let spec = self.image_dict(&d, mask)?; + self.decode_image(spec)? + } + } + _ => { + let spec = self.image_operands(op)?; + self.decode_image(spec)? + } + }; + let Some(img) = img else { return Ok(()) }; + self.place_image(img) + } + + /// ImageType 3: an image and the mask that says where it paints (where the mask's samples + /// read below one half through its `Decode`, as an image mask paints its 0 bits), in separate + /// sources (`InterleaveType` 3) or interleaved with the image's by row (2) or by sample (1). + fn masked_image(&mut self, d: &DictRef) -> Res> { + let get = |k: &str| d.borrow().get(&Key::name(k)).cloned(); + let (Some(Obj::Dict(data)), Some(Obj::Dict(mask))) = (get("DataDict"), get("MaskDict")) else { + return ps_err("typecheck", "ImageType 3"); + }; + let interleave = get("InterleaveType").and_then(|o| o.as_num()).unwrap_or(1.0); + let mut image = self.image_dict(&data, false)?; + let mut stencil = self.image_dict(&mask, false)?; + stencil.space = Space::Gray; + let n = image.space.n(); + if image.w.saturating_mul(image.h) > MAX_PIXELS || stencil.w.saturating_mul(stencil.h) > MAX_PIXELS { + self.out.warn(TOO_LARGE); + return Ok(None); + } + if interleave != 3.0 { + let src = image.sources.first().cloned().ok_or(PsError::Ps("undefined", "DataSource".into()))?; + let (pixels, samples) = if interleave == 2.0 { + let (mrow, drow) = (stencil.row_bytes(1), image.row_bytes(n)); + let total = mrow.saturating_mul(stencil.h).saturating_add(drow.saturating_mul(image.h)); + self.alloc(total)?; + let raw = self.source_bytes(&src, total.min(MAX_DATA))?; + by_rows(&raw, (mrow, stencil.h), (drow, image.h)) + } else { + // Each sample: the mask's value, then the colour's, as many bits each. + stencil.bpc = image.bpc; + (stencil.w, stencil.h) = (image.w, image.h); + let row = (image.w * (n + 1) * usize::from(image.bpc)).div_ceil(8); + self.alloc(row.saturating_mul(image.h))?; + let raw = self.source_bytes(&src, row.saturating_mul(image.h).min(MAX_DATA))?; + by_samples(&raw, image.w, image.h, n, image.bpc) + }; + stencil.sources = vec![Source::Str(pixels)]; + image.sources = vec![Source::Str(samples)]; + } + let Some(mut img) = self.decode_image(image)? else { return Ok(None) }; + let Some(m) = self.decode_image(stencil)? else { return Ok(Some(img)) }; + let (w, h, mw, mh) = (img.w as usize, img.h as usize, m.w as usize, m.h as usize); + for (i, px) in img.rgba.as_chunks_mut::<4>().0.iter_mut().enumerate() { + let (x, y) = (i % w, i / w); + // The mask's pixel over this one (in 64 bits: rows times rows overflow 32). + let at = (y as u64 * mh as u64 / h.max(1) as u64) * mw as u64 + x as u64 * mw as u64 / w.max(1) as u64; + if usize::try_from(at * 4).ok().and_then(|i| m.rgba.get(i)).is_none_or(|v| *v >= 128) + && let Some(a) = px.get_mut(3) + { + *a = 0; + } + } + Ok(Some(img)) + } + + /// The dictionary form of `image` and `imagemask`. + fn image_dict(&mut self, d: &DictRef, mask: bool) -> Res { + 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 = 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 sources = match (get("DataSource"), multi) { + (None, _) => vec![], + (Some(Obj::Array { items, exec: false }), true) => items.borrow().iter().map(source).collect::>>()?, + (Some(src), _) => vec![source(&src)?], + }; + let mask_color: Option> = (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 { + 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, and a CMYK image's ink + /// amounts. + fn decode_image(&mut self, s: Spec) -> Res> { + 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(TOO_LARGE); + 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 gray, RGB or CMYK samples whatever the color 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(4) => (4, 8, Space::Cmyk), + 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); + // Several sources are read in turn, a row at a time each (zeros past their ends). + let sources = if multi { &s.sources[..] } else { s.sources.get(..1).ok_or(PsError::Ps("undefined", "DataSource".into()))? }; + if sources.len() > n { + return ps_err("rangecheck", "DataSource"); + } + let plane = row.checked_mul(s.h).filter(|p| *p <= MAX_DATA).ok_or(PsError::Limit("decoded data is too large"))?; + self.alloc(plane.saturating_mul(sources.len()))?; + let mut planes = self.read_sources(sources, row * s.h, if multi { row } else { row * s.h })?; + for p in &mut planes { + p.resize(row * s.h, 0); + } + 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 = match &space { + Space::Indexed { .. } => vec![0.0, max], + _ => (0..n).flat_map(|_| [0.0, 1.0]).collect(), + }; + // The Decode array applies to a CMYK JPEG's samples as stored; gray and RGB JPEGs are read + // with the default Decode array. + let decode = if s.decode.len() >= 2 * n && jpeg.is_none_or(|c| c == 4) { 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); + // CMYK samples keep their ink amounts, as the Decode array reads them. + let mut inks = if matches!(space, Space::Cmyk) && s.stencil.is_none() { + self.alloc(s.w * s.h * 4)?; + Some(Vec::with_capacity(s.w * s.h * 4)) + } else { + None + }; + let mut cache: HashMap, [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; + } + if let Some(inks) = inks.as_mut() { + inks.extend(comps.iter().map(|v| (v.clamp(0.0, 1.0) * 255.0).round() as u8)); + } + 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, inks, 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(()); + } + if self.g.null { + return Ok(()); + } + // CMYK samples keep their ink amounts as a CMYK TIFF, as the PDF importer keeps an 8- or + // 16-bit CMYK image's samples, and transparent pixels (`MaskColor`, an ImageType 3 mask) + // become its opacity mask. + if let Some(blob) = img.inks.and_then(|i| Inks::new(img.w, img.h, i)).and_then(|i| ImageBlob::cmyk_tiff(&i)) { + let alpha: Vec = img.rgba.iter().skip(3).step_by(4).copied().collect(); + let mask = self.alpha_mask(&alpha, img.w, img.h, xf); + return self.add_image(blob, img.w, img.h, xf, mask); + } + 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(()); + } + self.add_image(ImageBlob::new("image/png", png), img.w, img.h, xf, None) + } + + /// Add image `blob` (`w` × `h` pixels, mapped into user space by `xf`) with `mask` as its + /// opacity mask. + fn add_image(&mut self, blob: ImageBlob, w: u32, h: u32, xf: Affine, mask: Option) -> Res { + let key = blob.content_key(); + self.out.doc.images.entry(key.clone()).or_insert(blob); + let im = ImageObject { key, width: w, height: h, xf, link: None, placement: Default::default(), pixels: None }; + let clips = self.g.clips.clone(); + self.out.push(Node::new(NodeId(0), NodeKind::Image(Box::new(im))).with_mask(mask.map(Box::new)), &clips); + Ok(()) + } + + /// An image's alpha (`w` × `h` bytes) as a grayscale image over the image (`xf`): its opacity + /// mask, as in PDF import. `None` when it hides nothing. + fn alpha_mask(&mut self, alpha: &[u8], w: u32, h: u32, xf: Affine) -> Option { + if alpha.iter().all(|v| *v == 255) { + return None; + } + let mut png = Vec::new(); + image::GrayImage::from_raw(w, h, alpha.to_vec())?.write_to(&mut std::io::Cursor::new(&mut png), image::ImageFormat::Png).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: w, height: h, xf, link: None, placement: Default::default(), pixels: None }; + Some(OpacityMask::new(Node::new(self.out.doc.alloc_id(), NodeKind::Image(Box::new(im))), true)) + } +} diff --git a/crates/eps/src/import/data/pixels.rs b/crates/eps/src/import/data/pixels.rs new file mode 100644 index 000000000..f61d10448 --- /dev/null +++ b/crates/eps/src/import/data/pixels.rs @@ -0,0 +1,106 @@ +use super::ops::Source; +use super::*; + +/// An image to decode. +pub(super) struct Spec { + pub(super) w: usize, + pub(super) h: usize, + pub(super) bpc: u8, + pub(super) space: Space, + pub(super) decode: Vec, + /// User space → image space. + pub(super) matrix: Affine, + /// One source, or one per component. + pub(super) sources: Vec, + /// `MaskColor` (ImageType 4): samples that are transparent. + pub(super) mask_color: Option>, + /// An image mask: whether its 1 bits paint. + pub(super) stencil: Option, +} + +impl Spec { + /// Bytes a row of `n` components takes. + pub(super) fn row_bytes(&self, n: usize) -> usize { + (self.w * n * usize::from(self.bpc)).div_ceil(8) + } +} + +/// Data of an ImageType 3 interleaved by row, split into the mask's and the image's: the one +/// with fewer rows gives one row before each run of the other's (`(row bytes, rows)` each). +pub(super) fn by_rows(raw: &[u8], (mrow, mh): (usize, usize), (drow, dh): (usize, usize)) -> (Vec, Vec) { + let room = |row: usize, rows: usize| row.saturating_mul(rows).min(raw.len()); + let (mut mask, mut data) = (Vec::with_capacity(room(mrow, mh)), Vec::with_capacity(room(drow, dh))); + let mut at = 0; + let mut take = |into: &mut Vec, n: usize| { + into.extend_from_slice(raw.get(at..at + n).unwrap_or_default()); + at += n; + }; + let (blocks, masks, rows) = if mh >= dh { (dh, mh / dh.max(1), 1) } else { (mh, 1, dh / mh.max(1)) }; + for _ in 0..blocks { + for _ in 0..masks { + take(&mut mask, mrow); + } + for _ in 0..rows { + take(&mut data, drow); + } + } + (mask, data) +} + +/// Data of an ImageType 3 interleaved by sample (the mask's value first, then `n` colour +/// components, `bpc` bits each, rows from whole bytes), split into the mask's and the image's. +pub(super) fn by_samples(raw: &[u8], w: usize, h: usize, n: usize, bpc: u8) -> (Vec, Vec) { + let bpc = usize::from(bpc); + let bit = |at: usize| raw.get(at / 8).is_some_and(|b| b & (0x80 >> (at % 8)) != 0); + let (mrow, drow, row) = ((w * bpc).div_ceil(8), (w * n * bpc).div_ceil(8), (w * (n + 1) * bpc).div_ceil(8)); + let (mut mask, mut data) = (vec![0u8; mrow * h], vec![0u8; drow * h]); + let put = |into: &mut [u8], at: usize| { + if let Some(b) = into.get_mut(at / 8) { + *b |= 0x80 >> (at % 8); + } + }; + for y in 0..h { + for x in 0..w { + let from = y * row * 8 + x * (n + 1) * bpc; + for k in 0..bpc { + if bit(from + k) { + put(&mut mask, y * mrow * 8 + x * bpc + k); + } + } + for k in 0..n * bpc { + if bit(from + bpc + k) { + put(&mut data, y * drow * 8 + x * n * bpc + k); + } + } + } + } + (mask, data) +} + +/// Decoded pixels. +pub(super) struct Pixels { + pub(super) w: u32, + pub(super) h: u32, + pub(super) rgba: Vec, + /// A CMYK image's ink amounts: C, M, Y and K bytes a pixel, 0 is no ink. + pub(super) inks: Option>, + pub(super) matrix: Affine, +} + +pub(super) fn source(o: &Obj) -> Res { + Ok(match o { + Obj::File { stream: 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)? +pub(super) 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)) +}