Skip to content
draw_target

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.

Traits