use crate::document::PdfDocument;
use crate::error::Result;
use crate::object::Object;
use std::collections::HashMap;
use tiny_skia::{Mask, Pixmap, Transform};
const MAX_TRIANGLES: usize = 4_000_000;
const MAX_PATCHES: usize = 500_000;
const MAX_SUBDIV: usize = 10;
const MAX_TYPE1_GRID: usize = 128;
pub(crate) type ColorResolver<'a> = dyn Fn(&[f32]) -> Option<(f32, f32, f32, f32)> + 'a;
#[allow(clippy::too_many_arguments)]
pub(crate) fn render_mesh_shading(
pixmap: &mut Pixmap,
shading: &HashMap<String, Object>,
shading_obj: &Object,
shading_type: i64,
transform: Transform,
doc: &PdfDocument,
clip_mask: Option<&Mask>,
resolve_color: &ColorResolver<'_>,
) -> Result<()> {
let function = shading
.get("Function")
.and_then(|f| doc.resolve_object(f).ok());
let to_rgba = |comps: &[f32]| -> (f32, f32, f32, f32) {
let cs_comps: Vec<f32> = match &function {
Some(f) => eval_pdf_function(f, doc, comps).unwrap_or_else(|| comps.to_vec()),
None => comps.to_vec(),
};
resolve_color(&cs_comps).unwrap_or((0.0, 0.0, 0.0, 1.0))
};
match shading_type {
1 => render_function_based(pixmap, shading, transform, clip_mask, &to_rgba),
4..=7 => {
let data = match shading_obj.decode_stream_data() {
Ok(d) => d,
Err(e) => {
log::debug!("Mesh shading type {shading_type}: stream decode failed: {e}");
return Ok(());
},
};
let params = match MeshParams::parse(shading) {
Some(p) => p,
None => {
log::debug!("Mesh shading type {shading_type}: missing/invalid stream params");
return Ok(());
},
};
match shading_type {
4 => {
let tris = decode_type4_stream(&data, ¶ms, MAX_TRIANGLES);
rasterize_raw_triangles(pixmap, &tris, transform, clip_mask, &to_rgba);
},
5 => {
let tris = decode_type5_stream(&data, ¶ms, MAX_TRIANGLES);
rasterize_raw_triangles(pixmap, &tris, transform, clip_mask, &to_rgba);
},
6 | 7 => {
let is_tensor = shading_type == 7;
let patches = decode_patches(&data, is_tensor, ¶ms, MAX_PATCHES);
render_patches(pixmap, &patches, is_tensor, transform, clip_mask, &to_rgba);
},
_ => unreachable!(),
}
Ok(())
},
other => {
log::debug!("Unsupported shading type {other} in mesh renderer");
Ok(())
},
}
}
struct BitReader<'a> {
data: &'a [u8],
bit_pos: usize,
}
impl<'a> BitReader<'a> {
fn new(data: &'a [u8]) -> Self {
Self { data, bit_pos: 0 }
}
fn remaining(&self) -> usize {
(self.data.len() * 8).saturating_sub(self.bit_pos)
}
fn read_bits(&mut self, nbits: u32) -> Option<u64> {
let nbits = nbits as usize;
if nbits == 0 {
return Some(0);
}
if nbits > 32 || self.remaining() < nbits {
return None;
}
let mut value: u64 = 0;
for _ in 0..nbits {
let byte = self.data[self.bit_pos >> 3];
let bit = (byte >> (7 - (self.bit_pos & 7))) & 1;
value = (value << 1) | bit as u64;
self.bit_pos += 1;
}
Some(value)
}
}
fn decode_value(raw: u64, nbits: u32, lo: f32, hi: f32) -> f32 {
let max = if nbits >= 64 {
u64::MAX
} else {
(1u64 << nbits) - 1
};
if max == 0 {
return lo;
}
let t = raw as f32 / max as f32;
lo + t * (hi - lo)
}
struct MeshParams {
bits_per_flag: u32,
bits_per_coord: u32,
bits_per_comp: u32,
vertices_per_row: usize,
decode: Vec<(f32, f32)>,
}
impl MeshParams {
fn parse(shading: &HashMap<String, Object>) -> Option<Self> {
let bits_per_coord = shading.get("BitsPerCoordinate")?.as_integer()? as u32;
let bits_per_comp = shading.get("BitsPerComponent")?.as_integer()? as u32;
let bits_per_flag = shading
.get("BitsPerFlag")
.and_then(|o| o.as_integer())
.unwrap_or(8) as u32;
if bits_per_coord == 0 || bits_per_coord > 32 || bits_per_comp == 0 || bits_per_comp > 32 {
return None;
}
if bits_per_flag > 32 {
return None;
}
let decode_arr = shading.get("Decode")?.as_array()?;
let decode: Vec<(f32, f32)> = decode_arr
.chunks_exact(2)
.map(|c| (num(&c[0]), num(&c[1])))
.collect();
if decode.len() < 3 {
return None;
}
let vertices_per_row = shading
.get("VerticesPerRow")
.and_then(|o| o.as_integer())
.unwrap_or(0)
.max(0) as usize;
Some(Self {
bits_per_flag,
bits_per_coord,
bits_per_comp,
vertices_per_row,
decode,
})
}
fn ncomps(&self) -> usize {
self.decode.len() - 2
}
fn read_vertex(&self, reader: &mut BitReader) -> Option<RawVertex> {
let rx = reader.read_bits(self.bits_per_coord)?;
let ry = reader.read_bits(self.bits_per_coord)?;
let x = decode_value(rx, self.bits_per_coord, self.decode[0].0, self.decode[0].1);
let y = decode_value(ry, self.bits_per_coord, self.decode[1].0, self.decode[1].1);
let comps = self.read_color(reader)?;
Some(RawVertex { x, y, comps })
}
fn read_color(&self, reader: &mut BitReader) -> Option<Vec<f32>> {
let n = self.ncomps();
let mut comps = Vec::with_capacity(n);
for i in 0..n {
let raw = reader.read_bits(self.bits_per_comp)?;
let (lo, hi) = self.decode[2 + i];
comps.push(decode_value(raw, self.bits_per_comp, lo, hi));
}
Some(comps)
}
}
#[derive(Clone, Debug)]
struct RawVertex {
x: f32,
y: f32,
comps: Vec<f32>,
}
fn decode_type4_stream(data: &[u8], p: &MeshParams, max_tris: usize) -> Vec<[RawVertex; 3]> {
let mut reader = BitReader::new(data);
let mut tris: Vec<[RawVertex; 3]> = Vec::new();
let mut prev: Option<[RawVertex; 3]> = None;
while reader.remaining() >= p.bits_per_flag as usize && tris.len() < max_tris {
let flag = match reader.read_bits(p.bits_per_flag) {
Some(f) => f,
None => break,
};
let v = match p.read_vertex(&mut reader) {
Some(v) => v,
None => break,
};
let tri = if flag == 0 {
let _f2 = reader.read_bits(p.bits_per_flag);
let v2 = match p.read_vertex(&mut reader) {
Some(v) => v,
None => break,
};
let _f3 = reader.read_bits(p.bits_per_flag);
let v3 = match p.read_vertex(&mut reader) {
Some(v) => v,
None => break,
};
[v, v2, v3]
} else {
let prev_tri = match &prev {
Some(t) => t,
None => break,
};
match flag {
1 => [prev_tri[1].clone(), prev_tri[2].clone(), v],
2 => [prev_tri[0].clone(), prev_tri[2].clone(), v],
_ => break,
}
};
prev = Some(tri.clone());
tris.push(tri);
}
tris
}
fn decode_type5_stream(data: &[u8], p: &MeshParams, max_tris: usize) -> Vec<[RawVertex; 3]> {
let vpr = p.vertices_per_row;
if vpr < 2 {
return Vec::new();
}
let mut reader = BitReader::new(data);
let mut tris: Vec<[RawVertex; 3]> = Vec::new();
let mut prev_row: Option<Vec<RawVertex>> = None;
loop {
let mut row = Vec::with_capacity(vpr);
for _ in 0..vpr {
match p.read_vertex(&mut reader) {
Some(v) => row.push(v),
None => break,
}
}
if row.len() < vpr {
break;
}
if let Some(top) = &prev_row {
for i in 0..vpr - 1 {
if tris.len() + 2 > max_tris {
return tris;
}
tris.push([top[i].clone(), top[i + 1].clone(), row[i].clone()]);
tris.push([top[i + 1].clone(), row[i + 1].clone(), row[i].clone()]);
}
}
prev_row = Some(row);
}
tris
}
type Pt = (f32, f32);
struct Patch {
boundary: [Pt; 12],
interior: [Pt; 4],
colors: [Vec<f32>; 4],
}
fn decode_patches(data: &[u8], is_tensor: bool, p: &MeshParams, max_patches: usize) -> Vec<Patch> {
let mut reader = BitReader::new(data);
let mut patches: Vec<Patch> = Vec::new();
let mut prev: Option<Patch> = None;
let total_points = if is_tensor { 16 } else { 12 };
let read_point = |r: &mut BitReader| -> Option<Pt> {
let rx = r.read_bits(p.bits_per_coord)?;
let ry = r.read_bits(p.bits_per_coord)?;
Some((
decode_value(rx, p.bits_per_coord, p.decode[0].0, p.decode[0].1),
decode_value(ry, p.bits_per_coord, p.decode[1].0, p.decode[1].1),
))
};
while reader.remaining() >= p.bits_per_flag as usize && patches.len() < max_patches {
let flag = match reader.read_bits(p.bits_per_flag) {
Some(f) => f,
None => break,
};
let mut boundary = [(0.0f32, 0.0f32); 12];
let mut interior = [(0.0f32, 0.0f32); 4];
let mut colors: [Vec<f32>; 4] = [Vec::new(), Vec::new(), Vec::new(), Vec::new()];
let (start_pt, start_color) = if flag == 0 {
(0usize, 0usize)
} else {
let prev_patch = match &prev {
Some(pp) => pp,
None => break,
};
let (pts, cols): ([usize; 4], [usize; 2]) = match flag {
1 => ([3, 4, 5, 6], [1, 2]),
2 => ([6, 7, 8, 9], [2, 3]),
3 => ([9, 10, 11, 0], [3, 0]),
_ => break,
};
for (dst, &src) in pts.iter().enumerate() {
boundary[dst] = prev_patch.boundary[src];
}
colors[0] = prev_patch.colors[cols[0]].clone();
colors[1] = prev_patch.colors[cols[1]].clone();
(4usize, 2usize)
};
let mut ok = true;
for slot in boundary.iter_mut().take(12).skip(start_pt) {
match read_point(&mut reader) {
Some(pt) => *slot = pt,
None => {
ok = false;
break;
},
}
}
if ok && is_tensor && total_points == 16 {
for slot in interior.iter_mut() {
match read_point(&mut reader) {
Some(pt) => *slot = pt,
None => {
ok = false;
break;
},
}
}
}
if ok {
for slot in colors.iter_mut().take(4).skip(start_color) {
match p.read_color(&mut reader) {
Some(c) => *slot = c,
None => {
ok = false;
break;
},
}
}
}
if !ok {
break;
}
let patch = Patch {
boundary,
interior,
colors,
};
prev = Some(Patch {
boundary: patch.boundary,
interior: patch.interior,
colors: patch.colors.clone(),
});
patches.push(patch);
}
patches
}
fn bezier(p0: Pt, p1: Pt, p2: Pt, p3: Pt, t: f32) -> Pt {
let mt = 1.0 - t;
let b0 = mt * mt * mt;
let b1 = 3.0 * mt * mt * t;
let b2 = 3.0 * mt * t * t;
let b3 = t * t * t;
(
b0 * p0.0 + b1 * p1.0 + b2 * p2.0 + b3 * p3.0,
b0 * p0.1 + b1 * p1.1 + b2 * p2.1 + b3 * p3.1,
)
}
fn tensor_surface(b: &[Pt; 12], interior: &[Pt; 4], s: f32, t: f32) -> Pt {
let g: [[Pt; 4]; 4] = [
[b[0], b[11], b[10], b[9]], [b[1], interior[3], interior[2], b[8]], [b[2], interior[0], interior[1], b[7]], [b[3], b[4], b[5], b[6]], ];
let bt = bernstein(t);
let bs = bernstein(s);
let mut x = 0.0;
let mut y = 0.0;
for (r, brow) in g.iter().enumerate() {
for (c, pt) in brow.iter().enumerate() {
let w = bt[r] * bs[c];
x += w * pt.0;
y += w * pt.1;
}
}
(x, y)
}
#[inline]
fn bernstein(t: f32) -> [f32; 4] {
let mt = 1.0 - t;
[mt * mt * mt, 3.0 * mt * mt * t, 3.0 * mt * t * t, t * t * t]
}
fn coons_point(b: &[Pt; 12], s: f32, t: f32) -> Pt {
let left = bezier(b[0], b[1], b[2], b[3], t);
let right = bezier(b[9], b[8], b[7], b[6], t);
let bottom = bezier(b[0], b[11], b[10], b[9], s);
let top = bezier(b[3], b[4], b[5], b[6], s);
let (p1, p4, p7, p10) = (b[0], b[3], b[6], b[9]);
let blend = |lb: f32, rb: f32, bb: f32, tb: f32, c1: f32, c4: f32, c7: f32, c10: f32| -> f32 {
(1.0 - t) * bb + t * tb + (1.0 - s) * lb + s * rb
- ((1.0 - s) * (1.0 - t) * c1 + s * (1.0 - t) * c10 + (1.0 - s) * t * c4 + s * t * c7)
};
(
blend(left.0, right.0, bottom.0, top.0, p1.0, p4.0, p7.0, p10.0),
blend(left.1, right.1, bottom.1, top.1, p1.1, p4.1, p7.1, p10.1),
)
}
fn render_patches(
pixmap: &mut Pixmap,
patches: &[Patch],
is_tensor: bool,
transform: Transform,
clip_mask: Option<&Mask>,
to_rgba: &dyn Fn(&[f32]) -> (f32, f32, f32, f32),
) {
let (w, h) = (pixmap.width() as f32, pixmap.height() as f32);
for patch in patches {
let c = [
to_rgba(&patch.colors[0]),
to_rgba(&patch.colors[1]),
to_rgba(&patch.colors[2]),
to_rgba(&patch.colors[3]),
];
let corners = [
patch.boundary[0],
patch.boundary[3],
patch.boundary[6],
patch.boundary[9],
];
let dev: Vec<Pt> = corners.iter().map(|&p| map_pt(transform, p)).collect();
let mut extent = 0.0f32;
for i in 0..dev.len() {
for j in i + 1..dev.len() {
let d = ((dev[i].0 - dev[j].0).powi(2) + (dev[i].1 - dev[j].1).powi(2)).sqrt();
extent = extent.max(d);
}
}
if dev
.iter()
.all(|p| p.0 < 0.0 || p.0 > w || p.1 < 0.0 || p.1 > h)
&& !bbox_intersects_canvas(&dev, w, h)
{
continue;
}
let n = ((extent / 16.0).ceil() as usize).clamp(1, MAX_SUBDIV);
let node = |i: usize, j: usize| -> ((f32, f32), (f32, f32, f32, f32)) {
let s = i as f32 / n as f32;
let t = j as f32 / n as f32;
let sp = if is_tensor {
tensor_surface(&patch.boundary, &patch.interior, s, t)
} else {
coons_point(&patch.boundary, s, t)
};
let dp = map_pt(transform, sp);
let col = bilerp_rgba(c[0], c[1], c[2], c[3], s, t);
(dp, col)
};
for i in 0..n {
for j in 0..n {
let (p00, c00) = node(i, j);
let (p10, c10) = node(i + 1, j);
let (p01, c01) = node(i, j + 1);
let (p11, c11) = node(i + 1, j + 1);
fill_gouraud_triangle(pixmap, clip_mask, (p00, c00), (p10, c10), (p11, c11));
fill_gouraud_triangle(pixmap, clip_mask, (p00, c00), (p11, c11), (p01, c01));
}
}
}
}
fn bilerp_rgba(
c1: (f32, f32, f32, f32),
c2: (f32, f32, f32, f32),
c3: (f32, f32, f32, f32),
c4: (f32, f32, f32, f32),
s: f32,
t: f32,
) -> (f32, f32, f32, f32) {
let w1 = (1.0 - s) * (1.0 - t);
let w2 = (1.0 - s) * t;
let w3 = s * t;
let w4 = s * (1.0 - t);
(
w1 * c1.0 + w2 * c2.0 + w3 * c3.0 + w4 * c4.0,
w1 * c1.1 + w2 * c2.1 + w3 * c3.1 + w4 * c4.1,
w1 * c1.2 + w2 * c2.2 + w3 * c3.2 + w4 * c4.2,
w1 * c1.3 + w2 * c2.3 + w3 * c3.3 + w4 * c4.3,
)
}
fn render_function_based(
pixmap: &mut Pixmap,
shading: &HashMap<String, Object>,
transform: Transform,
clip_mask: Option<&Mask>,
to_rgba: &dyn Fn(&[f32]) -> (f32, f32, f32, f32),
) -> Result<()> {
let (dx0, dx1, dy0, dy1) = shading
.get("Domain")
.and_then(|o| o.as_array())
.filter(|a| a.len() >= 4)
.map(|a| (num(&a[0]), num(&a[1]), num(&a[2]), num(&a[3])))
.unwrap_or((0.0, 1.0, 0.0, 1.0));
let matrix = shading
.get("Matrix")
.and_then(|o| o.as_array())
.filter(|a| a.len() >= 6)
.map(|a| {
[
num(&a[0]),
num(&a[1]),
num(&a[2]),
num(&a[3]),
num(&a[4]),
num(&a[5]),
]
})
.unwrap_or([1.0, 0.0, 0.0, 1.0, 0.0, 0.0]);
let map_domain = |u: f32, v: f32| -> Pt {
let sx = matrix[0] * u + matrix[2] * v + matrix[4];
let sy = matrix[1] * u + matrix[3] * v + matrix[5];
map_pt(transform, (sx, sy))
};
let corners = [
map_domain(dx0, dy0),
map_domain(dx1, dy0),
map_domain(dx1, dy1),
map_domain(dx0, dy1),
];
let mut extent = 0.0f32;
for i in 0..corners.len() {
for j in i + 1..corners.len() {
let d = ((corners[i].0 - corners[j].0).powi(2) + (corners[i].1 - corners[j].1).powi(2))
.sqrt();
extent = extent.max(d);
}
}
let n = (extent.ceil() as usize).clamp(2, MAX_TYPE1_GRID);
let mut grid: Vec<((f32, f32), (f32, f32, f32, f32))> = Vec::with_capacity((n + 1) * (n + 1));
for i in 0..=n {
for j in 0..=n {
let u = dx0 + (dx1 - dx0) * (i as f32 / n as f32);
let v = dy0 + (dy1 - dy0) * (j as f32 / n as f32);
let rgba = to_rgba(&[u, v]);
grid.push((map_domain(u, v), rgba));
}
}
let at = |i: usize, j: usize| grid[i * (n + 1) + j];
for i in 0..n {
for j in 0..n {
let a = at(i, j);
let b = at(i + 1, j);
let c = at(i + 1, j + 1);
let d = at(i, j + 1);
fill_gouraud_triangle(pixmap, clip_mask, a, b, c);
fill_gouraud_triangle(pixmap, clip_mask, a, c, d);
}
}
Ok(())
}
fn rasterize_raw_triangles(
pixmap: &mut Pixmap,
tris: &[[RawVertex; 3]],
transform: Transform,
clip_mask: Option<&Mask>,
to_rgba: &dyn Fn(&[f32]) -> (f32, f32, f32, f32),
) {
for tri in tris {
let mut verts = [((0.0f32, 0.0f32), (0.0f32, 0.0f32, 0.0f32, 0.0f32)); 3];
for (k, rv) in tri.iter().enumerate() {
verts[k] = (map_pt(transform, (rv.x, rv.y)), to_rgba(&rv.comps));
}
fill_gouraud_triangle(pixmap, clip_mask, verts[0], verts[1], verts[2]);
}
}
#[inline]
fn map_pt(transform: Transform, p: Pt) -> Pt {
let mut pt = tiny_skia::Point { x: p.0, y: p.1 };
transform.map_point(&mut pt);
(pt.x, pt.y)
}
fn bbox_intersects_canvas(corners: &[Pt], w: f32, h: f32) -> bool {
let (mut minx, mut miny, mut maxx, mut maxy) = (f32::MAX, f32::MAX, f32::MIN, f32::MIN);
for &(x, y) in corners {
minx = minx.min(x);
miny = miny.min(y);
maxx = maxx.max(x);
maxy = maxy.max(y);
}
minx <= w && maxx >= 0.0 && miny <= h && maxy >= 0.0
}
type ColoredVertex = ((f32, f32), (f32, f32, f32, f32));
fn fill_gouraud_triangle(
pixmap: &mut Pixmap,
clip_mask: Option<&Mask>,
v0: ColoredVertex,
v1: ColoredVertex,
v2: ColoredVertex,
) {
let width = pixmap.width() as i32;
let height = pixmap.height() as i32;
if width == 0 || height == 0 {
return;
}
let (p0, c0) = v0;
let (p1, c1) = v1;
let (p2, c2) = v2;
let minx = p0.0.min(p1.0).min(p2.0).floor().max(0.0) as i32;
let maxx = p0.0.max(p1.0).max(p2.0).ceil().min(width as f32) as i32;
let miny = p0.1.min(p1.1).min(p2.1).floor().max(0.0) as i32;
let maxy = p0.1.max(p1.1).max(p2.1).ceil().min(height as f32) as i32;
if minx >= maxx || miny >= maxy {
return;
}
let denom = (p1.1 - p2.1) * (p0.0 - p2.0) + (p2.0 - p1.0) * (p0.1 - p2.1);
if denom.abs() < 1e-9 {
return;
}
let inv_denom = 1.0 / denom;
let mask_data = clip_mask.map(|m| m.data());
let dest = pixmap.data_mut();
for py in miny..maxy {
for px in minx..maxx {
let fx = px as f32 + 0.5;
let fy = py as f32 + 0.5;
let w0 = ((p1.1 - p2.1) * (fx - p2.0) + (p2.0 - p1.0) * (fy - p2.1)) * inv_denom;
let w1 = ((p2.1 - p0.1) * (fx - p2.0) + (p0.0 - p2.0) * (fy - p2.1)) * inv_denom;
let w2 = 1.0 - w0 - w1;
if w0 < -1e-4 || w1 < -1e-4 || w2 < -1e-4 {
continue;
}
let mut a = w0 * c0.3 + w1 * c1.3 + w2 * c2.3;
if a <= 0.0 {
continue;
}
let pixel_idx = (py * width + px) as usize;
if let Some(md) = mask_data {
if let Some(&m) = md.get(pixel_idx) {
a *= m as f32 / 255.0;
if a <= 0.0 {
continue;
}
}
}
let r = w0 * c0.0 + w1 * c1.0 + w2 * c2.0;
let g = w0 * c0.1 + w1 * c1.1 + w2 * c2.1;
let b = w0 * c0.2 + w1 * c1.2 + w2 * c2.2;
blend_premul(dest, pixel_idx * 4, r, g, b, a);
}
}
}
#[inline]
fn blend_premul(dest: &mut [u8], off: usize, r: f32, g: f32, b: f32, a: f32) {
if off + 3 >= dest.len() {
return;
}
let a = a.clamp(0.0, 1.0);
let sr = r.clamp(0.0, 1.0) * a;
let sg = g.clamp(0.0, 1.0) * a;
let sb = b.clamp(0.0, 1.0) * a;
let inv = 1.0 - a;
let dr = dest[off] as f32 / 255.0;
let dg = dest[off + 1] as f32 / 255.0;
let db = dest[off + 2] as f32 / 255.0;
let da = dest[off + 3] as f32 / 255.0;
dest[off] = ((sr + dr * inv) * 255.0).round().clamp(0.0, 255.0) as u8;
dest[off + 1] = ((sg + dg * inv) * 255.0).round().clamp(0.0, 255.0) as u8;
dest[off + 2] = ((sb + db * inv) * 255.0).round().clamp(0.0, 255.0) as u8;
dest[off + 3] = ((a + da * inv) * 255.0).round().clamp(0.0, 255.0) as u8;
}
fn eval_pdf_function(func: &Object, doc: &PdfDocument, inputs: &[f32]) -> Option<Vec<f32>> {
if let Object::Array(arr) = func {
let mut out = Vec::with_capacity(arr.len());
for f in arr {
let resolved = doc.resolve_object(f).ok()?;
let mut r = eval_pdf_function(&resolved, doc, inputs)?;
out.append(&mut r);
}
return Some(out);
}
let dict = func.as_dict()?;
let ftype = dict.get("FunctionType").and_then(|o| o.as_integer())?;
match ftype {
2 => eval_type2(dict, inputs),
3 => eval_type3(dict, doc, inputs),
0 => eval_type0(func, dict, inputs),
4 => eval_type4(func, dict, inputs),
_ => None,
}
}
fn eval_type2(dict: &HashMap<String, Object>, inputs: &[f32]) -> Option<Vec<f32>> {
let x = *inputs.first()?;
let c0 = dict
.get("C0")
.and_then(|o| o.as_array())
.map(|a| a.iter().map(num).collect::<Vec<_>>())
.unwrap_or_else(|| vec![0.0]);
let c1 = dict
.get("C1")
.and_then(|o| o.as_array())
.map(|a| a.iter().map(num).collect::<Vec<_>>())
.unwrap_or_else(|| vec![1.0]);
let n = dict.get("N").map(num).unwrap_or(1.0);
let xp = x.abs().powf(n) * x.signum();
let len = c0.len().max(c1.len());
Some(
(0..len)
.map(|i| {
let a = c0.get(i).copied().unwrap_or(0.0);
let b = c1.get(i).copied().unwrap_or(0.0);
a + xp * (b - a)
})
.collect(),
)
}
fn eval_type3(
dict: &HashMap<String, Object>,
doc: &PdfDocument,
inputs: &[f32],
) -> Option<Vec<f32>> {
let x = *inputs.first()?;
let funcs = dict.get("Functions").and_then(|o| o.as_array())?;
if funcs.is_empty() {
return None;
}
let domain = dict.get("Domain").and_then(|o| o.as_array())?;
let (d0, d1) = (num(&domain[0]), num(domain.get(1)?));
let bounds: Vec<f32> = dict
.get("Bounds")
.and_then(|o| o.as_array())
.map(|a| a.iter().map(num).collect())
.unwrap_or_default();
let encode: Vec<f32> = dict
.get("Encode")
.and_then(|o| o.as_array())
.map(|a| a.iter().map(num).collect())
.unwrap_or_default();
let xc = x.clamp(d0.min(d1), d0.max(d1));
let mut k = 0usize;
while k < bounds.len() && xc >= bounds[k] {
k += 1;
}
k = k.min(funcs.len() - 1);
let lo = if k == 0 { d0 } else { bounds[k - 1] };
let hi = if k < bounds.len() { bounds[k] } else { d1 };
let (e0, e1) = (
encode.get(2 * k).copied().unwrap_or(0.0),
encode.get(2 * k + 1).copied().unwrap_or(1.0),
);
let xe = if (hi - lo).abs() < f32::EPSILON {
e0
} else {
e0 + (xc - lo) * (e1 - e0) / (hi - lo)
};
let sub = doc.resolve_object(&funcs[k]).ok()?;
eval_pdf_function(&sub, doc, &[xe])
}
fn eval_type4(func: &Object, dict: &HashMap<String, Object>, inputs: &[f32]) -> Option<Vec<f32>> {
let bytes = func.decode_stream_data().ok()?;
let domain = pairs(dict.get("Domain"));
let range = pairs(dict.get("Range"));
let in64: Vec<f64> = inputs.iter().map(|&v| v as f64).collect();
let out = crate::functions::evaluate_type4_clamped(&bytes, &in64, &domain, &range).ok()?;
Some(out.into_iter().map(|v| v as f32).collect())
}
fn eval_type0(func: &Object, dict: &HashMap<String, Object>, inputs: &[f32]) -> Option<Vec<f32>> {
let bytes = func.decode_stream_data().ok()?;
let domain = pairs(dict.get("Domain"));
let range = pairs(dict.get("Range"));
let size: Vec<usize> = dict
.get("Size")
.and_then(|o| o.as_array())?
.iter()
.map(|o| o.as_integer().unwrap_or(0).max(0) as usize)
.collect();
let bps = dict.get("BitsPerSample").and_then(|o| o.as_integer())? as u32;
let m = size.len();
let n = range.len();
if m == 0 || m > 2 || n == 0 || n > 8 || bps == 0 || bps > 32 {
return None;
}
if size.contains(&0) || domain.len() < m {
return None;
}
let encode: Vec<f32> = dict
.get("Encode")
.and_then(|o| o.as_array())
.map(|a| a.iter().map(num).collect())
.unwrap_or_else(|| {
size.iter()
.flat_map(|&s| [0.0, (s.saturating_sub(1)) as f32])
.collect()
});
let decode: Vec<(f32, f32)> = dict
.get("Decode")
.and_then(|o| o.as_array())
.map(|a| {
a.chunks_exact(2)
.map(|c| (num(&c[0]), num(&c[1])))
.collect()
})
.unwrap_or_else(|| range.iter().map(|r| (r[0] as f32, r[1] as f32)).collect());
let mut e = [0.0f32; 2];
for i in 0..m {
let (d0, d1) = (domain[i][0] as f32, domain[i][1] as f32);
let x = inputs
.get(i)
.copied()
.unwrap_or(0.0)
.clamp(d0.min(d1), d0.max(d1));
let (en0, en1) = (
encode.get(2 * i).copied().unwrap_or(0.0),
encode
.get(2 * i + 1)
.copied()
.unwrap_or((size[i] - 1) as f32),
);
let ec = if (d1 - d0).abs() < f32::EPSILON {
en0
} else {
en0 + (x - d0) * (en1 - en0) / (d1 - d0)
};
e[i] = ec.clamp(0.0, (size[i] - 1) as f32);
}
let max_sample = if bps >= 32 {
u32::MAX as f64
} else {
((1u64 << bps) - 1) as f64
};
let sample = |coord: &[usize; 2]| -> Vec<f32> {
let mut flat = 0usize;
let mut stride = 1usize;
for i in 0..m {
flat += coord[i].min(size[i] - 1) * stride;
stride *= size[i];
}
(0..n)
.map(|o| {
let bit_off = (flat * n + o) * bps as usize;
let raw = read_bits_at(&bytes, bit_off, bps).unwrap_or(0);
(raw as f64 / max_sample) as f32
})
.collect()
};
let corners = 1usize << m;
let mut acc = vec![0.0f32; n];
for c in 0..corners {
let mut coord = [0usize; 2];
let mut weight = 1.0f32;
for i in 0..m {
let base = e[i].floor() as usize;
let frac = e[i] - base as f32;
let hi = (c >> i) & 1;
if hi == 1 {
coord[i] = (base + 1).min(size[i] - 1);
weight *= frac;
} else {
coord[i] = base;
weight *= 1.0 - frac;
}
}
if weight == 0.0 {
continue;
}
let s = sample(&coord);
for o in 0..n {
acc[o] += weight * s[o];
}
}
Some(
acc.iter()
.enumerate()
.map(|(o, &v)| {
let (lo, hi) = decode.get(o).copied().unwrap_or((0.0, 1.0));
lo + v * (hi - lo)
})
.collect(),
)
}
fn read_bits_at(bytes: &[u8], bit_off: usize, nbits: u32) -> Option<u32> {
let nbits = nbits as usize;
if nbits == 0 {
return Some(0);
}
if bit_off + nbits > bytes.len() * 8 {
return None;
}
let mut value: u32 = 0;
for i in 0..nbits {
let pos = bit_off + i;
let byte = bytes[pos >> 3];
let bit = (byte >> (7 - (pos & 7))) & 1;
value = (value << 1) | bit as u32;
}
Some(value)
}
fn num(o: &Object) -> f32 {
o.as_real()
.map(|v| v as f32)
.or_else(|| o.as_integer().map(|i| i as f32))
.unwrap_or(0.0)
}
fn pairs(o: Option<&Object>) -> Vec<[f64; 2]> {
o.and_then(|o| o.as_array())
.map(|a| {
a.chunks_exact(2)
.map(|c| {
let lo = c[0]
.as_real()
.or_else(|| c[0].as_integer().map(|i| i as f64))
.unwrap_or(0.0);
let hi = c[1]
.as_real()
.or_else(|| c[1].as_integer().map(|i| i as f64))
.unwrap_or(0.0);
[lo, hi]
})
.collect()
})
.unwrap_or_default()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn bit_reader_reads_msb_first_and_bounds() {
let data = [0b1011_0010u8, 0b1100_0000u8];
let mut r = BitReader::new(&data);
assert_eq!(r.read_bits(1), Some(1));
assert_eq!(r.read_bits(3), Some(0b011));
assert_eq!(r.read_bits(4), Some(0b0010));
assert_eq!(r.read_bits(2), Some(0b11));
assert_eq!(r.read_bits(8), None);
}
#[test]
fn decode_value_maps_endpoints() {
assert!((decode_value(0, 8, -2.0, 2.0) - (-2.0)).abs() < 1e-6);
assert!((decode_value(255, 8, -2.0, 2.0) - 2.0).abs() < 1e-6);
assert!((decode_value(128, 8, 0.0, 1.0) - 0.5019608).abs() < 1e-4);
}
#[test]
fn type4_stream_decodes_flags_and_triangles() {
let params = MeshParams {
bits_per_flag: 8,
bits_per_coord: 8,
bits_per_comp: 8,
vertices_per_row: 0,
decode: vec![(0.0, 1.0), (0.0, 1.0), (0.0, 1.0)],
};
let mut bytes: Vec<u8> = Vec::new();
let mut push_v = |flag: u8, x: u8, y: u8, c: u8| {
bytes.extend_from_slice(&[flag, x, y, c]);
};
push_v(0, 0, 0, 0); push_v(0, 255, 0, 255); push_v(0, 0, 255, 128); push_v(1, 255, 255, 255);
let tris = decode_type4_stream(&bytes, ¶ms, 100);
assert_eq!(tris.len(), 2, "expected two triangles");
assert!((tris[0][0].x - 0.0).abs() < 1e-4 && (tris[0][0].y - 0.0).abs() < 1e-4);
assert!((tris[0][1].x - 1.0).abs() < 1e-4);
assert!((tris[0][2].y - 1.0).abs() < 1e-4);
assert!((tris[1][0].x - 1.0).abs() < 1e-4); assert!((tris[1][1].y - 1.0).abs() < 1e-4); assert!((tris[1][2].x - 1.0).abs() < 1e-4 && (tris[1][2].y - 1.0).abs() < 1e-4);
}
#[test]
fn type5_lattice_tessellates_rows() {
let params = MeshParams {
bits_per_flag: 8,
bits_per_coord: 8,
bits_per_comp: 8,
vertices_per_row: 3,
decode: vec![(0.0, 1.0), (0.0, 1.0), (0.0, 1.0)],
};
let mut bytes: Vec<u8> = Vec::new();
for &x in &[0u8, 128, 255] {
bytes.extend_from_slice(&[x, 0, 0]); }
for &x in &[0u8, 128, 255] {
bytes.extend_from_slice(&[x, 255, 255]);
}
let tris = decode_type5_stream(&bytes, ¶ms, 100);
assert_eq!(tris.len(), 4);
}
#[test]
fn gouraud_triangle_interpolates_colours() {
let mut pixmap = Pixmap::new(10, 10).unwrap();
let v0 = ((1.0, 1.0), (1.0, 0.0, 0.0, 1.0)); let v1 = ((8.0, 1.0), (0.0, 1.0, 0.0, 1.0)); let v2 = ((1.0, 8.0), (0.0, 0.0, 1.0, 1.0)); fill_gouraud_triangle(&mut pixmap, None, v0, v1, v2);
let data = pixmap.data();
let px = |x: usize, y: usize| {
let o = (y * 10 + x) * 4;
(data[o], data[o + 1], data[o + 2], data[o + 3])
};
let (r, g, b, a) = px(1, 1);
assert!(a > 0, "corner must be painted");
assert!(r > g && r > b, "near red corner should be reddish: {r},{g},{b}");
}
#[test]
fn degenerate_triangle_is_skipped() {
let mut pixmap = Pixmap::new(8, 8).unwrap();
let c = (1.0, 1.0, 1.0, 1.0);
fill_gouraud_triangle(&mut pixmap, None, ((0.0, 0.0), c), ((4.0, 4.0), c), ((8.0, 8.0), c));
assert!(pixmap.data().iter().all(|&b| b == 0), "no pixels painted");
}
#[test]
fn type4_renders_non_background_pixels() {
let params = MeshParams {
bits_per_flag: 8,
bits_per_coord: 8,
bits_per_comp: 8,
vertices_per_row: 0,
decode: vec![(0.0, 40.0), (0.0, 40.0), (0.0, 1.0)],
};
let mut bytes: Vec<u8> = Vec::new();
bytes.extend_from_slice(&[0, 0, 0, 255]); bytes.extend_from_slice(&[0, 255, 0, 255]); bytes.extend_from_slice(&[0, 128, 255, 255]);
let tris = decode_type4_stream(&bytes, ¶ms, 100);
assert_eq!(tris.len(), 1);
let mut pixmap = Pixmap::new(50, 50).unwrap();
let to_rgba = |c: &[f32]| -> (f32, f32, f32, f32) {
let v = c.first().copied().unwrap_or(0.0);
(v, 0.0, 0.0, 1.0)
};
rasterize_raw_triangles(&mut pixmap, &tris, Transform::identity(), None, &to_rgba);
let data = pixmap.data();
let o = (13 * 50 + 20) * 4;
assert!(data[o] > 100, "shaded region should be red, got r={}", data[o]);
assert!(data[o + 3] > 0, "shaded region should be opaque");
let corner = (48 * 50 + 48) * 4;
assert_eq!(data[corner + 3], 0, "outside triangle stays background");
}
#[test]
fn type2_function_interpolates() {
let mut dict = HashMap::new();
dict.insert("FunctionType".to_string(), Object::Integer(2));
dict.insert(
"C0".to_string(),
Object::Array(vec![Object::Real(0.0), Object::Real(0.0), Object::Real(0.0)]),
);
dict.insert(
"C1".to_string(),
Object::Array(vec![Object::Real(1.0), Object::Real(0.5), Object::Real(0.0)]),
);
dict.insert("N".to_string(), Object::Integer(1));
let out = eval_type2(&dict, &[0.5]).unwrap();
assert!((out[0] - 0.5).abs() < 1e-6);
assert!((out[1] - 0.25).abs() < 1e-6);
assert!((out[2] - 0.0).abs() < 1e-6);
}
}