Skip to content

Releases: posit-dev/hephaestus

Hephaestus v0.4.1

Choose a tag to compare

@thomasp85 thomasp85 released this 07 Sep 10:03
40a4f7d

Fixed

  • A frame wider or taller than 4096 px renders on the Hybrid backend. The device is asked for max_texture_dimension_2d up to 16384 rather than wgpu's 8192 default, and the rasterizer's cap on intermediate layer textures is raised to match, so a clipped panel past 4096 px no longer fails the render; past a device's own limit — on either rasterizing backend — the frame is a BackendError rather than a wgpu panic.
  • A document holding a polar plot reads back. The decoder restores the projection before it attaches the axes, so a polar placement no longer panics against the not-yet-set Cartesian default; a placement that genuinely mismatches is reported as a DocumentError rather than panicking.

Hephaestus v0.4.0

Choose a tag to compare

@thomasp85 thomasp85 released this 03 Sep 15:57
beb8a8c

Changed

  • The PNG writers take a PngCompression, before their trailing dpi, the way the TIFF writers take a TiffCompression. Balanced matches the previous output byte for byte; Fast encodes a dense frame in 8 ms rather than 146 ms, for about half again the bytes, which is what a host serializing a PNG per animation frame needs. Breaking: write_png / write_png_to / encode_png gained the argument.

Hephaestus v0.3.0

Choose a tag to compare

@thomasp85 thomasp85 released this 03 Sep 12:31
bcb05fd

Added

  • Chrome is pickable. SceneBuilder::push_pick_scope / pop_pick_scope record the tree a primitive is drawn in, and plot::pick names it: a hit on an axis tick label, gridline, legend key, strip or title reports its plot, region, part and ordinal through PlotPath, and the scope stack is the path an event bubbles along.
  • PickIndex::hits_at returns every hit, topmost first, each carrying its scope chain — it costs the same as topmost-only. pick_at remains the one-number convenience.
  • Rectangle and lasso queries. hits_in (bounds intersect), hits_within (bounds enclosed, and exact) and hits_in_path (bounds-centre inside a path) for brushing and marquee selection, plus *_into variants that reuse a caller's buffer.
  • Slot::from_name and Slot::ALL — the reverse of Slot::name, so a region name recovered from a hit round-trips back into CompositionLayout::get.
  • Plot::index_in_patch and Axis::id — a plot's position within its patch's attach list, and the handle an axis was attached under. (patch_id, index_in_patch) is the pair update_plot_at already addresses plots by.
  • RecordingScene::draw_ops and scope_at — the ops that draw something, and the pick-scope stack in effect at an op.
  • PDF export, behind the pdf feature. hephaestus::pdf::write_pdf / write_pdf_to / encode_pdf over PdfScene, which implements SceneBuilder and not Renderer, so PlotComposition::render feeds it unchanged. Where svg aims at editable output this aims at a fixed one, so every glyph a plot draws is embedded — as a subset TrueType synthesized from the outlines actually used, which is a few kB rather than the megabyte face and covers CFF/OTF sources, variable-font instances and ttcf collections in one path. Text stays selectable and searchable through a /ToUnicode CMap; markdown links become /Link annotations; a filled-and-stroked mark is one B operator; gradients are ShadingType 2 / 3 patterns with a non-zero radial focal radius expressed natively; a mesh is a native ShadingType 4 Gouraud shading rather than one fill per triangle; a gradient or mesh whose alpha varies across it — a confidence band that fades, say — is carried by a luminosity soft mask rather than flattened to one opacity; a translucent or blended layer is a real transparency group; and color emoji render, both COLR paint graphs and bitmap strikes. Whatever PDF cannot express is reported by PdfScene::warnings. Adds only skrifa and flate2, both already in the tree, so --no-default-features --features document-read,pdf builds with no renderer on rustc 1.86; a bitmap color glyph additionally needs png.
  • primitives::RibbonOptions::seam_bleed and primitives::ribbon_band_mesh_with_bleed — control the overlap ribbon tessellation gives adjacent quads to hide the antialiased seam between independent fills. Both default to the existing values; 0.0 is for a consumer that paints the mesh as one object, where there is no seam and the overlap is distortion.
  • SVG export, behind the svg feature. hephaestus::svg::write_svg / write_svg_to / encode_svg over SvgScene, which implements SceneBuilder and not Renderer, so PlotComposition::render feeds it unchanged. Labels are <text>: markdown spans become <tspan>s, [text](url) an <a href>, underline and strikethrough text-decoration-line, and a run is placed by one anchor plus textLength. A filled-and-stroked mark is one <path>, a wrapped label one <text>. The face request — family, size, weight, style, font-stretch, letter spacing, OpenType features and variable-font axes — is named on the root and inherited, with only the spans that differ naming their own; @import covers families resolved through fetch_google_font, and SvgConfig::embed_fonts inlines faces as @font-face. Whatever SVG cannot express is reported by SvgScene::warnings. Adds only skrifa, so --no-default-features --features document-read,svg builds with no renderer on rustc 1.86; embedding a raster image needs png.
  • hephaestus::scene::TextSource — what a glyph run was shaped from, carried on GlyphRun and OwnedGlyphRun: the source substring, a FontSpec, the run's advance, its decorations, a link destination, and a TextGroup naming the runs laid out together. None where a caller positioned glyphs itself.
  • hephaestus::style_vocab::FontSpec, beside FontFamilyEntry, GenericFamilyKind, FontStyleKind, FontFeatureSetting and FontVariationSetting, which moved there from text. Re-exported from crate::text, so every existing path still resolves.
  • TextRun::text / TextRun::font_spec — a shaped run keeps what it was shaped from.
  • hephaestus::text::google_fonts::google_fetched_families — which families this process resolved through Google Fonts.
  • A plot can be on screen before the renderer exists. PlotView.create takes a placeholder — an <img> already in the page, or a URL — and shows it until the first live frame lands. Measured in headless Chrome: plot pixels at 57 ms against 185 ms, with 0 of 378,000 pixels differing between a native vello-hybrid render and the client's WebGL2 frame, no blank frame between them and zero layout shift. The placeholder becomes the saveOnRightClick overlay rather than a second element, which skips that overlay's eager toDataURL and keeps the page's alt. Nothing touches the element until there is a frame to reveal, so a false isSupported(), a failed wasm fetch and an unreadable document all leave the picture on screen; www/index.html?nowasm=1 demonstrates it.
  • Every raster write entry point takes a trailing Option<f64> dpi, so a file declares the resolution it was rendered at rather than leaving a viewer to assume 72 dpi: a PNG pHYs chunk, the JFIF density fields, the TIFF resolution tags, and an EXIF block in the WebP container. None records nothing.
  • hephaestus::document::read_document — the composition and the hints in one pass. read_hints is only cheap in isolation; beside read_composition it decodes the head twice. ReadContext::builtin() is a shared context beside it, since ReadContext::new builds a fifteen-entry geom factory table per call.
  • examples/document_placeholder.rs — reads a document, registers the faces the wasm client ships, rasterizes through vello-hybrid at a device-pixel box and writes a PNG with its dpi. Built with document-read alone, so the picture is made from what the reader rebuilds rather than from the live composition.
  • crates/hephaestus-wasm/bench/ — a dependency-free measurement harness driving the DevTools Protocol from Node: the pixel diff, the swap and first-paint checks, and a static server whose application/wasm MIME and compression switches are the experiment.
  • An "Embedding: the producer's side" section in crates/hephaestus-wasm/CLAUDE.md — what a host emits per render, the page shape, the three ways to get the wasm to the page and what each costs, and what is known and unknown about Positron's plot pane.
  • Images render in rich text. ![alt](location) draws wherever markdown reaches: a TextGeom or TextFitGeom row, a plot or composition title, an axis title, a strip label, a legend title, a break label. Alt text is dropped — the location is the key. A registered name wins; otherwise the location is read as a filesystem path or an http(s) URL and cached back into the register, through the new ImageRegistry::resolve (get still answers for registered entries alone, and loaded_names reports what a register read for itself). An inline image stands one em tall and takes its width from the pixel aspect ratio, centered on the font's ink band; a tag alone in its paragraph is a block image that fills the column, inherits the reserved img selector, and at natural width reads its own pixels as pt. A location that resolves to nothing draws a one-em placeholder styled by the reserved broken_image selector. flatten_rich_run drops images, so text on a curve keeps the advance and loses the picture.
  • hephaestus::image::decode_image / read_image — decode by signature rather than by named format. A recognized format whose codec is not compiled in reports io::ErrorKind::Unsupported, distinguishing it from bytes that are not an image.
  • image-url (off by default) — read an image named by an http(s) URL. Synchronous fetch on first use, memoized for the process, no on-disk cache.
  • A composition-level image register. PlotComposition::image_registry / set_image_registry / image_registry_mut / image_registry_ref. Composition chrome resolves against it and a plot's chrome and geoms against their own plot's, with no fallback between them.
  • Documents carry both registers. The imgs chunk includes names a register read from a location, not only those a caller registered, plus the composition's own register, which rides a composition field of its own.
  • ImageGeom — raster images in a panel, with size, rotation, anchor, opacity, sampling mode and aspect-fit rule. Pixels live in a plot::ImageRegistry and the "image" channel carries the name, so a discrete scale maps a category onto an image. Each axis takes its extent from the channels supplied: "x2" / "y2" spans a data-space rect, absent anchors at "x" / "y" with "width" / "height" in pt, and the two mix freely; with neither size channel the image's own pixels are read as pt, with one the other follows the aspect. "fit" is "stretch" (default), "contain" or "cover", and "anchor_x" / "anchor_y" distribute the slack or overflow. Band offsets default to 0.0 on all four edges, unlike RectGeom; an image that should fill its band sets "x_band" and "x2_band" itself. Three limits: under a non-linear projection a data-space extent is the bounding box of its projected corners (...
Read more

Hephaestus v0.2.0

Choose a tag to compare

@thomasp85 thomasp85 released this 22 Aug 22:31
ab59ea9

Added

  • A second rasterizing backend, behind the off-by-default vello-hybrid feature. backend::hybrid::{HybridScene, HybridRenderer} rasterize through Vello Hybrid's sparse strips — path processing and coverage on the CPU, a plain render pipeline on the GPU — independently of vello: either, both, or neither. Picking returns only ids that were actually drawn, there is no draw-count ceiling, and the rasterizer is under half the wasm size of the compute-shader one. Parity covers fills, strokes, gradients, clip and blend layers, meshes, images and text.
  • A wasm build that needs no WebGPU, behind the off-by-default webgl feature. backend::hybrid::HybridWebGlRenderer runs the same sparse-strip rasterizer against a canvas's WebGL2 context and pulls in no wgpu at all; window::WebGlHost presents it with the same render / resize / dispatch surface CanvasHost offers. It cannot rasterize offscreen and does not implement Renderer, since the canvas is the only render target.
  • Live window presentation, behind the off-by-default window feature (winit). window::run(config, app) opens an OS window and pumps an event loop; the app implements WindowApp, drawing into a Frame — scene, physical size and dpi — and taking resize, cursor and mouse-button events. A resize renders at the new size rather than stretching the last frame. Picking is opt-in through WindowConfig::picking, after which EventCtx::pick_at answers the id under any pixel. winit stays out of the public surface. Native only; see examples/window.rs.
  • Canvas presentation, behind the off-by-default canvas feature (wasm32 only, no winit). window::CanvasHost attaches to a <canvas> already on a page and shares WindowApp, Frame and Event with the desktop window, but the page keeps its own event loop and calls render, resize and dispatch. Picking answers from a frame or two earlier rather than parking the main thread.
  • Presentation runs on either rasterizing backend. WindowConfig::backend names it, and its variants exist only for the backends compiled in. window and canvas require a rasterizing backend rather than vello specifically. On the sparse-strip backend the hosts present straight into the swap chain, skipping the intermediate texture and its per-frame full-screen blit; HybridRenderer::set_target_format is how a host names its surface's format.
  • The hitmap can refresh less often than the frame. WindowConfig::pick_interval caps how often the pick pass runs and set_refresh_pick is the per-render control under it; frames in between reuse the previous hitmap, so pick_at may describe a slightly older frame. The pick pass rasterizes the scene a second time — at 100k marks it costs 60 ms of a 145 ms frame — so throttling it during a resize drag or an animation recovers all of that.
  • crates/hephaestus-wasm — a wasm render client, shipped as the npm package hephaestus-wasm: a page points it at a canvas and a .hplot document and gets a plot that re-solves its layout on resize instead of stretching. WebGL2 is the default configuration and needs no WebGPU; the mutually exclusive wgpu-backend feature swaps in the compute-shader path. PlotView exposes render, resize, setDark, isSupported, hasFonts and documentFormatVersion, plus saveOnRightClick — an overlaid, coincident <img> that gives the plot an ordinary image context menu (Save image as…, Copy image, drag-to-save) under a caller-supplied filename, with the render dpi written into the exported PNG. ./build.sh assembles dist/ and verify-dist.mjs checks it the way a consumer loads it.
  • The wasm client registers fonts once per page. WOFF and WOFF2 decode behind the default-on webfonts feature, registerGoogleFont needs no API key, and four static Roboto faces are fetched as a fallback when the page registers nothing of its own. CJK stays a bring-your-own case.
  • Plot documents — behind the off-by-default document-read and document-write features (document enables both). document::write_composition captures a PlotComposition as a self-contained byte string and document::read_composition rebuilds a live one, so a plot can be authored in one process and drawn in another. Nothing shaped, measured or solved is written: the consumer calls render at whatever size it has and the plot reflows rather than scaling a frozen image. WriteOptions carries the render hints, embed_fonts, and a lossy switch for the items unsupported_items reports as unwritable; ReadContext takes the host's own geom constructors and named formatters. Hand-rolled and dependency-free, so --no-default-features --features document-write is a complete writer with no renderer at all. See examples/document_save.rs and examples/document_load.rs.
  • document::read_hints and DocumentHints — read a document's render hints (background, size, dpi) without rebuilding the composition. Decodes the head alone, so it is cheap enough to call first.
  • document::FORMAT_VERSION_MAJOR / FORMAT_VERSION_MINOR — the document format version this build speaks. The reader requires an exact major match, so it is a hard compatibility boundary between writer and reader.
  • JPEG, TIFF and WebP writers — behind the new jpeg, tiff and webp features, one pure-Rust encoder each. Every format offers the same three entry points as PNG: write_* to a path, write_*_to for any writer, encode_* for the bytes. TIFF and WebP are lossless and carry alpha; TIFF takes a TiffCompression, and write_jpeg takes a quality (1–100) and a background Color to composite onto.
  • hephaestus::image — the home of every raster writer, including PNG. hephaestus::png::{write_png, encode_png, write_png_to} continue to resolve as aliases.
  • scene::recording::RecordingScene::replay — issue every recorded op against any SceneBuilder, in order. What lets a backend defer draws until it knows the frame size, or rasterize a second scene from the same draws.
  • RecordingScene, Op and OwnedGlyphRun are PartialEq. Equality is op-for-op, which makes a recording a test oracle over drawing rather than pixels. Font compares by face — the same bytes at the same index — not by blob identity.
  • examples/backend_perf.rs — where a frame's time goes on each backend at a given mark count, reporting the fastest of ten runs.
  • Markdown in every chrome text slot. Legend titles, colorbar titles, break labels and legend text swatches read the markdown flag, so a theme that turns rich text on gets it everywhere. Polar titles that stamp glyphs along an arc stay plain.
  • RichTextRun answers the metrics TextRun does — baseline_offset, cap_height, ink_top_offset and inked_height — so a caller can anchor either kind of run the same way. The inked band unions glyph ink with every block paint box.
  • RichTextStyleSheet::iter, len and is_empty — walk a sheet's named style deltas.
  • AxisTheme::resolved_with_root — resolve an AxisTheme against a root text element, as Theme::resolved_axis already did for PerAxis.
  • Geom::kind — a stable wire name for the concrete geom type behind a Box<dyn Geom>. Every geom in the crate returns Some; the default is None, so a downstream geom opts in by overriding it and registering a matching constructor with ReadContext::with_geom. GeomBuilder::from_parts is the inverse of into_parts.
  • Plot::add_boxed_geom and Plot::geoms — the counterpart to remove_geom, and a borrowing iterator over every geom with its id in draw order. Alongside them, getters for settings that had only setters: aspect_ratio_ref, is_clipped, tracks_identity, title_ref, subtitle_ref, caption_ref and shape_registry_ref.
  • Scale::with_named_format / set_named_format, and Scale::format_spec — a label formatter registered under a name, plus FormatSpec (Default, Named, Custom) describing which kind a scale carries. A name is what lets a scale's labels be reproduced in another process; an anonymous with_format closure stays Custom.
  • Scale::try_with_bins and try_set_bins — fallible siblings of with_bins, returning BinEdgeError (TooFew, NotFinite, NotIncreasing) instead of panicking. try_set_bins leaves the existing ladder in place when it rejects.
  • linetype::try_pattern and linetype::check_pattern — fallible siblings of pattern and validate_pattern, returning PatternError (OddLength, Misaligned).
  • text::register_font_families — register a font blob and get the family names back, so a host with no system fonts can pair a blob with set_generic_family instead of guessing from a filename. register_font_bytes keeps returning the face count.
  • text::registered_families — the font families the context knows, so a caller can decide whether a fallback font is needed.
  • text::font_faces_for_family, generic_family_names and set_generic_family — read the font files backing a family, or a generic family's resolution order, out of the font context and reinstate them elsewhere. font_faces_for_family returns one entry per distinct file, since a collection holds a whole family in one.

Changed

  • Locale is a tag and nothing more: Locale::EN_US, Locale::from("ar-EG"), locale.tag(). Describing a locale correctly takes a CLDR-sized table, which belongs with whatever formats labels rather than with the renderer that draws them. The tag rides on Theme, reaches every LabelFormatter, and is written into a plot document. It is carried verbatim, so ar_EG and ar-EG are distinct locales.
  • The default tick formatter ignores the locale. Numbers render with a . decimal and temporal values as compact YYYY-MM-DD / HH:MM:SS, whatever locale is passed. An axis that should follow a locale supplies a closu...
Read more

Hephaestus v0.1.0

Choose a tag to compare

@thomasp85 thomasp85 released this 17 Aug 18:57
ff574e6

First release