initial commit
This commit is contained in:
+506
@@ -0,0 +1,506 @@
|
||||
package pdf
|
||||
|
||||
import "core:math"
|
||||
|
||||
// Patterns (ISO 32000-2, 8.7.3 and 8.7.4).
|
||||
//
|
||||
// PATTERN SPACE IS ANCHORED TO THE PAGE, NOT TO THE FILL
|
||||
//
|
||||
// A pattern's /Matrix maps pattern space to the DEFAULT coordinate space of
|
||||
// the page -- or of the form XObject, Type 3 glyph or annotation appearance
|
||||
// stream -- in which the pattern is a resource (8.7.3.1). It does NOT compose
|
||||
// with the CTM that happens to be in force when the fill runs. So the mapping
|
||||
// every procedure in this file builds is
|
||||
//
|
||||
// pattern space --(/Matrix)--> default space --(base CTM)--> device
|
||||
//
|
||||
// and `ctm` throughout this file means that base CTM: the one the interpreter
|
||||
// started the content stream with, saved before the first `cm`, never
|
||||
// `gs.ctm`.
|
||||
//
|
||||
// Two fills with the same pattern under different CTMs must therefore land on
|
||||
// exactly the same lattice. A patterned rectangle drawn twice -- once plainly,
|
||||
// once inside `q 2 0 0 2 30 40 cm ... Q` -- shows the same hatch, at the same
|
||||
// phase, in the same place, and only the shape being filled moves. Composing
|
||||
// with the live CTM instead is the classic pattern bug: the texture slides
|
||||
// with whatever transform preceded the fill. It looks plausible in isolation
|
||||
// and is obvious the moment two shapes with different CTMs share an edge, or
|
||||
// the moment the page is rendered at a second zoom level.
|
||||
//
|
||||
// One consequence is worth stating because the interpreter has to act on it: a
|
||||
// form XObject re-anchors pattern space. A pattern named from a form's
|
||||
// /Resources is anchored to the form's default space -- the CTM at the moment
|
||||
// the form was invoked, including the form's own /Matrix -- so entering a form
|
||||
// means saving a new base CTM, and leaving it means restoring the old one.
|
||||
// The same goes for a Type 3 glyph and an annotation appearance stream.
|
||||
//
|
||||
// WHAT THE INTERPRETER STILL HAS TO DO
|
||||
//
|
||||
// `pattern_tiles` says where to replay a tiling cell, not how. For each matrix
|
||||
// it returns, the interpreter sets the CTM to that matrix, clips to the
|
||||
// pattern's /BBox (a rotated pattern makes that a path, not a scissor
|
||||
// rectangle), and runs `content` against `resources`. For an uncoloured
|
||||
// pattern -- `pattern_needs_color` -- every colour operator inside the cell is
|
||||
// ignored and the whole cell paints in the colour supplied with the pattern
|
||||
// name. Recursion into a pattern that fills with a pattern is the
|
||||
// interpreter's to bound.
|
||||
|
||||
Pattern_Kind :: enum u8 {
|
||||
Tiling = 1,
|
||||
Shading = 2,
|
||||
}
|
||||
|
||||
Paint_Type :: enum u8 {
|
||||
Coloured = 1, // the cell supplies its own colour
|
||||
Uncoloured = 2, // the cell is a stencil painted in the caller's colour
|
||||
}
|
||||
|
||||
Tiling_Type :: enum u8 {
|
||||
Constant_Spacing = 1,
|
||||
No_Distortion = 2,
|
||||
Faster_Constant_Spacing = 3,
|
||||
}
|
||||
|
||||
Pattern :: struct {
|
||||
kind: Pattern_Kind,
|
||||
// Pattern space to the default space of the page or form the pattern is
|
||||
// used in -- NOT to the CTM in force when it is painted (8.7.3.1). Getting
|
||||
// this wrong makes patterns slide around under a transformed fill.
|
||||
mtx: Matrix,
|
||||
|
||||
// Tiling.
|
||||
paint_type: Paint_Type,
|
||||
tiling_type: Tiling_Type,
|
||||
bbox: Rect,
|
||||
xstep: f64,
|
||||
ystep: f64,
|
||||
resources: ^Dict,
|
||||
content: ^Stream,
|
||||
|
||||
// Shading.
|
||||
shading: ^Shading,
|
||||
ext_gstate: ^Dict,
|
||||
|
||||
// The Document this was read from. A lattice can only be found to be
|
||||
// pathological once the area to cover is known, which is inside
|
||||
// `pattern_tiles`, and the repair that records it has to be attached to
|
||||
// something.
|
||||
doc: ^Document,
|
||||
// Set once the tile cap has been reported for this pattern. A page that
|
||||
// fills with the same hostile pattern five hundred times should cost one
|
||||
// repair, not five hundred crowding every other one out of doc.repairs.
|
||||
cap_reported: bool,
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Limits
|
||||
//
|
||||
// Each of these bounds a number that came out of a file, on a path any file
|
||||
// can reach: `/Pattern cs /P1 scn ... f` is three operators.
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// Cells one call may ask the interpreter to replay. A full page at 300 dpi is
|
||||
// 2550 x 3300 device pixels, so even a 16-pixel cell covering all of it is
|
||||
// about 33 000 -- this is twice that, and everything past it is a file whose
|
||||
// /XStep is small enough to make one fill cost a million content streams.
|
||||
@(private)
|
||||
MAX_PATTERN_TILES :: 1 << 16
|
||||
|
||||
// How many lattice steps a cell may span before the step is treated as
|
||||
// degenerate rather than as overlapping cells. Cells that overlap are a real
|
||||
// design -- a brick bond with a bleed, a hatch drawn with a fat pen -- so a
|
||||
// generous factor is allowed before the step is snapped back to the cell size.
|
||||
@(private)
|
||||
MAX_TILE_OVERLAP :: 64.0
|
||||
|
||||
// Bound on a lattice index before it is narrowed to an int. The quotients that
|
||||
// produce the index range are computed from file-derived numbers and can be
|
||||
// 1e300; converting that to an int is undefined, so the range is clamped in
|
||||
// f64 first. 2^24 is exact in f64 and is already 16 million cells along one
|
||||
// axis, far past what the tile cap will let through.
|
||||
@(private)
|
||||
MAX_TILE_INDEX :: 1 << 24
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Loading
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// pattern_load builds the pattern `obj` describes: a stream for a tiling
|
||||
// pattern (/PatternType 1), whose dictionary carries the pattern entries and
|
||||
// whose bytes are the cell's content, or a dictionary for a shading pattern
|
||||
// (/PatternType 2).
|
||||
//
|
||||
// `res` is the /Resources dictionary the pattern was named from. It resolves a
|
||||
// colour space named by a bare name inside a shading pattern's /Shading, and
|
||||
// it stands in for a tiling pattern that omits its own /Resources.
|
||||
//
|
||||
// The result is owned by the Document and must not be freed. Nothing is
|
||||
// cached: like a shading, a pattern is built once per `scn` that names it, and
|
||||
// the content stream it points at is decoded by `stream_data`, which caches.
|
||||
pattern_load :: proc(doc: ^Document, obj: Object, res: ^Dict) -> (^Pattern, Error) {
|
||||
if doc == nil {
|
||||
return nil, .Invalid_Argument
|
||||
}
|
||||
o := resolve(doc, obj)
|
||||
d, has_dict := to_dict(o)
|
||||
if !has_dict {
|
||||
return nil, .Bad_Pattern
|
||||
}
|
||||
stm, is_stream := to_stream(o)
|
||||
|
||||
kind := dict_int(doc, d, "PatternType", 0)
|
||||
if kind != 1 && kind != 2 {
|
||||
// /PatternType is required, but a file that leaves it out still says
|
||||
// plainly which kind it meant: only a tiling pattern is a stream, and
|
||||
// only a shading pattern has a /Shading.
|
||||
switch {
|
||||
case dict_has(d, "Shading"): kind = 2
|
||||
case is_stream: kind = 1
|
||||
case: return nil, .Bad_Pattern
|
||||
}
|
||||
add_repair(doc, .Bad_Resource, 0)
|
||||
}
|
||||
|
||||
p := Pattern{doc = doc}
|
||||
// `object_to_matrix` is the finiteness boundary for a matrix out of a
|
||||
// file, so a /Matrix that is short, non-numeric or infinite arrives here
|
||||
// as the identity -- which is the format's own default and the only
|
||||
// sensible guess.
|
||||
p.mtx = dict_matrix(doc, d, "Matrix")
|
||||
|
||||
if kind == 2 {
|
||||
p.kind = .Shading
|
||||
sh, sh_err := shading_load(doc, dict_get(doc, d, "Shading"), res)
|
||||
// A shading pattern is nothing but its shading; with no shading to
|
||||
// paint there is no degraded version of it to hand back.
|
||||
if sh_err != .None || sh == nil {
|
||||
return nil, .Bad_Pattern
|
||||
}
|
||||
p.shading = sh
|
||||
// /ExtGState is applied by the caller: it belongs to the graphics
|
||||
// state the shading paints under, not to the shading itself, and it
|
||||
// can carry a soft mask that only the interpreter can build.
|
||||
p.ext_gstate = dict_dict(doc, d, "ExtGState")
|
||||
return pattern_intern(doc, p), .None
|
||||
}
|
||||
|
||||
p.kind = .Tiling
|
||||
if !is_stream {
|
||||
// The cell's content is the stream's own bytes. A tiling pattern
|
||||
// written as a bare dictionary has no content at all, and an empty
|
||||
// cell tiled across a fill is indistinguishable from not filling.
|
||||
return nil, .Bad_Pattern
|
||||
}
|
||||
p.content = stm
|
||||
|
||||
bbox, bbox_ok := object_to_rect(doc, dict_get(doc, d, "BBox"))
|
||||
// Both corners are finite by the time `object_to_rect` returns, but their
|
||||
// difference need not be: a /BBox of [-1e308 -1e308 1e308 1e308] has an
|
||||
// infinite width, and a step of infinity multiplied by a lattice index of
|
||||
// zero is a NaN in the middle of a cell matrix. Nothing below the Device
|
||||
// line may see one, and a cell the size of the number line is not a cell.
|
||||
bw := rect_width(bbox)
|
||||
bh := rect_height(bbox)
|
||||
if !bbox_ok || rect_is_empty(bbox) || !is_finite(bw) || !is_finite(bh) {
|
||||
// 8.7.3.1: the cell is clipped to /BBox, so a missing or degenerate
|
||||
// one clips every cell away to nothing.
|
||||
return nil, .Bad_Pattern
|
||||
}
|
||||
p.bbox = bbox
|
||||
// After this the steps are finite and strictly positive, which is the
|
||||
// invariant `pattern_bounds` and `pattern_tiles` are written against.
|
||||
p.xstep = pattern_step(doc, dict_num(doc, d, "XStep"), bw)
|
||||
p.ystep = pattern_step(doc, dict_num(doc, d, "YStep"), bh)
|
||||
|
||||
// Both are required entries, and an out-of-range value is a broken file
|
||||
// rather than a new feature; the defaults are what a viewer shows.
|
||||
p.paint_type = dict_int(doc, d, "PaintType", 1) == 2 ? .Uncoloured : .Coloured
|
||||
switch dict_int(doc, d, "TilingType", 1) {
|
||||
case 2: p.tiling_type = .No_Distortion
|
||||
case 3: p.tiling_type = .Faster_Constant_Spacing
|
||||
case: p.tiling_type = .Constant_Spacing
|
||||
}
|
||||
|
||||
p.resources = dict_dict(doc, d, "Resources")
|
||||
if p.resources == nil {
|
||||
// /Resources is required (8.7.3.3), but a cell that only draws
|
||||
// rectangles needs nothing out of it, so its absence is not on its own
|
||||
// a defect worth recording. Falling back to the resources of the
|
||||
// stream that named the pattern is what every viewer does, and it is
|
||||
// the only other dictionary in scope; a name that still does not
|
||||
// resolve is reported by the interpreter that looks it up.
|
||||
p.resources = res
|
||||
}
|
||||
return pattern_intern(doc, p), .None
|
||||
}
|
||||
|
||||
// pattern_intern copies a finished pattern into the Document arena. Patterns
|
||||
// are built on the stack first so that a file that fails half way through
|
||||
// validation costs no arena at all -- an arena is never reclaimed before
|
||||
// `close`, and a page can name a broken pattern in every one of its fills.
|
||||
@(private)
|
||||
pattern_intern :: proc(doc: ^Document, p: Pattern) -> ^Pattern {
|
||||
out := new(Pattern, doc.allocator)
|
||||
out^ = p
|
||||
return out
|
||||
}
|
||||
|
||||
// pattern_step normalises /XStep or /YStep against the size of the cell.
|
||||
//
|
||||
// Only the magnitude spaces the lattice, so a negative step is read as its
|
||||
// absolute value rather than rejected. Zero is forbidden by the format and
|
||||
// occurs anyway; it would stack every cell of an infinite row at one place, so
|
||||
// it falls back to the cell size, which is what the step means when cells
|
||||
// abut.
|
||||
//
|
||||
// A step far smaller than the cell is the same denial of service written
|
||||
// differently: at a thousandth of the cell width every device pixel is covered
|
||||
// by a thousand cells, and one fill costs a thousand times what it looks like.
|
||||
// Overlapping cells are a legitimate design, so an overlap of up to
|
||||
// MAX_TILE_OVERLAP-fold is honoured before the step is snapped to the cell.
|
||||
@(private)
|
||||
pattern_step :: proc(doc: ^Document, step, cell: f64) -> f64 {
|
||||
s := abs(step)
|
||||
if !is_finite(s) || s <= 0 || s * MAX_TILE_OVERLAP < cell {
|
||||
add_repair(doc, .Bad_Resource, 0)
|
||||
return cell
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// The one composition every caller needs
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// pattern_matrix maps the pattern's own space to device space.
|
||||
//
|
||||
// `ctm` is the DEFAULT-space CTM of the page or form the pattern is a resource
|
||||
// of, not the CTM of the fill being painted -- see the note at the top of this
|
||||
// file. This is the only place the two are composed, so an interpreter that
|
||||
// routes everything through here cannot get 8.7.3.1 wrong in one operator and
|
||||
// right in another.
|
||||
@(private)
|
||||
pattern_matrix :: proc(p: ^Pattern, ctm: Matrix) -> Matrix {
|
||||
if p == nil {
|
||||
return ctm
|
||||
}
|
||||
return matrix_mul(p.mtx, ctm)
|
||||
}
|
||||
|
||||
// pattern_needs_color reports whether the caller must supply the colour: an
|
||||
// uncoloured tiling pattern (/PaintType 2) is a stencil, and every colour
|
||||
// operator inside its cell is ignored in favour of the components given
|
||||
// alongside the pattern name in `scn` (8.7.3.1). Those components live in the
|
||||
// underlying colour space of the /Pattern space, which is what
|
||||
// `Colorspace.under` holds.
|
||||
@(private)
|
||||
pattern_needs_color :: proc(p: ^Pattern) -> bool {
|
||||
return p != nil && p.kind == .Tiling && p.paint_type == .Uncoloured
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Bounds
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// pattern_bounds is the device-space region the pattern can paint under the
|
||||
// default-space `ctm`.
|
||||
//
|
||||
// A tiling pattern repeats over the whole plane, so the honest answer is
|
||||
// RECT_INFINITE, the identity for `rect_intersect`: a caller that intersects
|
||||
// it with the clip gets the clip. A shading pattern is bounded exactly when
|
||||
// its shading is -- an un-extended radial one is, an axial one is not.
|
||||
//
|
||||
// A transform that collapses the plane to a line or a point paints nothing at
|
||||
// all, and that is RECT_EMPTY rather than RECT_INFINITE, because the two are
|
||||
// opposite identities and a caller intersecting with the wrong one would fill
|
||||
// the whole clip with a pattern that has no area.
|
||||
pattern_bounds :: proc(p: ^Pattern, ctm: Matrix) -> Rect {
|
||||
if p == nil || !matrix_is_finite(ctm) {
|
||||
return RECT_EMPTY
|
||||
}
|
||||
m := pattern_matrix(p, ctm)
|
||||
if !matrix_is_finite(m) {
|
||||
return RECT_EMPTY
|
||||
}
|
||||
if _, ok := matrix_invert(m); !ok {
|
||||
return RECT_EMPTY
|
||||
}
|
||||
|
||||
switch p.kind {
|
||||
case .Shading:
|
||||
// `shading_bounds` answers RECT_EMPTY for a nil shading, which is the
|
||||
// right answer and is not worth a second guard here.
|
||||
return shading_bounds(p.shading, m)
|
||||
case .Tiling:
|
||||
// The lattice is infinite in both directions: /XStep and /YStep are
|
||||
// finite and non-zero after loading, so there is always another cell.
|
||||
return RECT_INFINITE
|
||||
}
|
||||
return RECT_EMPTY
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Tiling
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// pattern_tiles returns the matrices at which the interpreter must replay a
|
||||
// tiling pattern's content stream to cover `area`.
|
||||
//
|
||||
// Each matrix maps the cell's own space -- the space /BBox is written in -- to
|
||||
// device space, and already carries the pattern matrix and `ctm`, so the
|
||||
// interpreter sets the CTM to it, clips to /BBox, and runs the content. Cell
|
||||
// (i, j) of the lattice is the cell translated by (i * /XStep, j * /YStep) in
|
||||
// pattern space, so the returned matrices differ only in that translation and
|
||||
// share the pattern's rotation and scale exactly; the cells cannot drift apart
|
||||
// at the far end of a long fill.
|
||||
//
|
||||
// area the device-space region to cover, normally the fill's bounding box
|
||||
// intersected with the clip. It must be finite: RECT_INFINITE, an
|
||||
// empty rectangle and a NaN one all mean nothing to draw, because
|
||||
// "cover an unbounded area with a repeating cell" has no answer.
|
||||
// ctm the DEFAULT-space CTM of the page or form -- see the top of this
|
||||
// file. It is emphatically not the CTM of the fill.
|
||||
//
|
||||
// An empty slice means nothing to draw, which is also what a shading pattern
|
||||
// always gets: it paints in one pass rather than as cells. The result is
|
||||
// allocated with `allocator` and belongs to the caller.
|
||||
//
|
||||
// A cell whose /BBox merely touches the edge of `area` is included. It is
|
||||
// clipped away to a hairline at worst, and being inclusive is what keeps the
|
||||
// common case -- a lattice exactly aligned with the area, where the quotients
|
||||
// land on integers -- from dropping a whole row to a rounding error.
|
||||
pattern_tiles :: proc(p: ^Pattern, area: Rect, ctm: Matrix,
|
||||
allocator := context.allocator) -> []Matrix {
|
||||
if p == nil || p.kind != .Tiling || p.content == nil {
|
||||
return nil
|
||||
}
|
||||
// RECT_INFINITE has to be caught by identity, not by is_finite: its
|
||||
// corners are 1e30, which is a perfectly finite number, and
|
||||
// `transform_rect` passes it through unchanged.
|
||||
if area == RECT_INFINITE || !rect_is_finite(area) || rect_is_empty(area) {
|
||||
return nil
|
||||
}
|
||||
if !matrix_is_finite(ctm) || p.xstep <= 0 || p.ystep <= 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
m := pattern_matrix(p, ctm)
|
||||
if !matrix_is_finite(m) {
|
||||
return nil
|
||||
}
|
||||
inv, invertible := matrix_invert(m)
|
||||
if !invertible {
|
||||
// A singular pattern matrix collapses every cell to zero area.
|
||||
return nil
|
||||
}
|
||||
|
||||
// The lattice is regular in pattern space and only there, so the area is
|
||||
// taken back into pattern space and the indices are read off directly. For
|
||||
// a rotated or skewed pattern the pulled-back rectangle is the bounding
|
||||
// box of a rotated one, so a few cells in the corners fall outside the
|
||||
// area and are clipped away; that costs a little work and never misses a
|
||||
// cell, which is the right way round.
|
||||
pa := transform_rect(inv, area)
|
||||
if !rect_is_finite(pa) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Cell (i, j) spans [bbox.x0 + i*xs, bbox.x1 + i*xs] across, so it meets
|
||||
// the area when bbox.x0 + i*xs <= pa.x1 and bbox.x1 + i*xs >= pa.x0.
|
||||
i0, i1, ok_x := tile_range(pa.x0, pa.x1, p.bbox.x0, p.bbox.x1, p.xstep)
|
||||
j0, j1, ok_y := tile_range(pa.y0, pa.y1, p.bbox.y0, p.bbox.y1, p.ystep)
|
||||
if !ok_x || !ok_y {
|
||||
return nil
|
||||
}
|
||||
|
||||
nx := i1 - i0 + 1
|
||||
ny := j1 - j0 + 1
|
||||
if nx <= 0 || ny <= 0 {
|
||||
return nil
|
||||
}
|
||||
// In f64 because the product of two clamped index counts overflows no f64
|
||||
// but would need care in an int.
|
||||
total := f64(nx) * f64(ny)
|
||||
n := nx * ny
|
||||
if total > f64(MAX_PATTERN_TILES) {
|
||||
// Painting fewer cells than were asked for leaves part of the fill
|
||||
// bare, which is the least bad of the three options: the whole fill
|
||||
// dropped, a lattice respaced to fit (which would show gaps at the
|
||||
// wrong pitch and look like a rendering bug rather than a damaged
|
||||
// file), or an unbounded amount of work. Row-major truncation keeps
|
||||
// the painted part contiguous.
|
||||
n = MAX_PATTERN_TILES
|
||||
if !p.cap_reported {
|
||||
p.cap_reported = true
|
||||
add_repair(p.doc, .Bad_Resource, 0)
|
||||
}
|
||||
}
|
||||
|
||||
out, alloc_err := make([]Matrix, n, allocator)
|
||||
if alloc_err != nil {
|
||||
return nil
|
||||
}
|
||||
k := 0
|
||||
rows: for j := j0; j <= j1; j += 1 {
|
||||
ty := f64(j) * p.ystep
|
||||
for i := i0; i <= i1; i += 1 {
|
||||
if k >= n {
|
||||
break rows
|
||||
}
|
||||
out[k] = matrix_mul(translate_matrix(f64(i) * p.xstep, ty), m)
|
||||
k += 1
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// tile_range is the inclusive range of lattice indices whose cell meets
|
||||
// [lo, hi], for a cell spanning [c0, c1] and a step of `step`.
|
||||
//
|
||||
// The quotients are formed from numbers that came out of a file and can be
|
||||
// enormous, so they are clamped while they are still f64: `int(1e300)` is
|
||||
// undefined, and the clamp is what makes the tile cap the only thing standing
|
||||
// between a hostile file and an allocation.
|
||||
@(private)
|
||||
tile_range :: proc(lo, hi, c0, c1, step: f64) -> (from, to: int, ok: bool) {
|
||||
if step <= 0 || !is_finite(step) {
|
||||
return 0, 0, false
|
||||
}
|
||||
a := math.ceil((lo - c1) / step)
|
||||
b := math.floor((hi - c0) / step)
|
||||
if !is_finite(a) || !is_finite(b) {
|
||||
return 0, 0, false
|
||||
}
|
||||
a = clamp(a, -MAX_TILE_INDEX, MAX_TILE_INDEX)
|
||||
b = clamp(b, -MAX_TILE_INDEX, MAX_TILE_INDEX)
|
||||
if b < a {
|
||||
return 0, 0, false
|
||||
}
|
||||
return int(a), int(b), true
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Painting a shading pattern
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// pattern_paint_shading composites a shading pattern's shading onto `bmp`
|
||||
// through `clip`, which the caller has already narrowed to the shape being
|
||||
// filled.
|
||||
//
|
||||
// This exists so that the pattern matrix is composed in exactly one place:
|
||||
// everything else about the call is `shading_paint`'s, including that `bmp` is
|
||||
// premultiplied and that `mask` is an optional soft mask aligned with it in
|
||||
// device pixels.
|
||||
//
|
||||
// `ctm` is the default-space CTM, not the fill's. The pattern's /ExtGState is
|
||||
// the caller's to apply -- `alpha`, `mode` and `mask` are where its effect
|
||||
// arrives here.
|
||||
@(private)
|
||||
pattern_paint_shading :: proc(doc: ^Document, p: ^Pattern, bmp: ^Bitmap, ctm: Matrix,
|
||||
clip: Raster_Clip, alpha: f64, mode: Blend_Mode,
|
||||
mask: ^Bitmap) -> Error {
|
||||
if p == nil || p.kind != .Shading || p.shading == nil {
|
||||
return .Invalid_Argument
|
||||
}
|
||||
return shading_paint(doc, p.shading, bmp, pattern_matrix(p, ctm), clip, alpha, mode, mask)
|
||||
}
|
||||
Reference in New Issue
Block a user