ntk 8.14.4 → 8.14.5
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/lib/cliprect.js +23 -8
- package/lib/path.js +85 -7
- package/lib/renderingcontext_2d.js +74 -11
- package/lib/text/font.js +24 -2
- package/package.json +1 -1
package/lib/cliprect.js
CHANGED
|
@@ -101,9 +101,19 @@ export function clipTrianglesToRect(tris, x0, y0, x1, y1) {
|
|
|
101
101
|
}
|
|
102
102
|
|
|
103
103
|
/**
|
|
104
|
-
* A flat `[x, y, w, h, …]` list of rectangles
|
|
105
|
-
* height)`, dropping the ones with nothing left: core X's
|
|
106
|
-
* bits. The list itself when every rectangle is already
|
|
104
|
+
* A flat `[x, y, w, h, …]` list of rectangles on whole pixels and cut to
|
|
105
|
+
* `[0, width) × [0, height)`, dropping the ones with nothing left: core X's
|
|
106
|
+
* rectangles are 16 bits. The list itself when every rectangle is already
|
|
107
|
+
* whole and on the surface.
|
|
108
|
+
*
|
|
109
|
+
* Each edge is rounded on its own. The wire truncates a rectangle's x and
|
|
110
|
+
* its width separately, so two rectangles meeting at a fractional edge —
|
|
111
|
+
* the selection bands of two lines whose height is the face's, 15.13px —
|
|
112
|
+
* left a row of neither between them every seventh or eighth line, and one
|
|
113
|
+
* that crossed the surface's edge was cut a pixel differently from the
|
|
114
|
+
* same rectangle moved: a scroll's copy and a repaint disagreed. Rounded
|
|
115
|
+
* edges meet whoever they are shared with, and move with a whole-pixel
|
|
116
|
+
* shift.
|
|
107
117
|
*/
|
|
108
118
|
export function rectsOnSurface(flat, width, height) {
|
|
109
119
|
let out = null;
|
|
@@ -112,16 +122,21 @@ export function rectsOnSurface(flat, width, height) {
|
|
|
112
122
|
const y = flat[i + 1];
|
|
113
123
|
const w = flat[i + 2];
|
|
114
124
|
const h = flat[i + 3];
|
|
125
|
+
const whole =
|
|
126
|
+
Number.isInteger(x) &&
|
|
127
|
+
Number.isInteger(y) &&
|
|
128
|
+
Number.isInteger(w) &&
|
|
129
|
+
Number.isInteger(h);
|
|
115
130
|
const inside = x >= 0 && y >= 0 && x + w <= width && y + h <= height;
|
|
116
|
-
if (inside) {
|
|
131
|
+
if (whole && inside) {
|
|
117
132
|
if (out) out.push(x, y, w, h);
|
|
118
133
|
continue;
|
|
119
134
|
}
|
|
120
135
|
if (!out) out = flat.slice(0, i);
|
|
121
|
-
const x0 = Math.max(x, 0);
|
|
122
|
-
const y0 = Math.max(y, 0);
|
|
123
|
-
const x1 = Math.min(x + w, width);
|
|
124
|
-
const y1 = Math.min(y + h, height);
|
|
136
|
+
const x0 = Math.max(Math.round(x), 0);
|
|
137
|
+
const y0 = Math.max(Math.round(y), 0);
|
|
138
|
+
const x1 = Math.min(Math.round(x + w), width);
|
|
139
|
+
const y1 = Math.min(Math.round(y + h), height);
|
|
125
140
|
if (x1 > x0 && y1 > y0) out.push(x0, y0, x1 - x0, y1 - y0);
|
|
126
141
|
}
|
|
127
142
|
return out ?? flat;
|
package/lib/path.js
CHANGED
|
@@ -491,13 +491,19 @@ function normalizeRadii(w, h, radii) {
|
|
|
491
491
|
br = take(radii[2]);
|
|
492
492
|
bl = take(radii[3]);
|
|
493
493
|
}
|
|
494
|
-
//
|
|
494
|
+
// Scale down so opposite corners never overlap: by the side's length over
|
|
495
|
+
// the radii along it, which is 0 for a side of no length, and the radii go
|
|
496
|
+
// with it. Only radii of nothing leave a side alone — `|| 1` guarded that
|
|
497
|
+
// 0 / 0 and took a real 0 for one as well, so a box of no height kept its
|
|
498
|
+
// corners, and a clip to it was two lobes of arc a radius above and below
|
|
499
|
+
// it that let what it should have hidden through.
|
|
500
|
+
const fit = (side, radii) => (radii > 0 ? side / radii : 1);
|
|
495
501
|
const s = Math.min(
|
|
496
502
|
1,
|
|
497
|
-
w
|
|
498
|
-
h
|
|
499
|
-
w
|
|
500
|
-
h
|
|
503
|
+
fit(w, tl.x + tr.x),
|
|
504
|
+
fit(h, tr.y + br.y),
|
|
505
|
+
fit(w, br.x + bl.x),
|
|
506
|
+
fit(h, bl.y + tl.y)
|
|
501
507
|
);
|
|
502
508
|
const f = (r) => ({ x: Math.max(0, r.x * s), y: Math.max(0, r.y * s) });
|
|
503
509
|
return [f(tl), f(tr), f(br), f(bl)];
|
|
@@ -803,6 +809,62 @@ function addCubic(pts, x0, y0, x1, y1, x2, y2, x3, y3, tol2, depth) {
|
|
|
803
809
|
addCubic(pts, mx, my, bcx, bcy, cx, cy, x3, y3, tol2, depth + 1);
|
|
804
810
|
}
|
|
805
811
|
|
|
812
|
+
/**
|
|
813
|
+
* Which way a cubic leaves (x0, y0), or arrives at (x, y) with `end`: the
|
|
814
|
+
* control point next to that end, or the next one along where it sits on
|
|
815
|
+
* the end itself. Null for a cubic that is a single point.
|
|
816
|
+
*/
|
|
817
|
+
function cubicTangent(x0, y0, x1, y1, x2, y2, x, y, end) {
|
|
818
|
+
if (end) {
|
|
819
|
+
if (x !== x2 || y !== y2) return [x - x2, y - y2];
|
|
820
|
+
if (x !== x1 || y !== y1) return [x - x1, y - y1];
|
|
821
|
+
return x !== x0 || y !== y0 ? [x - x0, y - y0] : null;
|
|
822
|
+
}
|
|
823
|
+
if (x1 !== x0 || y1 !== y0) return [x1 - x0, y1 - y0];
|
|
824
|
+
if (x2 !== x0 || y2 !== y0) return [x2 - x0, y2 - y0];
|
|
825
|
+
return x !== x0 || y !== y0 ? [x - x0, y - y0] : null;
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
/**
|
|
829
|
+
* The tangent at a curve command's end or start, in the flattened
|
|
830
|
+
* polyline's space; (x0, y0) is where the command begins, already there.
|
|
831
|
+
*/
|
|
832
|
+
function commandTangent(c, x0, y0, m, end) {
|
|
833
|
+
const p = (x, y) => (m ? matApply(m, x, y) : [x, y]);
|
|
834
|
+
const [x, y] = p(c.x, c.y);
|
|
835
|
+
if (c.type === 'Q') {
|
|
836
|
+
const [qx, qy] = p(c.x1, c.y1);
|
|
837
|
+
return cubicTangent(
|
|
838
|
+
x0, y0,
|
|
839
|
+
x0 + (2 / 3) * (qx - x0), y0 + (2 / 3) * (qy - y0),
|
|
840
|
+
x + (2 / 3) * (qx - x), y + (2 / 3) * (qy - y),
|
|
841
|
+
x, y,
|
|
842
|
+
end
|
|
843
|
+
);
|
|
844
|
+
}
|
|
845
|
+
const [x1, y1] = p(c.x1, c.y1);
|
|
846
|
+
const [x2, y2] = p(c.x2, c.y2);
|
|
847
|
+
return cubicTangent(x0, y0, x1, y1, x2, y2, x, y, end);
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
/**
|
|
851
|
+
* The tangents a flattened subpath leaves its first point and arrives at its
|
|
852
|
+
* last with, where that end is a curve — null where it is a line, whose
|
|
853
|
+
* chord already says which way the path heads. Worked out on demand from the
|
|
854
|
+
* commands `flattenPath` kept: only a stroke whose ends meet asks.
|
|
855
|
+
*/
|
|
856
|
+
export function subpathHead(poly) {
|
|
857
|
+
return poly.head
|
|
858
|
+
? commandTangent(poly.head, poly.pts[0], poly.pts[1], poly.m, false)
|
|
859
|
+
: null;
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
export function subpathTail(poly) {
|
|
863
|
+
return poly.tail
|
|
864
|
+
? commandTangent(poly.tail, poly.tailX, poly.tailY, poly.m, true)
|
|
865
|
+
: null;
|
|
866
|
+
}
|
|
867
|
+
|
|
806
868
|
/**
|
|
807
869
|
* Chords of the arc a tagged cubic stands for, appended to `pts`. Returns
|
|
808
870
|
* false — flatten the cubic instead — when the path's current point is not
|
|
@@ -858,6 +920,13 @@ function addArcChords(pts, cmd, m, tol, x0, y0) {
|
|
|
858
920
|
* geometry (see addArcChords); genuine beziers — hand-built paths, SVG curve
|
|
859
921
|
* commands, font outlines — take the adaptive bisection route.
|
|
860
922
|
*
|
|
923
|
+
* A subpath that begins or ends on a curve also keeps the command it began
|
|
924
|
+
* or ended with, for `subpathHead` and `subpathTail`: flattening turns a
|
|
925
|
+
* curve's end into a chord, which points half a chord's angle away from the
|
|
926
|
+
* curve, and the stroker asks for the curve's own tangent where that
|
|
927
|
+
* difference shows (a ring's seam). Kept rather than worked out, because a
|
|
928
|
+
* fill never asks.
|
|
929
|
+
*
|
|
861
930
|
* @returns {Array<{pts: number[], closed: boolean}>} flat [x0,y0,x1,y1,…]
|
|
862
931
|
*/
|
|
863
932
|
export function flattenPath(cmds, m = null, tol = FLATTEN_TOLERANCE) {
|
|
@@ -872,19 +941,24 @@ export function flattenPath(cmds, m = null, tol = FLATTEN_TOLERANCE) {
|
|
|
872
941
|
case 'M': {
|
|
873
942
|
if (pts && pts.pts.length >= 4) polys.push(pts);
|
|
874
943
|
const [x, y] = p(c.x, c.y);
|
|
875
|
-
pts = { pts: [x, y], closed: false };
|
|
944
|
+
pts = { pts: [x, y], closed: false, head: null, tail: null, tailX: 0, tailY: 0, m };
|
|
876
945
|
break;
|
|
877
946
|
}
|
|
878
947
|
case 'L': {
|
|
879
948
|
if (!pts) break;
|
|
880
949
|
const [x, y] = p(c.x, c.y);
|
|
881
950
|
pts.pts.push(x, y);
|
|
951
|
+
pts.tail = null;
|
|
882
952
|
break;
|
|
883
953
|
}
|
|
884
954
|
case 'C': {
|
|
885
955
|
if (!pts) break;
|
|
886
956
|
const x0 = pts.pts[pts.pts.length - 2];
|
|
887
957
|
const y0 = pts.pts[pts.pts.length - 1];
|
|
958
|
+
if (pts.pts.length === 2) pts.head = c;
|
|
959
|
+
pts.tail = c;
|
|
960
|
+
pts.tailX = x0;
|
|
961
|
+
pts.tailY = y0;
|
|
888
962
|
if (c.arc && addArcChords(pts.pts, c, m, tol, x0, y0)) break;
|
|
889
963
|
const [x1, y1] = p(c.x1, c.y1);
|
|
890
964
|
const [x2, y2] = p(c.x2, c.y2);
|
|
@@ -896,6 +970,10 @@ export function flattenPath(cmds, m = null, tol = FLATTEN_TOLERANCE) {
|
|
|
896
970
|
if (!pts) break;
|
|
897
971
|
const x0 = pts.pts[pts.pts.length - 2];
|
|
898
972
|
const y0 = pts.pts[pts.pts.length - 1];
|
|
973
|
+
if (pts.pts.length === 2) pts.head = c;
|
|
974
|
+
pts.tail = c;
|
|
975
|
+
pts.tailX = x0;
|
|
976
|
+
pts.tailY = y0;
|
|
899
977
|
const [qx, qy] = p(c.x1, c.y1);
|
|
900
978
|
const [x, y] = p(c.x, c.y);
|
|
901
979
|
// degree elevation: quadratic -> cubic
|
|
@@ -916,7 +994,7 @@ export function flattenPath(cmds, m = null, tol = FLATTEN_TOLERANCE) {
|
|
|
916
994
|
polys.push(pts);
|
|
917
995
|
}
|
|
918
996
|
// a new subpath continues from the subpath start
|
|
919
|
-
pts = { pts: [pts.pts[0], pts.pts[1]], closed: false };
|
|
997
|
+
pts = { pts: [pts.pts[0], pts.pts[1]], closed: false, head: null, tail: null, tailX: 0, tailY: 0, m };
|
|
920
998
|
}
|
|
921
999
|
break;
|
|
922
1000
|
}
|
|
@@ -22,6 +22,8 @@ import {
|
|
|
22
22
|
matInvert,
|
|
23
23
|
matIsIdentity,
|
|
24
24
|
matMultiply,
|
|
25
|
+
subpathHead,
|
|
26
|
+
subpathTail,
|
|
25
27
|
} from "./path.js";
|
|
26
28
|
import Picture from "./picture.js";
|
|
27
29
|
import { formatForDepth } from "./pictformat.js";
|
|
@@ -524,16 +526,23 @@ class RenderingContext2d {
|
|
|
524
526
|
);
|
|
525
527
|
}
|
|
526
528
|
if (typeof window.on === "function") {
|
|
527
|
-
|
|
529
|
+
// Kept, so `destroy` can take them off again: a surface drawn through
|
|
530
|
+
// `render()` gets a context a call, and each one left three listeners
|
|
531
|
+
// on the pixmap — holding the dead context — for as long as the
|
|
532
|
+
// surface lived. A fade redraws its group surface every frame.
|
|
533
|
+
this._onBacking = () => this._bindTarget();
|
|
528
534
|
// masks are sized to the drawable — recreate them after a resize
|
|
529
|
-
|
|
535
|
+
this._onResize = () => this._dropMasks();
|
|
530
536
|
// window-backed pictures are freed server-side with the window
|
|
531
|
-
|
|
537
|
+
this._onDestroyed = () => {
|
|
532
538
|
if (this._picture && this._target === this.window) {
|
|
533
539
|
this._picture.forget();
|
|
534
540
|
this._picture = null;
|
|
535
541
|
}
|
|
536
|
-
}
|
|
542
|
+
};
|
|
543
|
+
window.on("_backing", this._onBacking);
|
|
544
|
+
window.on("resize", this._onResize);
|
|
545
|
+
window.on("_destroyed", this._onDestroyed);
|
|
537
546
|
}
|
|
538
547
|
|
|
539
548
|
this.fillMask = null;
|
|
@@ -876,6 +885,13 @@ class RenderingContext2d {
|
|
|
876
885
|
if (this._destroyed) return;
|
|
877
886
|
this._destroyed = true;
|
|
878
887
|
gcRegistry.unregister(this);
|
|
888
|
+
const drawable = this.window;
|
|
889
|
+
if (this._onBacking && typeof drawable?.removeListener === "function") {
|
|
890
|
+
drawable.removeListener("_backing", this._onBacking);
|
|
891
|
+
drawable.removeListener("resize", this._onResize);
|
|
892
|
+
drawable.removeListener("_destroyed", this._onDestroyed);
|
|
893
|
+
this._onBacking = this._onResize = this._onDestroyed = null;
|
|
894
|
+
}
|
|
879
895
|
|
|
880
896
|
this._setClips([]);
|
|
881
897
|
this._dropMasks();
|
|
@@ -2401,6 +2417,32 @@ class RenderingContext2d {
|
|
|
2401
2417
|
addDisk(pts[pts.length - 1][0], pts[pts.length - 1][1]);
|
|
2402
2418
|
}
|
|
2403
2419
|
};
|
|
2420
|
+
// An open subpath that ends where it began, heading the way it began —
|
|
2421
|
+
// a full circle from arc(), which leaves its subpath open. Stroked
|
|
2422
|
+
// open, each end got a cap square to its own chord, and a flattened
|
|
2423
|
+
// curve's first and last chords point a chord's angle apart: a wedge
|
|
2424
|
+
// of ink went missing outside the seam, a hairline across every
|
|
2425
|
+
// stroked ring, and doubled inside it. Where the two ends meet head to
|
|
2426
|
+
// tail the caps and a join are the same ink, so it is stroked as the
|
|
2427
|
+
// loop it is. A path that comes back to its start at a corner keeps
|
|
2428
|
+
// its caps, as the spec gives them.
|
|
2429
|
+
const smoothLoop = (poly, pts) => {
|
|
2430
|
+
const n = pts.length;
|
|
2431
|
+
if (n < 4) return false;
|
|
2432
|
+
const [fx, fy] = pts[0];
|
|
2433
|
+
const [lx, ly] = pts[n - 1];
|
|
2434
|
+
if (Math.abs(fx - lx) > 1e-6 || Math.abs(fy - ly) > 1e-6) return false;
|
|
2435
|
+
const [hx, hy] = (poly.head && subpathHead(poly)) ?? [
|
|
2436
|
+
pts[1][0] - fx,
|
|
2437
|
+
pts[1][1] - fy,
|
|
2438
|
+
];
|
|
2439
|
+
const [tx, ty] = (poly.tail && subpathTail(poly)) ?? [
|
|
2440
|
+
lx - pts[n - 2][0],
|
|
2441
|
+
ly - pts[n - 2][1],
|
|
2442
|
+
];
|
|
2443
|
+
const lengths = Math.hypot(hx, hy) * Math.hypot(tx, ty);
|
|
2444
|
+
return lengths > 0 && (hx * tx + hy * ty) / lengths > 1 - 1e-9;
|
|
2445
|
+
};
|
|
2404
2446
|
// dash boundaries can duplicate run endpoints; collapse them
|
|
2405
2447
|
const cleanRun = (run) => {
|
|
2406
2448
|
const out = [run[0]];
|
|
@@ -2433,12 +2475,13 @@ class RenderingContext2d {
|
|
|
2433
2475
|
pts.push([fx, fy]);
|
|
2434
2476
|
}
|
|
2435
2477
|
if (pts.length < 2) continue;
|
|
2478
|
+
const closed = poly.closed || smoothLoop(poly, pts);
|
|
2436
2479
|
|
|
2437
2480
|
if (!dash) {
|
|
2438
|
-
extrudeRun(pts,
|
|
2481
|
+
extrudeRun(pts, closed);
|
|
2439
2482
|
continue;
|
|
2440
2483
|
}
|
|
2441
|
-
const dashed = dashPolyline(pts,
|
|
2484
|
+
const dashed = dashPolyline(pts, closed, dash, dashOffset, view);
|
|
2442
2485
|
if (!dashed) {
|
|
2443
2486
|
extrudeRun(pts, poly.closed);
|
|
2444
2487
|
} else if (dashed.closedLoop) {
|
|
@@ -3056,6 +3099,25 @@ class RenderingContext2d {
|
|
|
3056
3099
|
* for the whole list and then draws the rectangles through here */
|
|
3057
3100
|
_fillRect(x, y, w, h) {
|
|
3058
3101
|
if (matIsIdentity(this._m)) {
|
|
3102
|
+
// On whole pixels, each edge rounded on its own (see rectsOnSurface):
|
|
3103
|
+
// the wire truncates x and width separately, so rectangles that met
|
|
3104
|
+
// at a fractional edge left a row of neither between them. Whole, a
|
|
3105
|
+
// rectangle under a rectangular clip also keeps the bounded
|
|
3106
|
+
// composite below instead of building a mask.
|
|
3107
|
+
if (
|
|
3108
|
+
!Number.isInteger(x) ||
|
|
3109
|
+
!Number.isInteger(y) ||
|
|
3110
|
+
!Number.isInteger(w) ||
|
|
3111
|
+
!Number.isInteger(h)
|
|
3112
|
+
) {
|
|
3113
|
+
const x0 = Math.round(x);
|
|
3114
|
+
const y0 = Math.round(y);
|
|
3115
|
+
w = Math.round(x + w) - x0;
|
|
3116
|
+
h = Math.round(y + h) - y0;
|
|
3117
|
+
x = x0;
|
|
3118
|
+
y = y0;
|
|
3119
|
+
if (!(w > 0 && h > 0)) return;
|
|
3120
|
+
}
|
|
3059
3121
|
// X coordinates and sizes are 16 bits, and a coordinate past 32,767
|
|
3060
3122
|
// throws out of the paint (lib/cliprect.js): a rectangle that reaches
|
|
3061
3123
|
// past the surface is filled as the part of it the surface has.
|
|
@@ -3224,13 +3286,14 @@ class RenderingContext2d {
|
|
|
3224
3286
|
let x1 = -Infinity;
|
|
3225
3287
|
let y1 = -Infinity;
|
|
3226
3288
|
for (let i = 0; i < flat.length; i += 4) {
|
|
3227
|
-
//
|
|
3228
|
-
|
|
3229
|
-
const
|
|
3289
|
+
// on whole pixels, each edge rounded as `_fillRect` rounds it, and the
|
|
3290
|
+
// part of it on the surface: the wire's coordinates are 16 bits
|
|
3291
|
+
const left = Math.round(flat[i]);
|
|
3292
|
+
const top = Math.round(flat[i + 1]);
|
|
3230
3293
|
const x = Math.max(0, left);
|
|
3231
3294
|
const y = Math.max(0, top);
|
|
3232
|
-
const w = Math.min(this.width,
|
|
3233
|
-
const h = Math.min(this.height,
|
|
3295
|
+
const w = Math.min(this.width, Math.round(flat[i] + flat[i + 2])) - x;
|
|
3296
|
+
const h = Math.min(this.height, Math.round(flat[i + 1] + flat[i + 3])) - y;
|
|
3234
3297
|
if (w <= 0 || h <= 0) continue;
|
|
3235
3298
|
rects.push(x, y, w, h);
|
|
3236
3299
|
if (x < x0) x0 = x;
|
package/lib/text/font.js
CHANGED
|
@@ -111,6 +111,28 @@ function answerWithCharacters(fk) {
|
|
|
111
111
|
Object.defineProperty(fk, '__ntkCharacters', { value: true });
|
|
112
112
|
}
|
|
113
113
|
|
|
114
|
+
/**
|
|
115
|
+
* A face's cap height or x-height, as its OS/2 table states it, or else the
|
|
116
|
+
* top of its `H` or `x`. A table older than version 2 has neither field, and
|
|
117
|
+
* a face with no OS/2 table at all — Apple's Courier.ttc — has no table to
|
|
118
|
+
* state them: fontkit answered 0 for one and nothing for the other, and an
|
|
119
|
+
* `ex` in either came out half an em (CSS Values 4, 6.1.1 lets the height
|
|
120
|
+
* of a lowercase glyph stand in, as browsers measure it). NaN where the face
|
|
121
|
+
* has no such glyph either, which is the "half an em" case the spec keeps.
|
|
122
|
+
*/
|
|
123
|
+
function statedOrGlyph(fk, field, codePoint) {
|
|
124
|
+
const os2 = fk['OS/2'];
|
|
125
|
+
const stated = os2 && os2.version >= 2 ? os2[field] : undefined;
|
|
126
|
+
if (stated > 0) return stated;
|
|
127
|
+
try {
|
|
128
|
+
if (!fk.hasGlyphForCodePoint(codePoint)) return NaN;
|
|
129
|
+
const top = fk.glyphForCodePoint(codePoint).bbox.maxY;
|
|
130
|
+
return top > 0 ? top : NaN;
|
|
131
|
+
} catch {
|
|
132
|
+
return NaN;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
114
136
|
export default class Font {
|
|
115
137
|
constructor(fkFont, path, postscriptName) {
|
|
116
138
|
answerWithCharacters(fkFont);
|
|
@@ -286,8 +308,8 @@ export default class Font {
|
|
|
286
308
|
ascent: this.fk.ascent,
|
|
287
309
|
descent: -this.fk.descent,
|
|
288
310
|
lineGap: this.fk.lineGap,
|
|
289
|
-
capHeight: this.fk
|
|
290
|
-
xHeight: this.fk
|
|
311
|
+
capHeight: statedOrGlyph(this.fk, 'capHeight', 0x48),
|
|
312
|
+
xHeight: statedOrGlyph(this.fk, 'xHeight', 0x78)
|
|
291
313
|
});
|
|
292
314
|
const s = this.scale(size);
|
|
293
315
|
const ascent = v.ascent * s;
|