pub struct EditDoc<'a> { /* private fields */ }Expand description
A document plus the edits made to it.
Cheap to create and to drop: it borrows the base and owns only what changed. Cloning copies the overlay’s map and shares its objects, so a save that must not disturb the session works on a clone.
Implementations§
Source§impl EditDoc<'_>
impl EditDoc<'_>
Sourcepub fn draw_page(
&mut self,
index: impl Into<PageIndex>,
limits: &Limits,
body: impl FnOnce(&mut Canvas<'_, '_>),
) -> Result<(), Error>
pub fn draw_page( &mut self, index: impl Into<PageIndex>, limits: &Limits, body: impl FnOnce(&mut Canvas<'_, '_>), ) -> Result<(), Error>
Draw on page index, appending what body draws as one new content
stream.
The canvas’s coordinate space is the page as displayed — see
Canvas. The stream is wrapped in q/Q and appended to the
page’s /Contents array, so the page’s own graphics state cannot leak
into the drawing and the drawing’s cannot leak into the page. The
page’s existing streams are not rewritten, which is why drawing on
a page costs none of the regeneration losses
PageEdit documents.
§The resource-merging rule
Fonts, images and graphics states the drawing used are merged into the
page’s /Resources under names of this crate’s own PdfrumC<n>
series, each checked against the names the page already holds, so a
merged name can collide with neither the producer’s nor
pdfrum-edit’s FX*. A /Resources the page shares with another
page is copied before it is written to, so drawing on one page cannot
change another.
use pdfrum::{Color, Document, Point, SaveOptions, StandardFont};
let doc = Document::open("tests/fixtures/hello_world.pdf")?;
let mut edit = doc.edit();
let font = edit.standard_font(StandardFont::Helvetica)?;
edit.draw_page(0, |c| {
c.text("drawn", &font, 12.0, Point::new(40.0, 40.0), Color::BLACK);
})?;
let mut bytes = Vec::new();
edit.write_to(&mut bytes, &SaveOptions::default())?;
let saved = Document::from_bytes(bytes)?;
assert!(saved.page(0)?.text().to_string().contains("drawn"));§Errors
Whatever body refused to draw — a character the font has no glyph
for, most often — and Error::InlinePage for a page with no
object of its own. Nothing is written when the drawing failed.
Sourcepub fn draw_page_with_fonts(
&mut self,
index: impl Into<PageIndex>,
limits: &Limits,
fonts: &SvgFonts,
body: impl FnOnce(&mut Canvas<'_, '_>),
) -> Result<(), Error>
pub fn draw_page_with_fonts( &mut self, index: impl Into<PageIndex>, limits: &Limits, fonts: &SvgFonts, body: impl FnOnce(&mut Canvas<'_, '_>), ) -> Result<(), Error>
EditDoc::draw_page with the faces an ingested SVG’s <text> is set
in; without them a <text> draws nothing and is reported instead.
§Errors
Sourcepub fn draw_pages(
&mut self,
limits: &Limits,
body: impl FnMut(&mut Canvas<'_, '_>),
) -> Result<(), Error>
pub fn draw_pages( &mut self, limits: &Limits, body: impl FnMut(&mut Canvas<'_, '_>), ) -> Result<(), Error>
Draw on every page, one canvas each.
The closure runs once per page in order and is handed that page’s own
canvas, so Canvas::size and Canvas::page are the page’s. A page
written inline in its parent’s /Kids is skipped rather than refused:
a whole-document watermark should not fail because one page of a
thousand cannot carry it.
use pdfrum::{Color, Document, SaveOptions, Stroke, Point};
let doc = Document::open("tests/fixtures/hello_world_2_pages.pdf")?;
let mut edit = doc.edit();
edit.draw_pages(|c| {
let y = c.size().height - 40.0;
c.line(Point::new(40.0, y), Point::new(c.size().width - 40.0, y),
Stroke::new(Color::BLACK, 0.5));
})?;
let mut bytes = Vec::new();
edit.write_to(&mut bytes, &SaveOptions::default())?;
assert!(bytes.starts_with(b"%PDF-"));§Errors
As EditDoc::draw_page, for the first page whose drawing failed.
Sourcepub fn draw_pages_with_fonts(
&mut self,
limits: &Limits,
fonts: &SvgFonts,
body: impl FnMut(&mut Canvas<'_, '_>),
) -> Result<(), Error>
pub fn draw_pages_with_fonts( &mut self, limits: &Limits, fonts: &SvgFonts, body: impl FnMut(&mut Canvas<'_, '_>), ) -> Result<(), Error>
EditDoc::draw_pages with the faces an ingested SVG’s <text> is
set in.
§Errors
As EditDoc::draw_page, for the first page whose drawing failed.
Source§impl<'a> EditDoc<'a>
impl<'a> EditDoc<'a>
Sourcepub fn add(&mut self, obj: Object) -> ObjRef
pub fn add(&mut self, obj: Object) -> ObjRef
Add obj as a new indirect object, returning the reference that names
it. Generation is always 0: the writer emits nothing else.
Sourcepub fn replace(&mut self, r: ObjRef, obj: Object)
pub fn replace(&mut self, r: ObjRef, obj: Object)
Replace what r names.
The base is untouched: the overlay simply answers first from now on.
Sourcepub fn remove(&mut self, r: ObjRef)
pub fn remove(&mut self, r: ObjRef)
Remove what r names. It then resolves as null and is not written.
Sourcepub fn is_removed(&self, num: u32) -> bool
pub fn is_removed(&self, num: u32) -> bool
Whether num was removed.
Sourcepub fn edited(&self) -> impl Iterator<Item = (u32, &Arc<Object>)>
pub fn edited(&self) -> impl Iterator<Item = (u32, &Arc<Object>)>
The overlay’s objects in ascending number order — everything this editing session added or replaced.
Sourcepub fn last_object_number(&self) -> u32
pub fn last_object_number(&self) -> u32
The highest object number in play, across the base and the overlay.
Sourcepub fn page_state(
&self,
index: PageIndex,
) -> Result<Option<(ObjRef, Dict, Dict)>, Error>
pub fn page_state( &self, index: PageIndex, ) -> Result<Option<(ObjRef, Dict, Dict)>, Error>
A page’s dictionary as the session’s edits leave it, with the reference the writer replaces and the resources the page reaches.
Read through the overlay, not the base, so an earlier edit of the same
page — a rotation, a stamp — is what a later content rewrite builds on.
None for a page written inline in its parent’s /Kids, which has no
object to replace.
§Errors
When index is outside the document.
Sourcepub fn apply_page(
&mut self,
page: &PageEdit,
shared: &ShareCounts,
) -> Result<(), Error>
pub fn apply_page( &mut self, page: &PageEdit, shared: &ShareCounts, ) -> Result<(), Error>
Turn one page edit into replacement objects on the session.
shared is shared_objects over the session, computed
once by the caller for however many pages it applies.
§Errors
When the page’s index is outside the document.
Source§impl EditDoc<'_>
impl EditDoc<'_>
Sourcepub fn embed_font(
&mut self,
program: &[u8],
encoding: FontEncoding,
) -> Result<EmbeddedFont, Error>
pub fn embed_font( &mut self, program: &[u8], encoding: FontEncoding, ) -> Result<EmbeddedFont, Error>
Embed program as a new /Font in this document.
The program kind is detected from its bytes: an OTTO tag is
OpenType/CFF, 0x00010000 / true / typ1 is TrueType, and a PFB
marker or %!PS banner is Type 1.
§Errors
Error::UnrecognisedFontProgram when no backend can read the bytes,
Error::EmptyFontProgram when the face declares no glyphs.
Sourcepub fn embed_cid_font(
&mut self,
program: &[u8],
to_unicode: &str,
cid_to_gid: &[u8],
) -> Result<EmbeddedFont, Error>
pub fn embed_cid_font( &mut self, program: &[u8], to_unicode: &str, cid_to_gid: &[u8], ) -> Result<EmbeddedFont, Error>
Embed program as a /Type0 + /CIDFontType2 font whose
/ToUnicode and /CIDToGIDMap are the caller’s, not generated
from the program’s own cmap.
The dictionary chain is the one Self::embed_font builds for
FontEncoding::Composite, with three differences, all of them the
point of the call:
/CIDToGIDMapis a stream holdingcid_to_gidverbatim: one big-endianu16glyph index per CID, indexed by CID (ISO 32000-1 §9.7.4.2).Self::embed_fontwrites no/CIDToGIDMapat all, which means/Identity— CID is GID./Wis computed per CID from that map rather than per GID from the cmap: each two-byte entry names the glyph whose advance that CID gets, so the array is dense from CID 0 and holds one width per map entry./ToUnicodeis a stream holdingto_unicodeverbatim.
The descendant is always a /CIDFontType2, because that is what
/CIDToGIDMap means: a /CIDFontType0 reaches glyphs with the CID as
the glyph index and never consults the map.
EmbeddedFont::encode on the result writes CIDs found by inverting
to_unicode, not GIDs found in the program.
§Errors
Error::UnrecognisedFontProgram when no backend can read the bytes,
Error::EmptyFontProgram when the face declares no glyphs,
Error::EmptyToUnicodeCMap for an empty to_unicode, and
Error::BadCidToGidMap when cid_to_gid is empty or is not a whole
number of two-byte entries.
Sourcepub fn standard_font(
&mut self,
which: StandardFont,
) -> Result<EmbeddedFont, Error>
pub fn standard_font( &mut self, which: StandardFont, ) -> Result<EmbeddedFont, Error>
Add a non-embedded standard-14 Type 1 font (/BaseFont, /Encoding /WinAnsiEncoding, no /Widths).
§Errors
This path does not fail today; the Result is for symmetry with
Self::embed_font and for a caller that wants to handle both the
same way.
Source§impl EditDoc<'_>
impl EditDoc<'_>
Sourcepub fn embed_jpeg(&mut self, bytes: &[u8]) -> Result<EmbeddedImage, Error>
pub fn embed_jpeg(&mut self, bytes: &[u8]) -> Result<EmbeddedImage, Error>
Embed a JPEG or JPEG 2000 codestream as a new image XObject.
The bytes become the stream verbatim under /DCTDecode or
/JPXDecode; nothing is decoded or re-encoded. /Width, /Height,
/ColorSpace and /BitsPerComponent are read from the codestream’s
own header, which is the file’s statement of them and overrides any a
caller could pass.
§Errors
Error::UnrecognisedImageData when the bytes are neither a JPEG nor
a JPEG 2000 codestream, or their header cannot be read.
Sourcepub fn embed_image(
&mut self,
pixels: &[u8],
width: u32,
height: u32,
format: PixelFormat,
) -> Result<EmbeddedImage, Error>
pub fn embed_image( &mut self, pixels: &[u8], width: u32, height: u32, format: PixelFormat, ) -> Result<EmbeddedImage, Error>
Embed raw interleaved samples as a new image XObject.
The samples are stored uncompressed and the stream writer flate-encodes
them (there is no /Filter on the dictionary this writes). An
PixelFormat::Rgba8 alpha channel is split off into a separate
/DeviceGray /SMask image; the colour channels keep their own
stream.
§Errors
Error::EmptyImage when either dimension is zero, and
Error::ImageDataLength when pixels is not exactly the length the
dimensions and format require.
Source§impl EditDoc<'_>
impl EditDoc<'_>
Sourcepub fn stamp_text(
&mut self,
text: &str,
options: &StampOptions,
limits: &Limits,
) -> Result<(), Error>
pub fn stamp_text( &mut self, text: &str, options: &StampOptions, limits: &Limits, ) -> Result<(), Error>
Draw text over every page.
The text is set in StampOptions::font at StampOptions::font_size,
its box placed by StampOptions::position on the page as displayed,
then turned by StampOptions::angle about the box’s centre. One
/Font object serves every page. Each page’s existing content is left
as it was; the stamp is one more stream after it, so it paints on top.
use pdfrum::{Document, SaveOptions, StampOptions, StampPosition};
let doc = Document::open("tests/fixtures/hello_world_2_pages.pdf")?;
let mut edit = doc.edit();
edit.stamp_text(
"DRAFT",
&StampOptions::builder()
.position(StampPosition::BottomRight)
.opacity(0.5)
.build(),
)?;
let mut bytes = Vec::new();
edit.write_to(&mut bytes, &SaveOptions::default())?;
let stamped = pdfrum::Document::from_bytes(bytes)?;
assert!(stamped.page(1)?.text().to_string().contains("DRAFT"));§Errors
When the font cannot be added, or a page cannot be opened.
Sourcepub fn stamp_image(
&mut self,
image: &EmbeddedImage,
width: f64,
options: &StampOptions,
limits: &Limits,
) -> Result<(), Error>
pub fn stamp_image( &mut self, image: &EmbeddedImage, width: f64, options: &StampOptions, limits: &Limits, ) -> Result<(), Error>
Draw image over every page, width points wide with its aspect
ratio kept.
Placed and turned as EditDoc::stamp_text places text, at
StampOptions::opacity; the font fields are not read. The image
is one this session embedded through EditDoc::embed_jpeg or
EditDoc::embed_image, and one XObject serves every page.
use pdfrum::{Document, PixelFormat, SaveOptions, StampOptions};
let doc = Document::open("tests/fixtures/hello_world.pdf")?;
let mut edit = doc.edit();
// A two-by-one image: red, then blue.
let image = edit.embed_image(&[255, 0, 0, 0, 0, 255], 2, 1, PixelFormat::Rgb8)?;
edit.stamp_image(&image, 200.0, &StampOptions::builder().opacity(0.5).build())?;
let mut bytes = Vec::new();
edit.write_to(&mut bytes, &SaveOptions::default())?;
assert!(bytes.starts_with(b"%PDF-"));§Errors
Error::EmptyImage when width is not positive or the image has no
width, and Error::PageIndexOutOfRange when a page cannot be opened.
Source§impl EditDoc<'_>
impl EditDoc<'_>
Sourcepub fn compile_svg(
&mut self,
svg: &str,
limits: &Limits,
fonts: &SvgFonts,
) -> Result<(SvgForm, SvgIngestReport), Error>
Available on crate feature svg-import only.
pub fn compile_svg( &mut self, svg: &str, limits: &Limits, fonts: &SvgFonts, ) -> Result<(SvgForm, SvgIngestReport), Error>
svg-import only.Compile an SVG document once, into a Form XObject any number of
pages can place.
The deduplicating half of Canvas::draw_svg. That method writes the
SVG’s operators inline into the page it is drawing on, which is
right for one placement and wasteful for many: the same logo on twenty
pages becomes twenty copies of the same content. This compiles the
document into a single /Subtype /Form object with its own /BBox
and /Resources, and Canvas::place_svg then writes one Do per
page against it.
Nothing about the mapping differs — a form’s content stream holds the
same operators draw_svg would have written, and the returned
SvgIngestReport is the same report. What differs is that the
operators are written once, and that the fit is chosen per
placement rather than baked in: the form’s box is the SVG’s own, so
one compiled logo can be placed SvgFit::Contain on one page and
SvgFit::Cover on another.
§Errors
Error::Svg when usvg cannot resolve the
document, exactly as Canvas::draw_svg reports it.
use pdfrum::{Document, Rect, SvgFit};
const LOGO: &str = "<svg xmlns=\"http://www.w3.org/2000/svg\" \
viewBox=\"0 0 10 10\">\
<rect width=\"10\" height=\"10\" fill=\"#0a0\"/></svg>";
let doc = Document::open("tests/fixtures/hello_world.pdf")?;
let mut edit = doc.edit();
let (logo, _) = edit.compile_svg(LOGO)?;
assert_eq!(logo.bbox().width(), 10.0);Sourcepub fn compile_svg_from(
&mut self,
svg: &str,
resources_dir: Option<&Path>,
limits: &Limits,
) -> Result<(SvgForm, SvgIngestReport), Error>
Available on crate feature svg-import only.
pub fn compile_svg_from( &mut self, svg: &str, resources_dir: Option<&Path>, limits: &Limits, ) -> Result<(SvgForm, SvgIngestReport), Error>
svg-import only.Compile an SVG whose relative <image href> links resolve against
resources_dir.
DocEdit::compile_svg is this with no
directory, which is right for
a document held in memory; one read from a file wants the file’s own
directory here, or its linked images do not load. The same pairing
Canvas::draw_svg and Canvas::draw_svg_from have.
§Errors
Sourcepub fn compile_svg_from_with_fonts(
&mut self,
svg: &str,
resources_dir: Option<&Path>,
limits: &Limits,
fonts: &SvgFonts,
) -> Result<(SvgForm, SvgIngestReport), Error>
Available on crate feature svg-import only.
pub fn compile_svg_from_with_fonts( &mut self, svg: &str, resources_dir: Option<&Path>, limits: &Limits, fonts: &SvgFonts, ) -> Result<(SvgForm, SvgIngestReport), Error>
svg-import only.[EditDoc::compile_svg_from] with the faces the SVG’s <text> is set
in; without them a <text> draws nothing and is reported instead.
§Errors
As [EditDoc::compile_svg_from].