Skip to main content

Rasterizer

Struct Rasterizer 

Source
pub struct Rasterizer { /* private fields */ }
Expand description

Accumulates a path’s boundary into cells.

The lifecycle is: Rasterizer::move_to and Rasterizer::line_to for each flattened subpath, Rasterizer::close_polygon to close it, then Rasterizer::sweep to turn the cells into spans. Curves are flattened by the caller — this type sees only straight segments, which is also all the oracle’s scan converter sees.

A caller that will throw away the spans outside a band of rows should say which band with Rasterizer::keep_rows, and pay for the rows it keeps rather than for the rows the path reaches.

use pdfrum_render::scanline::{Coverage, FillRule, Rasterizer};

let mut rasterizer = Rasterizer::new();
rasterizer.move_to(0.0, 0.0);
rasterizer.line_to(4.0, 0.0);
rasterizer.line_to(4.0, 2.0);
rasterizer.line_to(0.0, 2.0);
rasterizer.close_polygon();

let mut spans = Vec::new();
rasterizer.sweep(FillRule::Winding, Coverage::Exact, |x, len, y, alpha| {
    spans.push((x, y, len, alpha));
});

// A 4x2 rectangle: two full rows, no partial coverage anywhere.
assert_eq!(spans, [(0, 0, 4, 255), (0, 1, 4, 255)]);

Implementations§

Source§

impl Rasterizer

Source

pub fn new() -> Self

A rasterizer with no cells.

use pdfrum_render::scanline::Rasterizer;

assert!(Rasterizer::new().is_empty());
Source

pub fn reset(&mut self)

Forget every cell, keeping the allocation and the kept row range.

use pdfrum_render::scanline::{Coverage, FillRule, Rasterizer};

let mut rasterizer = Rasterizer::new();
rasterizer.move_to(0.0, 0.0);
rasterizer.line_to(4.0, 0.0);
rasterizer.line_to(4.0, 2.0);
rasterizer.line_to(0.0, 2.0);
rasterizer.close_polygon();

let mut spans = Vec::new();
rasterizer.sweep(FillRule::Winding, Coverage::Exact, |x, len, y, alpha| {
    spans.push((x, y, len, alpha));
});

// The allocation and the kept row range survive; the cells do not.
rasterizer.reset();
assert!(rasterizer.is_empty());
Source

pub fn keep_rows(&mut self, rows: Range<i32>)

Keep only the cells on rows in rows, a half-open range of pixel rows.

This is a promise about the caller, not a change to the geometry: it says the caller will discard every span outside rows anyway, so the rasterizer may drop those cells rather than sort and sweep them. The spans that do come out are byte-for-byte the ones an unrestricted rasterizer emits, because Rasterizer::sweep resolves each row from that row’s cells alone — the running cover is reset at every row boundary, so a dropped row can change no other.

It is worth setting whenever the path may extend far outside the target: a path whose bounding box is tens of thousands of rows tall on an 842-row page otherwise pays for every row it crosses.

Only rows are restricted. A span too far left or right still costs its cells, because the horizontal extent is bounded by the path’s own segment count rather than by the rows it crosses.

use pdfrum_render::scanline::{Coverage, FillRule, Rasterizer};

// A promise about the caller: it will discard every span outside
// row 0 anyway, so those cells need not be sorted or swept.
let mut rasterizer = Rasterizer::new();
rasterizer.keep_rows(0..1);
rasterizer.move_to(0.0, 0.0);
rasterizer.line_to(4.0, 0.0);
rasterizer.line_to(4.0, 2.0);
rasterizer.line_to(0.0, 2.0);
rasterizer.close_polygon();

let mut rows = Vec::new();
rasterizer.sweep(FillRule::Winding, Coverage::Exact, |_, _, y, _| rows.push(y));
// Byte-for-byte the spans an unrestricted rasterizer emits for row 0.
assert_eq!(rows, [0]);
Source

pub fn is_empty(&self) -> bool

Whether any boundary has been accumulated.

A path entirely outside Rasterizer::keep_rows’s range is empty by this test once it has been swept, which is what it means for the caller to have said those rows do not matter.

use pdfrum_render::scanline::Rasterizer;

let mut rasterizer = Rasterizer::new();
assert!(rasterizer.is_empty());
rasterizer.move_to(0.0, 0.0);
rasterizer.line_to(4.0, 2.0);
rasterizer.close_polygon();
assert!(!rasterizer.is_empty());
Source

pub fn move_to(&mut self, x: f64, y: f64)

Start a new subpath at a device-space point.

use pdfrum_render::scanline::{Coverage, FillRule, Rasterizer};

let mut rasterizer = Rasterizer::new();
rasterizer.move_to(0.0, 0.0);
rasterizer.line_to(4.0, 0.0);
rasterizer.line_to(4.0, 2.0);
rasterizer.line_to(0.0, 2.0);
rasterizer.close_polygon();

let mut spans = Vec::new();
rasterizer.sweep(FillRule::Winding, Coverage::Exact, |x, len, y, alpha| {
    spans.push((x, y, len, alpha));
});

assert_eq!(spans.len(), 2);
Source

pub fn line_to(&mut self, x: f64, y: f64)

Extend the current subpath to a device-space point.

use pdfrum_render::scanline::Rasterizer;

// A `line_to` with no `move_to` before it is ignored rather than
// treated as starting at the origin.
let mut rasterizer = Rasterizer::new();
rasterizer.line_to(4.0, 2.0);
assert!(rasterizer.is_empty());
Source

pub fn close_polygon(&mut self)

Close the current subpath back to its start.

A fill’s boundary is closed by definition, so this runs implicitly before every move_to and before the sweep: an unclosed subpath in the input is filled as though the caller had closed it, which is what both PDF’s f and the oracle’s scan converter do.

use pdfrum_render::scanline::{Coverage, FillRule, Rasterizer};

// An unclosed subpath fills as though the caller had closed it,
// which is what `f` and the oracle both do -- so closing explicitly
// and leaving it open give the same spans.
let build = |close: bool| {
    let mut rasterizer = Rasterizer::new();
    rasterizer.move_to(0.0, 0.0);
    rasterizer.line_to(4.0, 0.0);
    rasterizer.line_to(4.0, 2.0);
    rasterizer.line_to(0.0, 2.0);
    if close {
        rasterizer.close_polygon();
    }
    let mut spans = Vec::new();
    rasterizer.sweep(FillRule::Winding, Coverage::Exact, |x, len, y, a| {
        spans.push((x, y, len, a));
    });
    spans
};
assert_eq!(build(true), build(false));
Source

pub fn add_path(&mut self, path: &BezPath, tolerance: f64)

Add a whole flattened path, in device space.

use pdfrum_render::scanline::{Coverage, FillRule, Rasterizer};

// Curves are flattened here; the rasterizer sees only segments.
use kurbo::Shape;

let path = kurbo::Rect::new(0.0, 0.0, 4.0, 2.0).to_path(0.1);
let mut rasterizer = Rasterizer::new();
rasterizer.add_path(&path, 0.25);

let mut spans = Vec::new();
rasterizer.sweep(FillRule::Winding, Coverage::Exact, |x, len, y, a| {
    spans.push((x, y, len, a));
});
assert_eq!(spans, [(0, 0, 4, 255), (0, 1, 4, 255)]);
Source

pub fn sweep( &mut self, rule: FillRule, coverage: Coverage, emit: impl FnMut(i32, i32, i32, u8), )

Turn the accumulated cells into horizontal spans of coverage.

emit receives (x, len, alpha) for each run of equal coverage, in increasing y then increasing x. Only non-zero alphas are emitted, so a consumer can blend unconditionally.

Each row is resolved from its own cells: the running cover starts at zero on every row and is not carried into the next, because a closed boundary crosses each scanline an even number of times and so returns the winding count to zero by the row’s end. That is what makes Rasterizer::keep_rows exact rather than approximate.

use pdfrum_render::scanline::{Coverage, FillRule, Rasterizer};

let mut rasterizer = Rasterizer::new();
rasterizer.move_to(0.0, 0.0);
rasterizer.line_to(4.0, 0.0);
rasterizer.line_to(4.0, 2.0);
rasterizer.line_to(0.0, 2.0);
rasterizer.close_polygon();

let mut spans = Vec::new();
rasterizer.sweep(FillRule::Winding, Coverage::Exact, |x, len, y, alpha| {
    spans.push((x, y, len, alpha));
});

// Increasing y then increasing x, and only non-zero alphas, so a
// consumer can blend unconditionally.
assert_eq!(spans, [(0, 0, 4, 255), (0, 1, 4, 255)]);

Trait Implementations§

Source§

impl Debug for Rasterizer

Source§

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

Formats the value using the given formatter. Read more
Source§

impl Default for Rasterizer

Source§

fn default() -> Rasterizer

Returns the “default value” for a type. Read more

Auto Trait Implementations§

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, 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.