use super::geometry::{PdfRect, RoundedRect};
use super::transforms::{PageContentTransform, PdfPaintSpace};
use super::{ImageRef, PdfWriter};
use crate::render::background::BackgroundPaintContext;
use crate::render::shading::{PdfRgb, ShadingEntry};
use crate::style::computed::BackgroundClip;
use crate::types::CornerRadii;
#[derive(Debug, Clone, Copy)]
pub(super) struct PdfBackgroundPaintContext {
pub(super) background: BackgroundPaintContext,
pub(super) paint_space: Option<PdfPaintSpace>,
}
impl PdfBackgroundPaintContext {
pub(super) const fn local(background: BackgroundPaintContext) -> Self {
Self {
background,
paint_space: None,
}
}
pub(super) const fn in_default_space(
background: BackgroundPaintContext,
paint_space: PdfPaintSpace,
) -> Self {
Self {
background,
paint_space: Some(paint_space),
}
}
}
pub(super) struct PdfBackgroundResources<'a> {
pub(super) writer: &'a mut PdfWriter,
pub(super) images: &'a mut Vec<ImageRef>,
pub(super) shadings: &'a mut Vec<ShadingEntry>,
pub(super) shading_counter: &'a mut usize,
pub(super) ext_gstates: Option<&'a mut Vec<(String, f32)>>,
}
impl<'a> PdfBackgroundResources<'a> {
pub(super) fn new(
writer: &'a mut PdfWriter,
images: &'a mut Vec<ImageRef>,
shadings: &'a mut Vec<ShadingEntry>,
shading_counter: &'a mut usize,
ext_gstates: Option<&'a mut Vec<(String, f32)>>,
) -> Self {
Self {
writer,
images,
shadings,
shading_counter,
ext_gstates,
}
}
}
pub(super) fn paint_solid_background(
content: &mut String,
color: crate::types::Color,
painting_box: RoundedRect,
ext_gstates: &mut Vec<(String, f32)>,
alpha_counter: &mut usize,
) {
let alpha = color.alpha();
if alpha <= 0.0 || painting_box.rect.is_empty() {
return;
}
let needs_alpha = alpha < 1.0;
if needs_alpha {
let name = format!("GSbackground{alpha_counter}");
*alpha_counter += 1;
ext_gstates.push((name.clone(), alpha));
content.push_str(&format!("/{name} gs\n"));
}
content.push_str(&PdfRgb::from(color).fill_operator());
content.push_str(&painting_box.path_or_rect());
content.push_str("f\n");
if needs_alpha {
content.push_str("/GSDefault gs\n");
}
}
pub(super) fn is_device_clippable_box_background(
clip: BackgroundClip,
radii: CornerRadii,
page_transform: PageContentTransform,
clip_rect: PdfRect,
) -> bool {
if !radii.is_zero() {
return false;
}
match clip {
BackgroundClip::Padding | BackgroundClip::Content => true,
BackgroundClip::Border => page_transform.page_edge_contact(clip_rect).any(),
BackgroundClip::Text => false,
}
}
pub(super) fn paint_device_clipped_css_solid(
content: &mut String,
page_transform: PageContentTransform,
paint: PdfRect,
clip: PdfRect,
color: PdfRgb,
) -> bool {
if paint.is_empty() || clip.is_empty() {
return false;
}
if paint == clip {
content.push_str(&color.fill_operator());
content.push_str(&paint.rect_path());
content.push_str("f\n");
return true;
}
let Some(device) = page_transform.device_space() else {
return false;
};
content.push_str("q\n");
let page_edges = page_transform.page_edge_contact(clip);
if page_edges.any() {
content.push_str(&device.layout_edge_correction_operator(page_edges));
}
content.push_str(&device.enter_operator());
content.push_str(&device.layout_rect(clip).rect_path());
content.push_str("W* n\n");
content.push_str(&device.css_page_operator());
content.push_str(&color.fill_operator());
content.push_str(&device.css_page_rect(paint).rect_path());
content.push_str("f\nQ\n");
true
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn equal_paint_and_clip_emit_a_direct_fill() {
let rect = PdfRect::new(0.0, 10.0, 20.0, 30.0);
let mut content = String::new();
assert!(paint_device_clipped_css_solid(
&mut content,
PageContentTransform::default(),
rect,
rect,
PdfRgb::from((0.25, 0.5, 0.75)),
));
assert_eq!(content, "0.25 0.5 0.75 rg\n0 10 20 30 re\nf\n");
}
}