Initial commit

This commit is contained in:
2026-04-15 17:08:39 +02:00
parent ae164c47a8
commit 47fd1c2b7a
1819 changed files with 685388 additions and 0 deletions

9
node_modules/@mapbox/tiny-sdf/LICENSE.txt generated vendored Normal file
View File

@@ -0,0 +1,9 @@
BSD-2-Clause
Copyright (c) 2016-2024 Mapbox, Inc.
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

63
node_modules/@mapbox/tiny-sdf/README.md generated vendored Normal file
View File

@@ -0,0 +1,63 @@
# TinySDF [![Volodymyr Agafonkin's projects](https://img.shields.io/badge/simply-awesome-brightgreen.svg)](https://github.com/mourner/projects) [![Node](https://github.com/mapbox/tiny-sdf/actions/workflows/node.yml/badge.svg)](https://github.com/mapbox/tiny-sdf/actions/workflows/node.yml)
TinySDF is a tiny and fast JavaScript library for generating SDF (signed distance field)
from system fonts on the browser using Canvas 2D and
[Felzenszwalb/Huttenlocher distance transform](https://cs.brown.edu/~pff/papers/dt-final.pdf).
This is very useful for [rendering text with WebGL](https://www.mapbox.com/blog/text-signed-distance-fields/).
## [Demo](http://mapbox.github.io/tiny-sdf)
## Usage
Create a TinySDF for drawing glyph SDFs based on font parameters:
```js
const tinySdf = new TinySDF({
fontSize: 24, // Font size in pixels
fontFamily: 'sans-serif', // CSS font-family
fontWeight: 'normal', // CSS font-weight
fontStyle: 'normal', // CSS font-style
buffer: 3, // Whitespace buffer around a glyph in pixels
radius: 8, // How many pixels around the glyph shape to use for encoding distance
cutoff: 0.25 // How much of the radius (relative) is used for the inside part of the glyph
});
const glyph = tinySdf.draw('泽'); // draw a single character
```
Returns an object with the following properties:
- `data` is a `Uint8ClampedArray` array of alpha values (0255) for a `width` x `height` grid.
- `width`: Width of the returned bitmap.
- `height`: Height of the returned bitmap.
- `glyphTop`: Maximum ascent of the glyph from alphabetic baseline.
- `glyphLeft`: Currently hardwired to 0 (actual glyph differences are encoded in the rasterization).
- `glyphWidth`: Width of the rasterized portion of the glyph.
- `glyphHeight` Height of the rasterized portion of the glyph.
- `glyphAdvance`: Layout advance.
TinySDF is provided as a ES module, so it's only supported on modern browsers, excluding IE.
```html
<script type="module">
import TinySDF from 'https://cdn.skypack.dev/@mapbox/tiny-sdf';
...
</script>
```
In Node, you can't use `require` — only `import` in ESM-capable versions (v12.15+):
```js
import TinySDF from '@mapbox/tiny-sdf';
```
## Development
```bash
npm test # run tests
npm start # start server for the demo page
```
## License
This implementation is licensed under the [BSD 2-Clause license](https://opensource.org/licenses/BSD-2-Clause). It's based directly on the algorithm published in the Felzenszwalb/Huttenlocher paper, and is not a port of the existing C++ implementation provided by the paper's authors.

24
node_modules/@mapbox/tiny-sdf/index.d.ts generated vendored Normal file
View File

@@ -0,0 +1,24 @@
export declare type TinySDFOptions = {
fontSize?: number;
buffer?: number;
radius?: number;
cutoff?: number;
fontFamily?: string;
fontWeight?: string;
fontStyle?: string;
lang?: string;
};
export default class TinySDF {
constructor(options: TinySDFOptions);
draw(char: string): {
data: Uint8ClampedArray;
width: number;
height: number;
glyphWidth: number;
glyphHeight: number;
glyphTop: number;
glyphLeft: number;
glyphAdvance: number;
};
}

158
node_modules/@mapbox/tiny-sdf/index.js generated vendored Normal file
View File

@@ -0,0 +1,158 @@
const INF = 1e20;
// lookup table for gamma-corrected, signed squared alpha distance values
const alphaTable = new Float64Array(256);
for (let i = 0; i < 256; i++) {
const d = 0.5 - Math.pow(i / 255, 1 / 2.2);
alphaTable[i] = d * Math.abs(d);
}
alphaTable[255] = -INF;
export default class TinySDF {
constructor({
fontSize = 24,
buffer = 3,
radius = 8,
cutoff = 0.25,
fontFamily = 'sans-serif',
fontWeight = 'normal',
fontStyle = 'normal',
lang = null
} = {}) {
this.buffer = buffer; // padding around a glyph's bounding box
this.radius = radius; // how many pixels around the glyph edge are encoded as signed distances
this.cutoff = cutoff; // how much of the SDF byte range represents inside vs outside the edge
this.lang = lang; // language of the Canvas drawing context
// make the canvas size big enough to both have the specified buffer around the glyph
// for "halo", and account for some glyphs possibly being larger than their font size
const size = this.size = fontSize + buffer * 4;
const canvas = this._createCanvas(size);
const ctx = this.ctx = canvas.getContext('2d', {willReadFrequently: true});
ctx.font = `${fontStyle} ${fontWeight} ${fontSize}px ${fontFamily}`;
ctx.textBaseline = 'alphabetic';
ctx.textAlign = 'left'; // Necessary so that RTL text doesn't have different alignment
ctx.fillStyle = 'black';
// two grids of squared distances: one for the outside of the glyph shape, one for the inside;
// the signed distance is derived as sqrt(outer) - sqrt(inner)
this.gridOuter = new Float64Array(size * size);
this.gridInner = new Float64Array(size * size);
this.f = new Float64Array(size);
this.z = new Float64Array(size + 1);
this.v = new Uint16Array(size);
}
_createCanvas(size) {
if (typeof OffscreenCanvas !== 'undefined') {
return new OffscreenCanvas(size, size);
}
const canvas = document.createElement('canvas');
canvas.width = canvas.height = size;
return canvas;
}
draw(char) {
const {
width: glyphAdvance,
actualBoundingBoxAscent,
actualBoundingBoxDescent,
actualBoundingBoxLeft,
actualBoundingBoxRight
} = this.ctx.measureText(char);
// The integer/pixel part of the alignment is encoded in metrics.glyphTop/glyphLeft
// The remainder is implicitly encoded in the rasterization
const glyphTop = Math.ceil(actualBoundingBoxAscent);
const glyphLeft = Math.floor(actualBoundingBoxLeft);
// If the glyph overflows the canvas size, it will be clipped at the bottom/right
const glyphWidth = Math.max(0, Math.min(this.size - this.buffer, Math.ceil(actualBoundingBoxRight) - glyphLeft));
const glyphHeight = Math.max(0, Math.min(this.size - this.buffer, glyphTop + Math.ceil(actualBoundingBoxDescent)));
const width = glyphWidth + 2 * this.buffer;
const height = glyphHeight + 2 * this.buffer;
const len = Math.max(width * height, 0);
const data = new Uint8ClampedArray(len);
const glyph = {data, width, height, glyphWidth, glyphHeight, glyphTop, glyphLeft, glyphAdvance};
if (glyphWidth === 0 || glyphHeight === 0) return glyph;
const {ctx, buffer, gridInner, gridOuter} = this;
if (this.lang) ctx.lang = this.lang;
ctx.clearRect(buffer, buffer, glyphWidth, glyphHeight);
ctx.fillText(char, buffer - glyphLeft, buffer + glyphTop);
const imgData = ctx.getImageData(buffer, buffer, glyphWidth, glyphHeight);
// default: outside the glyph (INF distance) for outer, inside (0 distance) for inner
gridOuter.fill(INF, 0, len);
gridInner.fill(0, 0, len);
// for anti-aliased pixels, treat partial coverage as a distance approximation:
// a fully covered pixel gets 0 outer / INF inner; a partial pixel gets a small
// non-zero outer or inner distance based on how far its coverage deviates from 0.5
let imgIdx = 3; // start at the alpha channel of the first pixel
for (let y = 0; y < glyphHeight; y++) {
let j = (y + buffer) * width + buffer;
for (let x = 0; x < glyphWidth; x++, imgIdx += 4, j++) {
const a = imgData.data[imgIdx]; // alpha value
if (a === 0) continue; // empty pixels
const t = alphaTable[a];
gridOuter[j] = Math.max(0, t);
gridInner[j] = Math.max(0, -t);
}
}
edt(gridOuter, 0, 0, width, height, width, this.f, this.v, this.z);
edt(gridInner, buffer, buffer, glyphWidth, glyphHeight, width, this.f, this.v, this.z);
// encode signed distance as a byte: inside the glyph maps to high values, outside to low,
// with the edge gradient spanning [-radius * cutoff, radius * (1 - cutoff)] pixels around the edge;
// Uint8ClampedArray clamps beyond that
const scale = 255 / this.radius;
const base = 255 * (1 - this.cutoff);
for (let i = 0; i < len; i++) {
const d = Math.sqrt(gridOuter[i]) - Math.sqrt(gridInner[i]);
data[i] = Math.round(base - scale * d);
}
return glyph;
}
}
// 2D Euclidean squared distance transform by Felzenszwalb & Huttenlocher https://cs.brown.edu/~pff/papers/dt-final.pdf
function edt(data, x0, y0, width, height, gridSize, f, v, z) {
for (let x = x0; x < x0 + width; x++) edt1d(data, y0 * gridSize + x, gridSize, height, f, v, z);
for (let y = y0; y < y0 + height; y++) edt1d(data, y * gridSize + x0, 1, width, f, v, z);
}
// 1D squared distance transform
function edt1d(grid, offset, stride, length, f, v, z) {
v[0] = 0;
z[0] = -INF;
z[1] = INF;
f[0] = grid[offset];
for (let q = 1, k = 0, s = 0; q < length; q++) {
f[q] = grid[offset + q * stride];
const q2 = q * q;
do {
const r = v[k];
s = (f[q] - f[r] + q2 - r * r) / (q - r) / 2;
} while (s <= z[k] && --k > -1);
k++;
v[k] = q;
z[k] = s;
z[k + 1] = INF;
}
for (let q = 0, k = 0; q < length; q++) {
while (z[k + 1] < q) k++;
const r = v[k];
const qr = q - r;
grid[offset + q * stride] = f[r] + qr * qr;
}
}

45
node_modules/@mapbox/tiny-sdf/package.json generated vendored Normal file
View File

@@ -0,0 +1,45 @@
{
"name": "@mapbox/tiny-sdf",
"version": "2.1.0",
"description": "Browser-side SDF font generator",
"type": "module",
"main": "index.js",
"exports": "./index.js",
"typings": "./index.d.ts",
"scripts": {
"pretest": "eslint index.js index.html test",
"test": "node --test",
"bench": "node ./test/bench.js",
"start": "st --no-cache --localhost --index index.html ."
},
"repository": {
"type": "git",
"url": "git+https://github.com/mapbox/tiny-sdf.git"
},
"keywords": [
"sdf",
"signed distance fields",
"font",
"canvas",
"text",
"distance transform"
],
"author": "Vladimir Agafonkin",
"license": "BSD-2-Clause",
"bugs": {
"url": "https://github.com/mapbox/tiny-sdf/issues"
},
"homepage": "https://github.com/mapbox/tiny-sdf#readme",
"files": [
"index.d.ts"
],
"devDependencies": {
"canvas": "^3.2.3",
"eslint": "^10.2.0",
"eslint-config-mourner": "^4.1.0",
"eslint-plugin-html": "^8.1.4",
"pixelmatch": "^7.1.0",
"pngjs": "^7.0.0",
"st": "^3.0.3"
}
}