rapier-math 0.0.0-stage → 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +484 -0
- package/LICENSE-MIT +21 -0
- package/README.md +110 -2
- package/check.mjs +97 -0
- package/index.mjs +2953 -0
- package/math-LICENSE.txt +21 -0
- package/math-components.json +71 -0
- package/math-font-LICENSE.txt +457 -0
- package/package.json +44 -4
- package/rapier-math-1.0.2.js +2952 -0
- package/src/math/FONT-LICENSE.txt +457 -0
- package/src/math/FONT-SOURCE.md +143 -0
- package/src/math/LICENSE-MIT +21 -0
- package/src/math/api.js +87 -0
- package/src/math/browser.js +181 -0
- package/src/math/build-font.py +622 -0
- package/src/math/fallbacks.js +111 -0
- package/src/math/font.js +547 -0
- package/src/math/layout.js +580 -0
- package/src/math/package-build.mjs +27 -0
- package/src/math/package-verify.mjs +40 -0
- package/src/math/parser.js +1064 -0
- package/src/math/responsive.js +251 -0
- package/src/math/symbols.js +98 -0
- package/src/package.json +9 -0
- package/src/shell/math-resources.json +105 -0
- package/src/tools/build-math.mjs +95 -0
- package/verify.mjs +40 -0
package/check.mjs
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
// Package entry, exact authored text and hostile-content boundaries; no appearance assertions.
|
|
3
|
+
import assert from 'node:assert/strict';
|
|
4
|
+
import {readFile} from 'node:fs/promises';
|
|
5
|
+
import {Script, createContext} from 'node:vm';
|
|
6
|
+
import {mathModule} from './src/math/package-build.mjs';
|
|
7
|
+
|
|
8
|
+
const read = path => readFile(new URL(path, import.meta.url), 'utf8');
|
|
9
|
+
const pkg = JSON.parse(await read('./package.json'));
|
|
10
|
+
const resources = JSON.parse(await read('./src/shell/math-resources.json'));
|
|
11
|
+
const classicSource = await read('./' + resources.bundle);
|
|
12
|
+
assert.equal(pkg.name, 'rapier-math');
|
|
13
|
+
assert.equal(pkg.version, resources.version);
|
|
14
|
+
assert.equal(pkg.type, 'module');
|
|
15
|
+
assert.equal(pkg.license, 'SEE LICENSE IN LICENSE');
|
|
16
|
+
assert.equal(Object.keys(pkg.dependencies || {}).length, 0);
|
|
17
|
+
assert.equal(Object.keys(pkg.scripts || {}).length, 0);
|
|
18
|
+
assert.equal(await read('./index.mjs'), mathModule(classicSource), 'exact generated module');
|
|
19
|
+
const rendererLicense = await read('./LICENSE-MIT'), fontLicense = await read('./math-font-LICENSE.txt');
|
|
20
|
+
for (const name of ['./LICENSE', './index.mjs', './' + resources.bundle]) {
|
|
21
|
+
const text = await read(name);
|
|
22
|
+
assert(text.includes(rendererLicense.trim()), name + ': renderer license');
|
|
23
|
+
assert(text.includes(fontLicense.trim()), name + ': complete font license');
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const globals = Object.getOwnPropertyDescriptors(globalThis);
|
|
27
|
+
const module = await import('./index.mjs');
|
|
28
|
+
const afterImport = Object.getOwnPropertyDescriptors(globalThis);
|
|
29
|
+
assert.deepEqual(Reflect.ownKeys(afterImport), Reflect.ownKeys(globals), 'module adds no globals');
|
|
30
|
+
for (const key of Reflect.ownKeys(globals)) for (const field of Object.keys(globals[key]))
|
|
31
|
+
assert(Object.is(afterImport[key][field], globals[key][field]), 'module preserves global ' + String(key));
|
|
32
|
+
assert.deepEqual(Object.keys(module).sort(), ['default', 'fit', 'observe', 'parse', 'render', 'renderToString', 'version']);
|
|
33
|
+
for (const name of Object.keys(module.default)) assert.equal(module[name], module.default[name], 'named/default export ' + name);
|
|
34
|
+
assert.equal(module.version, resources.version);
|
|
35
|
+
assert(Object.isFrozen(module.default));
|
|
36
|
+
|
|
37
|
+
const context = createContext({atob, URL});
|
|
38
|
+
new Script(classicSource, {filename: resources.bundle}).runInContext(context);
|
|
39
|
+
const classic = context.RapierMath;
|
|
40
|
+
const plain = value => JSON.parse(JSON.stringify(value));
|
|
41
|
+
const unescape = text => text.replace(/&(amp|lt|gt|quot|apos|#\d+|#x[\da-f]+);/gi, (_, entity) =>
|
|
42
|
+
entity[0] === '#' ? String.fromCodePoint(entity[1] === 'x' ? parseInt(entity.slice(2), 16) : Number(entity.slice(1))) :
|
|
43
|
+
({amp: '&', lt: '<', gt: '>', quot: '"', apos: "'"})[entity]);
|
|
44
|
+
function sourceAlternative(result, source) {
|
|
45
|
+
const label = /\baria-label="([^"]*)"/.exec(result.svg)?.[1];
|
|
46
|
+
const title = /<title>([^<]*)<\/title>/.exec(result.svg)?.[1];
|
|
47
|
+
const utf16 = /\bdata-tex-utf16="([0-9a-f]+)"/.exec(result.svg)?.[1];
|
|
48
|
+
if (utf16) assert.equal(utf16.match(/.{4}/g).map(part => String.fromCharCode(parseInt(part, 16))).join(''), source, 'exact UTF-16 alternative');
|
|
49
|
+
else { assert.equal(unescape(label), source, 'exact aria alternative'); assert.equal(unescape(title), source, 'exact title alternative'); }
|
|
50
|
+
}
|
|
51
|
+
function inert(svg) {
|
|
52
|
+
assert(!/<\s*(?:script|foreignObject|iframe|object|embed|image|style|animate|set)\b/i.test(svg), 'no executable or remote SVG element');
|
|
53
|
+
for (const tag of svg.match(/<[^>]+>/g) || []) {
|
|
54
|
+
const attributes = [...tag.matchAll(/\s([^\s=]+)="([^"]*)"/g)];
|
|
55
|
+
for (const [, name, value] of attributes) {
|
|
56
|
+
assert(!/^on/i.test(name), 'no event attribute');
|
|
57
|
+
if (/^(?:href|xlink:href)$/i.test(name)) assert(/^https?:\/\//i.test(unescape(value)), 'only HTTP(S) links');
|
|
58
|
+
if (!/^(?:aria-label|data-tex-utf16)$/.test(name)) assert(!/url\s*\(|javascript:|vbscript:/i.test(unescape(value)), 'no executable attribute');
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const examples = [
|
|
64
|
+
[String.raw`\frac{-b\pm\sqrt{b^2-4ac}}{2a}`, {displayMode: true}],
|
|
65
|
+
[String.raw`\begin{aligned}a&=b+c\\d&=e+f\end{aligned}`, {displayMode: true, maxWidth: 4}],
|
|
66
|
+
[String.raw`\newcommand{\squareof}[1]{#1^2}\squareof{x}`, {screen: {fontSize: 16, pixelRatio: 2, dark: false}}],
|
|
67
|
+
[String.raw`\text{A < B & "C" 'D'} + α`, {}],
|
|
68
|
+
[String.raw`\href{https://example.com/?a=1&b=2}{x}`, {}],
|
|
69
|
+
[String.raw`\unsupported{content}`, {}],
|
|
70
|
+
['x\u0000y\ud800z\udfff', {}],
|
|
71
|
+
];
|
|
72
|
+
for (const [source, options] of examples) {
|
|
73
|
+
const result = module.render(source, options);
|
|
74
|
+
assert.deepEqual(plain(result), plain(classic.render(source, options)), 'module/classic result');
|
|
75
|
+
assert.equal(module.renderToString(source, options), result.svg, 'string export agrees with render');
|
|
76
|
+
sourceAlternative(result, source); inert(result.svg);
|
|
77
|
+
}
|
|
78
|
+
assert.deepEqual(plain(module.parse(examples[0][0])), plain(classic.parse(examples[0][0])), 'module/classic parse');
|
|
79
|
+
assert.equal(module.render(examples.at(-1)[0]).errors[0].code, 'invalid-source-character');
|
|
80
|
+
assert.equal(module.render(examples.at(-2)[0]).errors[0].code, 'unsupported-command');
|
|
81
|
+
|
|
82
|
+
const hostile = [
|
|
83
|
+
String.raw`\text{</title><script>alert(1)</script>}`,
|
|
84
|
+
String.raw`\href{javascript:alert(1)}{x}`,
|
|
85
|
+
String.raw`\href{data:text/html,test}{x}`,
|
|
86
|
+
String.raw`\href{https://example.com/" onload="alert(1)}{x}`,
|
|
87
|
+
String.raw`\color{url(https://example.com/payload)}x`,
|
|
88
|
+
String.raw`\def\again{\again}\again`,
|
|
89
|
+
];
|
|
90
|
+
for (const source of hostile) {
|
|
91
|
+
const result = module.render(source);
|
|
92
|
+
sourceAlternative(result, source); inert(result.svg);
|
|
93
|
+
assert.deepEqual(plain(result), plain(classic.render(source)), 'module/classic hostile result');
|
|
94
|
+
}
|
|
95
|
+
for (const source of hostile.slice(1)) assert(module.render(source).errors.length, 'unsafe or exhausted input produces a diagnostic');
|
|
96
|
+
console.log(JSON.stringify({name: pkg.name, version: module.version, module: 'native ESM; no global changes',
|
|
97
|
+
rebuild: 'exact module', classicAgreement: examples.length + hostile.length, sourceAlternatives: 'exact', hostileContent: 'inert', licenses: 'complete'}));
|