draw_target
Mojo module 🡭
draw_target
DrawTarget: the drawing-primitive interface a higher-level charting layer renders through, so a plot/scale/theme layer can target a raster Canvas, a vector SvgCanvas or a PdfCanvas page without knowing which it holds. Neither vector backend has a fixed pixel resolution, so nothing rendered through the trait deals in supersampling.
Twelve drawing primitives are declared – fill_rect,
fill_rect_gradient, draw_line_aa, fill_circle_aa,
fill_circles_aa, fill_ellipses_aa, draw_circle_aa,
fill_ellipse_aa,
draw_ellipse_aa, fill_arc_aa, fill_ring_sector_aa,
stroke_path_aa and fill_path_aa – a subset of canvas.shapes.
fill_circles_aa is the one that is not a distinct shape: it is
fill_circle_aa in bulk, on the trait because a generic caller has no
other way to reach the raster backend’s batched path. fill_mesh is
on the trait for the stronger version of that reason: adjacent faces
filled one at a time seam along every shared edge, and only a call
that sees the whole mesh can draw it seam-free (#425). fill_polygon, radial gradients and
path-shaped gradients are not on the trait; each exists as a free
function or a Canvas method instead. The two strokes, draw_line_aa
and stroke_path_aa, take the full stroke style – dashes,
dash_offset, cap, join, miter_limit – so a dashed series or a
square-capped rule renders the same way on every backend.
Circle and ellipse outlines take sub-pixel centers and radii plus a stroke width. They do not support dashes.
draw_image is the one primitive that is not a shape: a block of
pixels, a Canvas, placed with its top-left at a user-space point and
scaled to a user-space box, so it lands where a fill_rect at the
same coordinates would on every backend, under the transform. It is
on the trait because the shapes cannot express a raster block: an
image plot drawn as a fill_rect per cell is fine on Canvas and
megabytes of <rect> elements on SvgCanvas, where the same block
as one <image> holding a PNG is kilobytes (#392). Canvas resamples
nearest-cell, SvgCanvas embeds a PNG data URI and PdfCanvas an
image XObject; a translucent pixel blends source-over on each.
Method parameters mirror the same-named function in
canvas.shapes/canvas.path, minus supersample: a raster
implementation picks its own supersample factor, and a vector one has
no equivalent knob. fill_path_aa does take a fill_rule,
defaulting to EVEN_ODD as the raster function does, because the two
backends would otherwise disagree on a path with more than one
sub-path: SVG’s own default is nonzero, so a hole that even-odd
punches in a PNG would fill solid in the SVG unless the element says
fill-rule="evenodd".
The transform state is on the trait too: save/restore,
translate/rotate/scale/transform/set_transform/
reset_transform, current_transform and has_transform, with the
semantics Canvas defines (see Canvas.save). A mark drawn through
the trait can set up a local frame – translate to a panel, rotate for
a wedge – and draw at the origin on any backend. SvgCanvas puts
the current matrix on each element as a transform attribute and
PdfCanvas as a cm inside the element’s q/Q pair. One
rule holds on both: strokes are built in user space, so under a
scale a stroke’s width, dashes and caps scale with the shape; the
state is for placement and rotation, and data still maps through
scales.
Placement is one rule on every backend. Pixel (px, py) is centered at
(px, py) and spans half a pixel each way. Int arguments are pixel
indices: a rectangle from column x with width w covers pixels x
through x + w - 1, whose geometric edges are x - 0.5 and x + w - 0.5;
a circle’s center (cx, cy) is a pixel’s center. Float64 arguments
are geometry: a rectangle from x spanning w has edges at x and x + w,
and fill_rect snaps each edge to the nearest pixel boundary, so
fill_rect(19.5, 4.5, 40.0, 30.0) and fill_rect(20, 5, 40, 30) are
the same call. Under a transform every primitive maps its geometry –
an Int rectangle maps its edges at x - 0.5, not x – and a
rectangle then snaps in the space it is drawn in: device space on
Canvas, user space on SvgCanvas and PdfCanvas, which have no
device pixels. That one rule is what makes the supersampling recipe on
downsample exact for rectangles as well as paths and text.
The blend mode is on the trait as well: set_blend_mode and
blend_mode, carried by save/restore, with the formulas in
canvas/blend.mojo. SvgCanvas emits the blend modes as
mix-blend-mode and draws the Porter-Duff operators source-over,
since CSS has no keyword for them; PdfCanvas emits them as an
ExtGState /BM. So is the color space – set_color_space and
color_space – which decides whether a later source-over blend mixes
in sRGB or linear light.
push_clip and pop_clip restrict drawing to a rectangle and undo
that, intersecting with whatever is already clipped so nested clips
compose. They are here because a caller generic over the trait
otherwise cannot clip at all: a chart’s marks are drawn through one
generic function, and with an axis domain narrower than the data they
paint over the axis furniture and off the canvas, which clipping at
the concrete backend outside that function cannot fix without
clipping the axes too (#403). The rectangle is in user space and
takes the current transform on every backend, as fill_rect does;
only the rectangle is on the trait, since a path clip is what
Canvas and SvgCanvas offer and PdfCanvas spells differently.
Neither raises.
begin_annotated_group and end_annotated_group label the enclosed
drawing. SVG and PDF preserve the label; Canvas treats both calls as
no-ops.
begin_batch and end_batch are the mirror image: Canvas defers
the anti-aliased shapes between them and draws them in one parallel
pass at end_batch, in order, which is the only way a caller generic
over the trait can reach that pass; SvgCanvas and PdfCanvas draw
nothing until serialized anyway and treat both calls as no-ops. Both
pairs are lossless to drop, which is what admits them here.
draw_text is on the trait. The three backends lay text out through
the same shaping and line-breaking code and differ only in how they
emit glyphs: Canvas composites masks, SvgCanvas writes a <text>
element and leaves the glyphs to the viewer, PdfCanvas embeds a
subset of the font. The one place they genuinely differ is family:
the raster and PDF backends resolve it against the installed fonts,
where SVG emits a CSS font-family and maps the generic names
("Sans", "Serif", "Monospace") to their CSS spellings and passes
anything else through verbatim. The FontCache every call takes is
what the raster backend resolves through; SVG has no glyphs to cache
and ignores it, and PDF keeps its own because the subset it embeds
lives with the document. measure_text is not on the trait: it is
backend-independent already. draw_text_runs is beside draw_text
for a label whose pieces differ in size or slant, and is where the
one-element-per-call shape of SVG text shows: it writes one <text>
of <tspan>s where the other backends draw the runs one at a time.
Conformance is nominal, not structural: Canvas (canvas/buffer.mojo),
SvgCanvas (canvas/vector/svg.mojo) and PdfCanvas
(canvas/vector/pdf.mojo) each name DrawTarget in their struct
declaration.