use super::*;
use crate::pdf::font::{
CHANGED, CharacterMap, Characters, FontProgram, GlyphCharacters, SubsetOutput, has_code,
mark_code,
};
use crate::pdf::{FontEmbedding, OpenTypeFont};
use std::io::Write;
const BMP_BITMAP_BYTES: usize = 8192;
const MAX_PAGE_FONTS: usize = 128;
#[derive(Clone, Copy)]
enum GlyphText {
Character,
Decoration,
Replacement(char),
Alias(char),
Mapped(char),
}
enum FontEncoding {
Unicode(Vec<u8>),
Mapped(CharacterMap),
}
impl FontEncoding {
fn characters(&self) -> Characters<'_> {
match self {
Self::Unicode(used) => Characters::Unicode(used),
Self::Mapped(map) => Characters::Mapped(&map.entries),
}
}
fn code(&mut self, glyph: char, text: GlyphText, limits: &Limits) -> Result<u16> {
match self {
Self::Unicode(used) => {
if matches!(text, GlyphText::Mapped(_)) {
return Err(Error::invalid(
"separate glyph/text selection requires a mapped PDF font",
));
}
mark_code(used, glyph as usize);
Ok(glyph as u16)
}
Self::Mapped(map) => map.code(
GlyphCharacters {
glyph,
text: match text {
GlyphText::Mapped(text) => text,
_ => glyph,
},
},
limits,
),
}
}
}
pub struct FontObject {
object: ObjectId,
slot: usize,
characters: Vec<u8>,
horizontal_bands: [Option<char>; 5],
}
impl FontObject {
pub(crate) fn has_horizontal_bands(&self, character: char) -> bool {
self.horizontal_bands.contains(&Some(character))
}
pub(crate) fn detect_horizontal_bands<S: RangedSource, C: Cancellation>(
&mut self,
font: &mut OpenTypeFont<'_, S>,
characters: &[char],
limits: &Limits,
cancellation: &C,
) -> Result<()> {
if characters.len() > self.horizontal_bands.len() {
return Err(Error::invalid("too many ornament glyph probes"));
}
for (slot, character) in characters.iter().enumerate() {
if self.supports(*character)
&& font.horizontal_bands(*character, limits, cancellation)?
{
self.horizontal_bands[slot] = Some(*character);
}
}
Ok(())
}
pub fn supports(&self, character: char) -> bool {
let code = character as usize;
code <= 0xffff && has_code(&self.characters, code)
}
}
pub(super) struct PendingFont {
ids: [ObjectId; 5],
mapping: Option<[ObjectId; 2]>,
name: String,
fingerprint: [u8; 32],
encoding: FontEncoding,
embedded: bool,
}
struct FontStream<'w, 'a, W: Write, C: Cancellation> {
writer: &'w mut PdfWriter<'a, W, C>,
deflate: &'w mut Deflate,
}
impl<'w, 'a, W: Write, C: Cancellation> FontStream<'w, 'a, W, C> {
fn new(writer: &'w mut PdfWriter<'a, W, C>, deflate: &'w mut Deflate) -> Self {
Self { writer, deflate }
}
fn finish(self) -> Result<()> {
self.deflate.finish(self.writer)?;
self.writer.end_stream()
}
}
impl<W: Write, C: Cancellation> SubsetOutput for FontStream<'_, '_, W, C> {
fn put(&mut self, bytes: &[u8]) -> Result<()> {
self.deflate.put(self.writer, bytes)
}
}
pub(super) fn ensure_fonts_embedded(fonts: &[PendingFont]) -> Result<()> {
if fonts.iter().any(|font| !font.embedded) {
return Err(Error::invalid(
"PDF font was added but its subset was not embedded",
));
}
Ok(())
}
fn changed() -> Error {
Error::invalid(CHANGED)
}
impl<'a, W: Write, C: Cancellation> PdfDocument<'a, W, C> {
pub fn add_font<S: RangedSource>(&mut self, font: &OpenTypeFont<'_, S>) -> Result<FontObject> {
self.register_font(font, false)
}
pub fn add_mapped_font<S: RangedSource>(
&mut self,
font: &OpenTypeFont<'_, S>,
) -> Result<FontObject> {
self.register_font(font, true)
}
fn register_font<S: RangedSource>(
&mut self,
font: &OpenTypeFont<'_, S>,
mapped: bool,
) -> Result<FontObject> {
self.ensure_image_page_intact()?;
let name = font.postscript_name()?.replace('#', "#23");
let face = font.face()?;
let mut characters = self.bitmap("PDF font character bitmap")?;
for code in 0..65536_u32 {
if bmp_glyph(&face, code)?.is_some() {
mark_code(&mut characters, code as usize);
}
}
let encoding = if mapped {
FontEncoding::Mapped(CharacterMap::new(u16::MAX - u16::from(font.is_cff())))
} else {
FontEncoding::Unicode(self.bitmap("PDF font used-character bitmap")?)
};
let refused = self
.limits
.allocation_refused("PDF fonts", size_of::<PendingFont>() as u64);
reserve(&mut self.fonts, 1, refused)?;
let cff = font.is_cff();
self.writer.prepare_objects(5 + 2 * usize::from(!cff))?;
let mut next = || self.writer.reserve_object();
let ids = [next()?, next()?, next()?, next()?, next()?];
let mapping = if cff { None } else { Some([next()?, next()?]) };
self.fonts.push(PendingFont {
ids,
mapping,
name,
fingerprint: font.fingerprint(),
encoding,
embedded: false,
});
Ok(FontObject {
object: ids[4],
slot: self.fonts.len() - 1,
characters,
horizontal_bands: [None; 5],
})
}
fn font(&self, handle: &FontObject) -> Result<&PendingFont> {
self.fonts
.get(handle.slot)
.ok_or(Error::invalid("PDF font was not added to this document"))
}
fn count_font_input(&mut self, bytes: u64) -> Result<()> {
let total = self.input_bytes_read.saturating_add(bytes);
self.limits.check_input_size(total)?;
self.input_bytes_read = total;
Ok(())
}
fn take_deflate(&mut self) -> Result<Deflate> {
self.deflate
.take()
.map_or_else(|| Deflate::new(self.limits), Ok)
}
fn bitmap(&self, resource: &'static str) -> Result<Vec<u8>> {
self.limits.check_allocation(BMP_BITMAP_BYTES as u64)?;
let mut bitmap = Vec::new();
let refused = self
.limits
.allocation_refused(resource, BMP_BITMAP_BYTES as u64);
reserve_exact(&mut bitmap, BMP_BITMAP_BYTES, refused)?;
bitmap.resize(BMP_BITMAP_BYTES, 0);
Ok(bitmap)
}
pub fn embed_font<S: RangedSource>(
&mut self,
handle: &FontObject,
font: &mut OpenTypeFont<'_, S>,
) -> Result<()> {
self.embed_font_with_embedding(handle, font, FontEmbedding::Subset)
}
pub fn embed_font_with_embedding<S: RangedSource>(
&mut self,
handle: &FontObject,
font: &mut OpenTypeFont<'_, S>,
embedding: FontEmbedding,
) -> Result<()> {
self.ensure_image_page_intact()?;
self.writer.ensure_idle()?;
let pending = self.font(handle)?;
if pending.embedded {
return Err(Error::invalid("PDF font subset is already embedded"));
}
if font.fingerprint() != pending.fingerprint {
return Err(changed());
}
let [file, file_length, descriptor, descendant, object] = pending.ids;
let mapping = pending.mapping;
let before = font.subset_bytes_read();
let used = pending.encoding.characters();
let plan = if embedding == FontEmbedding::Full && used.codes().next().is_some() {
FontProgram::Full(font.plan_full(MAX_PDF_INTEGER, self.limits, self.cancellation)?)
} else {
font.subset(used, MAX_PDF_INTEGER, self.limits, self.cancellation)?
};
self.count_font_input(font.subset_bytes_read() - before)?;
let pending = &self.fonts[handle.slot];
let name = plan.name(&pending.name);
let mut deflate = self.take_deflate()?;
let before = font.subset_bytes_read();
self.image_page_failed = true;
let unicode = self.font_unicode_cmap(handle.slot, &mut deflate)?;
let (program, file_key, subtype) = match &plan {
FontProgram::TrueType(_) | FontProgram::Full(_) => (
format!("/Length1 {}", plan.length()),
"FontFile2",
"CIDFontType2",
),
FontProgram::Cff(_) => (
"/Subtype /CIDFontType0C".to_owned(),
"FontFile3",
"CIDFontType0",
),
};
self.writer.begin_stream(
file,
file_length,
format!("{program}\n/Filter /FlateDecode").as_bytes(),
)?;
let mut stream = FontStream::new(&mut self.writer, &mut deflate);
font.write(&plan, &mut stream, self.limits, self.cancellation)?;
stream.finish()?;
self.count_font_input(font.subset_bytes_read() - before)?;
let face = font.face()?;
let mut cid_map = String::new();
if let Some([mapping, mapping_length]) = mapping {
self.writer
.begin_stream(mapping, mapping_length, b"/Filter /FlateDecode")?;
let mut stream = FontStream::new(&mut self.writer, &mut deflate);
let used = self.fonts[handle.slot].encoding.characters();
let subset = if let FontProgram::TrueType(subset) = &plan {
Some(subset)
} else {
None
};
let last = used.codes().next_back();
for code in last.map_or(0..0, |last| 0..usize::from(last) + 1) {
let glyph = used.glyph(code).map_or(0, |character| {
subset.map_or_else(
|| face.glyph_index(character).map_or(0, |id| id.0),
|plan| plan.glyph(&face, character),
)
});
stream.put(&glyph.to_be_bytes())?;
}
stream.finish()?;
cid_map = format!(" /CIDToGIDMap {} 0 R", mapping.number());
}
let scale = 1000.0 / f64::from(face.units_per_em());
let bbox = face.global_bounding_box();
let flags = 4 | u32::from(face.is_monospaced()) | if face.is_italic() { 64 } else { 0 };
let desc = format!(
"<< /Type /FontDescriptor /FontName /{name} /Flags {flags} /FontBBox [{} {} {} {}] /ItalicAngle {} /Ascent {} /Descent {} /CapHeight {} /StemV 0 /{file_key} {} 0 R >>",
f64::from(bbox.x_min) * scale,
f64::from(bbox.y_min) * scale,
f64::from(bbox.x_max) * scale,
f64::from(bbox.y_max) * scale,
face.italic_angle(),
f64::from(face.ascender()) * scale,
f64::from(face.descender()) * scale,
f64::from(face.capital_height().unwrap_or(face.ascender())) * scale,
file.number()
);
self.writer.write_object(descriptor, desc.as_bytes())?;
self.writer.begin_object(descendant)?;
self.writer.write_bytes(format!("<< /Type /Font /Subtype /{subtype} /BaseFont /{name} /CIDSystemInfo << /Registry (Adobe) /Ordering (Identity) /Supplement 0 >> /FontDescriptor {} 0 R{cid_map} /DW 1000 /W [", descriptor.number()).as_bytes())?;
let used = self.fonts[handle.slot].encoding.characters();
for first in (0..65536).step_by(256) {
let mut drawn = (first..first + 256).filter(|code| used.contains(*code));
let Some(low) = drawn.next() else {
continue;
};
let high = drawn.next_back().unwrap_or(low);
let mut widths = format!(" {low} [");
for code in low..=high {
let glyph = used
.glyph(code)
.map(|character| bmp_glyph(&face, character as u32))
.transpose()?
.flatten();
let width = glyph.map_or(1000.0, |glyph| f64::from(glyph.advance) * scale);
widths += &format!(" {width}");
}
widths.push_str(" ]");
self.writer.write_bytes(widths.as_bytes())?;
}
self.writer.write_bytes(b" ] >>")?;
self.writer.end_object()?;
self.writer.write_object(object, format!("<< /Type /Font /Subtype /Type0 /BaseFont /{name} /Encoding /Identity-H /DescendantFonts [{} 0 R] /ToUnicode {} 0 R >>", descendant.number(), unicode.number()).as_bytes())?;
self.fonts[handle.slot].embedded = true;
self.deflate = Some(deflate);
self.image_page_failed = false;
Ok(())
}
fn unicode_cmap(&mut self, deflate: &mut Deflate) -> Result<ObjectId> {
if let Some(unicode) = self.to_unicode {
return Ok(unicode);
}
self.writer.prepare_objects(2)?;
let unicode = self.writer.reserve_object()?;
let length = self.writer.reserve_object()?;
self.writer
.begin_stream(unicode, length, b"/Filter /FlateDecode")?;
let mut stream = FontStream::new(&mut self.writer, deflate);
let mut cmap = String::from(
"/CIDInit /ProcSet findresource begin\n12 dict begin\nbegincmap\n/CIDSystemInfo << /Registry (Adobe) /Ordering (UCS) /Supplement 0 >> def\n/CMapName /CajUnicode def\n/CMapType 2 def\n1 begincodespacerange\n<0000> <FFFF>\nendcodespacerange\n",
);
for first in (0..256_u32).step_by(32) {
let blocks = (first..first + 32).filter(|high| !(0xd8..=0xdf).contains(high));
cmap += &format!("{} beginbfrange\n", blocks.clone().count());
for high in blocks {
cmap += &format!("<{high:02X}00> <{high:02X}FF> <{high:02X}00>\n");
}
cmap += "endbfrange\n";
}
cmap += "endcmap\nCMapName currentdict /CMap defineresource pop\nend\nend\n";
stream.put(cmap.as_bytes())?;
stream.finish()?;
self.to_unicode = Some(unicode);
Ok(unicode)
}
fn font_unicode_cmap(&mut self, slot: usize, deflate: &mut Deflate) -> Result<ObjectId> {
let FontEncoding::Mapped(map) = &self.fonts[slot].encoding else {
return self.unicode_cmap(deflate);
};
self.writer.prepare_objects(2)?;
let unicode = self.writer.reserve_object()?;
let length = self.writer.reserve_object()?;
self.writer
.begin_stream(unicode, length, b"/Filter /FlateDecode")?;
let mut stream = FontStream::new(&mut self.writer, deflate);
stream.put(b"/CIDInit /ProcSet findresource begin\n12 dict begin\nbegincmap\n/CIDSystemInfo << /Registry (Adobe) /Ordering (UCS) /Supplement 0 >> def\n/CMapName /CajMappedUnicode def\n/CMapType 2 def\n1 begincodespacerange\n<0000> <FFFF>\nendcodespacerange\n")?;
for (block, entries) in map.entries.chunks(100).enumerate() {
stream.put(format!("{} beginbfchar\n", entries.len()).as_bytes())?;
for (index, pair) in entries.iter().enumerate() {
let cid = block * 100 + index + 1;
let mut line = format!("<{cid:04X}> <");
for unit in pair.text.encode_utf16(&mut [0; 2]) {
line += &format!("{unit:04X}");
}
line += ">\n";
stream.put(line.as_bytes())?;
}
stream.put(b"endbfchar\n")?;
}
stream.put(b"endcmap\nCMapName currentdict /CMap defineresource pop\nend\nend\n")?;
stream.finish()?;
Ok(unicode)
}
pub fn begin_content_page<'d, 'r>(
&'d mut self,
page: PageSpec,
fonts: &'r [&'r FontObject],
images: &'r [ImageObject],
) -> Result<ContentPageWriter<'d, 'a, 'r, W, C>> {
self.ensure_image_page_intact()?;
self.writer.ensure_idle()?;
let width = pdf_page_number(page.width_points)?;
let height = pdf_page_number(page.height_points)?;
if fonts.len() > MAX_PAGE_FONTS || images.len() > MAX_PAGE_IMAGE_PLACEMENTS {
return Err(Error::invalid("too many resources for a PDF content page"));
}
for font in fonts {
if self.font(font)?.embedded {
return Err(Error::invalid("PDF font subset is already embedded"));
}
}
self.check_next_page()?;
let deflate = self.take_deflate()?;
self.reserve_page_index_slot()?;
self.prepare_page_objects()?;
self.image_page_failed = true;
self.ensure_leaf()?;
let content = self.writer.reserve_object()?;
let length = self.writer.reserve_object()?;
let page = self.writer.reserve_object()?;
self.writer
.begin_stream(content, length, b"/Filter /FlateDecode")?;
Ok(ContentPageWriter {
deflate,
document: self,
fonts,
images,
width,
height,
content,
page,
multiply_images: false,
failed: false,
})
}
}
fn bmp_glyph(
face: &xberg_ttf_parser::Face<'_>,
code: u32,
) -> Result<Option<crate::pdf::FontGlyph>> {
let Some(character) = char::from_u32(code) else {
return Ok(None);
};
let Some(id) = face.glyph_index(character).filter(|id| id.0 != 0) else {
return Ok(None);
};
if id.0 >= face.number_of_glyphs() {
return Err(Error::invalid(
"font character map references an invalid glyph",
));
}
let advance = face
.glyph_hor_advance(id)
.ok_or(Error::invalid("font glyph has no horizontal advance"))?;
Ok(Some(crate::pdf::FontGlyph { id: id.0, advance }))
}
pub struct ContentPageWriter<'d, 'a, 'r, W: Write, C: Cancellation> {
document: &'d mut PdfDocument<'a, W, C>,
fonts: &'r [&'r FontObject],
images: &'r [ImageObject],
width: String,
height: String,
content: ObjectId,
page: ObjectId,
deflate: Deflate,
multiply_images: bool,
failed: bool,
}
impl<W: Write, C: Cancellation> ContentPageWriter<'_, '_, '_, W, C> {
fn emit(&mut self, bytes: &[u8]) -> Result<()> {
self.deflate.put(&mut self.document.writer, bytes)
}
fn start_draw(&mut self) -> Result<()> {
if self.failed {
return Err(Error::invalid(
"PDF content page cannot continue after a failed draw",
));
}
self.failed = true;
self.document.writer.write_stream_bytes(&[])
}
pub fn glyph(&mut self, font: usize, character: char, transform: [f64; 6]) -> Result<()> {
self.draw_glyph(font, character, transform, None, None, GlyphText::Character)
}
pub fn glyph_with_gray(
&mut self,
font: usize,
character: char,
transform: [f64; 6],
gray: u8,
) -> Result<()> {
self.draw_glyph(
font,
character,
transform,
Some(gray),
None,
GlyphText::Character,
)
}
pub fn mapped_glyph(
&mut self,
font: usize,
glyph_character: char,
text: char,
transform: [f64; 6],
gray: u8,
) -> Result<()> {
self.draw_glyph(
font,
glyph_character,
transform,
Some(gray),
None,
GlyphText::Mapped(text),
)
}
pub(crate) fn glyph_with_replacement_text(
&mut self,
font: usize,
alias: char,
transform: [f64; 6],
gray: u8,
character: char,
) -> Result<()> {
self.draw_glyph(
font,
alias,
transform,
Some(gray),
None,
GlyphText::Replacement(character),
)
}
pub(crate) fn glyph_with_alias_text(
&mut self,
font: usize,
alias: char,
transform: [f64; 6],
gray: u8,
character: char,
) -> Result<()> {
self.draw_glyph(
font,
alias,
transform,
Some(gray),
None,
GlyphText::Alias(character),
)
}
pub fn glyph_with_clip(
&mut self,
font: usize,
character: char,
transform: [f64; 6],
clip: [f64; 4],
) -> Result<()> {
self.draw_glyph(
font,
character,
transform,
None,
Some(clip),
GlyphText::Character,
)
}
pub fn decoration_glyph(
&mut self,
font: usize,
character: char,
transform: [f64; 6],
clip: [f64; 4],
) -> Result<()> {
self.draw_glyph(
font,
character,
transform,
None,
Some(clip),
GlyphText::Decoration,
)
}
fn draw_glyph(
&mut self,
font: usize,
character: char,
transform: [f64; 6],
gray: Option<u8>,
clip: Option<[f64; 4]>,
text: GlyphText,
) -> Result<()> {
self.start_draw()?;
let resource = self
.fonts
.get(font)
.ok_or(Error::invalid("PDF page font index is out of range"))?;
if !resource.supports(character) {
return Err(Error::invalid(
"PDF font has no supported BMP glyph for the character",
));
}
let code = self.document.fonts[resource.slot].encoding.code(
character,
text,
self.document.limits,
)?;
let matrix = decimals(&transform)?;
if gray.is_some() || clip.is_some() {
self.emit(b"q ")?;
}
if let Some([left, bottom, width, height]) = clip {
decimals(&[left, bottom, width, height, left + width, bottom + height])?;
if width <= 0.0 || height <= 0.0 {
return Err(Error::invalid(
"PDF glyph clipping extents must be positive",
));
}
self.emit(format!("{left} {bottom} {width} {height} re W n\n").as_bytes())?;
}
if let Some(gray) = gray {
self.emit(format!("{:.6} g\n", f64::from(gray) / 255.0).as_bytes())?;
}
match text {
GlyphText::Character | GlyphText::Mapped(_) => (),
GlyphText::Decoration => {
self.emit(b"/Artifact BMC\n/Span << /ActualText () >> BDC\n")?
}
GlyphText::Replacement(character) | GlyphText::Alias(character) => {
self.emit(b"/Span << /ActualText <FEFF")?;
for unit in character.encode_utf16(&mut [0; 2]) {
self.emit(format!("{unit:04X}").as_bytes())?;
}
self.emit(b"> >> BDC\n")?;
}
}
self.emit(format!("BT /F{font} 1 Tf\n").as_bytes())?;
self.emit(matrix.as_bytes())?;
self.emit(format!(" Tm <{code:04X}> Tj ET\n").as_bytes())?;
match text {
GlyphText::Character | GlyphText::Mapped(_) => (),
GlyphText::Decoration => self.emit(b"EMC\nEMC\n")?,
GlyphText::Replacement(_) | GlyphText::Alias(_) => {
self.emit(b"EMC\n")?;
self.document.substituted_glyphs +=
u64::from(matches!(text, GlyphText::Replacement(_)));
}
}
if gray.is_some() || clip.is_some() {
self.emit(b"Q\n")?;
}
self.failed = false;
Ok(())
}
pub fn image(&mut self, index: usize, transform: [f64; 6]) -> Result<()> {
self.draw_image(index, transform, false)
}
pub(crate) fn image_is_bilevel(&self, index: usize) -> bool {
self.images.get(index).is_some_and(|image| image.bilevel)
}
pub(crate) fn bilevel_overlay(&mut self, index: usize, transform: [f64; 6]) -> Result<()> {
self.draw_image(index, transform, true)
}
fn draw_image(&mut self, index: usize, transform: [f64; 6], overlay: bool) -> Result<()> {
self.start_draw()?;
if index >= self.images.len() {
return Err(Error::invalid("PDF page image index is out of range"));
}
if overlay && !self.image_is_bilevel(index) {
return Err(Error::invalid(
"PDF image overlay requires a bilevel resource",
));
}
let matrix = decimals(&transform)?;
self.emit(b"q\n")?;
if overlay {
self.multiply_images = true;
self.emit(b"/BilevelOverlay gs\n")?;
}
self.emit(matrix.as_bytes())?;
self.emit(format!(" cm /Im{index} Do Q\n").as_bytes())?;
self.failed = false;
Ok(())
}
pub fn segment(&mut self, from: [f64; 2], to: [f64; 2], width: f64) -> Result<()> {
self.start_draw()?;
decimals(&[from[0], from[1], to[0], to[1], width, 0.0])?;
if width < 0.0 {
return Err(Error::invalid("PDF stroke width must be nonnegative"));
}
let command = format!(
"q {width} w {} {} m {} {} l S Q\n",
from[0], from[1], to[0], to[1]
);
self.emit(command.as_bytes())?;
self.failed = false;
Ok(())
}
pub(crate) fn stroke_polyline(
&mut self,
points: &[[f64; 2]],
width: f64,
gray: u8,
) -> Result<()> {
self.start_draw()?;
if !(2..=8).contains(&points.len()) || width < 0.0 {
return Err(Error::invalid(
"PDF polyline requires two to eight points and nonnegative width",
));
}
let width = decimals(&[width])?;
self.emit(b"q 0 J 0 j 10 M ")?;
self.emit(width.as_bytes())?;
self.emit(format!(" w {:.6} G\n", f64::from(gray) / 255.0).as_bytes())?;
for (index, point) in points.iter().enumerate() {
self.emit(decimals(point)?.as_bytes())?;
let operator = if index == 0 { b" m\n" } else { b" l\n" };
self.emit(operator)?;
}
self.emit(b"S Q\n")?;
self.failed = false;
Ok(())
}
pub fn fill_polygon(&mut self, points: &[[f64; 2]]) -> Result<()> {
self.start_draw()?;
if !(3..=8).contains(&points.len()) {
return Err(Error::invalid(
"PDF polygon must have three to eight vertices",
));
}
self.emit(b"q 0 g\n")?;
for (index, point) in points.iter().enumerate() {
self.emit(decimals(point)?.as_bytes())?;
let operator = if index == 0 { b" m\n" } else { b" l\n" };
self.emit(operator)?;
}
self.emit(b"h f Q\n")?;
self.failed = false;
Ok(())
}
pub fn finish(self) -> Result<u32> {
self.finish_with_links(std::iter::empty())
}
pub(crate) fn finish_with_links<'u>(
self,
links: impl Iterator<Item = LinkAnnotation<'u>> + Clone,
) -> Result<u32> {
if self.failed {
return Err(Error::invalid(
"PDF content page cannot finish after a failed draw",
));
}
let document = self.document;
let mut deflate = self.deflate;
deflate.finish(&mut document.writer)?;
document.writer.end_stream()?;
document.deflate = Some(deflate);
let annotations = document.write_link_annotations(links)?;
let parent = document
.leaf
.as_ref()
.ok_or(Error::invalid("PDF page-tree leaf is missing"))?
.id;
document.writer.begin_object(self.page)?;
document.writer.write_bytes(
format!(
"<< /Type /Page /Parent {} 0 R /MediaBox [0 0 {} {}] /Resources << /Font <<",
parent.number(),
self.width,
self.height
)
.as_bytes(),
)?;
for (index, font) in self.fonts.iter().enumerate() {
document
.writer
.write_bytes(format!(" /F{index} {} 0 R", font.object.number()).as_bytes())?;
}
document.writer.write_bytes(b" >> /XObject <<")?;
for (index, image) in self.images.iter().enumerate() {
document
.writer
.write_bytes(format!(" /Im{index} {} 0 R", image.object.number()).as_bytes())?;
}
if self.multiply_images {
document.writer.write_bytes(
b" >> /ExtGState << /BilevelOverlay << /Type /ExtGState /BM /Multiply >>",
)?;
}
document
.writer
.write_bytes(format!(" >> >> /Contents {} 0 R", self.content.number()).as_bytes())?;
if let Some(annotations) = annotations {
document
.writer
.write_bytes(format!(" /Annots {} 0 R", annotations.number()).as_bytes())?;
}
document.writer.write_bytes(b" >>")?;
document.writer.end_object()?;
let index = document.register_page(self.page)?;
document.image_page_failed = false;
Ok(index)
}
}
#[cfg(test)]
mod tests;