reladraw 0.21.0 → 0.22.1

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/dist/search.js DELETED
@@ -1,490 +0,0 @@
1
- /**
2
- * Finding a line's way through the open space between boxes.
3
- *
4
- * Every box is a wall. A line may turn only where two tracks cross, and the
5
- * tracks are few: a line a little way out from each side of each wall, one
6
- * down the middle of each gap between walls, and the lines through the two
7
- * ends. That is the whole of the open space a right-angled line ever needs,
8
- * reduced to a grid of a few hundred points for a diagram of ordinary size.
9
- *
10
- * The line leaves its start heading straight out and arrives at its end heading
11
- * straight in, with a straight stretch at each end at least `stub` long, so an
12
- * arrowhead always lands on a straight piece. Between those two stretches it
13
- * takes the cheapest way through the grid, where cost is length plus a charge
14
- * for every turn and for every line already placed that it crosses. On a
15
- * right-angled grid many ways tie on length — a staircase is exactly as long as
16
- * one bend — and the turn charge breaks those ties toward the fewest bends,
17
- * which is the line a person would draw. What still ties after that goes over
18
- * the top rather than under, and round the right rather than the left.
19
- *
20
- * A way through a box is not a losing option here. It is not a way at all.
21
- */
22
- /**
23
- * The cheapest right-angled line from `start` to `end` that enters no wall, or
24
- * undefined when there is none.
25
- */
26
- export function searchRoute(start, end, walls, options) {
27
- const { clear, turn } = options;
28
- // The stretches at the ends are fixed. Each crosses its own box's margin and
29
- // runs `stub` further, or, where another box is closer than that, stops in
30
- // the middle of the gap between the two margins: a node placed a `tight` gap
31
- // away still leaves room to get out.
32
- const others = (own) => walls.filter((wall) => !own.includes(wall));
33
- const reach = (from, own) => {
34
- const exit = Math.max(0, ...own.map((wall) => depth(from, wall)));
35
- const wanted = Math.max(options.stub, exit + 1);
36
- const hit = Math.min(Infinity, ...others(own).map((wall) => distanceTo(from, wall, wanted)));
37
- const length = hit >= wanted ? wanted : (exit + hit) / 2;
38
- if (length <= exit)
39
- return undefined;
40
- return { x: snap(from.x + from.dx * length), y: snap(from.y + from.dy * length) };
41
- };
42
- const out = reach(start, options.ownStart);
43
- const back = reach(end, options.ownEnd);
44
- if (!out || !back)
45
- return undefined;
46
- if (inside(out, walls) || inside(back, walls))
47
- return undefined;
48
- // No way between the two is shorter than straight across and straight down.
49
- const limit = options.limit ?? Infinity;
50
- if (Math.abs(back.x - out.x) + Math.abs(back.y - out.y) - 1e-6 >= limit)
51
- return undefined;
52
- const bounds = options.bounds;
53
- const gate = options.gate;
54
- // How far a stretch keeps from a placed one beside it: a lane, or clear of
55
- // the text either of them carries there.
56
- const halves = options.takenHalves ?? [];
57
- const ownHalf = options.ownHalf ?? 0;
58
- const inGate = (a, b) => {
59
- if (!gate)
60
- return false;
61
- const along = gate.run === 'x' ? Math.abs(a.y - b.y) < 0.5 : Math.abs(a.x - b.x) < 0.5;
62
- const level = gate.run === 'x' ? a.y : a.x;
63
- return along && level > gate.lo && level < gate.hi;
64
- };
65
- const spacing = (line, stretch, own) => Math.max(options.lane, (halves[line]?.[stretch] ?? 0) + own);
66
- const gateTracks = (axis) => !gate ? [] : axis === gate.run ? [gate.at] : [(gate.lo + gate.hi) / 2];
67
- const xs = tracks(walls, 'x', clear, [out.x, back.x, ...gateTracks('x')], options.taken, spacing, ownHalf)
68
- .filter((x) => !bounds || (x >= bounds.minX && x <= bounds.maxX) || x === out.x || x === back.x);
69
- const ys = tracks(walls, 'y', clear, [out.y, back.y, ...gateTracks('y')], options.taken, spacing, ownHalf)
70
- .filter((y) => !bounds || (y >= bounds.minY && y <= bounds.maxY) || y === out.y || y === back.y);
71
- const ix = new Map(xs.map((x, index) => [x, index]));
72
- const iy = new Map(ys.map((y, index) => [y, index]));
73
- const width = xs.length;
74
- const height = ys.length;
75
- const steps = stepTable(xs, ys, walls, clear, options.taken, spacing, ownHalf, (a, b) => (inGate(a, b) ? ownHalf : 0));
76
- const open = (i, j) => steps.open[j * width + i] === 1;
77
- // Four headings: 0 right, 1 down, 2 left, 3 up.
78
- const STEP = [
79
- [1, 0],
80
- [0, 1],
81
- [-1, 0],
82
- [0, -1],
83
- ];
84
- const heading = (dx, dy) => (dx > 0 ? 0 : dy > 0 ? 1 : dx < 0 ? 2 : 3);
85
- const first = heading(start.dx, start.dy);
86
- // The last stretch travels into the end, against the way its side faces.
87
- const last = heading(-end.dx, -end.dy);
88
- const si = ix.get(out.x);
89
- const sj = iy.get(out.y);
90
- const ti = ix.get(back.x);
91
- const tj = iy.get(back.y);
92
- // Cost is compared first on length and turns, then on a tie-break that
93
- // prefers a stretch higher up the page, or further right, so that of two
94
- // ways round that are exactly as long the line goes over the top. A state
95
- // also records whether the line has passed its gate yet; it may only finish
96
- // once it has.
97
- const size = width * height * 8;
98
- const cost = new Float64Array(size).fill(Infinity);
99
- const tie = new Float64Array(size).fill(Infinity);
100
- const from = new Int32Array(size).fill(-1);
101
- const state = (i, j, h, p) => ((j * width + i) * 4 + h) * 2 + p;
102
- const done = gate ? 1 : 0;
103
- // The search reaches out toward the end first: a state waits by what it has
104
- // cost so far plus the least the rest could cost, straight across and
105
- // straight down to the end. It does not settle each state once, since a
106
- // way just as long can arrive later with a better tie-break; it keeps on
107
- // until nothing waiting could come in as cheap as the end already has, and
108
- // what it finds is exactly what searching every direction alike would.
109
- // Until the line has passed its gate, that is by way of the gate.
110
- const toGo = (current) => {
111
- const cell = (current - (current % 8)) / 8;
112
- const i = cell % width;
113
- const point = { x: xs[i], y: ys[(cell - i) / width] };
114
- return leastLength(point, back, current % 2 === 1 ? undefined : gate);
115
- };
116
- const heap = new MinHeap();
117
- const begin = state(si, sj, first, 0);
118
- const goal = state(ti, tj, last, done);
119
- cost[begin] = 0;
120
- tie[begin] = 0;
121
- heap.push(begin, toGo(begin), 0);
122
- while (heap.size > 0) {
123
- const [current, f, t] = heap.pop();
124
- const c = cost[current];
125
- const due = c + toGo(current);
126
- if (f > due || (f === due && t > tie[current]))
127
- continue;
128
- // Everything still waiting costs at least this much.
129
- if (f >= limit || f > cost[goal] + 1e-9)
130
- break;
131
- const p = current % 2;
132
- const rest = (current - p) / 2;
133
- const h = rest % 4;
134
- const cell = (rest - h) / 4;
135
- const i = cell % width;
136
- const j = (cell - i) / width;
137
- if (current === goal)
138
- continue;
139
- for (let next = 0; next < 4; next += 1) {
140
- // A line never doubles straight back on itself.
141
- if ((next + 2) % 4 === h)
142
- continue;
143
- if (next !== h) {
144
- // Turning on the spot costs a turn and moves nowhere.
145
- relax(current, state(i, j, next, p), c + turn, t);
146
- continue;
147
- }
148
- const ni = i + STEP[next][0];
149
- const nj = j + STEP[next][1];
150
- if (ni < 0 || nj < 0 || ni >= width || nj >= height || !open(ni, nj))
151
- continue;
152
- // A step is filed under the point it starts from on its way right or down.
153
- const axis = next % 2;
154
- const key = next < 2 ? j * width + i : nj * width + ni;
155
- if (steps.closed[axis][key] === 1)
156
- continue;
157
- const a = { x: xs[i], y: ys[j] };
158
- const b = { x: xs[ni], y: ys[nj] };
159
- // Passing the gate: a stretch through it, reaching the line at `at`.
160
- const through = inGate(a, b) &&
161
- Math.min(gate.run === 'x' ? a.x : a.y, gate.run === 'x' ? b.x : b.y) <= gate.at + 0.01 &&
162
- Math.max(gate.run === 'x' ? a.x : a.y, gate.run === 'x' ? b.x : b.y) >= gate.at - 0.01;
163
- const np = p === 1 || through ? 1 : 0;
164
- // Running closer to a wall than `clear` counts double: the line may pass
165
- // a narrow gap, but where there is room it keeps its distance. Crossing a
166
- // line already placed costs what a turn does, so of two ways much the
167
- // same length the one that crosses nothing wins.
168
- const length = Math.abs(b.x - a.x) + Math.abs(b.y - a.y) + steps.crowd[axis][key] +
169
- turn * steps.cross[axis][key];
170
- // Higher up for a stretch across, further right for one down: a smaller
171
- // tie-break is preferred, so the across level counts as it is and the
172
- // down level counts negated.
173
- const lean = next % 2 === 0 ? a.y * length : -a.x * length;
174
- relax(current, state(ni, nj, next, np), c + length, t + lean);
175
- }
176
- }
177
- if (!(cost[goal] < limit))
178
- return undefined;
179
- function relax(previous, next, c, t) {
180
- if (c < cost[next] - 1e-9 || (Math.abs(c - cost[next]) <= 1e-9 && t < tie[next])) {
181
- cost[next] = c;
182
- tie[next] = t;
183
- from[next] = previous;
184
- heap.push(next, c + toGo(next), t);
185
- }
186
- }
187
- const path = [];
188
- for (let at = goal; at >= 0; at = from[at]) {
189
- const rest = (at - (at % 2)) / 2;
190
- const cell = (rest - (rest % 4)) / 4;
191
- const i = cell % width;
192
- const j = (cell - i) / width;
193
- const point = { x: xs[i], y: ys[j] };
194
- const previous = path[path.length - 1];
195
- if (!previous || previous.x !== point.x || previous.y !== point.y)
196
- path.push(point);
197
- }
198
- path.reverse();
199
- return { points: straightRuns([start, ...path, end]), cost: cost[goal] };
200
- }
201
- /**
202
- * The shortest a right-angled line from `a` to `b` could be: straight across
203
- * and straight down, or with a gate, across to its line at `at` and to a
204
- * level within it on the way.
205
- */
206
- function leastLength(a, b, gate) {
207
- if (!gate)
208
- return Math.abs(a.x - b.x) + Math.abs(a.y - b.y);
209
- const [u, v, bu, bv] = gate.run === 'x' ? [a.x, a.y, b.x, b.y] : [a.y, a.x, b.y, b.x];
210
- const off = Math.max(0, gate.lo - Math.max(v, bv), Math.min(v, bv) - gate.hi);
211
- return Math.abs(u - gate.at) + Math.abs(gate.at - bu) + Math.abs(v - bv) + 2 * off;
212
- }
213
- /**
214
- * The tracks on one axis: beside each wall at `clear` where that is open,
215
- * down the middle of every gap between walls, through the ends, and a lane
216
- * either side of each line already placed — a wider one where a text rides,
217
- * and wider again by `own` for this line's own text.
218
- */
219
- function tracks(walls, axis, clear, ends, taken, spacing, own) {
220
- const lo = (wall) => (axis === 'x' ? wall.minX : wall.minY);
221
- const hi = (wall) => (axis === 'x' ? wall.maxX : wall.maxY);
222
- // The other axis, to tell a real gap — two walls facing each other across
223
- // it — from two edges that merely happen to lie near each other on this one.
224
- const olo = (wall) => (axis === 'x' ? wall.minY : wall.minX);
225
- const ohi = (wall) => (axis === 'x' ? wall.maxY : wall.maxX);
226
- const found = new Set(ends);
227
- for (const wall of walls) {
228
- found.add(lo(wall) - clear);
229
- found.add(hi(wall) + clear);
230
- }
231
- for (const a of walls) {
232
- for (const b of walls) {
233
- if (hi(a) < lo(b) && olo(a) < ohi(b) && olo(b) < ohi(a))
234
- found.add((hi(a) + lo(b)) / 2);
235
- }
236
- }
237
- taken.forEach((line, which) => {
238
- for (let index = 0; index + 1 < line.length; index += 1) {
239
- const [a, b] = [line[index], line[index + 1]];
240
- // A stretch across sits at a level on y; one down, at a level on x.
241
- const across = Math.abs(a.y - b.y) < 0.5;
242
- if ((axis === 'y') !== across)
243
- continue;
244
- // A lane either side of a placed line. One that runs close beside a box
245
- // is paid for as crowding, like any other track.
246
- const level = axis === 'y' ? a.y : a.x;
247
- for (const by of new Set([spacing(which, index, 0), spacing(which, index, own)])) {
248
- found.add(level - by);
249
- found.add(level + by);
250
- }
251
- }
252
- });
253
- return [...new Set([...found].map(snap))].sort((p, q) => p - q);
254
- }
255
- /** Coordinates to a hundredth, so a track and the end it runs through compare equal. */
256
- function snap(value) {
257
- return Math.round(value * 100) / 100;
258
- }
259
- /** How far along its heading an end runs before it leaves a wall it starts inside; 0 if it is outside. */
260
- function depth(from, wall) {
261
- if (!(from.x >= wall.minX && from.x <= wall.maxX && from.y >= wall.minY && from.y <= wall.maxY))
262
- return 0;
263
- if (from.dx > 0)
264
- return wall.maxX - from.x;
265
- if (from.dx < 0)
266
- return from.x - wall.minX;
267
- if (from.dy > 0)
268
- return wall.maxY - from.y;
269
- return from.y - wall.minY;
270
- }
271
- /**
272
- * How far along its heading an end runs before it meets a wall it is outside
273
- * of, looking no further than `limit`; Infinity if it meets none.
274
- */
275
- function distanceTo(from, wall, limit) {
276
- const tip = { x: from.x + from.dx * limit, y: from.y + from.dy * limit };
277
- if (!blocked(from, tip, [wall]))
278
- return Infinity;
279
- if (from.dx > 0)
280
- return wall.minX - from.x;
281
- if (from.dx < 0)
282
- return from.x - wall.maxX;
283
- if (from.dy > 0)
284
- return wall.minY - from.y;
285
- return from.y - wall.maxY;
286
- }
287
- /** Whether a point lies strictly inside any wall. */
288
- function inside(point, walls) {
289
- return walls.some((wall) => point.x > wall.minX && point.x < wall.maxX && point.y > wall.minY && point.y < wall.maxY);
290
- }
291
- /** Whether a level or upright stretch passes through the inside of any wall. */
292
- function blocked(a, b, walls) {
293
- return walls.some((wall) => blocks(a, b, wall));
294
- }
295
- /** Whether a level or upright stretch passes through the inside of one wall. */
296
- function blocks(a, b, wall) {
297
- const minX = Math.min(a.x, b.x);
298
- const maxX = Math.max(a.x, b.x);
299
- const minY = Math.min(a.y, b.y);
300
- const maxY = Math.max(a.y, b.y);
301
- if (minY === maxY)
302
- return minY > wall.minY && minY < wall.maxY && maxX > wall.minX && minX < wall.maxX;
303
- return minX > wall.minX && minX < wall.maxX && maxY > wall.minY && minY < wall.maxY;
304
- }
305
- /**
306
- * Every single step through the grid — from a point to its neighbor on the
307
- * right, or the one below — and what it costs beyond its length: whether it is
308
- * closed, by a wall or by running along a placed line; how far it runs close
309
- * beside a wall; how many placed lines it crosses. The search takes each step
310
- * many times over, once for each heading it arrives with, so these are worked
311
- * out here once, and each wall and each placed stretch is tested only against
312
- * the steps that lie near it. A step is filed at the index of the point it
313
- * starts from: `[0]` the step right, `[1]` the step down.
314
- */
315
- function stepTable(xs, ys, walls, clear, taken, spacing, widest, ownAt) {
316
- const width = xs.length;
317
- const height = ys.length;
318
- const cells = width * height;
319
- const open = new Uint8Array(cells).fill(1);
320
- const closed = [new Uint8Array(cells), new Uint8Array(cells)];
321
- const crowd = [new Float64Array(cells), new Float64Array(cells)];
322
- const cross = [new Int32Array(cells), new Int32Array(cells)];
323
- // The indices of the tracks lying within `lo`..`hi`, with one to spare either
324
- // side: a range to test exactly, never a test itself.
325
- const near = (values, lo, hi) => [Math.max(0, firstAtLeast(values, lo) - 1), Math.min(values.length - 1, firstAtLeast(values, hi) + 1)];
326
- const each = ([i0, i1], [j0, j1], visit) => {
327
- for (let j = j0; j <= j1; j += 1) {
328
- for (let i = i0; i <= i1; i += 1) {
329
- const a = { x: xs[i], y: ys[j] };
330
- if (i + 1 < width)
331
- visit(0, a, { x: xs[i + 1], y: ys[j] }, j * width + i);
332
- if (j + 1 < height)
333
- visit(1, a, { x: xs[i], y: ys[j + 1] }, j * width + i);
334
- }
335
- }
336
- };
337
- for (const wall of walls) {
338
- const [i0, i1] = near(xs, wall.minX, wall.maxX);
339
- const [j0, j1] = near(ys, wall.minY, wall.maxY);
340
- for (let j = j0; j <= j1; j += 1) {
341
- for (let i = i0; i <= i1; i += 1) {
342
- const x = xs[i];
343
- const y = ys[j];
344
- if (x > wall.minX && x < wall.maxX && y > wall.minY && y < wall.maxY)
345
- open[j * width + i] = 0;
346
- }
347
- }
348
- each([i0, i1], [j0, j1], (axis, a, b, key) => {
349
- if (blocks(a, b, wall))
350
- closed[axis][key] = 1;
351
- });
352
- each(near(xs, wall.minX - clear, wall.maxX + clear), near(ys, wall.minY - clear, wall.maxY + clear), (axis, a, b, key) => {
353
- crowd[axis][key] = Math.max(crowd[axis][key], crowding(a, b, wall, clear));
354
- });
355
- }
356
- taken.forEach((line, which) => {
357
- for (let index = 0; index + 1 < line.length; index += 1) {
358
- const [p, q] = [line[index], line[index + 1]];
359
- const reach = spacing(which, index, widest);
360
- const xr = near(xs, Math.min(p.x, q.x) - reach, Math.max(p.x, q.x) + reach);
361
- const yr = near(ys, Math.min(p.y, q.y) - reach, Math.max(p.y, q.y) + reach);
362
- each(xr, yr, (axis, a, b, key) => {
363
- if (runsAlong(a, b, p, q, spacing(which, index, ownAt(a, b))))
364
- closed[axis][key] = 1;
365
- if (crosses(a, b, p, q))
366
- cross[axis][key] = cross[axis][key] + 1;
367
- });
368
- }
369
- });
370
- return { open, closed, crowd, cross };
371
- }
372
- /** The index of the first of the sorted values at least `value`, or their count if none is. */
373
- function firstAtLeast(values, value) {
374
- let lo = 0;
375
- let hi = values.length;
376
- while (lo < hi) {
377
- const mid = (lo + hi) >> 1;
378
- if (values[mid] < value)
379
- lo = mid + 1;
380
- else
381
- hi = mid;
382
- }
383
- return lo;
384
- }
385
- /** How much of a stretch runs within `clear` of one wall's side, alongside it; 0 if none does. */
386
- function crowding(a, b, wall, clear) {
387
- const across = Math.abs(a.y - b.y) < 0.5;
388
- const level = across ? a.y : a.x;
389
- const from = across ? Math.min(a.x, b.x) : Math.min(a.y, b.y);
390
- const to = across ? Math.max(a.x, b.x) : Math.max(a.y, b.y);
391
- const [lo, hi] = across ? [wall.minY, wall.maxY] : [wall.minX, wall.maxX];
392
- const [alo, ahi] = across ? [wall.minX, wall.maxX] : [wall.minY, wall.maxY];
393
- const near = (level > lo - clear + 0.01 && level <= lo) || (level >= hi && level < hi + clear - 0.01);
394
- return near ? Math.max(0, Math.min(to, ahi) - Math.max(from, alo)) : 0;
395
- }
396
- /**
397
- * Whether a stretch runs along one stretch of a placed line, for any distance,
398
- * closer than `spacing`: a lane, or clear of a text either carries.
399
- */
400
- function runsAlong(a, b, p, q, spacing) {
401
- const across = Math.abs(a.y - b.y) < 0.5;
402
- if ((Math.abs(p.y - q.y) < 0.5) !== across)
403
- return false;
404
- const level = across ? a.y : a.x;
405
- const other = across ? p.y : p.x;
406
- if (Math.abs(other - level) >= spacing - 0.5)
407
- return false;
408
- const lo = across ? Math.min(a.x, b.x) : Math.min(a.y, b.y);
409
- const hi = across ? Math.max(a.x, b.x) : Math.max(a.y, b.y);
410
- const pLo = across ? Math.min(p.x, q.x) : Math.min(p.y, q.y);
411
- const pHi = across ? Math.max(p.x, q.x) : Math.max(p.y, q.y);
412
- return Math.min(hi, pHi) - Math.max(lo, pLo) > 0.5;
413
- }
414
- /** Whether a stretch crosses one stretch of a placed line square on, touching neither end of either. */
415
- function crosses(a, b, p, q) {
416
- const across = Math.abs(a.y - b.y) < 0.5;
417
- if ((Math.abs(p.y - q.y) < 0.5) === across)
418
- return false;
419
- const level = across ? a.y : a.x;
420
- const lo = across ? Math.min(a.x, b.x) : Math.min(a.y, b.y);
421
- const hi = across ? Math.max(a.x, b.x) : Math.max(a.y, b.y);
422
- const at = across ? p.x : p.y;
423
- const pLo = across ? Math.min(p.y, q.y) : Math.min(p.x, q.x);
424
- const pHi = across ? Math.max(p.y, q.y) : Math.max(p.x, q.x);
425
- return at > lo + 0.5 && at < hi - 0.5 && level > pLo + 0.5 && level < pHi - 0.5;
426
- }
427
- /** The points with every one dropped that sits on a straight run between its neighbors. */
428
- function straightRuns(points) {
429
- const kept = [];
430
- for (const point of points) {
431
- const previous = kept[kept.length - 1];
432
- if (previous && Math.abs(previous.x - point.x) < 0.01 && Math.abs(previous.y - point.y) < 0.01)
433
- continue;
434
- kept.push(point);
435
- while (kept.length >= 3) {
436
- const [a, b, c] = kept.slice(-3);
437
- const level = Math.abs(a.y - b.y) < 0.01 && Math.abs(b.y - c.y) < 0.01;
438
- const upright = Math.abs(a.x - b.x) < 0.01 && Math.abs(b.x - c.x) < 0.01;
439
- if (!level && !upright)
440
- break;
441
- kept.splice(kept.length - 2, 1);
442
- }
443
- }
444
- return kept;
445
- }
446
- /** A binary heap of states, cheapest first, ties broken by the second cost. */
447
- class MinHeap {
448
- items = [];
449
- get size() {
450
- return this.items.length;
451
- }
452
- push(state, cost, tie) {
453
- const items = this.items;
454
- items.push([state, cost, tie]);
455
- let at = items.length - 1;
456
- while (at > 0) {
457
- const parent = (at - 1) >> 1;
458
- if (!before(items[at], items[parent]))
459
- break;
460
- [items[at], items[parent]] = [items[parent], items[at]];
461
- at = parent;
462
- }
463
- }
464
- pop() {
465
- const items = this.items;
466
- const top = items[0];
467
- const tail = items.pop();
468
- if (items.length > 0) {
469
- items[0] = tail;
470
- let at = 0;
471
- for (;;) {
472
- const left = at * 2 + 1;
473
- const right = left + 1;
474
- let best = at;
475
- if (left < items.length && before(items[left], items[best]))
476
- best = left;
477
- if (right < items.length && before(items[right], items[best]))
478
- best = right;
479
- if (best === at)
480
- break;
481
- [items[at], items[best]] = [items[best], items[at]];
482
- at = best;
483
- }
484
- }
485
- return top;
486
- }
487
- }
488
- function before(p, q) {
489
- return p[1] < q[1] - 1e-9 || (Math.abs(p[1] - q[1]) <= 1e-9 && p[2] < q[2]);
490
- }