@mapka/font-sdf 0.2.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.txt +25 -0
- package/README.md +31 -0
- package/lib/.buildInfo.json +1 -0
- package/lib/curve.d.ts +36 -0
- package/lib/curve.d.ts.map +1 -0
- package/lib/curve.js +279 -0
- package/lib/curve.js.map +1 -0
- package/lib/font.d.ts +11 -0
- package/lib/font.d.ts.map +1 -0
- package/lib/font.js +75 -0
- package/lib/font.js.map +1 -0
- package/lib/glyphs.d.ts +7 -0
- package/lib/glyphs.d.ts.map +1 -0
- package/lib/glyphs.js +69 -0
- package/lib/glyphs.js.map +1 -0
- package/lib/index.d.ts +5 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +4 -0
- package/lib/index.js.map +1 -0
- package/lib/pbf.d.ts +36 -0
- package/lib/pbf.d.ts.map +1 -0
- package/lib/pbf.js +39 -0
- package/lib/pbf.js.map +1 -0
- package/lib/sdf.d.ts +13 -0
- package/lib/sdf.d.ts.map +1 -0
- package/lib/sdf.js +230 -0
- package/lib/sdf.js.map +1 -0
- package/package.json +28 -0
- package/src/curve.js +307 -0
- package/src/font.ts +99 -0
- package/src/glyphs.js +47 -0
- package/src/index.ts +9 -0
- package/src/pbf.ts +69 -0
- package/src/sdf.js +271 -0
package/src/sdf.js
ADDED
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
import { Glyph } from "opentype.js";
|
|
2
|
+
import {Curve3Div, Curve4Div} from "./curve.js";
|
|
3
|
+
import rbush from "rbush";
|
|
4
|
+
|
|
5
|
+
// calculates the bbox for a line segment from a to b
|
|
6
|
+
function bbox(a, b) {
|
|
7
|
+
return [
|
|
8
|
+
/* x1 */ Math.min(a[0], b[0]),
|
|
9
|
+
/* y1 */ Math.min(a[1], b[1]),
|
|
10
|
+
/* x2 */ Math.max(a[0], b[0]),
|
|
11
|
+
/* y2 */ Math.max(a[1], b[1]),
|
|
12
|
+
a,
|
|
13
|
+
b,
|
|
14
|
+
];
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function pt(x, y) {
|
|
18
|
+
return [x, y];
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// point in polygon ray casting algorithm
|
|
22
|
+
function polyContainsPoint(rings, p) {
|
|
23
|
+
var c = false,
|
|
24
|
+
ring,
|
|
25
|
+
p1,
|
|
26
|
+
p2;
|
|
27
|
+
|
|
28
|
+
for (var k = 0; k < rings.length; k++) {
|
|
29
|
+
ring = rings[k];
|
|
30
|
+
for (var i = 0, j = ring.length - 1; i < ring.length; j = i++) {
|
|
31
|
+
p1 = ring[i];
|
|
32
|
+
p2 = ring[j];
|
|
33
|
+
if (
|
|
34
|
+
p1[1] > p.y != p2[1] > p.y &&
|
|
35
|
+
p.x < ((p2[0] - p1[0]) * (p.y - p1[1])) / (p2[1] - p1[1]) + p1[0]
|
|
36
|
+
) {
|
|
37
|
+
c = !c;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return c;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function squaredDistance(v, w) {
|
|
45
|
+
var a = v[0] - w[0];
|
|
46
|
+
var b = v[1] - w[1];
|
|
47
|
+
return a * a + b * b;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function projectPointOnLineSegment(p, v, w) {
|
|
51
|
+
var l2 = squaredDistance(v, w);
|
|
52
|
+
if (l2 === 0) return v;
|
|
53
|
+
var t = ((p[0] - v[0]) * (w[0] - v[0]) + (p[1] - v[1]) * (w[1] - v[1])) / l2;
|
|
54
|
+
if (t < 0) return v;
|
|
55
|
+
if (t > 1) return w;
|
|
56
|
+
return [v[0] + t * (w[0] - v[0]), v[1] + t * (w[1] - v[1])];
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function squaredDistanceToLineSegment(p, v, w) {
|
|
60
|
+
var s = projectPointOnLineSegment(p, v, w);
|
|
61
|
+
return squaredDistance(p, s);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function minDistanceToLineSegment(tree, p, radius) {
|
|
65
|
+
var squaredRadius = radius * radius;
|
|
66
|
+
var segments = tree.search([p[0] - radius, p[1] - radius, p[0] + radius, p[1] + radius]);
|
|
67
|
+
var squaredDistance = Number.POSITIVE_INFINITY;
|
|
68
|
+
for (var i = 0; i < segments.length; i++) {
|
|
69
|
+
var segment = segments[i];
|
|
70
|
+
var v = segment[4];
|
|
71
|
+
var w = segment[5];
|
|
72
|
+
var dist = squaredDistanceToLineSegment(p, v, w);
|
|
73
|
+
if (dist < squaredDistance && dist < squaredRadius) {
|
|
74
|
+
squaredDistance = dist;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return Math.sqrt(squaredDistance);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function pathToRings(path) {
|
|
81
|
+
var rings = [];
|
|
82
|
+
var ring = [];
|
|
83
|
+
|
|
84
|
+
var curve3 = new Curve3Div();
|
|
85
|
+
curve3.approximation_scale = 2;
|
|
86
|
+
|
|
87
|
+
var curve4 = new Curve4Div();
|
|
88
|
+
curve4.approximation_scale = 2;
|
|
89
|
+
|
|
90
|
+
function closeRing(ring) {
|
|
91
|
+
var first = ring[0];
|
|
92
|
+
var last = ring[ring.length - 1];
|
|
93
|
+
if (first[0] !== last[0] || first[1] !== last[1]) {
|
|
94
|
+
ring.push([first[0], first[1]]);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Build a list of contours (= rings) and flatten the bezier curves into regular line segments.
|
|
99
|
+
for (var i = 0; i < path.length; i++) {
|
|
100
|
+
var segment = path[i];
|
|
101
|
+
if (segment.type == "M") {
|
|
102
|
+
if (ring.length) {
|
|
103
|
+
closeRing(ring);
|
|
104
|
+
rings.push(ring);
|
|
105
|
+
}
|
|
106
|
+
ring = [[segment.x, segment.y]];
|
|
107
|
+
} else if (segment.type == "L") {
|
|
108
|
+
ring.push([segment.x, segment.y]);
|
|
109
|
+
} else if (segment.type == "Q") {
|
|
110
|
+
var prev = ring.pop();
|
|
111
|
+
curve3.init(prev[0], prev[1], segment.x1, segment.y1, segment.x, segment.y);
|
|
112
|
+
ring = ring.concat(curve3.points);
|
|
113
|
+
} else if (segment.type == "C") {
|
|
114
|
+
var prev = ring.pop();
|
|
115
|
+
curve4.init(
|
|
116
|
+
prev[0],
|
|
117
|
+
prev[1],
|
|
118
|
+
segment.x1,
|
|
119
|
+
segment.y1,
|
|
120
|
+
segment.x2,
|
|
121
|
+
segment.y2,
|
|
122
|
+
segment.x,
|
|
123
|
+
segment.y,
|
|
124
|
+
);
|
|
125
|
+
ring = ring.concat(curve4.points);
|
|
126
|
+
} else if (segment.type == "Z") {
|
|
127
|
+
if (ring.length) {
|
|
128
|
+
ring.push([ring[0][0], ring[0][1]]);
|
|
129
|
+
}
|
|
130
|
+
} else {
|
|
131
|
+
throw segment;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
if (ring.length) {
|
|
135
|
+
closeRing(ring);
|
|
136
|
+
rings.push(ring);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return rings;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function ringsToSDF(rings, width, height, buffer, cutoff) {
|
|
143
|
+
width += 2 * buffer;
|
|
144
|
+
height += 2 * buffer;
|
|
145
|
+
|
|
146
|
+
var tree = rbush(9);
|
|
147
|
+
var offset = 0.5;
|
|
148
|
+
var radius = 8;
|
|
149
|
+
var data = new Uint8ClampedArray(width * height);
|
|
150
|
+
|
|
151
|
+
// Buffer
|
|
152
|
+
for (var j = 0; j < rings.length; j++) {
|
|
153
|
+
var ring = rings[j];
|
|
154
|
+
for (var i = 0; i < ring.length; i++) {
|
|
155
|
+
var point = ring[i];
|
|
156
|
+
point[0] += buffer;
|
|
157
|
+
point[1] += buffer;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Insert all line segments into the rtree, regardless of what contour they belong to.
|
|
162
|
+
for (var j = 0; j < rings.length; j++) {
|
|
163
|
+
var ring = rings[j];
|
|
164
|
+
for (var i = 1; i < ring.length; i++) {
|
|
165
|
+
tree.insert(bbox([ring[i - 1][0], ring[i - 1][1]], [ring[i][0], ring[i][1]]));
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Loop over every pixel and determine the positive/negative distance to the outline.
|
|
170
|
+
for (var y = 0; y < height; y++) {
|
|
171
|
+
for (var x = 0; x < width; x++) {
|
|
172
|
+
var i = y * width + x;
|
|
173
|
+
|
|
174
|
+
var p = pt(x + offset, y + offset);
|
|
175
|
+
|
|
176
|
+
var d = minDistanceToLineSegment(tree, p, radius) * (256 / radius);
|
|
177
|
+
|
|
178
|
+
// Invert if point is inside.
|
|
179
|
+
var inside = polyContainsPoint(rings, { x: x + offset, y: y + offset });
|
|
180
|
+
if (inside) {
|
|
181
|
+
d = -d;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Shift the 0 so that we can fit a few negative values into our 8 bits.
|
|
185
|
+
d += cutoff * 256;
|
|
186
|
+
|
|
187
|
+
// Note that info.data is /clamped/ to 0-255. This makes sure there are no overflows
|
|
188
|
+
// or underflows.
|
|
189
|
+
data[i] = 255 - d;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
return data;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
export const glyphToSDF = (font, glyph, fontSize, buffer, cutoff) => {
|
|
197
|
+
var fontScale = font.unitsPerEm / fontSize;
|
|
198
|
+
var ascender = Math.round(font.ascender / fontScale);
|
|
199
|
+
|
|
200
|
+
var info = {
|
|
201
|
+
width: 0,
|
|
202
|
+
height: 0,
|
|
203
|
+
data: null,
|
|
204
|
+
|
|
205
|
+
glyphBearingX: 0,
|
|
206
|
+
glyphWidth: 0,
|
|
207
|
+
glyphBearingY: 0,
|
|
208
|
+
glyphHeight: 0,
|
|
209
|
+
glyphTop: 0,
|
|
210
|
+
glyphAdvance: Math.round(glyph.advanceWidth / fontScale),
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
// Extract the glyph's path and scale it to the correct font size.
|
|
214
|
+
var path = glyph.getPath(0, 0, fontSize).commands;
|
|
215
|
+
var rings = pathToRings(path);
|
|
216
|
+
|
|
217
|
+
if (!rings.length) {
|
|
218
|
+
return info;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// Calculate the real glyph bbox.
|
|
222
|
+
var xMin = Number.POSITIVE_INFINITY,
|
|
223
|
+
yMin = Number.POSITIVE_INFINITY;
|
|
224
|
+
var xMax = Number.NEGATIVE_INFINITY,
|
|
225
|
+
yMax = Number.NEGATIVE_INFINITY;
|
|
226
|
+
|
|
227
|
+
for (var j = 0; j < rings.length; j++) {
|
|
228
|
+
var ring = rings[j];
|
|
229
|
+
for (var i = 0; i < ring.length; i++) {
|
|
230
|
+
var point = ring[i];
|
|
231
|
+
if (point[0] > xMax) xMax = point[0];
|
|
232
|
+
if (point[0] < xMin) xMin = point[0];
|
|
233
|
+
if (point[1] > yMax) yMax = point[1];
|
|
234
|
+
if (point[1] < yMin) yMin = point[1];
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
xMin = Math.round(xMin);
|
|
239
|
+
yMin = Math.round(yMin);
|
|
240
|
+
xMax = Math.round(xMax);
|
|
241
|
+
yMax = Math.round(yMax);
|
|
242
|
+
|
|
243
|
+
// Offset so that glyph outlines are in the bounding box.
|
|
244
|
+
for (var j = 0; j < rings.length; j++) {
|
|
245
|
+
var ring = rings[j];
|
|
246
|
+
for (var i = 0; i < ring.length; i++) {
|
|
247
|
+
var point = ring[i];
|
|
248
|
+
point[0] += -xMin;
|
|
249
|
+
point[1] += -yMin;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
if (xMax - xMin === 0 || (yMax == yMin) === 0) {
|
|
254
|
+
return info;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
info.glyphBearingX = xMin;
|
|
258
|
+
info.glyphWidth = xMax - xMin;
|
|
259
|
+
info.glyphBearingY = -yMin;
|
|
260
|
+
info.glyphHeight = yMax - yMin;
|
|
261
|
+
info.glyphTop = -yMin - ascender;
|
|
262
|
+
info.width = info.glyphWidth + 2 * buffer;
|
|
263
|
+
info.height = info.glyphHeight + 2 * buffer;
|
|
264
|
+
info.data = ringsToSDF(rings, info.glyphWidth, info.glyphHeight, buffer, cutoff);
|
|
265
|
+
|
|
266
|
+
return info;
|
|
267
|
+
};
|
|
268
|
+
|
|
269
|
+
export function pathToSDF(path, width, height, buffer, cutoff) {
|
|
270
|
+
return ringsToSDF(pathToRings(path), width, height, buffer, cutoff);
|
|
271
|
+
}
|