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.
@@ -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(Render.PictOp.Over, src, batch);
935
+ ctx.drawGlyphs(op, src, batch);
933
936
  } else {
934
- drawGlyphRuns(app, Render.PictOp.Over, src.id, ctx.picture.id, batch);
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, or a
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
- const ys = [];
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
- ys.sort((a, b) => a - b);
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
- // { l: leftEdge, r: rightEdge, at: index of the trap in `out` }
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
- for (let i = active.length - 1; i >= 0; --i) {
85
- if (active[i].y1 <= ya) active.splice(i, 1);
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 (const e of active) {
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
- active.sort((a, b) => a.xt - b.xt || a.xb - b.xb);
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
- prevSpans = emitSpans(active, y, yb, true, evenodd, prevSpans, out);
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 (const e of active) e.xc = e.x0 + (yc - e.y0) * e.slope;
129
- prevSpans = emitSpans(active, y, yc, false, evenodd, prevSpans, out);
130
- for (const e of active) e.xt = e.xc;
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
- * @returns the spans, for the next slab to merge with
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
- const spans = [];
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 (const e of active) {
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 (const p of prevSpans) {
168
- if (p.l === left && p.r === e && out[p.at + 5] === ya) {
169
- out[p.at + 3] = lb;
170
- out[p.at + 4] = rb;
171
- out[p.at + 5] = yb;
172
- spans.push(p);
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({ l: left, r: e, at: out.length });
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) */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ntk",
3
- "version": "8.18.0",
3
+ "version": "8.19.0",
4
4
  "description": "Desktop UI toolkit for X11 with canvas-like 2d and OpenGL rendering",
5
5
  "author": "Andrey Sidorov <sidorares@yandex.ru>",
6
6
  "license": "MIT",