use super::*;
#[derive(Clone, Copy)]
pub(crate) struct RenderOpts {
pub compress: bool,
pub jpeg_quality: u8,
pub auto_resize_images: bool,
pub raster_quality: crate::style::raster_quality::RasterQuality,
pub occlusion_cull: bool,
}
impl Default for RenderOpts {
fn default() -> Self {
Self {
compress: true,
jpeg_quality: DEFAULT_JPEG_QUALITY,
auto_resize_images: true,
raster_quality: crate::style::raster_quality::RasterQuality::default(),
occlusion_cull: false,
}
}
}
#[derive(Default)]
pub(crate) struct PdfWriter {
pub(super) objects: Vec<String>,
pub(super) binary_objects: std::collections::HashMap<usize, Vec<u8>>,
pub(super) layout_image_objects:
std::collections::HashMap<super::writer_images::LayoutImageCacheKey, usize>,
pub(super) page_ids: Vec<usize>,
pub(super) page_annotations: Vec<Vec<usize>>,
pub(super) page_images: Vec<Vec<ImageRef>>,
pub(super) page_ext_gstates: Vec<Vec<(String, f32)>>,
pub(super) page_shadings: Vec<Vec<ShadingEntry>>,
pub(super) conic_shadings: Vec<ConicShadingEntry>,
pub(super) custom_font_entries: Vec<CustomFontEntry>,
pub(super) soft_mask_gstates: Vec<PdfSoftMaskGState>,
pub(super) tiling_patterns: Vec<PdfTilingPatternEntry>,
pub(super) pdf_patterns: Vec<PdfPatternEntry>,
pub(super) local_forms: Vec<PdfLocalFormEntry>,
pub(super) svg_defs: crate::parser::svg::SvgDefs,
pub(super) opts: RenderOpts,
pub(super) page_content_transform: PageContentTransform,
pub(super) graphics_state: PdfGraphicsState,
}
#[derive(Debug, Clone, Copy, Default)]
pub(super) struct PdfGraphicsState {
local_to_layout: PdfMatrix,
box_paint_grid: BoxPaintGrid,
}
impl PdfWriter {
pub(super) fn new() -> Self {
Self::default()
}
pub(super) fn paint_matrix(&self, local: PdfMatrix) -> PdfMatrix {
self.graphics_state.local_to_layout * local
}
pub(super) fn has_active_paint_transform(&self) -> bool {
self.graphics_state.local_to_layout != PdfMatrix::IDENTITY
}
pub(super) fn current_content_space(&self) -> PdfContentSpace {
PdfContentSpace::page_css(self.page_content_transform)
}
pub(super) fn resolve_box_geometry(&self, layout: LayoutBoxGeometry) -> BoxPaintGeometry {
layout.for_paint(
self.page_content_transform,
self.graphics_state.box_paint_grid,
)
}
pub(super) fn enter_atomic_inline_paint_grid(
&mut self,
origin: PdfPoint,
) -> Option<BoxPaintGrid> {
matches!(self.graphics_state.box_paint_grid, BoxPaintGrid::Page).then(|| {
std::mem::replace(
&mut self.graphics_state.box_paint_grid,
BoxPaintGrid::AtomicInline(origin),
)
})
}
pub(super) fn restore_box_paint_grid(&mut self, grid: BoxPaintGrid) {
self.graphics_state.box_paint_grid = grid;
}
pub(super) fn transformed_paint_space(&self, page_bounds: PdfRect) -> Option<PdfPaintSpace> {
let local_to_layout = self.graphics_state.local_to_layout;
(local_to_layout != PdfMatrix::IDENTITY)
.then(|| PdfPaintSpace::new(local_to_layout, self.page_content_transform, page_bounds))
}
pub(super) fn enter_paint_transform(&mut self, local: PdfMatrix) -> PdfMatrix {
let prior = self.graphics_state.local_to_layout;
self.graphics_state.local_to_layout = prior * local;
prior
}
pub(super) fn restore_paint_transform(&mut self, prior: PdfMatrix) {
self.graphics_state.local_to_layout = prior;
}
pub(super) fn next_id(&self) -> usize {
self.objects.len() + 1
}
pub(crate) fn image_dimensions(&self, object_id: usize) -> Option<RasterDimensions> {
let object = self.objects.get(object_id.checked_sub(1)?)?;
let value_after = |name| {
object
.split_ascii_whitespace()
.zip(object.split_ascii_whitespace().skip(1))
.find_map(|(key, value)| (key == name).then(|| value.parse().ok()).flatten())
};
Some(RasterDimensions {
width: value_after("/Width")?,
height: value_after("/Height")?,
})
}
pub(super) fn add_conic_shading(
&mut self,
domain: PdfRect,
function: ConicShadingFunction,
) -> String {
let name = format!("ShConic{}", self.conic_shadings.len());
self.conic_shadings.push(ConicShadingEntry {
name: name.clone(),
domain,
function,
});
name
}
pub(super) fn add_transparency_group_form(
&mut self,
stream: String,
paint_box: PdfRect,
) -> ImageRef {
self.add_local_form(
stream,
paint_box,
Some("/Group << /Type /Group /S /Transparency /CS /DeviceRGB /I true /K false >>"),
)
}
pub(super) fn register_soft_mask(&mut self, name: String, form_id: usize) {
self.soft_mask_gstates.push(PdfSoftMaskGState {
name,
form_id,
mode: PdfSoftMaskMode::Luminosity,
});
}
pub(super) fn register_alpha_soft_mask(&mut self, name: String, form_id: usize) {
self.soft_mask_gstates.push(PdfSoftMaskGState {
name,
form_id,
mode: PdfSoftMaskMode::Alpha,
});
}
pub(super) fn add_plain_local_form(&mut self, stream: String, paint_box: PdfRect) -> ImageRef {
self.add_local_form(stream, paint_box, None)
}
fn add_local_form(
&mut self,
stream: String,
paint_box: PdfRect,
group: Option<&str>,
) -> ImageRef {
let form_id = self.next_id();
let [x, x1, y, y1] = paint_box.xy_domain();
let resources = PdfResourceUsage::from_stream(&stream);
let bytes = stream.into_bytes();
let group = group.map_or_else(String::new, |group| format!(" {group}"));
self.objects.push(format!(
"{form_id} 0 obj\n<< /Type /XObject /Subtype /Form /FormType 1 /BBox [{x} {y} {x1} {y1}]{group} /Resources __IP_LOCAL_FORM_RESOURCES__ /Length {len} >>\nstream\n",
len = bytes.len(),
));
self.binary_objects.insert(form_id, bytes);
self.local_forms
.push(PdfLocalFormEntry { form_id, resources });
ImageRef {
name: format!("Fm{form_id}"),
obj_id: form_id,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub(super) enum PdfSoftMaskMode {
#[default]
Luminosity,
Alpha,
}
impl PdfSoftMaskMode {
pub(super) const fn pdf_name(self) -> &'static str {
match self {
Self::Luminosity => "/Luminosity",
Self::Alpha => "/Alpha",
}
}
}
pub(super) struct PdfSoftMaskGState {
pub(super) name: String,
pub(super) form_id: usize,
pub(super) mode: PdfSoftMaskMode,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn nested_paint_scopes_compose_and_restore_resource_matrices() {
let mut writer = PdfWriter::new();
let parent = PdfMatrix::rotate_around(PdfPoint::new(40.0, 30.0), 0.5, 0.866_025_4);
let child = PdfMatrix::translate(PdfPoint::new(7.0, -3.0));
let resource = PdfMatrix::scale(PdfVector::new(0.75, -0.75));
let root = writer.enter_paint_transform(parent);
let parent_state = writer.enter_paint_transform(child);
assert_eq!(writer.paint_matrix(resource), parent * child * resource);
writer.restore_paint_transform(parent_state);
assert_eq!(writer.paint_matrix(resource), parent * resource);
writer.restore_paint_transform(root);
assert_eq!(writer.paint_matrix(resource), resource);
}
}