ntk 8.18.0 → 8.19.0
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/app.js +217 -35
- package/lib/precise.js +90 -36
- package/lib/renderingcontext_2d.js +1105 -442
- package/lib/text/layout.js +12 -6
- package/lib/trapezoid.js +48 -26
- package/package.json +1 -1
package/lib/text/layout.js
CHANGED
|
@@ -916,6 +916,9 @@ export class TextLayout {
|
|
|
916
916
|
draw(ctx, x = 0, y = 0) {
|
|
917
917
|
const app = ctx.window.app;
|
|
918
918
|
const Render = app.display.Render;
|
|
919
|
+
// The context's composite op, as `fillText` takes it. A context that is
|
|
920
|
+
// not ntk's has none to give, and its text goes source-over.
|
|
921
|
+
const op = ctx._op?.() ?? Render.PictOp.Over;
|
|
919
922
|
// One batch for the whole layout, not one per line: every positioned
|
|
920
923
|
// run carries its own absolute baseline, so a uniformly coloured
|
|
921
924
|
// paragraph becomes a single glyph composite instead of one per line.
|
|
@@ -929,9 +932,9 @@ export class TextLayout {
|
|
|
929
932
|
// (drawGlyphRuns composites straight onto the picture and can see
|
|
930
933
|
// neither)
|
|
931
934
|
if (typeof ctx.drawGlyphs === 'function') {
|
|
932
|
-
ctx.drawGlyphs(
|
|
935
|
+
ctx.drawGlyphs(op, src, batch);
|
|
933
936
|
} else {
|
|
934
|
-
drawGlyphRuns(app,
|
|
937
|
+
drawGlyphRuns(app, op, src.id, ctx.picture.id, batch);
|
|
935
938
|
}
|
|
936
939
|
batch = [];
|
|
937
940
|
};
|
|
@@ -942,7 +945,7 @@ export class TextLayout {
|
|
|
942
945
|
// height and its font's extent again on either side for ink that
|
|
943
946
|
// overhangs the box, is outside the rows the clip lets through: nothing
|
|
944
947
|
// of it could have landed on a pixel.
|
|
945
|
-
const rows = visibleRows(ctx, y);
|
|
948
|
+
const rows = visibleRows(ctx, y, op);
|
|
946
949
|
for (const line of this.lines) {
|
|
947
950
|
if (rows) {
|
|
948
951
|
const reach = line.height + line.ascent + line.descent;
|
|
@@ -1493,12 +1496,15 @@ function sliceLevels(key, start, end) {
|
|
|
1493
1496
|
* The rows of a layout drawn at `y` that the context can show — its clip's
|
|
1494
1497
|
* box, or the whole surface with no clip — in the layout's own coordinates.
|
|
1495
1498
|
* Null where that cannot be said, and the whole layout is drawn: a context
|
|
1496
|
-
* that is not ntk's, a transform that is more than a translation,
|
|
1497
|
-
* shadow, which lands where the text is not
|
|
1499
|
+
* that is not ntk's, a transform that is more than a translation, a
|
|
1500
|
+
* shadow, which lands where the text is not, or an `op` like `copy` that
|
|
1501
|
+
* clears the box round all the glyphs drawn — a line the clip hides still
|
|
1502
|
+
* widens that box along the rows it does not hide.
|
|
1498
1503
|
*/
|
|
1499
|
-
function visibleRows(ctx, y) {
|
|
1504
|
+
function visibleRows(ctx, y, op) {
|
|
1500
1505
|
if (typeof ctx.drawGlyphs !== 'function') return null;
|
|
1501
1506
|
if (typeof ctx._clipExtents !== 'function') return null;
|
|
1507
|
+
if (typeof ctx._maskBounded === 'function' && !ctx._maskBounded(op)) return null;
|
|
1502
1508
|
const m = ctx._m;
|
|
1503
1509
|
if (!m || m[0] !== 1 || m[1] !== 0 || m[2] !== 0 || m[3] !== 1) return null;
|
|
1504
1510
|
if (typeof ctx._shadowed !== 'function' || ctx._shadowed()) return null;
|
package/lib/trapezoid.js
CHANGED
|
@@ -37,7 +37,7 @@ export function trapezoidize(polys, dx = 0, dy = 0, out = [], rule = 'nonzero')
|
|
|
37
37
|
const evenodd = rule === 'evenodd';
|
|
38
38
|
// collect non-horizontal edges, normalized to y0 < y1 with winding dir
|
|
39
39
|
const edges = [];
|
|
40
|
-
|
|
40
|
+
let ys = [];
|
|
41
41
|
// the largest coordinate magnitude, which sets the rounding noise
|
|
42
42
|
let reach = 1;
|
|
43
43
|
for (const poly of polys) {
|
|
@@ -66,14 +66,17 @@ export function trapezoidize(polys, dx = 0, dy = 0, out = [], rule = 'nonzero')
|
|
|
66
66
|
// coordinate that fits one
|
|
67
67
|
const eps = reach * 2 ** -44;
|
|
68
68
|
|
|
69
|
-
|
|
69
|
+
// a typed array sorts numerically without a comparator call per step
|
|
70
|
+
ys = Float64Array.from(ys).sort();
|
|
70
71
|
edges.sort((a, b) => a.y0 - b.y0);
|
|
71
72
|
|
|
72
73
|
const active = [];
|
|
73
74
|
let nextEdge = 0;
|
|
74
|
-
// spans open from the previous slab, for vertical merging
|
|
75
|
-
//
|
|
75
|
+
// spans open from the previous slab, for vertical merging, and the ones
|
|
76
|
+
// this slab opens: flat [leftEdge, rightEdge, index of the trap in `out`]
|
|
77
|
+
// triples, two buffers that trade places every slab
|
|
76
78
|
let prevSpans = [];
|
|
79
|
+
let spans = [];
|
|
77
80
|
|
|
78
81
|
for (let yi = 0; yi < ys.length - 1; ++yi) {
|
|
79
82
|
const ya = ys[yi];
|
|
@@ -81,25 +84,38 @@ export function trapezoidize(polys, dx = 0, dy = 0, out = [], rule = 'nonzero')
|
|
|
81
84
|
if (yb === ya) continue;
|
|
82
85
|
|
|
83
86
|
// update the active edge list for this slab
|
|
84
|
-
|
|
85
|
-
|
|
87
|
+
let kept = 0;
|
|
88
|
+
for (let i = 0; i < active.length; ++i) {
|
|
89
|
+
if (active[i].y1 > ya) active[kept++] = active[i];
|
|
86
90
|
}
|
|
91
|
+
active.length = kept;
|
|
87
92
|
while (nextEdge < edges.length && edges[nextEdge].y0 <= ya) {
|
|
88
93
|
if (edges[nextEdge].y1 > ya) active.push(edges[nextEdge]);
|
|
89
94
|
nextEdge++;
|
|
90
95
|
}
|
|
91
96
|
if (active.length === 0) {
|
|
92
|
-
prevSpans =
|
|
97
|
+
prevSpans.length = 0;
|
|
93
98
|
continue;
|
|
94
99
|
}
|
|
95
100
|
|
|
96
101
|
// x at slab top/bottom for each active edge; sort left-to-right just
|
|
97
102
|
// below the top (edges leaving one point go by where they are headed)
|
|
98
|
-
for (
|
|
103
|
+
for (let i = 0; i < active.length; ++i) {
|
|
104
|
+
const e = active[i];
|
|
99
105
|
e.xt = e.x0 + (ya - e.y0) * e.slope;
|
|
100
106
|
e.xb = e.x0 + (yb - e.y0) * e.slope;
|
|
101
107
|
}
|
|
102
|
-
|
|
108
|
+
// a slab holds a handful of edges, mostly in last slab's order: an
|
|
109
|
+
// insertion sort, stable as Array#sort is, so the order is the same
|
|
110
|
+
for (let i = 1; i < active.length; ++i) {
|
|
111
|
+
const e = active[i];
|
|
112
|
+
let j = i - 1;
|
|
113
|
+
while (j >= 0 && (active[j].xt - e.xt || active[j].xb - e.xb) > 0) {
|
|
114
|
+
active[j + 1] = active[j];
|
|
115
|
+
--j;
|
|
116
|
+
}
|
|
117
|
+
active[j + 1] = e;
|
|
118
|
+
}
|
|
103
119
|
|
|
104
120
|
// cut the slab at each crossing, top to bottom; `y` is where the part
|
|
105
121
|
// still to emit starts, and `xt` each edge's x there
|
|
@@ -121,13 +137,18 @@ export function trapezoidize(polys, dx = 0, dy = 0, out = [], rule = 'nonzero')
|
|
|
121
137
|
}
|
|
122
138
|
}
|
|
123
139
|
if (k < 0 || yc >= yb - eps) {
|
|
124
|
-
|
|
140
|
+
emitSpans(active, y, yb, true, evenodd, prevSpans, spans, out);
|
|
141
|
+
[prevSpans, spans] = [spans, prevSpans];
|
|
125
142
|
break;
|
|
126
143
|
}
|
|
127
144
|
if (yc > y + eps) {
|
|
128
|
-
for (
|
|
129
|
-
|
|
130
|
-
|
|
145
|
+
for (let i = 0; i < active.length; ++i) {
|
|
146
|
+
const e = active[i];
|
|
147
|
+
e.xc = e.x0 + (yc - e.y0) * e.slope;
|
|
148
|
+
}
|
|
149
|
+
emitSpans(active, y, yc, false, evenodd, prevSpans, spans, out);
|
|
150
|
+
[prevSpans, spans] = [spans, prevSpans];
|
|
151
|
+
for (let i = 0; i < active.length; ++i) active[i].xt = active[i].xc;
|
|
131
152
|
y = yc;
|
|
132
153
|
}
|
|
133
154
|
const a = active[k];
|
|
@@ -144,15 +165,16 @@ export function trapezoidize(polys, dx = 0, dy = 0, out = [], rule = 'nonzero')
|
|
|
144
165
|
* parity for even-odd). Each edge is at `xt` at the top, and at the bottom
|
|
145
166
|
* at `xb` when this is the `last` part of its vertex slab, else at `xc`. A
|
|
146
167
|
* span bounded by the same two edges as one ending at `ya` extends that
|
|
147
|
-
* trapezoid instead.
|
|
148
|
-
*
|
|
168
|
+
* trapezoid instead. The spans go into `spans`, emptied first, for the next
|
|
169
|
+
* slab to merge with.
|
|
149
170
|
*/
|
|
150
|
-
function emitSpans(active, ya, yb, last, evenodd, prevSpans, out) {
|
|
151
|
-
|
|
171
|
+
function emitSpans(active, ya, yb, last, evenodd, prevSpans, spans, out) {
|
|
172
|
+
spans.length = 0;
|
|
152
173
|
let winding = 0;
|
|
153
174
|
let crossings = 0;
|
|
154
175
|
let left = null;
|
|
155
|
-
for (
|
|
176
|
+
for (let i = 0; i < active.length; ++i) {
|
|
177
|
+
const e = active[i];
|
|
156
178
|
const wasInside = evenodd ? (crossings & 1) === 1 : winding !== 0;
|
|
157
179
|
winding += e.dir;
|
|
158
180
|
crossings++;
|
|
@@ -164,24 +186,24 @@ function emitSpans(active, ya, yb, last, evenodd, prevSpans, out) {
|
|
|
164
186
|
const rb = last ? e.xb : e.xc;
|
|
165
187
|
// merge with a span from the previous slab bounded by the same edges
|
|
166
188
|
let merged = false;
|
|
167
|
-
for (
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
out[
|
|
171
|
-
out[
|
|
172
|
-
|
|
189
|
+
for (let p = 0; p < prevSpans.length; p += 3) {
|
|
190
|
+
const at = prevSpans[p + 2];
|
|
191
|
+
if (prevSpans[p] === left && prevSpans[p + 1] === e && out[at + 5] === ya) {
|
|
192
|
+
out[at + 3] = lb;
|
|
193
|
+
out[at + 4] = rb;
|
|
194
|
+
out[at + 5] = yb;
|
|
195
|
+
spans.push(left, e, at);
|
|
173
196
|
merged = true;
|
|
174
197
|
break;
|
|
175
198
|
}
|
|
176
199
|
}
|
|
177
200
|
if (!merged && (e.xt > left.xt || rb > lb)) {
|
|
178
|
-
spans.push(
|
|
201
|
+
spans.push(left, e, out.length);
|
|
179
202
|
out.push(left.xt, e.xt, ya, lb, rb, yb);
|
|
180
203
|
}
|
|
181
204
|
left = null;
|
|
182
205
|
}
|
|
183
206
|
}
|
|
184
|
-
return spans;
|
|
185
207
|
}
|
|
186
208
|
|
|
187
209
|
/** area covered by a trapezoid list (useful for tests/diagnostics) */
|