npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@commandagi/graphics

v0.1.0

Published

The CommandAGI 2D graphics substrate: colour, surfaces, typography, geometry, Scene2D, CompositionPlan2D, typed view spaces, a region/tile runtime and pluggable backends that always report what they could not do.

Readme

@commandagi/graphics

One 2D graphics substrate, under every authoring app.

The apps stay different. Draw owns vector and diagram authoring; Paint owns brush-centric stroke history; Photo owns adjustment layers and high-precision imagery; Decks owns slides, masters and themes; Video owns time; EDA owns electrical topology; 3D owns geometry and UV projection. A common graphics runtime is not a common document schema.

What they share is the machine underneath, and one boundary makes it possible:

domain document  ≠  Scene2D  ≠  CompositionPlan2D  ≠  output surface

| layer | what it states | | --- | --- | | domain document | what the author made — a .drawx op-graph, a .imgx stack, a .deckx deck | | Scene2D | what it LOOKS like: groups, paths, paints, images, text-layout references, clips, masks, blend/opacity/isolation, effects, bounds, provenance | | CompositionPlan2D | the ordered blending PROGRAM: sources, stacks, masks, clip-to-previous, backdrop effects, isolation, intermediates | | backend | pixels, vectors, PDF primitives, PPTX shapes, a hit map — and always a RenderReport |

Subpaths

@commandagi/graphics/color        sRGB, HSL, the 16 W3C blend modes, 4 colour spaces, ONE CSS parser
@commandagi/graphics/image        SurfaceDescriptor, Surface2D, regions, tiles, mips, PNG
@commandagi/graphics/text         UAX#9 bidi, UAX#14 breaking, metrics providers, TextLayoutPlan
@commandagi/graphics/2d/geometry  Mat2D, Path, exact bounds, stroke expansion, hit testing, spatial index
@commandagi/graphics/2d/scene     Scene2D
@commandagi/graphics/2d/composition  CompositionPlan2D
@commandagi/graphics/2d/view      typed coordinate spaces, viewport, snapping, handles, edit intents
@commandagi/graphics/2d/runtime   region requests, render reports, caches, the plan executor
@commandagi/graphics/2d/backends  reference CPU, Canvas2D, SVG
@commandagi/graphics/operators    semantic operators vs. their implementations

Three rules that are load-bearing

Storage is a request, not a property. SurfaceDescriptor names sample format, colour space, alpha convention, storage location, extent, tiling and mip levels. A thumbnail asks for rgba8, a print export asks for rgba32f in a wide gamut, and they are one operation with two requests — not two engines. rgba16f is stored in f32 lanes whose values are always exactly representable as binary16, so the precision is honestly half.

The request is a region. render(region, scale, target, policy), never "the whole buffer again". A one-pixel edit in a gigapixel document touches one tile; report.cache.hits is the measurement.

A degradation that is not reported is a lie. Every render returns a RenderReport naming the backend, the input digest, the fonts and assets actually used, the operator implementations selected, the cache statistics, whether the output is deterministic, and every substitution, approximation, dropped feature and refusal. A face with no registered outlines still draws as boxes in the reference backend, named glyph-outlines-unavailable and counted; the SVG backend refuses textAsOutlines for that face rather than emitting boxes that claim to be glyphs.

Typography is canonical

layoutText() produces a TextLayoutPlan: styled runs, resolved bidi levels, glyph ids, glyph positions, line boxes, baselines, fonts with digests, substitutions, missing codepoints, and a named conformance level for the bidi and line-break algorithms. Browser, canvas, SVG, PDF, PPTX, video and thumbnail paths all consume it. They may rasterize glyphs differently. They may not independently decide line breaks, fallback fonts or glyph placement.

Metrics come from a PROVIDER, and the provider's identity is part of the plan's digest — the same text against browser metrics and against AFM metrics is genuinely two layouts, and a cache that conflated them would serve one to the other.

A generated result is a pinned record, not a function call

blur resolves to whichever implementation policy asks for and the answer is the same either way. inpaint does not: it INVENTS content, and that content becomes part of the document. So an operator declaring requiresMaterialization is evaluated through operators/generative.ts, where reopening returns the pinned asset and invokes nothing, a stale pin is reported and still shown, and generating in the first place requires a policy that says so. decideGenerative is a pure function, which is the only shape in which "would this reopen call a model" is answerable without a model.

What it does not do

Partial OpenType shaping, named rather than claimed. Cursive joining (from Unicode's own DerivedJoiningType data) and GSUB SingleSubst/LigatureSubst DO run, so Arabic joins and fi fuses. Mark attachment, cursive attachment, contextual substitution (GSUB 5-6) and Indic reordering do NOT, so Devanagari and Khmer are still wrong. plan.shaper.openTypeShaping stays false because it claims all four; plan.shaper.substitution says exactly what ran and what was refused.

Pair kerning IS applied when the metrics provider offers it, and it is positioning rather than substitution — which is why it does not make openTypeShaping true. plan.shaper.kerning names exactly which tables were read, which subtables were refused, and where the adjustments were applied.

Glyph outlines come from a parsed font file (text/fontFile.ts): TrueType glyf and CFF Type 2 charstrings, no dependency. A plan laid out against a real face renders real glyphs in the reference and SVG backends. A plan laid out against the built-in AFM metrics has no font file behind it, so it still draws boxes at the plan's exact positions and names the limit glyph-outlines-unavailable — which is a different failure from the one this replaced: the old Draw pure rasterizer box-drew text and measured it wrongly, so its line breaks disagreed with the browser's.

The numbers

pnpm --filter @commandagi/graphics bench (or node --import tsx bench/regions.ts) measures the claims above and exits non-zero if one regresses. On a 2026 laptop:

| claim | measured | | --- | --- | | a one-pixel edit in a 2048² document, tiled vs. whole-frame | 9.8x cheaper — 1 tile recomputed, 63 from cache | | a fit-to-window preview of a 4096² document at 1/16 | mip level 4, 0.6 ms; the whole pyramid costs 85 MiB over a 256 MiB base | | a brush stroke on a 20000² canvas, tiled vs. dense | 3 MiB vs 5.96 GiB — 2035x, 3 tiles allocated | | editing a layer's samples | invalidates its tiles; 0 stale hits |

The first number was 1.6x until the benchmark existed. The cost was planNodeDigest hashing all 64 MB of a live layer's samples on every frame, which is correct and expensive; PlanSurfaceNode gained an owner-supplied contentDigest for exactly that case. Omitting it is slow and never wrong; supplying a stale one is wrong, which is why it is the buffer owner's job and not the runtime's guess.