diagrams-raster

    Portable binary image export for diagrams.mbt generated SVG and playback

    Download zip
    Author
    Version
    0.4.0
    License
    Apache-2.0
    Last updated
    6 days ago
    Downloads
    17

    #Portable raster export

    moonrockz/diagrams-raster converts SVG generated by diagrams.mbt to PNG and JPEG bytes on wasm, wasm-gc, JavaScript and native. It also exports a bounded source-bound playback plan as a real animated GIF. The module needs no browser, external executable, remote service, UI framework or host font discovery.

    The existing text renderers keep their signatures. Binary results use RasterResult.image, with immutable bytes, media type, actual dimensions and format. Failure returns diagnostics and no partial image.

    ///|
    test "PNG is the default still format" {
    let svg = "<svg width=\"32\" height=\"16\"><rect x=\"4\" y=\"4\" width=\"8\" height=\"8\" fill=\"red\"/></svg>"
    let result = @raster.rasterize(
    svg,
    options=@raster.StillOptions::new(size=Width(128)),
    )
    assert_eq(result.diagnostics.length(), 0)
    let image = result.image.unwrap()
    assert_eq(image.media_type, "image/png")
    assert_eq(image.width, 128)
    assert_eq(image.height, 64)
    }

    ///|
    test "JPEG requires an explicit opaque background" {
    let svg = "<svg width=\"32\" height=\"16\"><rect width=\"8\" height=\"8\" fill=\"red\"/></svg>"
    let result = @raster.rasterize(
    svg,
    options=@raster.StillOptions::new(
    format=Jpeg(quality=85),
    background=@raster.Rgba::white(),
    theme=Light,
    ),
    )
    assert_eq(result.diagnostics.length(), 0)
    assert_eq(result.image.unwrap().media_type, "image/jpeg")
    }

    #Size, background and theme

    RasterSize accepts original dimensions, a positive finite scale, a width, a height or a fitting box. Explicit width and height requests keep that axis exact; the derived axis rounds up, using exact ratios for integral viewBox dimensions. Fit(w,h) fits the complete diagram inside the box without exceeding either bound. No option crops the diagram.

    PNG defaults to a transparent background. JPEG accepts quality 1–100 and requires an opaque background. Explicit export backgrounds replace only the emitter's top-level <rect class="bg">. Nested icon, mask and foreground backgrounds stay intact. Compositing affects a derived export scene and never rewrites the input. Adaptive SVG resolves to the explicit light or dark export theme. Fixed diagram colors retain their supplied values.

    #Explicit fonts

    FontSet::default() is a licensed profile using Noto Sans Mono regular/bold and Noto Sans Symbols 2. It explicitly maps the writer's documented monospaced CSS stack to those faces. It does not inspect system fonts or fetch fonts. Exact originals, licenses, derivation ranges and byte hashes are under fonts/.

    The default subset covers Latin, Greek, Cyrillic, punctuation, arrows, control pictures, box drawing and required checkbox symbols that exist in those source fonts. Every scalar in the current gallery labels has a checked regression. A required glyph outside that set produces raster/missing-glyph. Supply an explicit static TrueType glyf face through FontFace::new and FontSet::new to cover other supported labels or family/style choices. Font family, weight and italic policy are explicit per face. The renderer diagnoses a missing style.

    The static outline profile validates font directories, table bounds, cmap ranges, simple glyph buffers and compound glyph cycles before calling the font parser. It supports horizontal kern format 0. Variable, CFF, color, compound point-attached or sheared glyph profiles and scripts that require complex shaping return diagnostics. They are never replaced with blank glyphs or bitmap fallback text.

    FontSet::subset_for_svg returns a checked font set containing only the SVG's label codepoints. Subsetting retains all required compound glyph dependencies and preserves the rendered pixels. Build tools can embed those immutable bytes in an offline artifact instead of embedding the full default profile in every player. This method returns FontSubsetResult with the same no-partial-result rule. It neither mutates supplied fonts nor changes the source.

    #Bounded work and reproducibility

    RasterLimits::default() bounds source units, XML nodes, dimensions, pixels, label glyphs, encoded bytes, frames and cumulative animation pixels. The default limits are 16 Mi UTF-16 source units, 200,000 XML nodes/attributes, 16,384 pixels per dimension, 16 Mi pixels per image, 100,000 label glyphs, 32 Mi encoded bytes, 300 frames and 64 Mi cumulative animation pixels. XML nesting is capped at 256. Unique supplied font bytes share the source budget. A font has at most 16 levels of compound glyphs and 100,000 expanded points per glyph.

    Invalid options fail before unsafe allocation. Source, font, normalized SVG, pixel and output exhaustion produce identified diagnostics. Missing resources, malformed XML, unsupported generated content and required glyph absence do not produce a successful image. The generated-SVG profile includes every implemented family, explicit palette resolution, text outlines, icons, curves, markers, transforms, clipping and connector crossing masks. Arbitrary third-party SVG is outside this API's contract.

    Equal inputs, options and fonts produce equal encoded bytes on all four targets. PNG uses the qualified published codec. JPEG uses a licensed fixed-point adaptation whose pinned integer DCT basis avoids platform cosine differences. Its accumulations fit exactly below 2^40, including the Double integer representation used to avoid JavaScript BigInt cost. File and network I/O belong in caller adapters.

    See tests/raster-spike/ for backend selection evidence and tests/raster-acceptance/ for public binary fixtures decoded independently in Chromium. Run mise run test:raster-consumer for a fresh public consumer.

    #Dependency and font licenses

    The SVG/XML/CSS, font and PNG dependencies use Apache-2.0. The GIF dependency uses MIT. The JPEG adaptation retains its upstream attribution in NOTICE.md. The bundled Noto fonts and deterministic subsets use SIL OFL-1.1. See fonts/OFL.txt, fonts/source.json and fonts/default/profile.json.

    #Animated GIF

    export_gif(scene, plan) requires a validated plan bound to that scene's source. It samples logical time, including repeated and concurrent occurrences, reversed final curves, node visits and connector masks. Export does not run a clock or change the scene or plan. GifSampler exposes the same pure samples for callers that inspect frames.

    ///|
    test "export a bounded source-bound animation" {
    let view = @swimlane.Swimlane::new("review", [
    @swimlane.Node::new("A"), @swimlane.Node::new("B"),
    ], [@swimlane.Connection::new("AB", "A", "B")])
    let scene = @svg.SvgRenderer::new().scene(@render.Diagram::Swimlane(view)).scene.unwrap()
    let key = scene.catalog.connection("AB").unwrap().key
    let plan = @animation.PlaybackPlan::scenario(scene.catalog,
    @animation.Motion::serial([Step(key, 100L), Step(key, 100L)]),
    ).plan.unwrap()
    let result = @raster.export_gif(scene, plan,
    options=@raster.GifOptions::new(size=Width(64), frame_rate=10, loop_behavior=Once),
    )
    assert_eq(result.image.unwrap().media_type, "image/gif")
    }

    GifInterval::Full covers the finite plan's duration or one illustrative period. Use Interval(start_ms=..., end_ms=...) to choose an explicit bounded interval. The exact first and final logical samples are included. Every sample has a positive hold, including the final sample; the encoded duration therefore includes that final-frame hold beyond the sampled interval. Delays round to centiseconds with a 10 ms minimum. Frame rate is an integer from 1 to 100.

    GifLoop::Once omits the loop extension. Forever repeats indefinitely when viewed, while the exported file still has a finite frame count. Repeat(n) means n additional plays, for n from 1 to 65,535. GIF uses a deterministic 256-color 3:3:2 palette. It is opaque and requires a background color. Colors can differ from PNG/JPEG after quantization. No transparent or partial frame is returned.

    Dimension, frame, cumulative-pixel and output limits apply before unsafe work. An interval that exceeds a finite plan, an empty plan or an exhausted budget fails with diagnostics. Shorten the interval, choose smaller dimensions or lower the frame rate explicitly instead of truncating the requested animation.

    gif_export_data prepares versioned source-bound scene/plan data for an offline worker. Rasterizer::new(fonts) is an immutable explicit-font profile whose rasterize and export_gif_data methods use the same conversion and codec path. The worker protocol validates source fingerprints, visible bindings, schedules and aggregate budgets. Its fingerprint checks consistency, not authenticity. FontSet::export_data deduplicates shared face bytes, and from_export_data validates the received profile. The browser adapter supplies per-player subsets or the gallery's complete declared default profile. Published libraries depend on no browser UI framework.

    GifError

    GIF motion samples the final measured connector, including bridge curves. Segment subdivision is bounded and independent of a browser clock or DOM.

    GifError::to_repr

    ExportTheme

    pub(all) enum ExportTheme {
    Light
    Dark
    } derive(Eq,
    Debug
    )

    ExportTheme::equal

    fn ExportTheme::equal(ExportTheme, ExportTheme) -> Bool

    ExportTheme::not_equal

    fn ExportTheme::not_equal(x : ExportTheme, y : ExportTheme) -> Bool

    FontDataResult

    pub struct FontDataResult {
    data : Json?
    diagnostics : ReadOnlyArray[RasterDiagnostic]
    }

    Explicit font bytes for an offline worker, deduplicated across face aliases.

    FontFace

    pub(all) struct FontFace {
    family : String
    weight : Int
    italic : Bool
    data : Bytes
    }

    A supplied face is selected by family, weight and italic style. The default profile is an explicit licensed set, never a host-font lookup.

    FontFace::new

    fn FontFace::new(family : String, data : Bytes, weight? : Int, italic? : Bool) -> FontFace

    FontSet

    pub(all) struct FontSet {
    faces : ReadOnlyArray[FontFace]
    fallback_families : ReadOnlyArray[String]
    }

    FontSet::default

    fn FontSet::default() -> FontSet

    The licensed default profile maps the writer's documented monospaced stack to Noto Sans Mono, with Noto Sans Symbols 2 for required diagram symbols.

    FontSet::export_data

    fn FontSet::export_data(self : FontSet, limits? : RasterLimits) -> FontDataResult

    FontSet::from_export_data

    fn FontSet::from_export_data(data : Json, limits? : RasterLimits) -> FontSubsetResult

    Decode the generated worker font protocol with byte and face budgets, then run the same font validation used by ordinary library callers.

    FontSet::new

    fn FontSet::new(faces : ArrayView[FontFace], fallback_families? : ArrayView[String]) -> FontSet

    FontSet::subset_for_svg

    fn FontSet::subset_for_svg(self : FontSet, source : String, limits? : RasterLimits) -> FontSubsetResult

    FontSubsetResult

    pub(all) struct FontSubsetResult {
    fonts : FontSet?
    diagnostics : ReadOnlyArray[RasterDiagnostic]
    }

    Font subsetting reports errors without returning a partial font set.

    GifDataResult

    pub struct GifDataResult {
    data : Json?
    diagnostics : ReadOnlyArray[RasterDiagnostic]
    }

    Versioned export data carries the generated scene and validated plan into an offline worker. It is a consistency protocol, not a signature or trust token.

    GifFrameResult

    pub struct GifFrameResult {
    frame : GifFrameSample?
    diagnostics : ReadOnlyArray[RasterDiagnostic]
    }

    GifFrameSample

    pub struct GifFrameSample {
    time_ms : Int64
    delay_centiseconds : Int
    sample :
    Sample

    occurrences : ReadOnlyArray[GifOccurrence]
    }

    The pure plan sample is retained alongside the measured drawing targets.

    GifInterval

    pub(all) enum GifInterval {
    Full
    Interval(start_ms~ : Int64, end_ms~ : Int64)
    } derive(Eq,
    Debug
    )

    GIF uses an explicit logical interval. Full samples a finite plan's complete duration, or one period of an illustrative plan.

    GifInterval::equal

    fn GifInterval::equal(GifInterval, GifInterval) -> Bool

    GifInterval::not_equal

    fn GifInterval::not_equal(x : GifInterval, y : GifInterval) -> Bool

    GifLoop

    pub(all) enum GifLoop {
    Once
    Forever
    Repeat(Int)
    } derive(Eq,
    Debug
    )

    Repeat is the number of additional plays after the first, from 1 to 65535. Once omits the Netscape extension; Forever writes its zero loop count.

    GifLoop::equal

    fn GifLoop::equal(GifLoop, GifLoop) -> Bool

    GifLoop::not_equal

    fn GifLoop::not_equal(x : GifLoop, y : GifLoop) -> Bool

    GifLoop::to_repr

    GifOccurrence

    pub struct GifOccurrence {
    occurrence :
    Active

    target : GifTarget
    }

    GifOptions

    pub(all) struct GifOptions {
    size : RasterSize
    background : Rgba
    theme : ExportTheme
    interval : GifInterval
    frame_rate : Int
    loop_behavior : GifLoop
    } derive(Eq,
    Debug
    )

    GIF composites onto an opaque background. Frame rate ranges from 1 to 100. Sampling includes the exact final logical view with one final frame hold. Delays are rounded deterministically to centiseconds, with a 10ms minimum.

    GifOptions::equal

    fn GifOptions::equal(GifOptions, GifOptions) -> Bool

    GifOptions::new

    fn GifOptions::new(size? : RasterSize, background? : Rgba, theme? : ExportTheme, interval? : GifInterval, frame_rate? : Int, loop_behavior? : GifLoop) -> GifOptions

    GifOptions::not_equal

    fn GifOptions::not_equal(x : GifOptions, y : GifOptions) -> Bool

    GifSampler

    pub struct GifSampler {
    frame_count : Int
    width : Int
    height : Int
    options : GifOptions
    // private fields
    }

    Validated, immutable sampling state. Frame queries do not mutate a scene, a plan or another frame, and do not read a wall clock.

    GifSampler::frame

    fn GifSampler::frame(self : GifSampler, index : Int) -> GifFrameResult

    Sample one logical frame, including repeated and concurrent occurrences.

    GifSampler::new

    Validate source identity, the logical interval and bounded path work.

    GifSamplerResult

    pub struct GifSamplerResult {
    sampler : GifSampler?
    diagnostics : ReadOnlyArray[RasterDiagnostic]
    }

    GifTarget

    pub(all) enum GifTarget {
    Connection(id~ : String, path~ : String, mask~ : String?, point~ :
    ConnectorPoint
    )
    Visit(
    NodeBindingKind
    )
    }

    A connection token follows its final path in the element's own coordinates. A visit identifies a real bound node rather than inventing a connector.

    RasterDiagnostic

    pub(all) struct RasterDiagnostic {
    code : String
    message : String
    source_id : String?
    } derive(Eq,
    Debug
    )

    RasterDiagnostic::equal

    RasterDiagnostic::not_equal

    fn RasterDiagnostic::not_equal(x : RasterDiagnostic, y : RasterDiagnostic) -> Bool

    RasterFormat

    pub(all) enum RasterFormat {
    Png
    Jpeg
    Gif
    } derive(Eq,
    Debug
    )

    Binary format metadata. Still options exclude GIF; animation has its own bounded interval and sampling options.

    RasterFormat::equal

    RasterFormat::not_equal

    fn RasterFormat::not_equal(x : RasterFormat, y : RasterFormat) -> Bool

    RasterImage

    pub(all) struct RasterImage {
    bytes : Bytes
    media_type : String
    width : Int
    height : Int
    format : RasterFormat
    }

    RasterLimits

    pub(all) struct RasterLimits {
    max_source_units : Int
    max_svg_nodes : Int
    max_dimension : Int
    max_pixels : Int64
    max_glyphs : Int
    max_output_bytes : Int
    max_frames : Int
    max_cumulative_pixels : Int64
    } derive(Eq,
    Debug
    )

    Limits bound work before unsafe arithmetic and allocation.

    RasterLimits::default

    fn RasterLimits::default() -> RasterLimits

    RasterLimits::equal

    RasterLimits::not_equal

    fn RasterLimits::not_equal(x : RasterLimits, y : RasterLimits) -> Bool

    RasterResult

    pub(all) struct RasterResult {
    image : RasterImage?
    diagnostics : ReadOnlyArray[RasterDiagnostic]
    }

    Failure has no partial image payload.

    RasterSize

    pub(all) enum RasterSize {
    Original
    Scale(Double)
    Width(Int)
    Height(Int)
    Fit(Int, Int)
    } derive(Eq,
    Debug
    )

    RasterSize::equal

    fn RasterSize::equal(RasterSize, RasterSize) -> Bool

    RasterSize::not_equal

    fn RasterSize::not_equal(x : RasterSize, y : RasterSize) -> Bool

    Rasterizer

    pub struct Rasterizer {
    fonts : FontSet
    limits : RasterLimits
    }

    An explicit font/limit profile for hosts and offline workers. This path carries no bundled font fallback; callers supply their chosen font bytes.

    Rasterizer::export_gif_data

    fn Rasterizer::export_gif_data(self : Rasterizer, data : Json, plan? : Json, options? : GifOptions) -> RasterResult

    Rasterizer::new

    fn Rasterizer::new(fonts : FontSet, limits? : RasterLimits) -> Rasterizer

    Rasterizer::rasterize

    fn Rasterizer::rasterize(self : Rasterizer, source : String, options? : StillOptions) -> RasterResult

    Rgba

    pub(all) struct Rgba {
    red : Int
    green : Int
    blue : Int
    alpha : Int
    } derive(Eq,
    Debug
    )

    Rgba::equal

    fn Rgba::equal(Rgba, Rgba) -> Bool

    Rgba::new

    fn Rgba::new(red : Int, green : Int, blue : Int, alpha? : Int) -> Rgba

    Rgba::not_equal

    fn Rgba::not_equal(x : Rgba, y : Rgba) -> Bool

    Rgba::to_repr

    Rgba::transparent

    fn Rgba::transparent() -> Rgba

    Rgba::white

    fn Rgba::white() -> Rgba

    StillFormat

    pub(all) enum StillFormat {
    Png
    Jpeg(quality~ : Int)
    } derive(Eq,
    Debug
    )

    PNG permits transparent backgrounds. JPEG requires an opaque background and quality between 1 and 100, checked before rendering.

    StillFormat::equal

    fn StillFormat::equal(StillFormat, StillFormat) -> Bool

    StillFormat::not_equal

    fn StillFormat::not_equal(x : StillFormat, y : StillFormat) -> Bool

    StillOptions

    pub(all) struct StillOptions {
    format : StillFormat
    size : RasterSize
    background : Rgba
    theme : ExportTheme
    } derive(Eq,
    Debug
    )

    Values are checked before source parsing, font parsing or allocation.

    StillOptions::equal

    StillOptions::new

    fn StillOptions::new(format? : StillFormat, size? : RasterSize, background? : Rgba, theme? : ExportTheme) -> StillOptions

    StillOptions::not_equal

    fn StillOptions::not_equal(x : StillOptions, y : StillOptions) -> Bool

    export_gif

    Export a source-bound plan through the same generated-SVG conversion path as PNG and JPEG. The selected interval includes an exact final logical view.

    export_gif_data

    fn export_gif_data(data : Json, plan? : Json, options? : GifOptions, fonts? : FontSet, limits? : RasterLimits) -> RasterResult

    Decode the versioned scene/plan data emitted by gif_export_data. A selected route must carry the same source_id and use its validated visible bindings.

    gif_export_data

    Prepare a source-bound scene once. Browser route changes may supply another bounded schedule over these same visible occurrence keys.

    rasterize

    fn rasterize(source : String, options? : StillOptions, fonts? : FontSet, limits? : RasterLimits) -> RasterResult

    Still conversion always receives the complete canonical SVG. Viewport and playback state have no representation in this API.