Skip to main content

Canvas

Struct Canvas 

Source
pub struct Canvas<'a, 'b> { /* private fields */ }
Expand description

One page’s drawing surface.

Handed to the closure of EditDoc::draw_page and EditDoc::draw_pages; it cannot be constructed otherwise, because a canvas is only meaningful against the page whose space it maps and the session whose resources it merges into.

§The coordinate space

Canvas coordinates are the page as displayed, in points:

  • the origin is the lower-left corner of the crop box after /Rotate;
  • x runs right and y runs up, as PDF page space does and unlike a raster;
  • the extent is Canvas::size, whose sides are the crop box’s swapped on a quarter or three-quarter turn.

So a caller places things where they see them: on a page with /Rotate 90, Point::new(0.0, 0.0) is the bottom-left corner on screen, and text drawn along +x reads upright there. The composition is exactly the inverse of pdfrum_page::Rotation::display_matrix over the crop box, which is the same matrix the renderer uses, so what a caller places and what a viewer shows cannot drift apart.

Every method takes canvas coordinates. Canvas::transform composes a further transform inside that space, so a rotation about a point is written in the coordinates the caller is already using.

Implementations§

Source§

impl Canvas<'_, '_>

Source

pub fn size(&self) -> Size

The page’s displayed size in points — the crop box’s, with its sides swapped on a quarter turn.

The canvas’s own extent: Rect::from_origin_size(Point::ZERO, size) is the whole visible page.

let doc = pdfrum::Document::open("tests/fixtures/hello_world.pdf")?;
let mut edit = doc.edit();
edit.draw_page(0, |c| {
    assert!(c.size().width > 0.0);
})?;
Source

pub fn bounds(&self) -> Rect

The whole visible page, as a rectangle in canvas coordinates.

let doc = pdfrum::Document::open("tests/fixtures/hello_world.pdf")?;
let mut edit = doc.edit();
edit.draw_page(0, |c| {
    assert_eq!(c.bounds().origin(), pdfrum::Point::ZERO);
})?;
Source

pub fn page(&self) -> Option<PageIndex>

The page this canvas draws on, or None when it is compiling a Form XObject that no page owns yet.

A canvas handed to EditDoc::draw_page or EditDoc::draw_pages always answers Some. The None case is a Form XObject compiled by EditDoc::compile_svg (feature svg-import), whose content belongs to no page until Canvas::place_svg puts it on one.

let doc = pdfrum::Document::open("tests/fixtures/hello_world_2_pages.pdf")?;
let mut edit = doc.edit();
edit.draw_pages(|c| assert!(c.page().is_some_and(|p| u32::from(p) < 2)))?;
Source

pub fn saved(&mut self, body: impl FnOnce(&mut Self))

Draw inside a saved graphics state, restored when body returns.

This is the only spelling of q/Q: there is no bare save a caller could leave unmatched, and no restore that could pop a state the caller did not push. Nesting is the closure nesting, so an unbalanced stream is not expressible.

use pdfrum::{Color, Paint, Rect};

let doc = pdfrum::Document::open("tests/fixtures/hello_world.pdf")?;
let mut edit = doc.edit();
edit.draw_page(0, |c| {
    c.saved(|c| {
        c.clip(Rect::new(0.0, 0.0, 100.0, 100.0), pdfrum::Fill::NonZero);
        c.fill_rect(Rect::new(0.0, 0.0, 500.0, 500.0), Color::from_rgb8(200, 0, 0));
    });
    // The clip is gone here.
    c.fill_rect(Rect::new(0.0, 0.0, 10.0, 10.0), Color::BLACK);
})?;
Source

pub fn transform(&mut self, transform: Affine)

Compose transform into the canvas space, for everything drawn after it.

Scoped by Canvas::saved, like every other graphics-state change; a transform outside one lasts for the rest of the drawing.

use pdfrum::{Affine, Color, Rect};

let doc = pdfrum::Document::open("tests/fixtures/hello_world.pdf")?;
let mut edit = doc.edit();
edit.draw_page(0, |c| {
    c.saved(|c| {
        c.transform(Affine::rotate_about(0.5, c.bounds().center()));
        c.fill_rect(Rect::new(0.0, 0.0, 100.0, 20.0), Color::BLACK);
    });
})?;
Source

pub fn clip(&mut self, shape: impl Shape, rule: Fill)

Intersect the clip with shape, for everything drawn after it.

Scoped by Canvas::saved: a PDF clip can only ever be narrowed, so a q/Q is the only way back.

use pdfrum::{Fill, Rect};

let doc = pdfrum::Document::open("tests/fixtures/hello_world.pdf")?;
let mut edit = doc.edit();
edit.draw_page(0, |c| {
    c.saved(|c| c.clip(Rect::new(10.0, 10.0, 90.0, 90.0), Fill::NonZero));
})?;
Source

pub fn fill(&mut self, shape: impl Shape, color: Color)

Fill shape with color, by the nonzero rule.

use pdfrum::{Color, Rect};

let doc = pdfrum::Document::open("tests/fixtures/hello_world.pdf")?;
let mut edit = doc.edit();
edit.draw_page(0, |c| {
    c.fill(Rect::new(0.0, 0.0, 50.0, 50.0), Color::from_rgb8(0, 0, 255));
})?;
Source

pub fn fill_rect(&mut self, rect: Rect, color: Color)

Fill rect with colorCanvas::fill on the commonest shape.

use pdfrum::{Color, Rect};

let doc = pdfrum::Document::open("tests/fixtures/hello_world.pdf")?;
let mut edit = doc.edit();
edit.draw_page(0, |c| {
    c.fill_rect(Rect::new(0.0, 0.0, 50.0, 50.0), Color::BLACK);
})?;
Source

pub fn fill_rounded_rect(&mut self, rect: Rect, radius: f64, color: Color)

Fill a rectangle with radius-point rounded corners.

use pdfrum::{Color, Rect};

let doc = pdfrum::Document::open("tests/fixtures/hello_world.pdf")?;
let mut edit = doc.edit();
edit.draw_page(0, |c| {
    c.fill_rounded_rect(Rect::new(0.0, 0.0, 80.0, 30.0), 6.0, Color::BLACK);
})?;
Source

pub fn stroke(&mut self, shape: impl Shape, stroke: Stroke)

Stroke shape.

use pdfrum::{Color, Rect, Stroke};

let doc = pdfrum::Document::open("tests/fixtures/hello_world.pdf")?;
let mut edit = doc.edit();
edit.draw_page(0, |c| {
    c.stroke(Rect::new(0.0, 0.0, 50.0, 50.0), Stroke::new(Color::BLACK, 1.0));
})?;
Source

pub fn line(&mut self, from: Point, to: Point, stroke: Stroke)

Stroke the straight segment from from to to — a header rule, a divider.

use pdfrum::{Color, Point, Stroke};

let doc = pdfrum::Document::open("tests/fixtures/hello_world.pdf")?;
let mut edit = doc.edit();
edit.draw_page(0, |c| {
    let y = c.size().height - 50.0;
    c.line(Point::new(50.0, y), Point::new(c.size().width - 50.0, y),
           Stroke::new(Color::BLACK, 0.75));
})?;
Source

pub fn draw(&mut self, shape: impl Shape, paint: Paint, rule: Fill)

Paint shape with paint, filling by rule.

The general case the other shape methods narrow: Canvas::fill is Paint::Fill with Fill::NonZero, Canvas::stroke is Paint::Stroke.

use pdfrum::{Color, Fill, Paint, Rect, Stroke};

let doc = pdfrum::Document::open("tests/fixtures/hello_world.pdf")?;
let mut edit = doc.edit();
edit.draw_page(0, |c| {
    c.draw(
        Rect::new(0.0, 0.0, 40.0, 40.0),
        Paint::FillStroke(Color::from_rgb8(255, 255, 0), Stroke::new(Color::BLACK, 2.0)),
        Fill::EvenOdd,
    );
})?;
Source

pub fn text( &mut self, text: &str, font: &EmbeddedFont, size: f64, at: Point, color: Color, )

Draw text in font at size, with its baseline starting at at.

font is one this session loaded through EditDoc::embed_font or EditDoc::standard_font, so its glyphs are subset and embedded by the machinery that already does that for a saved font. A base-14 face from standard_font needs no embedded program.

One string, one point, one line: see the module documentation for why there is no wrapping.

§Errors

A character font has no glyph for is an error, not a blank — the canvas records it and EditDoc::draw_page returns it. Nothing of this call is written when it fails, so a refused string leaves no half-drawn run behind.

use pdfrum::{Color, Point, StandardFont};

let doc = pdfrum::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("Page 1", &font, 10.0, Point::new(72.0, 72.0), Color::BLACK);
})?;
Source

pub fn text_width(&self, text: &str, font: &EmbeddedFont, size: f64) -> f64

The advance of text in font at size, in canvas units.

The only measurement this API offers, and it is what centring a single string needs. It is not a layout engine and does not claim to be: no line breaking, no kerning beyond the font’s own advances, and no vertical metrics.

0.0 for a string font cannot encode.

use pdfrum::StandardFont;

let doc = pdfrum::Document::open("tests/fixtures/hello_world.pdf")?;
let mut edit = doc.edit();
let font = edit.standard_font(StandardFont::Helvetica)?;
edit.draw_page(0, |c| {
    assert!(c.text_width("Hello", &font, 12.0) > 0.0);
})?;
Source

pub fn image(&mut self, image: &EmbeddedImage, rect: Rect)

Draw image stretched onto rect.

image is one this session embedded through EditDoc::embed_jpeg or EditDoc::embed_image. Nothing preserves the aspect ratio: a caller who wants it kept sizes rect from EmbeddedImage::width and EmbeddedImage::height.

use pdfrum::Rect;

let doc = pdfrum::Document::open("tests/fixtures/hello_world.pdf")?;
let mut edit = doc.edit();
let logo = edit.embed_jpeg(include_bytes!("../tests/fixtures/mona_lisa.jpg"))?;
edit.draw_page(0, |c| {
    c.image(&logo, Rect::new(20.0, 20.0, 80.0, 80.0));
})?;
Source

pub fn opacity(&mut self, alpha: f64)

Set the constant alpha for everything drawn after it, as an /ExtGState naming /ca and /CA.

Scoped by Canvas::saved. The alpha a Color already carries is applied on top of this, so a translucent colour under a 0.5 opacity is twice translucent.

use pdfrum::{Color, Rect};

let doc = pdfrum::Document::open("tests/fixtures/hello_world.pdf")?;
let mut edit = doc.edit();
edit.draw_page(0, |c| {
    c.saved(|c| {
        c.opacity(0.2);
        c.fill_rect(Rect::new(0.0, 0.0, 100.0, 100.0), Color::from_rgb8(255, 0, 0));
    });
})?;
Source§

impl Canvas<'_, '_>

Source

pub fn draw_svg( &mut self, svg: &str, into: Rect, fit: SvgFit, ) -> Result<SvgIngestReport, Error>

Available on crate feature svg-import only.

Draw an SVG document into into, as vectors.

svg is the document’s source, and fit says how its own coordinate box is placed in the rectangle — see SvgFit. The whole drawing is scoped: it is wrapped in one q/Q and clipped to into, so nothing the SVG does escapes the rectangle the caller named and the canvas’s own graphics state is untouched afterwards.

The returned SvgIngestReport lists every construct that could not be carried, and an empty report means the whole document went in.

§Errors

Error::Svg when usvg cannot resolve the document at all — malformed XML, or an <svg> with no usable size. A construct that resolves but does not map is a report item, not an error.

use pdfrum::{Document, Rect, SvgFit};

// A plain string with escaped quotes rather than a raw one: a `#`
// inside a doc comment ends the raw-string hash count.
const LOGO: &str = "<svg xmlns=\"http://www.w3.org/2000/svg\" \
    viewBox=\"0 0 10 10\">\
    <circle cx=\"5\" cy=\"5\" r=\"4\" fill=\"#c00\"/></svg>";

let doc = Document::open("tests/fixtures/hello_world.pdf")?;
let mut edit = doc.edit();
edit.draw_page(0, |c| {
    let report = c.draw_svg(LOGO, Rect::new(40.0, 40.0, 140.0, 140.0), SvgFit::Contain);
    assert!(report.is_ok_and(|r| r.is_empty()));
})?;
Source

pub fn draw_svg_from( &mut self, svg: &str, into: Rect, fit: SvgFit, resources_dir: Option<&Path>, ) -> Result<SvgIngestReport, Error>

Available on crate feature svg-import only.

Draw an SVG document whose relative <image href> links resolve against resources_dir.

Canvas::draw_svg is this with no directory, which is right for a document held in memory; a document read from a file wants the file’s own directory here, or its linked images do not load.

§Errors

As Canvas::draw_svg.

Source

pub fn place_svg(&mut self, form: &SvgForm, into: Rect, fit: SvgFit)

Available on crate feature svg-import only.

Place an SvgForm compiled by DocEdit::compile_svg, fitting its box into into the way Canvas::draw_svg fits a document.

The deduplicating spelling of draw_svg: the SVG is compiled once and this writes one Do per placement, so the same logo on twenty pages is one content stream rather than twenty. The un-fitted placement is this without the fit, stretching the form’s box onto the rectangle.

use pdfrum::{Document, Rect, SvgFit};

const LOGO: &str = "<svg xmlns=\"http://www.w3.org/2000/svg\" \
    viewBox=\"0 0 10 10\">\
    <circle cx=\"5\" cy=\"5\" r=\"4\" fill=\"#c00\"/></svg>";

let doc = Document::open("tests/fixtures/hello_world_2_pages.pdf")?;
let mut edit = doc.edit();
let (logo, report) = edit.compile_svg(LOGO)?;
assert!(report.is_empty());
edit.draw_pages(|c| c.place_svg(&logo, Rect::new(10.0, 10.0, 60.0, 60.0), SvgFit::Contain))?;

Trait Implementations§

Source§

impl Debug for Canvas<'_, '_>

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more

Auto Trait Implementations§

§

impl<'a, 'b> !RefUnwindSafe for Canvas<'a, 'b>

§

impl<'a, 'b> !UnwindSafe for Canvas<'a, 'b>

§

impl<'a, 'b> Freeze for Canvas<'a, 'b>

§

impl<'a, 'b> Send for Canvas<'a, 'b>

§

impl<'a, 'b> Sync for Canvas<'a, 'b>

§

impl<'a, 'b> Unpin for Canvas<'a, 'b>

§

impl<'a, 'b> UnsafeUnpin for Canvas<'a, 'b>

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = !

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, !>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.