@react-x11/components 0.4.0 → 0.5.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/README.md +125 -4
- package/dist/charts/components.d.ts +3 -1
- package/dist/charts/components.d.ts.map +1 -1
- package/dist/charts/components.js +8 -4
- package/dist/charts/components.js.map +1 -1
- package/dist/charts/node.d.ts +13 -6
- package/dist/charts/node.d.ts.map +1 -1
- package/dist/charts/node.js +75 -24
- package/dist/charts/node.js.map +1 -1
- package/dist/charts/render.d.ts +4 -0
- package/dist/charts/render.d.ts.map +1 -1
- package/dist/charts/render.js +11 -6
- package/dist/charts/render.js.map +1 -1
- package/dist/code-editor/node.d.ts +47 -16
- package/dist/code-editor/node.d.ts.map +1 -1
- package/dist/code-editor/node.js +121 -51
- package/dist/code-editor/node.js.map +1 -1
- package/dist/flow/draw.d.ts +19 -1
- package/dist/flow/draw.d.ts.map +1 -1
- package/dist/flow/draw.js +64 -35
- package/dist/flow/draw.js.map +1 -1
- package/dist/flow/index.d.ts.map +1 -1
- package/dist/flow/index.js +24 -8
- package/dist/flow/index.js.map +1 -1
- package/dist/flow/model.d.ts +4 -2
- package/dist/flow/model.d.ts.map +1 -1
- package/dist/flow/model.js +4 -2
- package/dist/flow/model.js.map +1 -1
- package/dist/flow/node.d.ts +40 -9
- package/dist/flow/node.d.ts.map +1 -1
- package/dist/flow/node.js +142 -53
- package/dist/flow/node.js.map +1 -1
- package/dist/flow/types.d.ts +46 -21
- package/dist/flow/types.d.ts.map +1 -1
- package/dist/formula/index.d.ts +4 -2
- package/dist/formula/index.d.ts.map +1 -1
- package/dist/formula/index.js.map +1 -1
- package/dist/formula/node.d.ts +5 -1
- package/dist/formula/node.d.ts.map +1 -1
- package/dist/formula/node.js +19 -3
- package/dist/formula/node.js.map +1 -1
- package/dist/html/controls.d.ts +4 -1
- package/dist/html/controls.d.ts.map +1 -1
- package/dist/html/controls.js.map +1 -1
- package/dist/html/css/cascade.d.ts +11 -5
- package/dist/html/css/cascade.d.ts.map +1 -1
- package/dist/html/css/cascade.js +18 -8
- package/dist/html/css/cascade.js.map +1 -1
- package/dist/html/css/parse.d.ts +15 -10
- package/dist/html/css/parse.d.ts.map +1 -1
- package/dist/html/css/parse.js +41 -16
- package/dist/html/css/parse.js.map +1 -1
- package/dist/html/css/style.d.ts +3 -0
- package/dist/html/css/style.d.ts.map +1 -1
- package/dist/html/css/style.js +3 -1
- package/dist/html/css/style.js.map +1 -1
- package/dist/html/css/values.d.ts +14 -1
- package/dist/html/css/values.d.ts.map +1 -1
- package/dist/html/css/values.js +11 -8
- package/dist/html/css/values.js.map +1 -1
- package/dist/html/index.d.ts.map +1 -1
- package/dist/html/index.js +1 -0
- package/dist/html/index.js.map +1 -1
- package/dist/html/layout/boxes.d.ts +12 -5
- package/dist/html/layout/boxes.d.ts.map +1 -1
- package/dist/html/layout/boxes.js +7 -4
- package/dist/html/layout/boxes.js.map +1 -1
- package/dist/html/layout/inline.d.ts.map +1 -1
- package/dist/html/layout/inline.js +22 -1
- package/dist/html/layout/inline.js.map +1 -1
- package/dist/html/node.d.ts +22 -8
- package/dist/html/node.d.ts.map +1 -1
- package/dist/html/node.js +97 -15
- package/dist/html/node.js.map +1 -1
- package/dist/html/paint.d.ts +3 -0
- package/dist/html/paint.d.ts.map +1 -1
- package/dist/html/paint.js +2 -2
- package/dist/html/paint.js.map +1 -1
- package/dist/index.d.ts +7 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +21 -0
- package/dist/index.js.map +1 -1
- package/dist/maps/clip.d.ts +43 -0
- package/dist/maps/clip.d.ts.map +1 -0
- package/dist/maps/clip.js +171 -0
- package/dist/maps/clip.js.map +1 -0
- package/dist/maps/gzip.d.ts +15 -0
- package/dist/maps/gzip.d.ts.map +1 -0
- package/dist/maps/gzip.js +56 -0
- package/dist/maps/gzip.js.map +1 -0
- package/dist/maps/index.d.ts +55 -0
- package/dist/maps/index.d.ts.map +1 -0
- package/dist/maps/index.js +141 -0
- package/dist/maps/index.js.map +1 -0
- package/dist/maps/labels.d.ts +123 -0
- package/dist/maps/labels.d.ts.map +1 -0
- package/dist/maps/labels.js +323 -0
- package/dist/maps/labels.js.map +1 -0
- package/dist/maps/mvt.d.ts +244 -0
- package/dist/maps/mvt.d.ts.map +1 -0
- package/dist/maps/mvt.js +820 -0
- package/dist/maps/mvt.js.map +1 -0
- package/dist/maps/node.d.ts +278 -0
- package/dist/maps/node.d.ts.map +1 -0
- package/dist/maps/node.js +1459 -0
- package/dist/maps/node.js.map +1 -0
- package/dist/maps/overlay.d.ts +157 -0
- package/dist/maps/overlay.d.ts.map +1 -0
- package/dist/maps/overlay.js +472 -0
- package/dist/maps/overlay.js.map +1 -0
- package/dist/maps/paint.d.ts +226 -0
- package/dist/maps/paint.d.ts.map +1 -0
- package/dist/maps/paint.js +705 -0
- package/dist/maps/paint.js.map +1 -0
- package/dist/maps/proj.d.ts +281 -0
- package/dist/maps/proj.d.ts.map +1 -0
- package/dist/maps/proj.js +451 -0
- package/dist/maps/proj.js.map +1 -0
- package/dist/maps/sources.d.ts +256 -0
- package/dist/maps/sources.d.ts.map +1 -0
- package/dist/maps/sources.js +210 -0
- package/dist/maps/sources.js.map +1 -0
- package/dist/maps/style.d.ts +141 -0
- package/dist/maps/style.d.ts.map +1 -0
- package/dist/maps/style.js +147 -0
- package/dist/maps/style.js.map +1 -0
- package/dist/maps/styles.d.ts +82 -0
- package/dist/maps/styles.d.ts.map +1 -0
- package/dist/maps/styles.js +898 -0
- package/dist/maps/styles.js.map +1 -0
- package/dist/maps/tiles.d.ts +283 -0
- package/dist/maps/tiles.d.ts.map +1 -0
- package/dist/maps/tiles.js +469 -0
- package/dist/maps/tiles.js.map +1 -0
- package/dist/maps/types.d.ts +244 -0
- package/dist/maps/types.d.ts.map +1 -0
- package/dist/maps/types.js +2 -0
- package/dist/maps/types.js.map +1 -0
- package/dist/reorder/hx.d.ts +18 -0
- package/dist/reorder/hx.d.ts.map +1 -0
- package/dist/reorder/hx.js +10 -0
- package/dist/reorder/hx.js.map +1 -0
- package/dist/reorder/index.d.ts +314 -0
- package/dist/reorder/index.d.ts.map +1 -0
- package/dist/reorder/index.js +1539 -0
- package/dist/reorder/index.js.map +1 -0
- package/dist/reorder/model.d.ts +107 -0
- package/dist/reorder/model.d.ts.map +1 -0
- package/dist/reorder/model.js +162 -0
- package/dist/reorder/model.js.map +1 -0
- package/dist/richtext/node.d.ts +15 -5
- package/dist/richtext/node.d.ts.map +1 -1
- package/dist/richtext/node.js +40 -11
- package/dist/richtext/node.js.map +1 -1
- package/dist/richtext/runs.d.ts +35 -13
- package/dist/richtext/runs.d.ts.map +1 -1
- package/dist/richtext/runs.js +83 -27
- package/dist/richtext/runs.js.map +1 -1
- package/dist/tabs/index.d.ts +17 -0
- package/dist/tabs/index.d.ts.map +1 -1
- package/dist/tabs/index.js +849 -186
- package/dist/tabs/index.js.map +1 -1
- package/dist/terminal/vt/fonts.d.ts +45 -15
- package/dist/terminal/vt/fonts.d.ts.map +1 -1
- package/dist/terminal/vt/fonts.js +89 -19
- package/dist/terminal/vt/fonts.js.map +1 -1
- package/dist/terminal/vt/node.d.ts +42 -0
- package/dist/terminal/vt/node.d.ts.map +1 -1
- package/dist/terminal/vt/node.js +95 -11
- package/dist/terminal/vt/node.js.map +1 -1
- package/dist/terminal/vt/renderer.d.ts.map +1 -1
- package/dist/terminal/vt/renderer.js +13 -3
- package/dist/terminal/vt/renderer.js.map +1 -1
- package/package.json +13 -3
- package/src/charts/components.ts +14 -7
- package/src/charts/node.ts +81 -26
- package/src/charts/render.ts +16 -6
- package/src/code-editor/node.ts +147 -53
- package/src/flow/draw.ts +104 -35
- package/src/flow/index.ts +33 -13
- package/src/flow/model.ts +4 -2
- package/src/flow/node.ts +158 -59
- package/src/flow/types.ts +46 -21
- package/src/formula/index.ts +4 -2
- package/src/formula/node.ts +24 -5
- package/src/html/controls.ts +4 -1
- package/src/html/css/cascade.ts +18 -7
- package/src/html/css/parse.ts +46 -21
- package/src/html/css/style.ts +6 -1
- package/src/html/css/values.ts +25 -9
- package/src/html/index.ts +1 -0
- package/src/html/layout/boxes.ts +19 -9
- package/src/html/layout/inline.ts +23 -1
- package/src/html/node.ts +110 -17
- package/src/html/paint.ts +17 -2
- package/src/index.ts +98 -0
- package/src/maps/clip.ts +184 -0
- package/src/maps/gzip.ts +66 -0
- package/src/maps/index.ts +316 -0
- package/src/maps/labels.ts +442 -0
- package/src/maps/mvt.ts +857 -0
- package/src/maps/node.ts +1819 -0
- package/src/maps/overlay.ts +639 -0
- package/src/maps/paint.ts +978 -0
- package/src/maps/proj.ts +641 -0
- package/src/maps/sources.ts +425 -0
- package/src/maps/style.ts +309 -0
- package/src/maps/styles.ts +1040 -0
- package/src/maps/tiles.ts +676 -0
- package/src/maps/types.ts +260 -0
- package/src/reorder/hx.ts +31 -0
- package/src/reorder/index.ts +2223 -0
- package/src/reorder/model.ts +232 -0
- package/src/richtext/node.ts +49 -14
- package/src/richtext/runs.ts +115 -30
- package/src/tabs/index.ts +1040 -204
- package/src/terminal/vt/fonts.ts +128 -36
- package/src/terminal/vt/node.ts +105 -12
- package/src/terminal/vt/renderer.ts +13 -3
|
@@ -0,0 +1,705 @@
|
|
|
1
|
+
// The rasterizer: a tile's features, drawn.
|
|
2
|
+
//
|
|
3
|
+
// One function does the whole of it ({@link drawTileLayers}), and it is
|
|
4
|
+
// written for one situation — twelve thousand features and a hundred and
|
|
5
|
+
// eighty thousand vertices, sixty times a second — so every decision in it
|
|
6
|
+
// is about the per-vertex and per-feature paths. In rough order of how much
|
|
7
|
+
// each is worth on the corpus in `scripts/bench/`:
|
|
8
|
+
//
|
|
9
|
+
// 1. **One path per style layer, not per feature.** ntk batches a fill
|
|
10
|
+
// into server-side trapezoids and a stroke into server-side spans, both
|
|
11
|
+
// gated on the whole path arriving before `fill()`. Drawing 2,700
|
|
12
|
+
// streets as 2,700 `beginPath`/`stroke` pairs is 2,700 round-trippable
|
|
13
|
+
// request groups; drawing them as one path is a handful. The path is
|
|
14
|
+
// flushed every {@link BATCH_VERTICES} vertices so a pathological layer
|
|
15
|
+
// cannot make one unbounded request.
|
|
16
|
+
// 2. **Vertex decimation against the target grid.** A vertex less than
|
|
17
|
+
// `tolerance` from the one before it lands on the same pixel, so it is
|
|
18
|
+
// dropped. Sounds marginal; is not — Shortbread's low-zoom `land`
|
|
19
|
+
// layer is a single 130,000-vertex forest multipolygon whose vertices
|
|
20
|
+
// are, at zoom 8, roughly a fifth of a pixel apart.
|
|
21
|
+
// 3. **A sub-pixel feature is skipped whole.** The bounding box came free
|
|
22
|
+
// from the geometry read, so a building a third of a pixel across is
|
|
23
|
+
// one comparison rather than a path.
|
|
24
|
+
// 4. **Paint resolved per layer.** A zoom ramp, a filter compile and a
|
|
25
|
+
// colour string are per layer, never per feature. The filter arrives
|
|
26
|
+
// already compiled ({@link compileFilter}).
|
|
27
|
+
// 5. **Nothing allocates per feature.** One {@link GeometryBuffer} and one
|
|
28
|
+
// {@link FeatureCursor} for the whole call.
|
|
29
|
+
//
|
|
30
|
+
// The unit convention is the other half of the file, and it is the thing to
|
|
31
|
+
// get right before changing anything: **everything here is in *target*
|
|
32
|
+
// pixels**, where the target is either a tile's own surface (the retained
|
|
33
|
+
// path, where the tile occupies the whole surface) or the window's paint
|
|
34
|
+
// context (the direct path). {@link TileDraw} carries the two numbers that
|
|
35
|
+
// make one out of the other, so the same code serves both and neither has a
|
|
36
|
+
// branch in the vertex loop.
|
|
37
|
+
import { clipRing, clipSegment } from './clip.js';
|
|
38
|
+
import { compileFilter, resolveZoomed } from './style.js';
|
|
39
|
+
import { GeomType, GeometryBuffer } from './mvt.js';
|
|
40
|
+
/**
|
|
41
|
+
* Whether a context can draw at all.
|
|
42
|
+
*
|
|
43
|
+
* The mock backend the headless suite runs on has no path API, which is the
|
|
44
|
+
* repo convention for keeping a drawn element testable: the element's paint
|
|
45
|
+
* becomes a no-op there rather than a throw, and everything worth asserting
|
|
46
|
+
* about it — the cover, the projection, the cache, the hit test — is
|
|
47
|
+
* asserted without pixels.
|
|
48
|
+
*/
|
|
49
|
+
export function isMapCanvas(ctx) {
|
|
50
|
+
const c = ctx;
|
|
51
|
+
return typeof c?.beginPath === 'function' && typeof c?.fill === 'function';
|
|
52
|
+
}
|
|
53
|
+
const globals = globalThis;
|
|
54
|
+
/**
|
|
55
|
+
* The clock a rasterization budget is measured on.
|
|
56
|
+
*
|
|
57
|
+
* `performance.now()` rather than `Date.now()`, because a budget is a
|
|
58
|
+
* sub-frame quantity and `Date.now()` counts whole milliseconds: an 8 ms
|
|
59
|
+
* budget measured on it carries 12% of error, and anything under 1 ms
|
|
60
|
+
* rounds to a deadline that has already passed — so `rasterBudgetMs: 0.5`
|
|
61
|
+
* would silently mean "never rasterize", which is not what it says.
|
|
62
|
+
* Reached through `globalThis` because `src/` compiles with `types: []`.
|
|
63
|
+
*/
|
|
64
|
+
export function now() {
|
|
65
|
+
return globals.performance?.now() ?? Date.now();
|
|
66
|
+
}
|
|
67
|
+
/** Vertices after which the accumulated path is flushed. Large enough that
|
|
68
|
+
* a whole ordinary layer is one path, small enough that the 130,000-vertex
|
|
69
|
+
* outlier is a few rather than one enormous request. */
|
|
70
|
+
export const BATCH_VERTICES = 12_000;
|
|
71
|
+
function emptyStats() {
|
|
72
|
+
return {
|
|
73
|
+
layers: 0,
|
|
74
|
+
features: 0,
|
|
75
|
+
vertices: 0,
|
|
76
|
+
decimated: 0,
|
|
77
|
+
culled: 0,
|
|
78
|
+
batches: 0,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Compile a style once.
|
|
83
|
+
*
|
|
84
|
+
* Runs rather than a global grouping by source layer, because the order a
|
|
85
|
+
* style lists its layers in *is* the cartography — water over landuse,
|
|
86
|
+
* buildings over both — and a grouping would reorder a style that
|
|
87
|
+
* interleaves two sources. A run preserves order exactly and captures the
|
|
88
|
+
* win whenever a style is written the way the casing-then-fill split forces
|
|
89
|
+
* anyway: a road network is fourteen consecutive layers over `streets`.
|
|
90
|
+
*/
|
|
91
|
+
export function prepareStyle(style) {
|
|
92
|
+
const layers = style.layers.map((layer) => ({
|
|
93
|
+
layer,
|
|
94
|
+
filter: compileFilter(layer.filter),
|
|
95
|
+
}));
|
|
96
|
+
const runs = [];
|
|
97
|
+
let i = 0;
|
|
98
|
+
while (i < layers.length) {
|
|
99
|
+
const sourceLayer = layers[i].layer.sourceLayer;
|
|
100
|
+
let end = i + 1;
|
|
101
|
+
while (end < layers.length &&
|
|
102
|
+
layers[end].layer.sourceLayer === sourceLayer) {
|
|
103
|
+
end++;
|
|
104
|
+
}
|
|
105
|
+
runs.push({ sourceLayer, from: i, to: end });
|
|
106
|
+
i = end;
|
|
107
|
+
}
|
|
108
|
+
return { layers, runs };
|
|
109
|
+
}
|
|
110
|
+
function num(value, zoom, fallback) {
|
|
111
|
+
return value === undefined ? fallback : resolveZoomed(value, zoom);
|
|
112
|
+
}
|
|
113
|
+
function str(value, zoom) {
|
|
114
|
+
return value === undefined ? undefined : resolveZoomed(value, zoom);
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Scratch a caller keeps between tiles, so nothing here allocates per
|
|
118
|
+
* feature or per layer.
|
|
119
|
+
*/
|
|
120
|
+
export class DrawScratch {
|
|
121
|
+
buffer = new GeometryBuffer();
|
|
122
|
+
/** One clipped ring at a time, reused, so the overzoom path allocates
|
|
123
|
+
* nothing per part either. */
|
|
124
|
+
ring = [];
|
|
125
|
+
stats = emptyStats();
|
|
126
|
+
_buckets = [];
|
|
127
|
+
/** `n` index lists, emptied. Grown to the widest run ever seen and then
|
|
128
|
+
* reused for the life of the element. */
|
|
129
|
+
buckets(n) {
|
|
130
|
+
while (this._buckets.length < n)
|
|
131
|
+
this._buckets.push([]);
|
|
132
|
+
for (let i = 0; i < n; i++)
|
|
133
|
+
this._buckets[i].length = 0;
|
|
134
|
+
return this._buckets;
|
|
135
|
+
}
|
|
136
|
+
resetStats() {
|
|
137
|
+
this.stats = emptyStats();
|
|
138
|
+
return this.stats;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
/** Whether a style layer draws at this zoom at all. */
|
|
142
|
+
function activeAt(layer, zoom) {
|
|
143
|
+
if (layer.visible === false)
|
|
144
|
+
return false;
|
|
145
|
+
if (layer.type === 'symbol')
|
|
146
|
+
return false; // placed and drawn per frame
|
|
147
|
+
if (layer.minZoom !== undefined && zoom < layer.minZoom)
|
|
148
|
+
return false;
|
|
149
|
+
if (layer.maxZoom !== undefined && zoom >= layer.maxZoom)
|
|
150
|
+
return false;
|
|
151
|
+
return true;
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Which geometry types a style layer draws.
|
|
155
|
+
*
|
|
156
|
+
* A `line` layer takes polygons too — stroking a ring is what a style
|
|
157
|
+
* asking for a pier's outline means — and a `fill` layer takes only
|
|
158
|
+
* polygons, since there is no area to fill in a line.
|
|
159
|
+
*/
|
|
160
|
+
function typeAllowed(type, geometry) {
|
|
161
|
+
if (type === 'fill')
|
|
162
|
+
return geometry === GeomType.Polygon;
|
|
163
|
+
if (type === 'circle')
|
|
164
|
+
return geometry === GeomType.Point;
|
|
165
|
+
if (type === 'line')
|
|
166
|
+
return geometry !== GeomType.Point;
|
|
167
|
+
return false;
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Draw every layer of the style this tile has data for.
|
|
171
|
+
*
|
|
172
|
+
* The style is walked in **runs of consecutive layers over one source
|
|
173
|
+
* layer**, and each run's features are seeked once for the whole run. That
|
|
174
|
+
* is worth stating plainly because it is the difference between a fast
|
|
175
|
+
* zoom-12 tile and a slow one: a road network is fourteen style layers over
|
|
176
|
+
* `streets` (seven classes, and a casing pass before the fill pass so that
|
|
177
|
+
* junctions knit), so a layer-at-a-time walk parses each of eight thousand
|
|
178
|
+
* features' headers and tags fourteen times over. A run parses them once
|
|
179
|
+
* and asks fourteen already-compiled filters about them.
|
|
180
|
+
*
|
|
181
|
+
* Runs rather than a global grouping by source layer, because the order a
|
|
182
|
+
* style lists its layers in *is* the cartography — water over landuse,
|
|
183
|
+
* buildings over both — and a grouping would reorder a style that
|
|
184
|
+
* interleaves. A run preserves order exactly and captures the win whenever
|
|
185
|
+
* a style is written the way the casing/fill split forces anyway.
|
|
186
|
+
*/
|
|
187
|
+
export function drawTileLayers(ctx, tile, prepared, draw, scratch) {
|
|
188
|
+
for (let run = 0; run < prepared.runs.length; run++) {
|
|
189
|
+
drawTileRun(ctx, tile, prepared, run, draw, scratch);
|
|
190
|
+
}
|
|
191
|
+
return scratch.stats;
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* One run of the style, drawn.
|
|
195
|
+
*
|
|
196
|
+
* The unit a caller budgets in: the node draws runs until its per-frame
|
|
197
|
+
* budget is spent, remembers where it stopped, and comes back next frame.
|
|
198
|
+
* Layers are painted bottom-up, so a tile stopped part-way looks like a map
|
|
199
|
+
* whose upper layers have not arrived — which is what a partly-drawn map
|
|
200
|
+
* should look like — rather than like a hole.
|
|
201
|
+
*/
|
|
202
|
+
export function drawTileRun(ctx, tile, prepared, runIndex, draw, scratch, resume) {
|
|
203
|
+
const stats = scratch.stats;
|
|
204
|
+
const run = prepared.runs[runIndex];
|
|
205
|
+
if (!run)
|
|
206
|
+
return -1;
|
|
207
|
+
const source = tile.layers.get(run.sourceLayer);
|
|
208
|
+
if (!source || source.length === 0)
|
|
209
|
+
return -1;
|
|
210
|
+
// The zoom gate before anything is touched. A style with forty layers
|
|
211
|
+
// draws eight at a given zoom, and the other thirty-two must not cost a
|
|
212
|
+
// feature walk.
|
|
213
|
+
const active = [];
|
|
214
|
+
for (let j = run.from; j < run.to; j++) {
|
|
215
|
+
if (activeAt(prepared.layers[j].layer, draw.zoom)) {
|
|
216
|
+
active.push(prepared.layers[j]);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
if (active.length === 0)
|
|
220
|
+
return -1;
|
|
221
|
+
const from = resume?.fromLayer ?? 0;
|
|
222
|
+
if (from >= active.length)
|
|
223
|
+
return -1;
|
|
224
|
+
stats.layers += active.length - from;
|
|
225
|
+
const deadline = resume?.deadline;
|
|
226
|
+
if (active.length === 1) {
|
|
227
|
+
// Everything but roads: one layer over the source, so there is nothing
|
|
228
|
+
// to share and no index list to build.
|
|
229
|
+
drawLayer(ctx, source, active[0], draw, scratch, null);
|
|
230
|
+
return -1;
|
|
231
|
+
}
|
|
232
|
+
// Several layers over one source: seek each feature once and ask every
|
|
233
|
+
// filter about it, rather than walking the layer once per style layer.
|
|
234
|
+
// On a zoom-12 tile that is one pass over 8,488 streets instead of
|
|
235
|
+
// fourteen.
|
|
236
|
+
const buckets = scratch.buckets(active.length);
|
|
237
|
+
const cursor = source.feature(0);
|
|
238
|
+
for (let f = 0; f < source.length; f++) {
|
|
239
|
+
source.seek(f, cursor);
|
|
240
|
+
for (let j = from; j < active.length; j++) {
|
|
241
|
+
const { layer, filter } = active[j];
|
|
242
|
+
if (!typeAllowed(layer.type, cursor.type))
|
|
243
|
+
continue;
|
|
244
|
+
if (!filter(cursor))
|
|
245
|
+
continue;
|
|
246
|
+
buckets[j].push(f);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
// Stopping between layers rather than only between runs, because a run is
|
|
250
|
+
// not a small unit: a road network is one run and fourteen layers, and
|
|
251
|
+
// one of those layers alone measured 90 ms on the profiling corpus. The
|
|
252
|
+
// budget has to be able to interrupt *inside* it or it is not a budget.
|
|
253
|
+
// Resuming re-does the bucketing pass above — one seek over the source
|
|
254
|
+
// layer, a few milliseconds — which is the price of not holding a frame's
|
|
255
|
+
// scratch across frames.
|
|
256
|
+
for (let j = from; j < active.length; j++) {
|
|
257
|
+
drawLayer(ctx, source, active[j], draw, scratch, buckets[j]);
|
|
258
|
+
if (deadline !== undefined && j + 1 < active.length && now() >= deadline) {
|
|
259
|
+
return j + 1;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
return -1;
|
|
263
|
+
}
|
|
264
|
+
function drawLayer(ctx, source, prepared, draw, scratch, indices) {
|
|
265
|
+
const { layer, filter } = prepared;
|
|
266
|
+
switch (layer.type) {
|
|
267
|
+
case 'fill':
|
|
268
|
+
drawFill(ctx, source, layer, filter, draw, scratch, indices);
|
|
269
|
+
break;
|
|
270
|
+
case 'line':
|
|
271
|
+
drawLine(ctx, source, layer, filter, draw, scratch, indices);
|
|
272
|
+
break;
|
|
273
|
+
case 'circle':
|
|
274
|
+
drawCircle(ctx, source, layer, filter, draw, scratch, indices);
|
|
275
|
+
break;
|
|
276
|
+
case 'symbol':
|
|
277
|
+
break;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
/**
|
|
281
|
+
* Append one part of a feature to the path, simplified against the target
|
|
282
|
+
* grid, and answer how many vertices were emitted.
|
|
283
|
+
*
|
|
284
|
+
* Two tests, in the order that makes the cheap one do most of the work:
|
|
285
|
+
*
|
|
286
|
+
* - **radial** — a vertex within `tolerance` of the one last kept lands on
|
|
287
|
+
* the same pixel, so it says nothing;
|
|
288
|
+
* - **perpendicular** — a vertex within `tolerance` of the straight line
|
|
289
|
+
* from the one last kept to the one after it is on that line, so it says
|
|
290
|
+
* nothing either. This is the one that matters on real data: a
|
|
291
|
+
* generalized coastline's vertices are about a pixel apart, which the
|
|
292
|
+
* radial test keeps, and nearly collinear, which this drops.
|
|
293
|
+
*
|
|
294
|
+
* The first and last vertices are always kept: a ring simplified down to
|
|
295
|
+
* its first point is not a ring, and a road whose end moved is a road that
|
|
296
|
+
* no longer meets the next one at the tile seam.
|
|
297
|
+
*/
|
|
298
|
+
function emitPart(ctx, coords, from, to, ox, oy, k,
|
|
299
|
+
/** Squared, so the vertex loop needs no square root. */
|
|
300
|
+
toleranceSq, closed, stats,
|
|
301
|
+
/**
|
|
302
|
+
* Clip the geometry to this box, or null to let the target clip it.
|
|
303
|
+
*
|
|
304
|
+
* Null on the ordinary path, where a tile is drawn at its own size and a
|
|
305
|
+
* vertex is a few thousand pixels at worst. Set when a tile is drawn past
|
|
306
|
+
* its own depth — one source tile rasterized into each cell of a grid
|
|
307
|
+
* over it — where a tile-wide polygon is `span` tiles wide in the
|
|
308
|
+
* surface's pixels: at 64 cells that is a hundred thousand, and ntk hands
|
|
309
|
+
* a stroke to XRender as 16.16 fixed point, which overflows a signed
|
|
310
|
+
* 32-bit word at 32,768. The surface would clip the *drawing* correctly;
|
|
311
|
+
* it is the numbers on the way there that do not fit.
|
|
312
|
+
*/
|
|
313
|
+
clip,
|
|
314
|
+
/** Scratch for the clipped ring, so a fill allocates nothing per part. */
|
|
315
|
+
scratch) {
|
|
316
|
+
if (clip) {
|
|
317
|
+
return emitClipped(ctx, coords, from, to, ox, oy, k, closed, clip, scratch, stats);
|
|
318
|
+
}
|
|
319
|
+
const count = to - from;
|
|
320
|
+
if (count < 2)
|
|
321
|
+
return 0;
|
|
322
|
+
let x = ox + coords[from * 2] * k;
|
|
323
|
+
let y = oy + coords[from * 2 + 1] * k;
|
|
324
|
+
ctx.moveTo(x, y);
|
|
325
|
+
let emitted = 1;
|
|
326
|
+
let keptX = x;
|
|
327
|
+
let keptY = y;
|
|
328
|
+
const last = to - 1;
|
|
329
|
+
for (let i = from + 1; i <= last; i++) {
|
|
330
|
+
x = ox + coords[i * 2] * k;
|
|
331
|
+
y = oy + coords[i * 2 + 1] * k;
|
|
332
|
+
if (toleranceSq > 0 && i !== last) {
|
|
333
|
+
const dx = x - keptX;
|
|
334
|
+
const dy = y - keptY;
|
|
335
|
+
if (dx * dx + dy * dy < toleranceSq) {
|
|
336
|
+
stats.decimated++;
|
|
337
|
+
continue;
|
|
338
|
+
}
|
|
339
|
+
const nx = ox + coords[(i + 1) * 2] * k;
|
|
340
|
+
const ny = oy + coords[(i + 1) * 2 + 1] * k;
|
|
341
|
+
const ax = nx - keptX;
|
|
342
|
+
const ay = ny - keptY;
|
|
343
|
+
const lenSq = ax * ax + ay * ay;
|
|
344
|
+
if (lenSq > 0) {
|
|
345
|
+
// The cross product is the parallelogram's area, so its square over
|
|
346
|
+
// `lenSq` is the squared perpendicular distance — compared as a
|
|
347
|
+
// product to keep the division out of the loop.
|
|
348
|
+
const cross = ax * dy - ay * dx;
|
|
349
|
+
if (cross * cross < toleranceSq * lenSq) {
|
|
350
|
+
stats.decimated++;
|
|
351
|
+
continue;
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
ctx.lineTo(x, y);
|
|
356
|
+
emitted++;
|
|
357
|
+
keptX = x;
|
|
358
|
+
keptY = y;
|
|
359
|
+
}
|
|
360
|
+
if (closed)
|
|
361
|
+
ctx.closePath();
|
|
362
|
+
return emitted;
|
|
363
|
+
}
|
|
364
|
+
/**
|
|
365
|
+
* One part, clipped to the target before it is emitted.
|
|
366
|
+
*
|
|
367
|
+
* A ring goes through Sutherland-Hodgman, because a fill needs a closed
|
|
368
|
+
* boundary — the part that crosses the window has to come back along the
|
|
369
|
+
* window's edge. A line goes through Cohen-Sutherland segment by segment,
|
|
370
|
+
* which turns one polyline into as many subpaths as it has visible
|
|
371
|
+
* stretches; joins at the boundary are lost, and they are off-screen.
|
|
372
|
+
*
|
|
373
|
+
* Simplification is deliberately skipped here: this path only runs for the
|
|
374
|
+
* handful of features that survive the box cull in an overzoomed cell, and
|
|
375
|
+
* at that magnification there is nothing to simplify away.
|
|
376
|
+
*/
|
|
377
|
+
function emitClipped(ctx, coords, from, to, ox, oy, k, closed, clip, scratch, stats) {
|
|
378
|
+
const count = to - from;
|
|
379
|
+
if (count < 2)
|
|
380
|
+
return 0;
|
|
381
|
+
scratch.length = 0;
|
|
382
|
+
for (let i = from; i < to; i++) {
|
|
383
|
+
scratch.push(ox + coords[i * 2] * k, oy + coords[i * 2 + 1] * k);
|
|
384
|
+
}
|
|
385
|
+
if (closed) {
|
|
386
|
+
const ring = clipRing(scratch, clip);
|
|
387
|
+
if (ring.length < 6)
|
|
388
|
+
return 0;
|
|
389
|
+
ctx.moveTo(ring[0], ring[1]);
|
|
390
|
+
for (let i = 2; i < ring.length; i += 2)
|
|
391
|
+
ctx.lineTo(ring[i], ring[i + 1]);
|
|
392
|
+
ctx.closePath();
|
|
393
|
+
return ring.length / 2;
|
|
394
|
+
}
|
|
395
|
+
let emitted = 0;
|
|
396
|
+
let open = false;
|
|
397
|
+
for (let i = 0; i + 3 < scratch.length; i += 2) {
|
|
398
|
+
const piece = clipSegment(scratch[i], scratch[i + 1], scratch[i + 2], scratch[i + 3], clip);
|
|
399
|
+
if (!piece) {
|
|
400
|
+
open = false;
|
|
401
|
+
continue;
|
|
402
|
+
}
|
|
403
|
+
const [x0, y0, x1, y1] = piece;
|
|
404
|
+
if (!open) {
|
|
405
|
+
ctx.moveTo(x0, y0);
|
|
406
|
+
open = true;
|
|
407
|
+
emitted++;
|
|
408
|
+
}
|
|
409
|
+
ctx.lineTo(x1, y1);
|
|
410
|
+
emitted++;
|
|
411
|
+
if (x1 !== scratch[i + 2] || y1 !== scratch[i + 3])
|
|
412
|
+
open = false;
|
|
413
|
+
}
|
|
414
|
+
void stats;
|
|
415
|
+
return emitted;
|
|
416
|
+
}
|
|
417
|
+
/** A draw's clip rectangle in the form the clippers take, inflated so a
|
|
418
|
+
* stroke's own width and its joins land outside the surface rather than
|
|
419
|
+
* against its edge. */
|
|
420
|
+
function clipRectOf(clip) {
|
|
421
|
+
if (!clip)
|
|
422
|
+
return null;
|
|
423
|
+
const margin = 64;
|
|
424
|
+
return {
|
|
425
|
+
minX: clip.x - margin,
|
|
426
|
+
minY: clip.y - margin,
|
|
427
|
+
maxX: clip.x + clip.width + margin,
|
|
428
|
+
maxY: clip.y + clip.height + margin,
|
|
429
|
+
};
|
|
430
|
+
}
|
|
431
|
+
/** Whether a part's box is too small to be worth a path. */
|
|
432
|
+
function partTooSmall(bounds, part, k, minFeature) {
|
|
433
|
+
if (minFeature <= 0)
|
|
434
|
+
return false;
|
|
435
|
+
const at = part * 4;
|
|
436
|
+
return ((bounds[at + 2] - bounds[at]) * k < minFeature &&
|
|
437
|
+
(bounds[at + 3] - bounds[at + 1]) * k < minFeature);
|
|
438
|
+
}
|
|
439
|
+
/**
|
|
440
|
+
* Whether a whole feature lands outside the target at all.
|
|
441
|
+
*
|
|
442
|
+
* Only ever true when the target is a *cell* of a larger tile — the
|
|
443
|
+
* overzoom case, where one tile's data is drawn into each of `span²`
|
|
444
|
+
* surfaces. Without this each cell would build a path for every feature in
|
|
445
|
+
* the tile and let the surface clip it, so an overzoomed frame would cost
|
|
446
|
+
* `span²` times what it should; the box came free from the geometry read,
|
|
447
|
+
* so the test is four comparisons.
|
|
448
|
+
*/
|
|
449
|
+
function outsideClip(buffer, ox, oy, k, clip,
|
|
450
|
+
/** Slack for a stroke's own width, in target pixels. */
|
|
451
|
+
slack) {
|
|
452
|
+
return (ox + buffer.maxX * k < clip.x - slack ||
|
|
453
|
+
ox + buffer.minX * k > clip.x + clip.width + slack ||
|
|
454
|
+
oy + buffer.maxY * k < clip.y - slack ||
|
|
455
|
+
oy + buffer.minY * k > clip.y + clip.height + slack);
|
|
456
|
+
}
|
|
457
|
+
/** Iterate a bucket, or the whole layer applying the filter. Written as a
|
|
458
|
+
* pair of loops in each drawer rather than a shared generator: a generator
|
|
459
|
+
* per feature is exactly the allocation this file exists to avoid. */
|
|
460
|
+
function featureCount(source, indices) {
|
|
461
|
+
return indices ? indices.length : source.length;
|
|
462
|
+
}
|
|
463
|
+
function drawFill(ctx, source, layer, filter, draw, scratch, indices) {
|
|
464
|
+
const stats = scratch.stats;
|
|
465
|
+
const buffer = scratch.buffer;
|
|
466
|
+
const k = draw.span / source.extent;
|
|
467
|
+
const opacity = num(layer.opacity, draw.zoom, 1);
|
|
468
|
+
if (opacity <= 0)
|
|
469
|
+
return;
|
|
470
|
+
const outline = str(layer.outlineColor, draw.zoom);
|
|
471
|
+
const tol = (draw.tolerance ?? 0) * (draw.tolerance ?? 0);
|
|
472
|
+
const minFeature = draw.minFeature ?? 0;
|
|
473
|
+
const clip = draw.clip ?? null;
|
|
474
|
+
const clipBox = clipRectOf(clip);
|
|
475
|
+
const cursor = source.feature(0);
|
|
476
|
+
const total = featureCount(source, indices);
|
|
477
|
+
const batch = draw.batchVertices ?? BATCH_VERTICES;
|
|
478
|
+
ctx.save();
|
|
479
|
+
if (ctx.globalAlpha !== undefined && opacity < 1)
|
|
480
|
+
ctx.globalAlpha = opacity;
|
|
481
|
+
ctx.fillStyle = resolveZoomed(layer.color, draw.zoom);
|
|
482
|
+
if (outline !== undefined) {
|
|
483
|
+
ctx.strokeStyle = outline;
|
|
484
|
+
ctx.lineWidth = Math.max(1, draw.pixelsPerLogical);
|
|
485
|
+
}
|
|
486
|
+
let pending = 0;
|
|
487
|
+
ctx.beginPath();
|
|
488
|
+
for (let n = 0; n < total; n++) {
|
|
489
|
+
source.seek(indices ? indices[n] : n, cursor);
|
|
490
|
+
if (!indices) {
|
|
491
|
+
if (cursor.type !== GeomType.Polygon)
|
|
492
|
+
continue;
|
|
493
|
+
if (!filter(cursor))
|
|
494
|
+
continue;
|
|
495
|
+
}
|
|
496
|
+
cursor.readGeometry(buffer);
|
|
497
|
+
if (buffer.parts === 0)
|
|
498
|
+
continue;
|
|
499
|
+
if (clip && outsideClip(buffer, draw.ox, draw.oy, k, clip, 2)) {
|
|
500
|
+
stats.culled++;
|
|
501
|
+
continue;
|
|
502
|
+
}
|
|
503
|
+
stats.features++;
|
|
504
|
+
for (let part = 0; part < buffer.parts; part++) {
|
|
505
|
+
// Rings go into one path and are filled with the non-zero rule, which
|
|
506
|
+
// is what makes a hole a hole: MVT winds an exterior ring one way and
|
|
507
|
+
// an interior ring the other, so the rule does the work and nothing
|
|
508
|
+
// here pairs them up.
|
|
509
|
+
//
|
|
510
|
+
// Which is exactly why the batch is flushed **only before an exterior
|
|
511
|
+
// ring**. A positive area is a new polygon (the specification's own
|
|
512
|
+
// definition), so that is the one point at which the path so far is a
|
|
513
|
+
// whole number of polygons; flushing between an exterior ring and its
|
|
514
|
+
// interior ones would fill the hole in. This is what `areas` is for.
|
|
515
|
+
const exterior = buffer.areas[part] > 0;
|
|
516
|
+
if (exterior && pending >= batch) {
|
|
517
|
+
ctx.fill();
|
|
518
|
+
if (outline !== undefined)
|
|
519
|
+
ctx.stroke();
|
|
520
|
+
stats.batches += outline === undefined ? 1 : 2;
|
|
521
|
+
stats.vertices += pending;
|
|
522
|
+
pending = 0;
|
|
523
|
+
ctx.beginPath();
|
|
524
|
+
}
|
|
525
|
+
if (partTooSmall(buffer.partBounds, part, k, minFeature)) {
|
|
526
|
+
stats.culled++;
|
|
527
|
+
continue;
|
|
528
|
+
}
|
|
529
|
+
pending += emitPart(ctx, buffer.coords, buffer.starts[part], buffer.starts[part + 1], draw.ox, draw.oy, k, tol, true, stats, clipBox, scratch.ring);
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
if (pending > 0) {
|
|
533
|
+
ctx.fill();
|
|
534
|
+
if (outline !== undefined)
|
|
535
|
+
ctx.stroke();
|
|
536
|
+
stats.batches += outline === undefined ? 1 : 2;
|
|
537
|
+
stats.vertices += pending;
|
|
538
|
+
}
|
|
539
|
+
ctx.restore();
|
|
540
|
+
}
|
|
541
|
+
function drawLine(ctx, source, layer, filter, draw, scratch, indices) {
|
|
542
|
+
const stats = scratch.stats;
|
|
543
|
+
const buffer = scratch.buffer;
|
|
544
|
+
const k = draw.span / source.extent;
|
|
545
|
+
const logical = num(layer.width, draw.zoom, 1);
|
|
546
|
+
if (logical <= 0)
|
|
547
|
+
return;
|
|
548
|
+
const opacity = num(layer.opacity, draw.zoom, 1);
|
|
549
|
+
if (opacity <= 0)
|
|
550
|
+
return;
|
|
551
|
+
// A road narrower than a pixel is drawn *at* a pixel rather than dropped:
|
|
552
|
+
// a motorway network that vanishes below zoom 6 is worse than one drawn a
|
|
553
|
+
// little heavy, and every map makes this same choice.
|
|
554
|
+
const width = Math.max(1, logical * draw.pixelsPerLogical);
|
|
555
|
+
const tol = (draw.tolerance ?? 0) * (draw.tolerance ?? 0);
|
|
556
|
+
const minFeature = draw.minFeature ?? 0;
|
|
557
|
+
const clip = draw.clip ?? null;
|
|
558
|
+
const clipBox = clipRectOf(clip);
|
|
559
|
+
const cursor = source.feature(0);
|
|
560
|
+
const total = featureCount(source, indices);
|
|
561
|
+
const batch = draw.batchVertices ?? BATCH_VERTICES;
|
|
562
|
+
ctx.save();
|
|
563
|
+
if (ctx.globalAlpha !== undefined && opacity < 1)
|
|
564
|
+
ctx.globalAlpha = opacity;
|
|
565
|
+
ctx.strokeStyle = resolveZoomed(layer.color, draw.zoom);
|
|
566
|
+
ctx.lineWidth = width;
|
|
567
|
+
if (layer.cap !== undefined)
|
|
568
|
+
ctx.lineCap = layer.cap;
|
|
569
|
+
if (layer.join !== undefined)
|
|
570
|
+
ctx.lineJoin = layer.join;
|
|
571
|
+
if (layer.dash && ctx.setLineDash) {
|
|
572
|
+
ctx.setLineDash(layer.dash.map((d) => d * draw.pixelsPerLogical));
|
|
573
|
+
}
|
|
574
|
+
let pending = 0;
|
|
575
|
+
ctx.beginPath();
|
|
576
|
+
for (let n = 0; n < total; n++) {
|
|
577
|
+
source.seek(indices ? indices[n] : n, cursor);
|
|
578
|
+
if (!indices) {
|
|
579
|
+
if (cursor.type === GeomType.Point)
|
|
580
|
+
continue;
|
|
581
|
+
if (!filter(cursor))
|
|
582
|
+
continue;
|
|
583
|
+
}
|
|
584
|
+
cursor.readGeometry(buffer);
|
|
585
|
+
if (buffer.parts === 0)
|
|
586
|
+
continue;
|
|
587
|
+
if (clip && outsideClip(buffer, draw.ox, draw.oy, k, clip, width)) {
|
|
588
|
+
stats.culled++;
|
|
589
|
+
continue;
|
|
590
|
+
}
|
|
591
|
+
stats.features++;
|
|
592
|
+
const closed = cursor.type === GeomType.Polygon;
|
|
593
|
+
for (let part = 0; part < buffer.parts; part++) {
|
|
594
|
+
// Any part boundary is a safe flush point for a stroke: a subpath is
|
|
595
|
+
// stroked independently of every other, so where the path is cut
|
|
596
|
+
// changes nothing about the result. Inside the loop rather than after
|
|
597
|
+
// the feature because the feature that needs it most is a single one
|
|
598
|
+
// — a `land` multipolygon at zoom 8, a `buildings` multipolygon at
|
|
599
|
+
// zoom 14 — with tens of thousands of vertices in it.
|
|
600
|
+
if (pending >= batch) {
|
|
601
|
+
ctx.stroke();
|
|
602
|
+
stats.batches++;
|
|
603
|
+
stats.vertices += pending;
|
|
604
|
+
pending = 0;
|
|
605
|
+
ctx.beginPath();
|
|
606
|
+
}
|
|
607
|
+
if (partTooSmall(buffer.partBounds, part, k, minFeature)) {
|
|
608
|
+
stats.culled++;
|
|
609
|
+
continue;
|
|
610
|
+
}
|
|
611
|
+
pending += emitPart(ctx, buffer.coords, buffer.starts[part], buffer.starts[part + 1], draw.ox, draw.oy, k, tol, closed, stats, clipBox, scratch.ring);
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
if (pending > 0) {
|
|
615
|
+
ctx.stroke();
|
|
616
|
+
stats.batches++;
|
|
617
|
+
stats.vertices += pending;
|
|
618
|
+
}
|
|
619
|
+
if (layer.dash && ctx.setLineDash)
|
|
620
|
+
ctx.setLineDash([]);
|
|
621
|
+
ctx.restore();
|
|
622
|
+
}
|
|
623
|
+
function drawCircle(ctx, source, layer, filter, draw, scratch, indices) {
|
|
624
|
+
const stats = scratch.stats;
|
|
625
|
+
const buffer = scratch.buffer;
|
|
626
|
+
const k = draw.span / source.extent;
|
|
627
|
+
const radius = num(layer.radius, draw.zoom, 3) * draw.pixelsPerLogical;
|
|
628
|
+
if (radius <= 0)
|
|
629
|
+
return;
|
|
630
|
+
const opacity = num(layer.opacity, draw.zoom, 1);
|
|
631
|
+
if (opacity <= 0)
|
|
632
|
+
return;
|
|
633
|
+
const stroke = str(layer.strokeColor, draw.zoom);
|
|
634
|
+
const strokeWidth = num(layer.strokeWidth, draw.zoom, 1) * draw.pixelsPerLogical;
|
|
635
|
+
const clip = draw.clip ?? null;
|
|
636
|
+
const cursor = source.feature(0);
|
|
637
|
+
const total = featureCount(source, indices);
|
|
638
|
+
const discBatch = Math.min(1024, draw.batchVertices ?? BATCH_VERTICES);
|
|
639
|
+
ctx.save();
|
|
640
|
+
if (ctx.globalAlpha !== undefined && opacity < 1)
|
|
641
|
+
ctx.globalAlpha = opacity;
|
|
642
|
+
ctx.fillStyle = resolveZoomed(layer.color, draw.zoom);
|
|
643
|
+
if (stroke !== undefined) {
|
|
644
|
+
ctx.strokeStyle = stroke;
|
|
645
|
+
ctx.lineWidth = strokeWidth;
|
|
646
|
+
}
|
|
647
|
+
let pending = 0;
|
|
648
|
+
ctx.beginPath();
|
|
649
|
+
for (let n = 0; n < total; n++) {
|
|
650
|
+
source.seek(indices ? indices[n] : n, cursor);
|
|
651
|
+
if (!indices) {
|
|
652
|
+
if (cursor.type !== GeomType.Point)
|
|
653
|
+
continue;
|
|
654
|
+
if (!filter(cursor))
|
|
655
|
+
continue;
|
|
656
|
+
}
|
|
657
|
+
cursor.readGeometry(buffer);
|
|
658
|
+
if (buffer.points > 0 &&
|
|
659
|
+
clip &&
|
|
660
|
+
outsideClip(buffer, draw.ox, draw.oy, k, clip, radius)) {
|
|
661
|
+
stats.culled++;
|
|
662
|
+
continue;
|
|
663
|
+
}
|
|
664
|
+
stats.features++;
|
|
665
|
+
for (let p = 0; p < buffer.points; p++) {
|
|
666
|
+
const x = draw.ox + buffer.coords[p * 2] * k;
|
|
667
|
+
const y = draw.oy + buffer.coords[p * 2 + 1] * k;
|
|
668
|
+
// A disc is a point, so there is nothing to clip — but a multipoint
|
|
669
|
+
// feature can straddle the target, and only the whole feature was
|
|
670
|
+
// culled above. Skip the ones that land outside it.
|
|
671
|
+
if (clip &&
|
|
672
|
+
(x + radius < clip.x ||
|
|
673
|
+
x - radius > clip.x + clip.width ||
|
|
674
|
+
y + radius < clip.y ||
|
|
675
|
+
y - radius > clip.y + clip.height)) {
|
|
676
|
+
continue;
|
|
677
|
+
}
|
|
678
|
+
// A fresh subpath per disc: an `arc` continuing from the previous
|
|
679
|
+
// point would join the two with a line.
|
|
680
|
+
ctx.moveTo(x + radius, y);
|
|
681
|
+
ctx.arc(x, y, radius, 0, Math.PI * 2);
|
|
682
|
+
pending++;
|
|
683
|
+
}
|
|
684
|
+
// Discs, not vertices — each is `moveTo` plus an `arc` — so the cap is
|
|
685
|
+
// its own, bounded by the backend's batch preference.
|
|
686
|
+
if (pending >= discBatch) {
|
|
687
|
+
ctx.fill();
|
|
688
|
+
if (stroke !== undefined)
|
|
689
|
+
ctx.stroke();
|
|
690
|
+
stats.batches += stroke === undefined ? 1 : 2;
|
|
691
|
+
stats.vertices += pending;
|
|
692
|
+
pending = 0;
|
|
693
|
+
ctx.beginPath();
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
if (pending > 0) {
|
|
697
|
+
ctx.fill();
|
|
698
|
+
if (stroke !== undefined)
|
|
699
|
+
ctx.stroke();
|
|
700
|
+
stats.batches += stroke === undefined ? 1 : 2;
|
|
701
|
+
stats.vertices += pending;
|
|
702
|
+
}
|
|
703
|
+
ctx.restore();
|
|
704
|
+
}
|
|
705
|
+
//# sourceMappingURL=paint.js.map
|