514 lines
18 KiB
Odin
514 lines
18 KiB
Odin
package pdf
|
|||
|
|
|
||
|
|
import "core:math"
|
||
|
|
import "core:mem"
|
||
|
|
|
||
|
|
// Anti-aliased scanline fill.
|
||
|
|
//
|
||
|
|
// WHICH ALGORITHM, AND WHY
|
||
|
|
//
|
||
|
|
// This is a signed-area cell accumulator in the style of FreeType's `smooth`
|
||
|
|
// renderer, not a sub-scanline sampler. Every line segment is chopped at the
|
||
|
|
// scanline boundaries it crosses and then at the pixel boundaries it crosses
|
||
|
|
// within a scanline, and each resulting piece deposits two numbers into the
|
||
|
|
// cell it lies in:
|
||
|
|
//
|
||
|
|
// cover[i] += dy the signed height the edge spans
|
||
|
|
// area [i] += dy * (fx1 + fx2) twice the trapezoid between the edge
|
||
|
|
// and the LEFT wall of the cell
|
||
|
|
//
|
||
|
|
// A left-to-right prefix sum over `cover` then gives the winding-weighted
|
||
|
|
// coverage of every cell exactly:
|
||
|
|
//
|
||
|
|
// coverage(i) = sum(cover[j] for j < i) + cover[i] - area[i]/2
|
||
|
|
//
|
||
|
|
// which is the integral of the winding number over the pixel -- exact in both
|
||
|
|
// axes for a polygon, with no sampling error at all. Sixteen sub-scanlines
|
||
|
|
// would quantise vertical coverage to 1/16 and cost sixteen passes over every
|
||
|
|
// edge; this costs one, and a near-horizontal glyph stem comes out smooth
|
||
|
|
// instead of banded. The one place it is an approximation rather than an
|
||
|
|
// identity is a pixel that a path enters with winding +1 and leaves with
|
||
|
|
// winding -1: the integral cancels where the true nonzero rule would fill.
|
||
|
|
// That is a sub-pixel artefact at a self-intersection, it is what FreeType,
|
||
|
|
// stb_truetype and every other accumulator-based rasteriser do, and it is
|
||
|
|
// invisible next to the cost of the alternative.
|
||
|
|
//
|
||
|
|
// The prefix sum is kept in floating point rather than 26.6 fixed point
|
||
|
|
// because the input is already f64 device coordinates: rounding to a fixed
|
||
|
|
// grid first would throw away precision that the analytic area then pretends
|
||
|
|
// to have.
|
||
|
|
|
||
|
|
// Span_Proc receives the coverage of one horizontal run on scanline `y`,
|
||
|
|
// beginning at device pixel `x0`. Values are 0..255. The slice is scratch
|
||
|
|
// owned by the rasteriser and is only valid for the duration of the call.
|
||
|
|
//
|
||
|
|
// Runs arrive in increasing `y`, and in increasing `x` within a row, and never
|
||
|
|
// overlap, so a consumer can write straight into a row without sorting.
|
||
|
|
@(private)
|
||
|
|
Span_Proc :: #type proc(user: rawptr, y: int, x0: int, coverage: []u8)
|
||
|
|
|
||
|
|
// Raster_Clip is the device-pixel box outside which nothing is drawn, given as
|
||
|
|
// a half-open range: x0 <= x < x1, y0 <= y < y1.
|
||
|
|
@(private)
|
||
|
|
Raster_Clip :: struct {
|
||
|
|
x0, y0, x1, y1: int,
|
||
|
|
}
|
||
|
|
|
||
|
|
// MAX_RASTER_EDGES caps the edge table. A page-sized fill needs a handful of
|
||
|
|
// edges and the most complex real content -- a vector map -- a few hundred
|
||
|
|
// thousand; a million is past anything legitimate and stops a hostile content
|
||
|
|
// stream from turning a 200-byte path into gigabytes of edges. Past the cap
|
||
|
|
// the remaining segments are dropped, which distorts the shape but keeps the
|
||
|
|
// page rendering, per the degrade-don't-fail rule.
|
||
|
|
@(private)
|
||
|
|
MAX_RASTER_EDGES :: 1 << 20
|
||
|
|
|
||
|
|
// RASTER_COORD_MAX is where coordinates stop being geometry and start being
|
||
|
|
// noise. Everything is clipped to the clip box anyway, so the only job of this
|
||
|
|
// clamp is to keep infinities out of the arithmetic that computes the clip
|
||
|
|
// intersection.
|
||
|
|
@(private)
|
||
|
|
RASTER_COORD_MAX :: 1e15
|
||
|
|
|
||
|
|
// Raster_Edge is one non-horizontal segment, already clipped to the raster
|
||
|
|
// box in both axes, oriented downwards (y0 < y1) with the original direction
|
||
|
|
// kept in `dir` -- the winding number needs the direction, the sweep needs the
|
||
|
|
// order, and carrying both separately is cheaper than re-deriving either.
|
||
|
|
@(private)
|
||
|
|
Raster_Edge :: struct {
|
||
|
|
x0, y0: f64,
|
||
|
|
x1, y1: f64,
|
||
|
|
// 1/(y1-y0), for interpolating x at a scanline boundary.
|
||
|
|
inv_dy: f64,
|
||
|
|
// +1 when the segment ran down the page, -1 when it ran up it.
|
||
|
|
dir: f64,
|
||
|
|
// Next edge starting on the same scanline, or -1.
|
||
|
|
next: i32,
|
||
|
|
}
|
||
|
|
|
||
|
|
@(private)
|
||
|
|
Raster_State :: struct {
|
||
|
|
// The raster box: the clip intersected with the path's bounds. Cells run
|
||
|
|
// rx0 ..= rx1 inclusive -- one past the right edge is a guard cell that
|
||
|
|
// collects the winding of everything clipped off to the right and is never
|
||
|
|
// emitted.
|
||
|
|
rx0, rx1, ry0, ry1: int,
|
||
|
|
fx0, fx1: f64,
|
||
|
|
|
||
|
|
edges: [dynamic]Raster_Edge,
|
||
|
|
// Head of the list of edges that start on each scanline, indexed from ry0.
|
||
|
|
buckets: []i32,
|
||
|
|
active: []i32,
|
||
|
|
n_active: int,
|
||
|
|
|
||
|
|
cover: []f64,
|
||
|
|
area: []f64,
|
||
|
|
covbuf: []u8,
|
||
|
|
|
||
|
|
// Cells touched on the row being built, so the next row only has to clear
|
||
|
|
// what was written. A page of text is thousands of narrow rows; clearing
|
||
|
|
// the full clip width for each would dominate the whole fill.
|
||
|
|
min_x, max_x: int,
|
||
|
|
}
|
||
|
|
|
||
|
|
// raster_fill scan-converts `fp` and reports its coverage through `span`.
|
||
|
|
//
|
||
|
|
// Subpaths are closed implicitly whether or not `Flat_Subpath.closed` is set:
|
||
|
|
// that is what the fill operators mean (ISO 32000-2, 8.5.3.3.1). Horizontal
|
||
|
|
// segments are dropped, since they carry no winding.
|
||
|
|
//
|
||
|
|
// Degenerate input -- no path, no points, a single point, a subpath that never
|
||
|
|
// leaves one horizontal line, a path wholly outside `clip` -- produces no
|
||
|
|
// spans and no error. `allocator` is used for scratch only; everything it
|
||
|
|
// allocates is released before this returns.
|
||
|
|
@(private)
|
||
|
|
raster_fill :: proc(fp: ^Flat_Path,
|
||
|
|
even_odd: bool,
|
||
|
|
clip: Raster_Clip,
|
||
|
|
span: Span_Proc,
|
||
|
|
user: rawptr,
|
||
|
|
allocator := context.allocator) -> Error {
|
||
|
|
if span == nil { return .Invalid_Argument }
|
||
|
|
if fp == nil || len(fp.points) == 0 || len(fp.subpaths) == 0 { return .None }
|
||
|
|
if clip.x1 <= clip.x0 || clip.y1 <= clip.y0 { return .None }
|
||
|
|
|
||
|
|
// Narrowing the raster box to the path is not just an optimisation: it is
|
||
|
|
// what keeps a glyph in the corner of a 300 dpi page from allocating and
|
||
|
|
// clearing a 2550-cell row, and what bounds a path whose bounds are 1e9
|
||
|
|
// wide to the size of the clip.
|
||
|
|
b := flat_path_bounds(fp)
|
||
|
|
r := Raster_State{
|
||
|
|
rx0 = raster_floor_clamped(b.x0, clip.x0, clip.x1),
|
||
|
|
rx1 = raster_ceil_clamped (b.x1, clip.x0, clip.x1),
|
||
|
|
ry0 = raster_floor_clamped(b.y0, clip.y0, clip.y1),
|
||
|
|
ry1 = raster_ceil_clamped (b.y1, clip.y0, clip.y1),
|
||
|
|
}
|
||
|
|
if r.rx1 <= r.rx0 || r.ry1 <= r.ry0 { return .None }
|
||
|
|
r.fx0 = f64(r.rx0)
|
||
|
|
r.fx1 = f64(r.rx1)
|
||
|
|
|
||
|
|
width := r.rx1 - r.rx0
|
||
|
|
height := r.ry1 - r.ry0
|
||
|
|
|
||
|
|
// One allocation each, up front. Nothing below this line allocates, which
|
||
|
|
// is what makes the per-scanline cost independent of the path.
|
||
|
|
segments := 0
|
||
|
|
for sp in fp.subpaths {
|
||
|
|
if sp.count >= 2 { segments += sp.count }
|
||
|
|
}
|
||
|
|
if segments <= 0 { return .None }
|
||
|
|
|
||
|
|
aerr: mem.Allocator_Error
|
||
|
|
r.edges, aerr = make([dynamic]Raster_Edge, 0, min(segments, 1024), allocator)
|
||
|
|
if aerr != nil { return .Out_Of_Memory }
|
||
|
|
defer delete(r.edges)
|
||
|
|
|
||
|
|
r.buckets, aerr = make([]i32, height, allocator)
|
||
|
|
if aerr != nil { return .Out_Of_Memory }
|
||
|
|
defer delete(r.buckets, allocator)
|
||
|
|
for &h in r.buckets { h = -1 }
|
||
|
|
|
||
|
|
r.cover, aerr = make([]f64, width + 1, allocator)
|
||
|
|
if aerr != nil { return .Out_Of_Memory }
|
||
|
|
defer delete(r.cover, allocator)
|
||
|
|
|
||
|
|
r.area, aerr = make([]f64, width + 1, allocator)
|
||
|
|
if aerr != nil { return .Out_Of_Memory }
|
||
|
|
defer delete(r.area, allocator)
|
||
|
|
|
||
|
|
r.covbuf, aerr = make([]u8, width, allocator)
|
||
|
|
if aerr != nil { return .Out_Of_Memory }
|
||
|
|
defer delete(r.covbuf, allocator)
|
||
|
|
|
||
|
|
for i in 0 ..< len(fp.subpaths) {
|
||
|
|
pts := flat_path_slice(fp, i)
|
||
|
|
if len(pts) < 2 { continue }
|
||
|
|
for j in 0 ..< len(pts) - 1 {
|
||
|
|
raster_add_edge(&r, pts[j], pts[j + 1])
|
||
|
|
}
|
||
|
|
// The closing segment. A subpath whose last point already coincides
|
||
|
|
// with its first would only add a zero-length edge, which is dropped
|
||
|
|
// by the horizontal test anyway, but skipping it keeps the edge table
|
||
|
|
// honest for the common case of an explicitly closed rectangle.
|
||
|
|
if pts[len(pts) - 1] != pts[0] {
|
||
|
|
raster_add_edge(&r, pts[len(pts) - 1], pts[0])
|
||
|
|
}
|
||
|
|
}
|
||
|
|
if len(r.edges) == 0 { return .None }
|
||
|
|
|
||
|
|
r.active, aerr = make([]i32, len(r.edges), allocator)
|
||
|
|
if aerr != nil { return .Out_Of_Memory }
|
||
|
|
defer delete(r.active, allocator)
|
||
|
|
|
||
|
|
for y in r.ry0 ..< r.ry1 {
|
||
|
|
for e := r.buckets[y - r.ry0]; e >= 0; {
|
||
|
|
next := r.edges[e].next
|
||
|
|
if r.n_active < len(r.active) {
|
||
|
|
r.active[r.n_active] = e
|
||
|
|
r.n_active += 1
|
||
|
|
}
|
||
|
|
e = next
|
||
|
|
}
|
||
|
|
if r.n_active == 0 { continue }
|
||
|
|
|
||
|
|
row_top := f64(y)
|
||
|
|
row_bot := f64(y + 1)
|
||
|
|
r.min_x = r.rx1 + 1
|
||
|
|
r.max_x = r.rx0 - 1
|
||
|
|
|
||
|
|
for k in 0 ..< r.n_active {
|
||
|
|
e := &r.edges[r.active[k]]
|
||
|
|
ya := max(row_top, e.y0)
|
||
|
|
yb := min(row_bot, e.y1)
|
||
|
|
if yb <= ya { continue }
|
||
|
|
xa := ya <= e.y0 ? e.x0 : raster_edge_x(&r, e, ya)
|
||
|
|
xb := yb >= e.y1 ? e.x1 : raster_edge_x(&r, e, yb)
|
||
|
|
raster_accumulate(&r, xa, ya, xb, yb, e.dir)
|
||
|
|
}
|
||
|
|
|
||
|
|
raster_sweep(&r, y, even_odd, span, user)
|
||
|
|
|
||
|
|
if r.min_x <= r.max_x {
|
||
|
|
for i in r.min_x ..= r.max_x {
|
||
|
|
r.cover[i - r.rx0] = 0
|
||
|
|
r.area [i - r.rx0] = 0
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
w := 0
|
||
|
|
for k in 0 ..< r.n_active {
|
||
|
|
if r.edges[r.active[k]].y1 > row_bot {
|
||
|
|
r.active[w] = r.active[k]
|
||
|
|
w += 1
|
||
|
|
}
|
||
|
|
}
|
||
|
|
r.n_active = w
|
||
|
|
}
|
||
|
|
return .None
|
||
|
|
}
|
||
|
|
|
||
|
|
// ---------------------------------------------------------------------------
|
||
|
|
// Building the edge table
|
||
|
|
// ---------------------------------------------------------------------------
|
||
|
|
|
||
|
|
// raster_add_edge clips one segment to the raster box and appends the one to
|
||
|
|
// three edges that survive.
|
||
|
|
//
|
||
|
|
// Clipping in y is a plain truncation: coverage on a scanline depends only on
|
||
|
|
// the parts of edges inside that scanline, so anything above or below the box
|
||
|
|
// cannot affect it. Clipping in x is NOT a truncation -- a segment to the left
|
||
|
|
// of the box still winds every pixel to its right -- so the parts outside are
|
||
|
|
// replaced by vertical edges standing on the box's walls, which wind
|
||
|
|
// identically. That substitution is what makes a path 10^9 units wide cost the
|
||
|
|
// same as one the width of the clip.
|
||
|
|
@(private)
|
||
|
|
raster_add_edge :: proc(r: ^Raster_State, p0, p1: Point) {
|
||
|
|
if len(r.edges) >= MAX_RASTER_EDGES { return }
|
||
|
|
|
||
|
|
x0 := raster_sane(p0.x)
|
||
|
|
y0 := raster_sane(p0.y)
|
||
|
|
x1 := raster_sane(p1.x)
|
||
|
|
y1 := raster_sane(p1.y)
|
||
|
|
|
||
|
|
if y0 == y1 { return }
|
||
|
|
dir := 1.0
|
||
|
|
if y0 > y1 {
|
||
|
|
x0, x1 = x1, x0
|
||
|
|
y0, y1 = y1, y0
|
||
|
|
dir = -1
|
||
|
|
}
|
||
|
|
|
||
|
|
top := f64(r.ry0)
|
||
|
|
bot := f64(r.ry1)
|
||
|
|
cy0 := max(y0, top)
|
||
|
|
cy1 := min(y1, bot)
|
||
|
|
if cy1 <= cy0 { return }
|
||
|
|
cx0 := cy0 == y0 ? x0 : raster_lerp_x(x0, y0, x1, y1, cy0)
|
||
|
|
cx1 := cy1 == y1 ? x1 : raster_lerp_x(x0, y0, x1, y1, cy1)
|
||
|
|
|
||
|
|
// Split where the segment crosses the left and right walls, so that each
|
||
|
|
// piece is wholly inside or wholly outside and clamping its endpoints is
|
||
|
|
// exact rather than a bend.
|
||
|
|
ys: [4]f64
|
||
|
|
n := 0
|
||
|
|
ys[n] = cy0; n += 1
|
||
|
|
dx := cx1 - cx0
|
||
|
|
if dx != 0 {
|
||
|
|
for wall in ([2]f64{r.fx0, r.fx1}) {
|
||
|
|
if (cx0 < wall) == (cx1 < wall) { continue }
|
||
|
|
t := (wall - cx0) / dx
|
||
|
|
if !(t > 0 && t < 1) { continue }
|
||
|
|
yy := cy0 + (cy1 - cy0) * t
|
||
|
|
if yy > cy0 && yy < cy1 {
|
||
|
|
ys[n] = yy; n += 1
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
ys[n] = cy1; n += 1
|
||
|
|
if n == 4 && ys[1] > ys[2] {
|
||
|
|
ys[1], ys[2] = ys[2], ys[1]
|
||
|
|
}
|
||
|
|
|
||
|
|
for i in 0 ..< n - 1 {
|
||
|
|
ya := ys[i]
|
||
|
|
yb := ys[i + 1]
|
||
|
|
if yb <= ya { continue }
|
||
|
|
xa := clamp(ya == cy0 ? cx0 : raster_lerp_x(cx0, cy0, cx1, cy1, ya), r.fx0, r.fx1)
|
||
|
|
xb := clamp(yb == cy1 ? cx1 : raster_lerp_x(cx0, cy0, cx1, cy1, yb), r.fx0, r.fx1)
|
||
|
|
raster_push_edge(r, xa, ya, xb, yb, dir)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
@(private)
|
||
|
|
raster_push_edge :: proc(r: ^Raster_State, x0, y0, x1, y1, dir: f64) {
|
||
|
|
if len(r.edges) >= MAX_RASTER_EDGES { return }
|
||
|
|
dy := y1 - y0
|
||
|
|
if !(dy > 0) { return }
|
||
|
|
|
||
|
|
row := int(math.floor(y0))
|
||
|
|
row = clamp(row, r.ry0, r.ry1 - 1)
|
||
|
|
|
||
|
|
idx := i32(len(r.edges))
|
||
|
|
append(&r.edges, Raster_Edge{
|
||
|
|
x0 = x0,
|
||
|
|
y0 = y0,
|
||
|
|
x1 = x1,
|
||
|
|
y1 = y1,
|
||
|
|
inv_dy = 1 / dy,
|
||
|
|
dir = dir,
|
||
|
|
next = r.buckets[row - r.ry0],
|
||
|
|
})
|
||
|
|
r.buckets[row - r.ry0] = idx
|
||
|
|
}
|
||
|
|
|
||
|
|
// raster_edge_x interpolates the edge's x at a scanline boundary. The guards
|
||
|
|
// are not decoration: an edge whose y extent is denormally small has an
|
||
|
|
// infinite 1/dy, and 0 * infinity is a NaN that would otherwise reach the cell
|
||
|
|
// walk and index an array with it.
|
||
|
|
@(private)
|
||
|
|
raster_edge_x :: proc(r: ^Raster_State, e: ^Raster_Edge, y: f64) -> f64 {
|
||
|
|
t := (y - e.y0) * e.inv_dy
|
||
|
|
if !is_finite(t) { t = 0 }
|
||
|
|
t = clamp(t, 0, 1)
|
||
|
|
x := e.x0 + (e.x1 - e.x0) * t
|
||
|
|
if !is_finite(x) { x = e.x0 }
|
||
|
|
return clamp(x, r.fx0, r.fx1)
|
||
|
|
}
|
||
|
|
|
||
|
|
@(private)
|
||
|
|
raster_lerp_x :: proc "contextless" (x0, y0, x1, y1, y: f64) -> f64 {
|
||
|
|
dy := y1 - y0
|
||
|
|
t := dy != 0 ? (y - y0) / dy : 0
|
||
|
|
if !is_finite(t) { t = 0 }
|
||
|
|
t = clamp(t, 0, 1)
|
||
|
|
x := x0 + (x1 - x0) * t
|
||
|
|
return is_finite(x) ? x : x0
|
||
|
|
}
|
||
|
|
|
||
|
|
@(private)
|
||
|
|
raster_sane :: proc "contextless" (v: f64) -> f64 {
|
||
|
|
if v != v { return 0 }
|
||
|
|
return clamp(v, -RASTER_COORD_MAX, RASTER_COORD_MAX)
|
||
|
|
}
|
||
|
|
|
||
|
|
// raster_floor_clamped and raster_ceil_clamped convert a bound to a pixel
|
||
|
|
// index without ever letting a huge or non-finite f64 reach the integer
|
||
|
|
// conversion, which is undefined when the value does not fit. A NaN bound
|
||
|
|
// widens to the clip rather than narrowing to nothing: dropping content is a
|
||
|
|
// worse failure than drawing too much.
|
||
|
|
@(private)
|
||
|
|
raster_floor_clamped :: proc "contextless" (v: f64, lo, hi: int) -> int {
|
||
|
|
if v != v || v <= f64(lo) { return lo }
|
||
|
|
if v >= f64(hi) { return hi }
|
||
|
|
return int(math.floor(v))
|
||
|
|
}
|
||
|
|
|
||
|
|
@(private)
|
||
|
|
raster_ceil_clamped :: proc "contextless" (v: f64, lo, hi: int) -> int {
|
||
|
|
if v != v || v >= f64(hi) { return hi }
|
||
|
|
if v <= f64(lo) { return lo }
|
||
|
|
return int(math.ceil(v))
|
||
|
|
}
|
||
|
|
|
||
|
|
// ---------------------------------------------------------------------------
|
||
|
|
// Accumulation
|
||
|
|
// ---------------------------------------------------------------------------
|
||
|
|
|
||
|
|
@(private)
|
||
|
|
raster_cell :: proc "contextless" (r: ^Raster_State, x: f64) -> int {
|
||
|
|
if !(x > r.fx0) { return r.rx0 }
|
||
|
|
if x >= r.fx1 { return r.rx1 }
|
||
|
|
return int(math.floor(x))
|
||
|
|
}
|
||
|
|
|
||
|
|
// raster_accumulate deposits one piece of one edge -- already confined to a
|
||
|
|
// single scanline -- into the cells it crosses.
|
||
|
|
@(private)
|
||
|
|
raster_accumulate :: proc(r: ^Raster_State, xa, ya, xb, yb, dir: f64) {
|
||
|
|
if !(yb > ya) { return }
|
||
|
|
|
||
|
|
ex1 := raster_cell(r, xa)
|
||
|
|
ex2 := raster_cell(r, xb)
|
||
|
|
|
||
|
|
if ex1 == ex2 {
|
||
|
|
raster_cell_add(r, ex1, (yb - ya) * dir, xa, xb)
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
// dy per unit x. Both endpoints sit inside the raster box, so the number
|
||
|
|
// of cells crossed is bounded by its width.
|
||
|
|
dydx := (yb - ya) / (xb - xa)
|
||
|
|
step := xb > xa ? 1 : -1
|
||
|
|
ex := ex1
|
||
|
|
xc := xa
|
||
|
|
yc := ya
|
||
|
|
for ex != ex2 {
|
||
|
|
xn := f64(step > 0 ? ex + 1 : ex)
|
||
|
|
yn := ya + (xn - xa) * dydx
|
||
|
|
if !(yn > yc) { yn = yc }
|
||
|
|
if yn > yb { yn = yb }
|
||
|
|
raster_cell_add(r, ex, (yn - yc) * dir, xc, xn)
|
||
|
|
xc = xn
|
||
|
|
yc = yn
|
||
|
|
ex += step
|
||
|
|
}
|
||
|
|
raster_cell_add(r, ex, (yb - yc) * dir, xc, xb)
|
||
|
|
}
|
||
|
|
|
||
|
|
// raster_cell_add records `dy` of signed height crossing cell `ex` between the
|
||
|
|
// x offsets `x_from` and `x_to`. `area` holds twice the trapezoid on the left
|
||
|
|
// of the edge, which the sweep subtracts from the running cover.
|
||
|
|
@(private)
|
||
|
|
raster_cell_add :: proc "contextless" (r: ^Raster_State, ex: int, dy, x_from, x_to: f64) {
|
||
|
|
if dy == 0 { return }
|
||
|
|
i := ex - r.rx0
|
||
|
|
if i < 0 || i >= len(r.cover) { return }
|
||
|
|
fx := f64(ex)
|
||
|
|
r.cover[i] += dy
|
||
|
|
r.area [i] += dy * ((x_from - fx) + (x_to - fx))
|
||
|
|
r.min_x = min(r.min_x, ex)
|
||
|
|
r.max_x = max(r.max_x, ex)
|
||
|
|
}
|
||
|
|
|
||
|
|
// ---------------------------------------------------------------------------
|
||
|
|
// Sweep
|
||
|
|
// ---------------------------------------------------------------------------
|
||
|
|
|
||
|
|
// raster_coverage folds a winding-weighted coverage into a 0..255 alpha.
|
||
|
|
//
|
||
|
|
// Nonzero clamps the magnitude; even-odd folds it, which is the floating-point
|
||
|
|
// spelling of the usual integer trick of masking the accumulator to nine bits
|
||
|
|
// and mirroring everything above the halfway point. Even-odd is not a corner
|
||
|
|
// case: it is what `f*` and `B*` mean, and Type1 glyph counters depend on it.
|
||
|
|
@(private)
|
||
|
|
raster_coverage :: proc "contextless" (cov: f64, even_odd: bool) -> u8 {
|
||
|
|
a := abs(cov)
|
||
|
|
if even_odd {
|
||
|
|
a -= 2 * math.floor(a * 0.5)
|
||
|
|
if a > 1 { a = 2 - a }
|
||
|
|
} else if a > 1 {
|
||
|
|
a = 1
|
||
|
|
}
|
||
|
|
// Written as a positive test so a NaN that survived every other guard
|
||
|
|
// becomes transparent rather than an out-of-range conversion.
|
||
|
|
if !(a > 0) { return 0 }
|
||
|
|
v := int(a * 255 + 0.5)
|
||
|
|
return u8(clamp(v, 0, 255))
|
||
|
|
}
|
||
|
|
|
||
|
|
@(private)
|
||
|
|
raster_sweep :: proc(r: ^Raster_State, y: int, even_odd: bool, span: Span_Proc, user: rawptr) {
|
||
|
|
if r.min_x > r.max_x { return }
|
||
|
|
|
||
|
|
lo := r.min_x
|
||
|
|
// The sweep stops at the last cell that any edge touched, and never needs
|
||
|
|
// a tail: a closed path's windings sum to zero at the far right, and the
|
||
|
|
// ones that would have closed beyond the clip were moved onto the guard
|
||
|
|
// cell rather than dropped, so `running` is back at zero by the time the
|
||
|
|
// touched range ends. The guard cell itself is one past the last emittable
|
||
|
|
// pixel, which is why `hi` is capped rather than clamped away.
|
||
|
|
hi := min(r.max_x, r.rx1 - 1)
|
||
|
|
if lo > hi { return }
|
||
|
|
|
||
|
|
end := hi + 1
|
||
|
|
running := 0.0
|
||
|
|
for i in lo ..= hi {
|
||
|
|
j := i - r.rx0
|
||
|
|
c := running + r.cover[j] - r.area[j] * 0.5
|
||
|
|
running += r.cover[j]
|
||
|
|
r.covbuf[j] = raster_coverage(c, even_odd)
|
||
|
|
}
|
||
|
|
|
||
|
|
i := lo
|
||
|
|
for i < end {
|
||
|
|
if r.covbuf[i - r.rx0] == 0 {
|
||
|
|
i += 1
|
||
|
|
continue
|
||
|
|
}
|
||
|
|
j := i
|
||
|
|
for j < end && r.covbuf[j - r.rx0] != 0 { j += 1 }
|
||
|
|
span(user, y, i, r.covbuf[i - r.rx0 : j - r.rx0])
|
||
|
|
i = j
|
||
|
|
}
|
||
|
|
}
|