Skip to content

Commit 9f67f75

Browse files
authored
Merge pull request #20 from yisibl/update-napi
feat: implement renderAsync
2 parents 34e80bf + d914941 commit 9f67f75

File tree

19 files changed

+5566
-306
lines changed

19 files changed

+5566
-306
lines changed

.eslintignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
js-binding.js
2+
js-binding.d.ts
3+
target

.gitattributes

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# Auto detect text files and perform LF normalization
2+
* text=auto
3+
4+
5+
*.ts text eol=lf merge=union
6+
*.tsx text eol=lf merge=union
7+
*.rs text eol=lf merge=union
8+
*.js text eol=lf merge=union
9+
*.json text eol=lf merge=union
10+
*.debug text eol=lf merge=union
11+
12+
# Generated files
13+
js-binding.js linguist-detectable=false
14+
js-binding.d.ts linguist-detectable=false

.github/workflows/CI.yaml

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ jobs:
4040
target: 'x86_64-pc-windows-msvc'
4141
- host: windows-latest
4242
build: |
43-
export CARGO_PROFILE_RELEASE_CODEGEN_UNITS=32;
43+
export CARGO_PROFILE_RELEASE_CODEGEN_UNITS=64;
4444
export CARGO_PROFILE_RELEASE_LTO=false
4545
yarn build --target i686-pc-windows-msvc
4646
yarn test
@@ -66,6 +66,11 @@ jobs:
6666
- host: macos-latest
6767
target: 'aarch64-apple-darwin'
6868
build: |
69+
sudo rm -Rf /Library/Developer/CommandLineTools/SDKs/*;
70+
export CC=$(xcrun -f clang);
71+
export CXX=$(xcrun -f clang++);
72+
SYSROOT=$(xcrun --sdk macosx --show-sdk-path);
73+
export CFLAGS="-isysroot $SYSROOT -isystem $SYSROOT";
6974
yarn build --target=aarch64-apple-darwin
7075
strip -x *.node
7176
- host: ubuntu-latest
@@ -84,14 +89,16 @@ jobs:
8489
target: 'aarch64-linux-android'
8590
build: |
8691
export CARGO_TARGET_AARCH64_LINUX_ANDROID_LINKER="${ANDROID_NDK_HOME}/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android24-clang"
92+
export PATH="${ANDROID_NDK_HOME}/toolchains/llvm/prebuilt/linux-x86_64/bin:${PATH}"
8793
yarn build --target aarch64-linux-android
94+
${ANDROID_NDK_HOME}/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android-strip *.node
8895
- host: ubuntu-latest
8996
target: 'aarch64-unknown-linux-musl'
9097
docker: |
9198
docker login -u $DOCKER_USERNAME -p $DOCKER_PASSWORD $DOCKER_REGISTRY_URL
9299
docker pull ghcr.io/napi-rs/napi-rs/nodejs-rust:lts-alpine
93100
docker tag ghcr.io/napi-rs/napi-rs/nodejs-rust:lts-alpine builder
94-
build: docker run --rm -v ~/.cargo/git:/root/.cargo/git -v ~/.cargo/registry:/root/.cargo/registry -v $(pwd):/resvgjs -w /resvgjs builder sh -c "yarn build -- --target=aarch64-unknown-linux-musl"
101+
build: docker run --rm -v ~/.cargo/git:/root/.cargo/git -v ~/.cargo/registry:/root/.cargo/registry -v $(pwd):/resvgjs -w /resvgjs builder sh -c "rustup toolchain install $(cat ./rust-toolchain) && rustup target add aarch64-unknown-linux-musl && yarn build -- --target=aarch64-unknown-linux-musl"
95102
- host: windows-latest
96103
target: 'aarch64-pc-windows-msvc'
97104
build: yarn build --target aarch64-pc-windows-msvc
@@ -105,15 +112,14 @@ jobs:
105112
- name: Setup node
106113
uses: actions/setup-node@v2
107114
with:
108-
node-version: 14
115+
node-version: 16
109116
check-latest: true
110117

111118
- name: Install
112119
uses: actions-rs/toolchain@v1
113120
with:
114121
profile: minimal
115122
override: true
116-
toolchain: stable
117123
target: ${{ matrix.settings.target }}
118124

119125
- name: Generate Cargo.lock
@@ -125,19 +131,19 @@ jobs:
125131
uses: actions/cache@v2
126132
with:
127133
path: ~/.cargo/registry
128-
key: ${{ matrix.settings.target }}-node@14-cargo-registry-trimmed-${{ hashFiles('**/Cargo.lock') }}
134+
key: ${{ matrix.settings.target }}-node@16-cargo-registry-trimmed-${{ hashFiles('**/Cargo.lock') }}
129135

130136
- name: Cache cargo index
131137
uses: actions/cache@v2
132138
with:
133139
path: ~/.cargo/git
134-
key: ${{ matrix.settings.target }}-node@14-cargo-index-trimmed-${{ hashFiles('**/Cargo.lock') }}
140+
key: ${{ matrix.settings.target }}-node@16-cargo-index-trimmed-${{ hashFiles('**/Cargo.lock') }}
135141

136142
- name: Cache NPM dependencies
137143
uses: actions/cache@v2
138144
with:
139145
path: node_modules
140-
key: npm-cache-${{ matrix.settings.target }}-node@14-${{ hashFiles('yarn.lock') }}
146+
key: npm-cache-${{ matrix.settings.target }}-node@16-${{ hashFiles('yarn.lock') }}
141147

142148
- name: Pull latest image
143149
run: ${{ matrix.settings.docker }}

.github/workflows/lint.yaml

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
name: Lint
2+
3+
on:
4+
push:
5+
branches:
6+
- main
7+
tags-ignore:
8+
- '**'
9+
pull_request:
10+
11+
jobs:
12+
lint:
13+
name: Lint
14+
runs-on: ubuntu-latest
15+
steps:
16+
- uses: actions/checkout@v2
17+
18+
- name: Setup node
19+
uses: actions/setup-node@v2
20+
with:
21+
node-version: 16
22+
23+
- name: Install
24+
uses: actions-rs/toolchain@v1
25+
with:
26+
profile: minimal
27+
override: true
28+
components: rustfmt, clippy
29+
30+
- name: Cache NPM dependencies
31+
uses: actions/cache@v2
32+
with:
33+
path: node_modules
34+
key: npm-cache-lint-node@16-${{ hashFiles('yarn.lock') }}
35+
36+
- name: 'Install dependencies'
37+
run: yarn install --frozen-lockfile --registry https://registry.npmjs.org --network-timeout 300000
38+
39+
- name: ESLint
40+
run: yarn lint
41+
42+
- name: Cargo fmt
43+
run: cargo fmt -- --check
44+
45+
- name: Clippy
46+
run: cargo clippy

.prettierignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,3 @@
11
target
2+
js-binding.js
3+
js-binding.d.ts

Cargo.toml

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,28 @@
11
[package]
2-
name = "resvg-js"
3-
version = "1.0.0"
42
authors = ["yisibl <[email protected]>"]
5-
license = "MPL-2.0"
63
edition = "2021"
4+
license = "MPL-2.0"
5+
name = "resvg-js"
6+
version = "1.0.0"
77

88
[lib]
99
crate-type = ["cdylib"]
1010

1111
[dependencies]
12-
napi = { version = "1", features = ["serde-json"] }
13-
napi-derive = "1"
14-
resvg = { version = "0.19.0", default-features = false, features = ["filter"] }
15-
usvg = { version = "0.19.0", default-features = false, features = ["export", "filter", "text"] }
16-
svgtypes = "0.8.0"
17-
tiny-skia = "0.6.1"
12+
env_logger = "0.9.0"
1813
fontdb = "0.7.0"
1914
log = "0.4"
20-
env_logger = "0.9.0"
21-
serde = { version = "1", features = ["derive"] }
15+
napi = {version = "2.0.0-alpha.2", features = ["serde-json"]}
16+
napi-derive = "2.0.0-alpha.3"
17+
resvg = {version = "0.19.0", default-features = false, features = ["filter"]}
18+
serde = {version = "1", features = ["derive"]}
19+
serde_json = "1"
20+
svgtypes = "0.8.0"
21+
tiny-skia = "0.6.1"
22+
usvg = {version = "0.19.0", default-features = false, features = ["export", "filter", "text"]}
23+
24+
[target.'cfg(all(not(all(target_os = "linux", target_arch = "aarch64", target_env = "musl")), not(all(target_os = "windows", target_arch = "aarch64"))))'.dependencies]
25+
mimalloc-rust = {version = "0.1"}
2226

2327
[build-dependencies]
2428
napi-build = "1"

__test__/index.spec.ts

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { join } from 'path'
44
import test from 'ava'
55
import jimp from 'jimp'
66

7-
import { render } from '../index'
7+
import { render, renderAsync } from '../index'
88

99
test('fit to width', async (t) => {
1010
const filePath = '../example/text.svg'
@@ -63,3 +63,49 @@ test('Load custom font', async (t) => {
6363
t.is(result.getWidth(), 1324)
6464
t.is(result.getHeight(), 687)
6565
})
66+
67+
test('Async rendering', async (t) => {
68+
const filePath = '../example/text.svg'
69+
const svg = await fs.readFile(join(__dirname, filePath))
70+
const params = {
71+
font: {
72+
fontFiles: ['./example/SourceHanSerifCN-Light-subset.ttf'], // Load custom fonts.
73+
loadSystemFonts: false, // It will be faster to disable loading system fonts.
74+
defaultFontFamily: 'Source Han Serif CN Light',
75+
},
76+
}
77+
const syncRenderingResult = render(svg, params)
78+
const asyncRenderingResult = await renderAsync(svg, params)
79+
t.is(syncRenderingResult.length, asyncRenderingResult.length)
80+
// Do not run this assert in non-x64 environment.
81+
// It's too slow
82+
if (process.arch === 'x64') {
83+
t.deepEqual(syncRenderingResult, asyncRenderingResult)
84+
}
85+
})
86+
87+
const MaybeTest = typeof AbortController !== 'undefined' ? test : test.skip
88+
89+
MaybeTest('should be able to abort queued async rendering', async (t) => {
90+
// fill the task queue
91+
for (const _ of Array.from({ length: 100 })) {
92+
process.nextTick(() => {})
93+
}
94+
const filePath = '../example/text.svg'
95+
const svg = await fs.readFile(join(__dirname, filePath))
96+
const params = {
97+
font: {
98+
fontFiles: ['./example/SourceHanSerifCN-Light-subset.ttf'], // Load custom fonts.
99+
loadSystemFonts: false, // It will be faster to disable loading system fonts.
100+
defaultFontFamily: 'Source Han Serif CN Light',
101+
},
102+
}
103+
const controller = new AbortController()
104+
const renderingPromise = renderAsync(svg, params, controller.signal)
105+
// renderingPromise is in the queue now and have not started yet.
106+
controller.abort()
107+
const err = await t.throwsAsync(() => renderingPromise)
108+
t.is(err.message, 'AbortError')
109+
// @ts-expect-error
110+
t.is(err.code, 'Cancelled')
111+
})

benchmark/bench.ts

Lines changed: 21 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
import { promises as fs } from 'fs'
22
import { join } from 'path'
3-
import b from 'benny'
43

5-
import { render } from '../index'
4+
import { createCanvas, Image } from '@napi-rs/canvas'
5+
import b from 'benny'
66
import sharp from 'sharp'
77
import svg2img from 'svg2img'
8-
import { createCanvas, Image } from '@napi-rs/canvas'
8+
9+
import { render } from '../index'
910

1011
async function run() {
1112
const svg1 = await fs.readFile(join(__dirname, '../example/text.svg'))
@@ -15,7 +16,7 @@ async function run() {
1516
await b.suite(
1617
'resize width',
1718
b.add('resvg-js(Rust)', () => {
18-
render(svg1.toString('utf-8'), {
19+
render(svg1, {
1920
background: '#eeebe6',
2021
fitTo: {
2122
mode: 'width',
@@ -33,7 +34,7 @@ async function run() {
3334
}),
3435

3536
// test from https://github.com/Brooooooklyn/canvas/blob/main/example/resize-svg.js
36-
b.add('skr-canvas(Rust)', async () => {
37+
b.add('skr-canvas(Rust)', () => {
3738
const image = new Image()
3839
image.src = svg1
3940

@@ -50,11 +51,11 @@ async function run() {
5051

5152
// fill the canvas with the image
5253
ctx.drawImage(image, 0, 0)
53-
await canvas.encode('png')
54+
canvas.toBuffer('image/png')
5455
}),
5556

5657
b.add('svg2img(canvg and node-canvas)', () => {
57-
svg2img(svg1, { width: 1200, height: 623 }, function (error, buffer) {})
58+
svg2img(svg1.toString('utf8'), { width: 1200, height: 623 }, function (_error, _buffer) {})
5859
}),
5960

6061
b.cycle(),
@@ -64,7 +65,7 @@ async function run() {
6465
await b.suite(
6566
'resize icon width',
6667
b.add('resvg-js(Rust)', () => {
67-
render(icon.toString('utf-8'), {
68+
render(icon, {
6869
fitTo: {
6970
mode: 'width',
7071
value: 386,
@@ -77,7 +78,7 @@ async function run() {
7778
}),
7879

7980
b.add('sharp', async () => {
80-
await sharp('__test__/icon-alarm.svg', {
81+
await sharp(icon, {
8182
// https://github.com/lovell/sharp/issues/1421#issuecomment-514446234
8283
density: (72 * 386) / 24, // 72 * width / actual width
8384
})
@@ -86,7 +87,7 @@ async function run() {
8687
}),
8788

8889
// test from https://github.com/Brooooooklyn/canvas/blob/main/example/resize-svg.js
89-
b.add('skr-canvas(Rust)', async () => {
90+
b.add('skr-canvas(Rust)', () => {
9091
const image = new Image()
9192
image.src = icon
9293

@@ -103,11 +104,11 @@ async function run() {
103104

104105
// fill the canvas with the image
105106
ctx.drawImage(image, 0, 0)
106-
await canvas.encode('png')
107+
canvas.toBuffer('image/png')
107108
}),
108109

109110
b.add('svg2img(canvg and node-canvas)', () => {
110-
svg2img(icon, { width: 386, height: 386 }, function (error, buffer) {})
111+
svg2img(icon.toString('utf8'), { width: 386, height: 386 }, function (_error, _buffer) {})
111112
}),
112113

113114
b.cycle(),
@@ -117,7 +118,7 @@ async function run() {
117118
await b.suite(
118119
'default options and no text',
119120
b.add('resvg-js(Rust)', () => {
120-
render(tiger.toString('utf-8'), {
121+
render(tiger, {
121122
font: {
122123
loadSystemFonts: false,
123124
},
@@ -126,10 +127,10 @@ async function run() {
126127
}),
127128

128129
b.add('sharp', async () => {
129-
await sharp('__test__/tiger.svg').toBuffer()
130+
await sharp(tiger).toBuffer()
130131
}),
131132

132-
b.add('skr-canvas(Rust)', async () => {
133+
b.add('skr-canvas(Rust)', () => {
133134
const image = new Image()
134135
image.src = tiger
135136

@@ -146,16 +147,18 @@ async function run() {
146147

147148
// fill the canvas with the image
148149
ctx.drawImage(image, 0, 0)
149-
await canvas.encode('png')
150+
canvas.toBuffer('image/png')
150151
}),
151152

152153
b.add('svg2img(canvg and node-canvas)', () => {
153-
svg2img(tiger, { width: 900, height: 900 }, function (error, buffer) {})
154+
svg2img(tiger.toString('utf8'), { width: 900, height: 900 }, function (_error, _buffer) {})
154155
}),
155156

156157
b.cycle(),
157158
b.complete(),
158159
)
159160
}
160161

161-
run()
162+
run().catch((e) => {
163+
console.error(e)
164+
})

0 commit comments

Comments
 (0)