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
impl Rasterizer
Sourcepub fn new() -> Self
pub fn new() -> Self
A rasterizer with no cells.
use pdfrum_render::scanline::Rasterizer;
assert!(Rasterizer::new().is_empty());Sourcepub fn reset(&mut self)
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());Sourcepub fn keep_rows(&mut self, rows: Range<i32>)
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]);Sourcepub fn is_empty(&self) -> bool
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());Sourcepub fn move_to(&mut self, x: f64, y: f64)
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);Sourcepub fn line_to(&mut self, x: f64, y: f64)
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());Sourcepub fn close_polygon(&mut self)
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));Sourcepub fn add_path(&mut self, path: &BezPath, tolerance: f64)
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)]);Sourcepub fn sweep(
&mut self,
rule: FillRule,
coverage: Coverage,
emit: impl FnMut(i32, i32, i32, u8),
)
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)]);