use pdf_writer::Content;
pub(super) fn fill_rgb(content: &mut Content, [r, g, b]: [u8; 3]) {
content.set_fill_rgb(r as f32 / 255.0, g as f32 / 255.0, b as f32 / 255.0);
}
pub(super) fn stroke_rgb(content: &mut Content, [r, g, b]: [u8; 3]) {
content.set_stroke_rgb(r as f32 / 255.0, g as f32 / 255.0, b as f32 / 255.0);
}
pub(super) fn fill_color_or_black(content: &mut Content, color: Option<[u8; 3]>) {
if let Some(c) = color {
fill_rgb(content, c);
} else {
content.set_fill_gray(0.0);
}
}
pub(super) fn stroke_color_or_black(content: &mut Content, color: Option<[u8; 3]>) {
if let Some(c) = color {
stroke_rgb(content, c);
} else {
content.set_stroke_gray(0.0);
}
}
pub(super) const SHADOW_SCALE: f32 = 0.5;
pub(super) fn generate_shadow_mask(
width_pt: f32,
height_pt: f32,
blur_radius: f32,
alpha: f32,
) -> (Vec<u8>, u32, u32) {
let total_w = width_pt + 2.0 * blur_radius;
let total_h = height_pt + 2.0 * blur_radius;
let px_w = (total_w * SHADOW_SCALE).ceil().max(1.0) as u32;
let px_h = (total_h * SHADOW_SCALE).ceil().max(1.0) as u32;
let mut buf = vec![0u8; (px_w * px_h) as usize];
let pad_x = (blur_radius * SHADOW_SCALE).round() as u32;
let pad_y = (blur_radius * SHADOW_SCALE).round() as u32;
let inner_val = (alpha * 255.0).round().min(255.0) as u8;
let inner_w = px_w.saturating_sub(2 * pad_x);
let inner_h = px_h.saturating_sub(2 * pad_y);
for row in 0..inner_h {
let y = pad_y + row;
if y >= px_h {
break;
}
for col in 0..inner_w {
let x = pad_x + col;
if x >= px_w {
break;
}
buf[(y * px_w + x) as usize] = inner_val;
}
}
let box_r = ((blur_radius * SHADOW_SCALE) / 3.0).ceil().max(0.0) as usize;
box_blur_3pass(&mut buf, px_w, px_h, box_r);
(buf, px_w, px_h)
}
fn box_blur_3pass(buf: &mut [u8], px_w: u32, px_h: u32, box_r: usize) {
if box_r == 0 {
return;
}
let w = px_w as usize;
let h = px_h as usize;
let mut tmp = vec![0u8; w * h];
for _pass in 0..3 {
for y in 0..h {
let mut acc: u32 = 0;
for x in 0..=box_r.min(w - 1) {
acc += buf[y * w + x] as u32;
}
for x in 0..w {
let right = x + box_r;
if right < w && x > 0 {
acc += buf[y * w + right] as u32;
}
let left_edge = if x > box_r { x - box_r } else { 0 };
let right_edge = right.min(w - 1);
let count = (right_edge - left_edge + 1) as u32;
tmp[y * w + x] = (acc / count).min(255) as u8;
if x >= box_r {
acc -= buf[y * w + (x - box_r)] as u32;
}
}
}
for x in 0..w {
let mut acc: u32 = 0;
for y in 0..=box_r.min(h - 1) {
acc += tmp[y * w + x] as u32;
}
for y in 0..h {
let bottom = y + box_r;
if bottom < h && y > 0 {
acc += tmp[bottom * w + x] as u32;
}
let top_edge = if y > box_r { y - box_r } else { 0 };
let bottom_edge = bottom.min(h - 1);
let count = (bottom_edge - top_edge + 1) as u32;
buf[y * w + x] = (acc / count).min(255) as u8;
if y >= box_r {
acc -= tmp[(y - box_r) * w + x] as u32;
}
}
}
}
}
pub(super) fn draw_image_shadow(
content: &mut Content,
shadow: &crate::model::ImageShadow,
x: f32,
y_bottom: f32,
width: f32,
height: f32,
shadow_xobj_name: Option<&str>,
) {
use pdf_writer::Name;
if let Some(name) = shadow_xobj_name {
let blur = shadow.blur_radius;
let sx = x + shadow.offset_x - blur;
let sy = y_bottom - shadow.offset_y - blur;
let sw = width + 2.0 * blur;
let sh = height + 2.0 * blur;
content.save_state();
content.transform([sw, 0.0, 0.0, sh, sx, sy]);
content.x_object(Name(name.as_bytes()));
content.restore_state();
return;
}
let sx = x + shadow.offset_x;
let sy = y_bottom - shadow.offset_y;
let a = shadow.alpha;
let blended = [
(a * shadow.color[0] as f32 + (1.0 - a) * 255.0) as u8,
(a * shadow.color[1] as f32 + (1.0 - a) * 255.0) as u8,
(a * shadow.color[2] as f32 + (1.0 - a) * 255.0) as u8,
];
content.save_state();
fill_color_or_black(content, Some(blended));
content.rect(sx, sy, width, height);
content.fill_nonzero();
content.restore_state();
}
pub(super) fn generate_soft_edge_mask(w: u32, h: u32, radius_px: f32) -> Vec<u8> {
let mut mask = vec![0u8; (w * h) as usize];
if radius_px <= 0.0 {
mask.fill(255);
return mask;
}
for y in 0..h {
for x in 0..w {
let dx = (x as f32).min((w - 1 - x) as f32);
let dy = (y as f32).min((h - 1 - y) as f32);
let d = dx.min(dy);
let v = (d / radius_px).min(1.0);
mask[(y * w + x) as usize] = (v * 255.0).round() as u8;
}
}
mask
}
pub(super) fn generate_inner_shadow_mask(
width_pt: f32,
height_pt: f32,
blur_radius: f32,
alpha: f32,
) -> (Vec<u8>, u32, u32) {
let px_w = (width_pt * SHADOW_SCALE).ceil().max(1.0) as u32;
let px_h = (height_pt * SHADOW_SCALE).ceil().max(1.0) as u32;
let mut buf = vec![0u8; (px_w * px_h) as usize];
let inner_val = (alpha * 255.0).round().min(255.0) as u8;
let border_px = (blur_radius * SHADOW_SCALE).round().max(1.0) as u32;
buf.fill(inner_val);
for y in border_px..px_h.saturating_sub(border_px) {
for x in border_px..px_w.saturating_sub(border_px) {
buf[(y * px_w + x) as usize] = 0;
}
}
let box_r = ((blur_radius * SHADOW_SCALE) / 3.0).ceil().max(0.0) as usize;
box_blur_3pass(&mut buf, px_w, px_h, box_r);
(buf, px_w, px_h)
}
pub(super) fn draw_inner_shadow(
content: &mut Content,
inner: &crate::model::InnerShadow,
x: f32,
y_bottom: f32,
width: f32,
height: f32,
xobj_name: Option<&str>,
) {
use pdf_writer::Name;
if let Some(name) = xobj_name {
content.save_state();
content.rect(x, y_bottom, width, height);
content.clip_nonzero();
content.end_path();
let sx = x + inner.offset_x;
let sy = y_bottom - inner.offset_y;
content.transform([width, 0.0, 0.0, height, sx, sy]);
content.x_object(Name(name.as_bytes()));
content.restore_state();
}
}
pub(super) fn draw_reflection(
content: &mut Content,
reflection: &crate::model::ImageReflection,
x: f32,
y_bottom: f32,
width: f32,
height: f32,
reflection_xobj_name: Option<&str>,
) {
use pdf_writer::Name;
if let Some(name) = reflection_xobj_name {
content.save_state();
let refl_y = y_bottom - reflection.distance;
content.transform([width, 0.0, 0.0, -height, x, refl_y]);
content.x_object(Name(name.as_bytes()));
content.restore_state();
}
}
pub(super) fn draw_image_glow(
content: &mut Content,
glow: &crate::model::ImageGlow,
x: f32,
y_bottom: f32,
width: f32,
height: f32,
glow_xobj_name: Option<&str>,
) {
use pdf_writer::Name;
if let Some(name) = glow_xobj_name {
let r = glow.radius;
let gx = x - r;
let gy = y_bottom - r;
let gw = width + 2.0 * r;
let gh = height + 2.0 * r;
content.save_state();
content.transform([gw, 0.0, 0.0, gh, gx, gy]);
content.x_object(Name(name.as_bytes()));
content.restore_state();
}
}