Skip to content

Commit eed424f

Browse files
miguelcalderonpweiskircher
authored andcommitted
Add Nutrient benchmark (WIP).
1 parent df99e62 commit eed424f

File tree

7 files changed

+258
-0
lines changed

7 files changed

+258
-0
lines changed

JetStreamDriver.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2248,6 +2248,21 @@ let BENCHMARKS = [
22482248
iterations: 40,
22492249
tags: ["Wasm"],
22502250
}),
2251+
// nutrient
2252+
new WasmEMCCBenchmark({
2253+
name: "nutrient-wasm",
2254+
files: [
2255+
"./wasm/nutrient/helper.js",
2256+
"./wasm/nutrient/build/nutrient-viewer.wasm.js",
2257+
"./wasm/nutrient/benchmark.js",
2258+
],
2259+
preload: {
2260+
pdfDocument: "./wasm/nutrient/assets/example.pdf",
2261+
wasmBinary: "./wasm/nutrient/build/nutrient-viewer.wasm",
2262+
},
2263+
iterations: 10,
2264+
tags: ["Wasm"],
2265+
}),
22512266
];
22522267

22532268
// LuaJSFight tests

in-depth.html

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -333,6 +333,14 @@ <h3>
333333
Source code: <a href="RexBench/OfflineAssembler/parser.js">OfflineAssembler.js</a>
334334
</dd>
335335

336+
<dt id="nutrient-wasm">nutrient-wasm</dt>
337+
<dd>
338+
Tests the <a href="https://www.nutrient.io/sdk/web">Nutrient Web SDK</a>, a client-side PDF framework that runs in the browser using WebAssembly.
339+
This benchmark loads a PDF document, renders a page, creates text annotations, and renders the annotations.
340+
It stresses WebAssembly performance and the interaction between JavaScript and WebAssembly for complex document processing operations.
341+
Source code: <a href="wasm/nutrient/benchmark.js">benchmark.js</a>
342+
</dd>
343+
336344
<dt id="octane-code-load">octane-code-load</dt>
337345
<dd>
338346
Test of code load speed of the jQuery and Closure libraries. Because this test allows

wasm/nutrient/assets/example.pdf

570 KB
Binary file not shown.

wasm/nutrient/benchmark.js

Lines changed: 212 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,212 @@
1+
// Copyright 2025 Nutrient. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
function getSingleJsonReplyFromResult(result) {
6+
if (result.getRepliesCount() !== 1) {
7+
throw new Error(
8+
`Expected 1 reply, but got ${result.getRepliesCount()} replies`
9+
);
10+
}
11+
if (!result.hasJSONReply(0)) {
12+
throw new Error("Expected JSON reply");
13+
}
14+
return JSON.parse(result.getJSONReply(0));
15+
}
16+
17+
function setupPrerequisites() {
18+
Module.ENVIRONMENT_IS_SHELL =
19+
typeof document === "undefined" && typeof window === "undefined";
20+
Module.ENVIRONMENT_IS_WEB = !Module.ENVIRONMENT_IS_SHELL;
21+
22+
if (Module.ENVIRONMENT_IS_SHELL) {
23+
// Nutrient WASM requires a location to be set. In shell mode, this isn't available by default.
24+
if (!globalThis.location) {
25+
globalThis.location = {
26+
origin: "http://localhost:8010",
27+
href: "http://localhost:8010/index.html",
28+
protocol: "http:",
29+
host: "localhost:8010",
30+
hostname: "localhost",
31+
port: "8010",
32+
pathname: "/",
33+
search: "",
34+
hash: "",
35+
};
36+
}
37+
38+
if (!globalThis.crypto) {
39+
globalThis.crypto = {};
40+
}
41+
if (!globalThis.crypto.getRandomValues) {
42+
globalThis.crypto.getRandomValues = function (array) {
43+
if (
44+
!(
45+
array instanceof Int8Array ||
46+
array instanceof Uint8Array ||
47+
array instanceof Int16Array ||
48+
array instanceof Uint16Array ||
49+
array instanceof Int32Array ||
50+
array instanceof Uint32Array ||
51+
array instanceof Uint8ClampedArray
52+
)
53+
)
54+
throw new TypeError("Expected an integer array");
55+
56+
if (array.byteLength > 65536)
57+
throw new RangeError("Can only request a maximum of 65536 bytes");
58+
59+
var i = 0,
60+
r;
61+
62+
for (; i < array.length; i++) {
63+
if ((i & 0x03) === 0) r = Math.random() * 0x100000000;
64+
array[i] = (r >>> ((i & 0x03) << 3)) & 0xff;
65+
}
66+
67+
return array;
68+
};
69+
}
70+
}
71+
}
72+
73+
async function initializeNutrient() {
74+
await PSPDFModuleInit(Module);
75+
76+
const rawResult = await Module.initPSPDFKit(
77+
"", // License key - not required.
78+
"JetStream", // Origin - not required.
79+
"", // Fonts path - not required.
80+
"", // Analytics - not required.
81+
"NodeJS", // Environment - "NodeJS" also works in the browser.
82+
JSON.stringify([]) // Feature flags - not required.
83+
);
84+
85+
const result = JSON.parse(rawResult);
86+
if (!result.success) {
87+
throw new Error("Failed to initialize Nutrient: " + result.error);
88+
}
89+
}
90+
91+
function openDocument(path) {
92+
const result = Module.openDocument(
93+
path,
94+
JSON.stringify({
95+
password: undefined,
96+
initialPageIndex: undefined,
97+
})
98+
);
99+
100+
// Check if document loading failed
101+
if (result && result.error) {
102+
throw new Error("Document loading failed:", result.error);
103+
}
104+
}
105+
106+
function renderPage(pageIndex, width, height) {
107+
const result = Module.dispatchCommand(
108+
JSON.stringify({
109+
type: "render_page",
110+
page: pageIndex,
111+
format: "png",
112+
page_width: width,
113+
page_height: height,
114+
})
115+
);
116+
117+
if (result && result.hasError()) {
118+
throw new Error("Failed to render page: " + result.getErrorMessage());
119+
}
120+
121+
if (result.getRepliesCount() !== 1) {
122+
throw new Error(
123+
`Expected 1 reply, but got ${result.getRepliesCount()} replies`
124+
);
125+
}
126+
127+
if (!result.hasBinaryReply(0)) {
128+
throw new Error("Expected binary reply for rendered page");
129+
}
130+
}
131+
132+
function createTextAnnotation(boundingBox, text) {
133+
const annotationJson = {
134+
bbox: boundingBox,
135+
borderStyle: "solid",
136+
borderWidth: 1,
137+
font: "Helvetica",
138+
fontColor: "#000000",
139+
fontSize: 14,
140+
horizontalAlign: "left",
141+
opacity: 1,
142+
pageIndex: 0,
143+
text: text,
144+
type: "pspdfkit/text",
145+
v: 1,
146+
verticalAlign: "top",
147+
};
148+
149+
const result = Module.dispatchCommand(
150+
JSON.stringify({
151+
type: "add_annotation",
152+
annotation: annotationJson,
153+
})
154+
);
155+
156+
if (result && result.hasError()) {
157+
throw new Error(
158+
"Failed to create text annotation: " + result.getErrorMessage()
159+
);
160+
}
161+
162+
return getSingleJsonReplyFromResult(result).annotation_id;
163+
}
164+
165+
function renderAnnotation(annotationId) {
166+
result = Module.dispatchCommand(
167+
JSON.stringify({
168+
type: "render_annotation",
169+
annotation_id: annotationId,
170+
page: 0,
171+
format: "png",
172+
bitmap_width: 350,
173+
bitmap_height: 250,
174+
})
175+
);
176+
177+
if (result && result.hasError()) {
178+
throw new Error("Failed to render annotation: " + result.getErrorMessage());
179+
}
180+
}
181+
182+
// Page width and height for the assets/example.pdf document.
183+
const PAGE_WIDTH = 1190;
184+
const PAGE_HEIGHT = 841;
185+
186+
class Benchmark {
187+
async init() {
188+
Module.wasmBinary = await getBinary(wasmBinary);
189+
190+
setupPrerequisites();
191+
injectRequiredGlobals(globalThis);
192+
}
193+
194+
async runIteration() {
195+
if (!Module.initPSPDFKit) {
196+
await initializeNutrient();
197+
}
198+
199+
Module.FS.writeFile("/document.pdf", await getBinary(pdfDocument));
200+
openDocument("/document.pdf");
201+
202+
var renderScale = 0.3;
203+
renderPage(0, PAGE_WIDTH * renderScale, PAGE_HEIGHT * renderScale);
204+
205+
var textAnnotationId = createTextAnnotation(
206+
[50, 50, 300, 200],
207+
"This is a text annotation created by the benchmark."
208+
);
209+
210+
renderAnnotation(textAnnotationId);
211+
}
212+
}
13.3 MB
Binary file not shown.

wasm/nutrient/build/nutrient-viewer.wasm.js

Lines changed: 22 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

wasm/nutrient/helper.js

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)