//! PDF page management
use lopdf;
use std::rc::Weak;
use std::cell::RefCell;
use indices::{PdfPageIndex, PdfLayerIndex};
use {
PdfResources, PdfLayer, PdfDocument, ExtendedGraphicsState, ExtendedGraphicsStateRef, Pattern, XObject, XObjectRef,
PdfLayerReference, PatternRef, Mm, Pt
};
/// PDF page
#[derive(Debug, Clone)]
pub struct PdfPage {
/// The index of the page in the document
pub(crate) index: usize,
/// page width in point
pub width: Pt,
/// page height in point
pub height: Pt,
/// Page layers
pub layers: Vec<PdfLayer>,
/// Resources used in this page
pub(crate) resources: PdfResources,
}
/// A "reference" to the current page, allows for inner mutability
/// but only inside this library
pub struct PdfPageReference {
/// A weak reference to the document, for inner mutability
pub document: Weak<RefCell<PdfDocument>>,
/// The index of the page this layer is on
pub page: PdfPageIndex,
}
impl PdfPage {
/// Create a new page, notice that width / height are in millimeter.
/// Page must contain at least one layer
#[inline]
pub fn new<S>(width: Mm,
height: Mm,
layer_name: S,
page_index: usize)
-> (Self, PdfLayerIndex) where S: Into<String>
{
let mut page = Self {
index: page_index,
width: width.into(),
height: height.into(),
layers: Vec::new(),
resources: PdfResources::new(),
};
let initial_layer = PdfLayer::new(layer_name);
page.layers.push(initial_layer);
let layer_index = page.layers.len() - 1;
(page, PdfLayerIndex(layer_index))
}
/// Iterates through the layers attached to this page and gathers all resources,
/// which the layers need. Then returns a dictonary with all the resources
/// (fonts, image XObjects, etc.)
///
/// While originally I had planned to build a system where you can reference contents
/// from all over the document, this turned out to be a problem, because each type had
/// to be handled differently (PDF weirdness)
///
/// `layers` should be a Vec with all layers (optional content groups) that were added
/// to the document on a document level, it should contain the indices of the layers
/// (they will be ignored, todo) and references to the actual OCG dictionaries
#[inline]
pub(crate) fn collect_resources_and_streams(self, doc: &mut lopdf::Document, layers: &[(usize, lopdf::Object)])
-> (lopdf::Dictionary, Vec<lopdf::Stream>)
{
let cur_layers = layers.iter().map(|l| l.1.clone()).collect();
let (resource_dictionary, ocg_refs) = self.resources.into_with_document_and_layers(doc, cur_layers);
// set contents
let mut layer_streams = Vec::<lopdf::Stream>::new();
use lopdf::content::Operation;
use lopdf::Object::*;
for (idx, mut layer) in self.layers.into_iter().enumerate() {
// push OCG and q to the beginning of the layer
layer.operations.insert(0, Operation::new("q".into(), vec![]));
layer.operations.insert(0, Operation::new("BDC".into(), vec![
Name("OC".into()),
Name(ocg_refs[idx].name.clone().into())
]));
// push OCG END and Q to the end of the layer stream
layer.operations.push(Operation::new("Q".into(), vec![]));
layer.operations.push(Operation::new("EMC".into(), vec![]));
// should end up looking like this:
// /OC /MC0 BDC
// q
// <layer stream content>
// Q
// EMC
let layer_stream = layer.into();
layer_streams.push(layer_stream);
}
(resource_dictionary, layer_streams)
}
/// Change the graphics state. Before this operation is done, you should save
/// the graphics state using the `save_graphics_state()` function. This will change the
/// current graphics state until the end of the page or until the page is reset to the
/// previous state.
/// Returns the old graphics state, in case it was overwritten, as well as a reference
/// to the currently active graphics state
#[inline]
pub fn add_graphics_state(&mut self, added_state: ExtendedGraphicsState)
-> ExtendedGraphicsStateRef
{
self.resources.add_graphics_state(added_state)
}
/// __STUB__: Adds a pattern to the pages resources
#[inline]
pub fn add_pattern(&mut self, pattern: Pattern)
-> PatternRef
{
self.resources.add_pattern(pattern)
}
/// __STUB__: Adds an XObject to the pages resources.
/// __NOTE__: Watch out for scaling. Your XObject might be invisible or only 1pt x 1pt big
#[inline]
pub fn add_xobject(&mut self, xobj: XObject)
-> XObjectRef
{
self.resources.add_xobject(xobj)
}
}
impl PdfPageReference {
/// Adds a page and returns the index of the currently added page
#[inline]
pub fn add_layer<S>(&self, layer_name: S)
-> PdfLayerReference where S: Into<String>
{
let doc = self.document.upgrade().unwrap();
let mut doc = doc.borrow_mut();
let page = &mut doc.pages[self.page.0];
let current_page_index = page.layers.len(); /* order is important */
let layer = PdfLayer::new(layer_name);
page.layers.push(layer);
let index = PdfLayerIndex(current_page_index);
PdfLayerReference {
document: self.document.clone(),
page: self.page,
layer: index,
}
}
/// Validates that a layer is present and returns a reference to it
#[inline]
#[cfg_attr(feature = "cargo-clippy", allow(no_effect))]
pub fn get_layer(&self, layer: PdfLayerIndex)
-> PdfLayerReference
{
let doc = self.document.upgrade().unwrap();
let doc = doc.borrow();
let _ = &doc.pages[self.page.0].layers[layer.0];
PdfLayerReference {
document: self.document.clone(),
page: self.page,
layer: layer,
}
}
}