-
Notifications
You must be signed in to change notification settings - Fork 2
Backlog
What's deliberately not built yet, and why — plus where this package's own scope ends. Everything here was considered directly against a concrete question, not left unexamined by default; each entry says what would make it worth building. For what's already built and the reasoning behind it, see Changelog.
-
UnsafePointer-backed buffer instead ofList[UInt8]— a performance path, not a correctness one. This item's own condition was "don't reach for it before profiling says theListbounds-checking is actually the bottleneck," and profiling has since said so: a checked byte write measures 1.73ns against 0.26ns unchecked, which at three bytes a pixel was roughly half the cost of a solid fill.What that produced was narrower than this item, though, so the item stands. Storage is still a
List; only the paths that had already established their bounds read and write throughunsafe_ptr--Canvas.write_pixel/read_pixel, and the hot per-byte loops in deflate, the PNG codec anddownsample(see the Changelog).set_pixel/get_pixelremain the checked entry points. Changing whatCanvasowns is a different and larger question, and nothing has needed it: the measured cost was the check, not the container. -
Named color palettes were removed from
Coloronce (seecolor.mojo's history) and came back on their own terms ascanvas.named_colors(#222): the CSS names as comptime constants in an opt-in module, not on the core type. What stays deferred is a palette -- an ordered set for categorical series -- which is chart vocabulary and belongs one layer up. -
Point-in-shape hit-testing left this list with #213:
Path.in_fillandPath.in_strokeare public, under either fill rule, alongsideTransform2D.inverse_pointfor the data-space question ("which data point is nearest the cursor") that a chart usually asks first andPath.bounds()for a coarse pass. -
Marker/symbol shape presets (star, cross, diamond, triangle -- common scatter-plot marker shapes beyond a plain circle). Considered and deliberately left out: every one of these is already directly expressible with existing primitives (
fill_polygon/fill_polygon_ aafor triangle/diamond/star, two thickdraw_line_aacalls for a cross) with no new canvas-level math behind any of them, unlike every primitive actually built this session (each needed its own real geometry or coverage algorithm). A marker preset library is a convenience/vocabulary layer that belongs one layer up, in whatever charting library gets built on top of this -- it's the one that knows which marker set an actual chart type wants, notcanvasguessing ahead of time. -
Selectable line joins/caps for hard-edged strokes (miter/bevel/ round on
draw_line/draw_polyline/draw_polygon, the way SVG and Canvas stroke APIs expose them). The antialiased strokes have had realLineCapandLineJoinparameters since the stroke was rewritten to fill its own outline, so this is now only about the hard-edged family -- which is definitionally 1px Bresenham, where a join style has nothing to act on. A thick hard-edged stroke with a chosen join would mean giving those functions a width first, and thick strokes in charts are reached through the antialiased path or throughfill_path/fill_path_aaas a filled ribbon instead. Real, isolable feature if a concrete need shows up. -
Anti-aliased axis-aligned rects (
fill_rectwithFloat64bounds and edge coverage). Since #244 everyInt-taking primitive on the trait has aFloat64overload, andfill_rect's snaps each edge to the nearest pixel boundary -- so a tightly-packed bar chart no longer accumulates a width wobble from each bar rounding on its own, which was the case that raised this. What remains deferred is a rect whose edges carry partial coverage: that isPath.rectwithfill_path_aatoday, and a dedicated primitive is worth building only if that route measures as a cost in a real chart. -
CFF subsetting in
PdfCanvas. A CFF-flavored OpenType font is embedded whole (FontFile3), which is correct but adds the entire file per document -- a few hundred kilobytes for a Nimbus face -- where a TrueType font adds a few kilobytes of subset. Subsetting a CFF program means rewriting its INDEX structures and charset, a separate job from theglyf/locarebuildpdf_font.mojodoes. Do it if PDF size with CFF fonts turns out to matter to someone. -
Color emoji and text on a path in
PdfCanvas. A color bitmap glyph has no outline in the embedded program and draws nothing; text on a path has noDrawTargetform and stays raster and SVG only. Both are reachable throughtext_outline/text_pathas shapes today, which is what a chart needs of them. -
Linear-light blending in
SvgCanvasandPdfCanvas. The SVG backend asks for it withcolor-interpolation="linearRGB"and viewers mostly ignore the request; PDF has no such switch. Both keep the setting forcolor_spaceand otherwise leave it to the raster backend, which is the only place it can be guaranteed. -
A tiled renderer (each worker owning a screen tile with an L1-resident accumulator, instead of row bands), step 5 of #382. Deliberately not built, from a measurement taken after the batch landed (v0.29.0, 2026-09-10): pinning the whole process to the L3 slice that holds the canvas is still worth 1.7x-2x on a large single shape (the r=250 disk 137 -> 82 us, the donut 73 -> 37) and 1.1x-1.4x on the chart-shaped batch rows (2,000 mixed disks 1,280 -> 917 at 32 pinned threads, 2,000 path markers 2,553 -> 1,813 on one slice), but that gain is task placement: every worker sitting on the slice the canvas lives in. A tile still writes the shared canvas, and the accumulator locality a tile would add is what the per-band scratch already provides. What would make it worth building: a runtime way to place tasks on a chosen CCX (upstream), or a canvas allocated across slices so no placement is wrong -- either of which is a change to where the pixels live, not to how they are split. The same run showed 32 pinned threads beating 64 unpinned on the compute-bound batch rows, which is a worker-cap question, filed separately.
-
Depth buffering for meshes.
fill_meshdraws faces in the order given, which is the painter's algorithm a depth-sorted surface relies on. Intersecting surfaces, and faces that cross each other in depth, have no correct draw order under it. A z-buffer would fix that and every static plotting library lives without one, because it brings the seam problem back in a harder form: a depth test needs edge ownership of a different kind, a top-left rule on a non-anti-aliased triangle fill, which is a second rasterizer beside the mesh one rather than an extension of it. Worth building only for a 3D scene with self-intersecting geometry, which a plot is not. -
A smooth-shaded mesh in
SvgCanvas. Mesh gradients are SVG 2 and no shipping browser renders them, sofill_mesh_shadedemits each face flat at the mean of its corners there, the way every SVG exporter does. Subdividing until the steps are sub-pixel multiplies the face count by the square of the subdivision. Revisit when a browser ships mesh gradients.
Anything chart-shaped — axes, scales, legends, series types, figure
layout. That's a higher-level charting library's job, built on top of
canvas and depending on it, never the reverse (see the repo root
README.md).
A window, an input layer, a frame loop, or a GPU. The package draws into buffers and files. What an interactive application or a game engine would need on top is a display surface, input, timing, a premultiplied-alpha pipeline for compositing-heavy frames, and blits that record into the batch; all of it is a layer above this one, and the parts that are rendering rather than glue are recorded as such here rather than left implicit.
Rich display integration — a Julia/IPython-style display()
protocol, so a Canvas renders inline in a notebook. Built once on a
display-protocol branch (2026-08-20) and deleted unmerged on
2026-08-24: the display protocol is language- or ecosystem-level
infrastructure, and belongs in Mojo proper or in a package dedicated to
it, not bolted onto one drawing library. canvas_mojo's side of that
contract is already complete anyway -- write_png produces the bytes
any such protocol would hand to a frontend. If Mojo grows a display
protocol, wiring a Canvas into it should be small and belongs
wherever the protocol lives.
Yes. canvas can fill any shape it can describe -- convex or not, self-intersecting or not, straight-edged or
curved, hard-edged or antialiased, flat or gradient-filled (linear or
radial), correctly hole-punched or union-merged depending on winding
rule -- which is the actual precondition for a higher-level charting
layer to render bar/line/area/scatter/pie/donut charts, stacked and
overlapping regions, and gradient-shaded fills without ever needing to
drop back down into raw pixel manipulation itself. Text can be
measured, aligned, rotated, and multi-line-laid-out, and now its
rotated footprint can be known before drawing (measure_text_block)
-- exactly what axis-tick-label placement needs. Every deferred item
above was considered and rejected for a concrete reason, usually that
it is already directly expressible with what exists or belongs one
layer up in a charting library that knows its own vocabulary. A real
need surfacing downstream is the signal to build one, and that is how
the bulk markers, the batch, the supersampled region and the mesh
arrived: each from a measured need in the charting library built on
this package.
That argument is made from capability alone -- canvas can draw
anything a chart needs, therefore it's enough. Coverage
checks the same claim from outside instead, against what real
visualizations actually use (Beagle's 41,000-visualization corpus) and
against the mark vocabulary Vega-Lite independently settled on. Both
agree with the conclusion here, which is worth more than either
argument alone.
The review did miss one thing: an ellipse turned out to be the single primitive shape a
trait-targeting caller could not draw exactly, because
fill_path_aa -- the escape hatch every other absent shape relies
on -- can only approximate one. Both fill_ellipse_aa and
draw_ellipse_aa were added to the trait as a result, making the
ellipse the only shape there carrying a fill and an outline.