Skip to content

Releases: p8nut/resvg-napi

v0.5.1

Choose a tag to compare

@github-actions github-actions released this 04 Oct 13:49
3d980f5

A patch release that only changes how the Linux x64 binary is built. No API change.

Fixed

  • resvg-napi-linux-x64-gnu loads on older Linux distributions again. It was compiled directly on the CI runner and therefore required glibc 2.35, so it failed to load on RHEL / Rocky / Alma 9, Amazon Linux 2023, Debian 11 and Ubuntu 20.04. It is now built against glibc 2.17 like the arm64 and armv7 binaries, and requires 2.14. Every release up to 0.5.0 had this problem. (#100)
  • CI now fails the build if any Linux glibc binary requires a version above 2.17, so the floor cannot creep up again unnoticed, for instance when ubuntu-latest moves to Ubuntu 26.

What's Changed

  • ci: build x86_64 linux-gnu against glibc 2.17, and check the floor by @p8nut in #100
  • chore(release): 0.5.1 by @p8nut in #101

Full Changelog: v0.5.0...v0.5.1

v0.5.0

Choose a tag to compare

@github-actions github-actions released this 04 Oct 13:23
957e906

More of resvg, usvg and fontdb exposed as they are. Every change adds to the API, nothing is removed or renamed.

New

  • Image.size: the encoded image's own size, which can differ from the element's width/height. (#96)
  • toString({ indent, attributesIndent }): number | 'tabs' | 'none', the output formatting usvg's writer offers. An unknown value throws. (#96)
  • ClipPath.clipPath() / Mask.mask(): a clip path or mask applied to another one. (#97)
  • PositionedGlyph.font: the face that drew a glyph, read from its own document's database. (#97)
  • FontDatabase.familyName(family): what a generic keyword such as serif resolves to in this database. (#98)
  • FontDatabase.faceSource(face): { kind: 'binary' | 'file' | 'sharedFile', path?, index }. (#98)

Fixed

  • faceOf(glyph) checks where the glyph comes from. A glyph laid out against another font database now returns null. Until now, two databases holding the same font issued the same keys, so faceOf could answer with an unrelated face. (#97)
  • Nodes reached through a definition (pattern, mask, clip path…) keep their document. Their clipPath() / mask() now resolve instead of returning null, and their renderPng() reports to doc.takeLogs(). (#97)
  • SvgNode.clipPath() / mask() find the definition by identity. Matching by id missed definitions without one and could pick a namesake. (#97)

What's Changed

  • ci: drop the NPM_TOKEN fallback, trusted publishing proven by 0.4.1 by @p8nut in #95
  • feat: Image.size, toString indent and attributesIndent by @p8nut in #96
  • feat: nested clip paths and masks, glyph.font with provenance by @p8nut in #97
  • feat: FontDatabase.familyName and faceSource by @p8nut in #98
  • chore(release): 0.5.0 by @p8nut in #99

Full Changelog: v0.4.1...v0.5.0

v0.4.1

Choose a tag to compare

@github-actions github-actions released this 04 Oct 11:12
abd1ebe

A patch release with no API change. index.d.ts is identical to 0.4.0's.

Fixed

  • A panic inside usvg or resvg no longer kills the Node process. Parsing, rendering and renderNodePng now turn it into a JS error, in both the sync methods and their async variants. Until now the unwind reached napi's extern "C" frame and aborted the process. (#91)
  • node.renderPng() / renderPngAsync() report to their document. Their messages now show up in doc.takeLogs() and no longer only in the global buffer. Nodes reached through a definition (pattern, mask…) have no document and still log globally. (#91)

Faster

  • The async variants share the document's image map instead of copying it on every call. With 500 embedded images, the cost per call drops from about 75 µs to about 30 µs. (#90)

Internal

  • as casts are replaced by From/TryFrom. The only casts left are the two float narrowings Rust has no other way to write. Rendering is unchanged: all 1715 conformance references still match. (#92)
  • clippy -D warnings now also covers the generated bindings. (#93)
  • First release published through npm trusted publishing (OIDC).

What's Changed

  • perf: async twins share the image map instead of copying it by @p8nut in #90
  • fix: contain usvg/resvg panics, route node renders to the document log by @p8nut in #91
  • refactor: replace as casts with From/TryFrom by @p8nut in #92
  • refactor: lint the generated bindings with clippy by @p8nut in #93
  • chore(release): 0.4.1 by @p8nut in #94

Full Changelog: v0.4.0...v0.4.1

v0.4.0

Choose a tag to compare

@github-actions github-actions released this 03 Oct 20:58
bd5dd2e

A minor: two behaviours change. Mostly security and correctness fixes from a
review of the binding.

Security

  • An untrusted SVG could abort the process. A declared size or a render
    scale large enough made the pixmap allocation fail, which aborts:
    renderAsync('<svg width="200000" height="100000"/>') asked for 80 GB.
    Renders now throw past RenderParams.maxPixels (default 2^28, 1 GiB). #83
  • An untrusted SVG could read local files. usvg's default resolver read any
    absolute href, and relative ones off the working directory, into the
    pixels and the toString() output; /dev/zero exhausted memory. Disk reads
    now need resourcesDir, and stay inside it (canonical path: no .., no
    symlink out). RenderOptions.resourcesRoot widens the fence to an ancestor
    for templates beside a shared assets folder. #83

Behaviour changes

  • Without resourcesDir the disk is not read: an href that resolved off the
    working directory now lands in pendingImages(). Set resourcesDir, or pass
    the bytes as images.
  • width / height are rounded, and throw unless a positive pixel count
    (napi's u32 used to wrap: -1 became 4294967295). A non-finite crop
    throws instead of rendering blank. #84

Fixes

  • FontDatabase.face() / removeFace() with a face from another database
    answered with, or deleted, a face of this one. #84
  • parseAsync enumerated the system fonts on the event loop on first use. #84
  • FontDatabase.query clamps the weight to 1..=1000 instead of truncating. #84
  • The codegen binds only against the upstream version Cargo.lock picked. #85

New

  • Resvg.takeLogs(): a document's own messages, async renders included.
    The module-level takeLogs() still receives everything. #87
  • RenderParams.maxPixels, RenderOptions.resourcesRoot. #83

Performance

  • A FontDatabase is shared with the parses that use it and copied on the first
    write: 0.19 ms -> 0.013 ms per parse with 359 faces. #86

Tooling: @napi-rs/cli 3.10.6 (WASI shims report a crashed worker instead of
hanging, #82), a weekly stale-Cargo.lock probe (#81), CI on notices-only PRs (#80).


What's Changed

  • build(deps-dev): bump @types/node from 26.5.0 to 26.5.1 in the dev-dependencies group across 1 directory by @dependabot[bot] in #58
  • build(deps-dev): bump typescript from 6.0.3 to 7.0.2 by @dependabot[bot] in #59
  • refactor(build): the crate a type comes from is a value, not a chain by @p8nut in #63
  • fix(licenses): the notice ships with the binary it describes by @p8nut in #64
  • refactor(build): a transform is recognised by its shape, not its name by @p8nut in #65
  • feat(build): public means what the crate root exports, re-exports included by @p8nut in #66
  • refactor(build): the crate a path is built from travels with the type by @p8nut in #67
  • build: refresh dependencies, and regenerate the loaders on @napi-rs/cli 3.10.4 by @p8nut in #68
  • build(deps): smallvec 1.16.2, siphasher 1.0.4 by @p8nut in #71
  • fix(licenses): Apache prose is not a copyright holder by @p8nut in #74
  • ci: say why npm ci can never work here by @p8nut in #72
  • build: @napi-rs/cli 3.10.5, and regenerate the WASI shims by @p8nut in #73
  • fix: refuse a signal that is not an AbortSignal by @p8nut in #76
  • ci: fetch the whole crate graph before checking the notices by @p8nut in #79
  • build(deps-dev): bump @types/node from 26.6.2 to 26.6.3 in the dev-dependencies group across 1 directory by @dependabot[bot] in #77
  • ci: keep what a dependabot run regenerates by @p8nut in #75
  • ci: run on pull requests that only touch the third-party notices by @p8nut in #80
  • ci: flag a stale Cargo.lock on the Monday run by @p8nut in #81
  • build: @napi-rs/cli 3.10.6 by @p8nut in #82
  • fix: cap the canvas size and confine image reads to resourcesDir by @p8nut in #83
  • fix: validate render sizes, check face provenance, parse off the loop by @p8nut in #84
  • build: bind only against the sources Cargo.lock picked by @p8nut in #85
  • perf: share FontDatabase with the parses that use it, copy on write by @p8nut in #86
  • feat: per-document logs, Resvg.takeLogs() by @p8nut in #87
  • ci: publish through npm trusted publishing by @p8nut in #89
  • chore(release): 0.4.0 by @p8nut in #88

Full Changelog: v0.3.1...v0.4.0

v0.3.1

Choose a tag to compare

@github-actions github-actions released this 14 Sep 06:32
1e12fbb

A patch, and the reason it could not wait.

0.3.0 could not be installed beside its own WASI package. The root asked for
resvg-napi-wasm32-wasi@0.2.0 — a pin written at the 0.2.0 release that nothing
moved since, because napi version does not touch dependency maps and the
publish-shape guard read optionalDependencies and nothing else:

npm i resvg-napi@0.3.0 resvg-napi-wasm32-wasi@0.3.0
npm error ERESOLVE — Conflicting peer dependency: resvg-napi-wasm32-wasi@0.2.0

Anyone taking the documented WASI path needed --force. An npm version is
immutable, so the fix had to travel in a new one. The guard now reads every map
that names a package of this family, and a second one compares the wasm
package's emnapi pins against the root's — the two ways this release found to
fail before it got out.

The binaries now carry the notices they owe. tiny-skia and tiny-skia-path —
the drawing surface this binding is named after — are BSD-3-Clause, copyright
Google Inc. and Yevhenii Reizner, statically linked into all thirteen .node
files and the .wasm. arrayref is BSD-2, libloading ISC, slotmap Zlib, and MIT
asks for its notice to travel with a copy. The package shipped LICENSE-APACHE,
LICENSE-MIT and nothing else; THIRD-PARTY-NOTICES.md is generated from the
crate graph and checked in CI, so a resvg bump that moves the graph fails there
rather than shipping a notice that no longer describes the binary.

Dependencies. The emnapi family moves to 1.11.3, which became possible only
when @napi-rs/cli 3.9.1 and @napi-rs/wasm-runtime 1.2.4 moved those packages
to peerDependencies — the exact pin a transitive wasm sub-package held over
@emnapi/core had blocked it twice before. Plus esbuild and @types/node.

No API change. faceOf, Font.families and everything else 0.3.0 added is
unchanged.


What's Changed

  • fix(demo): fetch a font the CDN refuses to serve by @p8nut in #54
  • feat(demo): the bench ships a font, so a first visit renders by @p8nut in #55
  • fix: Cargo.toml was left at 0.2.0 by the 0.3.0 release by @p8nut in #57
  • chore(deps): the emnapi family moves, and dependabot can carry it again by @p8nut in #56
  • fix(pkg): the wasm peer pin, and the notices the binaries owe by @p8nut in #60
  • chore(release): 0.3.1 by @p8nut in #61
  • fix(pkg): the wasm package declares the emnapi the root uses by @p8nut in #62

Full Changelog: v0.3.0...v0.3.1

v0.3.0

Choose a tag to compare

@github-actions github-actions released this 11 Sep 00:23
b33f3e4

One addition, and the reason it exists.

pendingFonts() has always named the families the database did not carry — the
loud failure. The quiet one is a family that is loaded and still did not draw
the text: a fallback renders, the page looks plausible, and nobody finds out
until it is printed.

const text = doc.node('title').text()
text.chunks[0].spans[0].font.families   // ['Brand Sans', 'DejaVu Sans'] — asked for
doc.faceOf(glyph)?.postScriptName        // 'DejaVuSans' — what actually drew it

Resvg.faceOf(glyph) returns the face that drew a glyph, looked up in the
database the document was parsed with; null means the glyph came from another
document. Font.families returns the families a span asked for, so the
answer has something to be compared against. The browser bench shows both: a
drawn with row, and a warning per substitution.

Nothing breaks. Two additions to the surface; no member changed shape or
left, which is why this is a minor.

Two things that landed earlier and reach npm only now, because a published
README changes only on publish: the README is rewritten around what the project
does rather than what it is, and toString() finally says out loud that it
converts text to paths. The bench is live at
p8nut.github.io/resvg-napi.


What's Changed

  • feat(demo): deploy the bench to GitHub Pages by @p8nut in #36
  • docs: titles that say what they contain, and the examples as images by @p8nut in #35
  • chore(ci): a v* tag creates a GitHub release, and CHANGELOG.md goes by @p8nut in #34
  • chore: dependabot for the actions and the dev dependencies by @p8nut in #37
  • docs: link the deployed demo by @p8nut in #39
  • chore(dependabot): the emnapi family moves in lockstep, majors on their own by @p8nut in #40
  • chore(ci): drop the npm credentials probe by @p8nut in #41
  • chore(dependabot): the CLI is the gate, so the emnapi pins wait for it by @p8nut in #44
  • ci: the wasm job bundles the demo by @p8nut in #47
  • build(deps-dev): bump @napi-rs/cli from 3.8.6 to 3.9.0 in the napi group across 1 directory by @dependabot[bot] in #46
  • build(deps-dev): bump the dev-dependencies group across 1 directory with 2 updates by @dependabot[bot] in #43
  • docs: npm says the same thing as the repo page by @p8nut in #48
  • chore: no tool config in the repository by @p8nut in #49
  • fix: npm's homepage is the README, not the demo by @p8nut in #50
  • docs: the text-as-outlines behaviour has a title of its own by @p8nut in #51
  • feat: every glyph names the face that drew it by @p8nut in #52
  • chore(release): 0.3.0 by @p8nut in #53

New Contributors

Full Changelog: v0.2.0...v0.3.0

v0.2.0

Choose a tag to compare

@p8nut p8nut released this 10 Sep 18:33

Three things you could not read before, and nineteen defects an adversarial
review found. The minor bump is for the API additions; two of the fixes change
shapes, and they are listed under Breaking.

New

SvgNode.image() returns the content of an image node, and the four raster
variants of its kind carry the encoded bytes exactly as the document supplied
them. usvg says a raster payload should be decoded by the caller; this is the
caller.

const img = node.image()
img.kind.type    // 'png' | 'jpeg' | 'gif' | 'webp' | 'svg'
img.kind.bytes   // Buffer

TextSpan.decoration gives underline, overline and lineThrough, each with
the fill and stroke it is drawn with. FontFace.style says
'normal' | 'italic' | 'oblique'.

Breaking

The string enums are type-only unions rather than ambient const enums. An
ambient const enum cannot be used under isolatedModules -- every bundler --
so shapeRendering, textRendering and imageRendering had no way to be set at
all. Write shapeRendering: 'geometricPrecision' instead of
ShapeRendering.GeometricPrecision; the runtime is unchanged.

Span and PositionedGlyph are read-only classes rather than plain objects.
They expose more than before -- PositionedGlyph gained id and text -- and
the object form only existed because those members were invisible.

Fixed, and each of these shipped

A requested width came back one pixel too wide. Seventeen of the first four
hundred widths were wrong on a 100x50 document; renderPng({width: 120})
produced a PNG whose IHDR read 121x61. The scale was an f32 round trip, and every
width in the test suite was an exact multiple, so nothing could see it.

Three fields declared string while holding a Path. napi maps a type
named Path to string from the name alone. span.underline.fill was a type
error on working code.

npm ci was impossible -- the lock file predated the rename.

A conformance case that started throwing was reported as an improvement.

The Matrix doc claimed SVG's matrix() field order. It is sx ky kx sy tx ty, and reading the fields positionally mirrors the transform.

browser.js now declares the wasm package it imports, fit.mjs ships type
declarations, setLogLevel declares its six levels, and the publish loop can be
retried.

One thing about npm ci worth knowing rather than filing: it works against a
published version and cannot work against an unpublished one. The manifest names
its platform packages at the version being released, and until that version is on
the registry npm install cannot resolve them, so the lock file has nothing to
record and npm ci calls them missing. That is why CI uses npm install.

Guarded

One new upstream accessor used to delete every public field of a data type,
silently. The POW_VEC precision clamp was keyed on a field-name suffix with no
floor -- a rename upstream meant an index 242 past a 13-entry table, aborting the
process. Handle discovery truncated at 24 while the report listed the classes it
had dropped as generated. All three fail the build now, and each was made to fail
before being trusted.

Known

defaultSizeWidth and defaultSizeHeight reach usvg and usvg does not honour
them: its converter rewrites the size from default_size and then recomputes it
from the attribute, ignoring the rewrite. test/options.mjs records both the
behaviour and the reason.

v0.1.2

Choose a tag to compare

@p8nut p8nut released this 10 Sep 18:33

Same contents as 0.1.1, which never fully published. The number moved because
npm burned it on one of the fourteen packages.

What happened, because it is worth knowing before unpublishing anything on npm:
0.1.0 was unpublished by mistake, and npm blocks republishing a name for 24
hours after that -- but the block outlives the clock. More than a day later the
registry still refused to save a new packument for those names, answering

409 Conflict — Failed to save packument. A common cause is if you try to
publish a new package before the previous package has been fully processed.

The publish job died on the first of the fourteen. Except the registry then
accepted that first PUT anyway, forty minutes later, while having told the
client it had failed -- so resvg-napi-android-arm-eabi@0.1.1 exists and 0.1.1
is spent there. A version set where thirteen packages are 0.1.1 and one cannot
be is worse than a skipped number.

Nothing was installable at any point: the root package publishes last, so a
failure part-way leaves platform packages that nothing references rather than a
root package pointing at binaries that are not there. That ordering was put in
for exactly this and is the reason this entry describes an inconvenience instead
of a broken release.