@versatiles/svg-renderer 2.2.0 → 2.3.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/dist/index.d.ts +22 -2
- package/dist/index.js +168 -51
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -2738,6 +2738,12 @@ declare class Feature implements Feature$1 {
|
|
|
2738
2738
|
* clipped edges along the tile border are not part of the outline.
|
|
2739
2739
|
*/
|
|
2740
2740
|
readonly outline?: Geometry;
|
|
2741
|
+
/**
|
|
2742
|
+
* For each line of the geometry, how far along the line in its tile it starts, in pixels
|
|
2743
|
+
* — where the line was clipped to its tile, which cut off its start. A dash pattern
|
|
2744
|
+
* starts where the line in the tile does.
|
|
2745
|
+
*/
|
|
2746
|
+
readonly lineStarts?: number[];
|
|
2741
2747
|
constructor(opt: {
|
|
2742
2748
|
type: 'LineString' | 'Point' | 'Polygon';
|
|
2743
2749
|
id?: unknown;
|
|
@@ -2745,6 +2751,7 @@ declare class Feature implements Feature$1 {
|
|
|
2745
2751
|
patterns?: Patterns;
|
|
2746
2752
|
geometry: Geometry;
|
|
2747
2753
|
outline?: Geometry;
|
|
2754
|
+
lineStarts?: number[];
|
|
2748
2755
|
});
|
|
2749
2756
|
getBbox(): Bbox;
|
|
2750
2757
|
doesOverlap(bbox: Bbox): boolean;
|
|
@@ -2771,6 +2778,18 @@ interface Padding {
|
|
|
2771
2778
|
left?: number;
|
|
2772
2779
|
}
|
|
2773
2780
|
|
|
2781
|
+
/**
|
|
2782
|
+
* The dashes of a line with `line-dasharray`, shared by every backend, as MapLibre GL JS
|
|
2783
|
+
* draws them (`line_sdf.vertex.glsl`, `LineAtlas`).
|
|
2784
|
+
*/
|
|
2785
|
+
/** A dash pattern in pixels. */
|
|
2786
|
+
interface LineDash {
|
|
2787
|
+
/** The lengths of dashes and gaps in turn, starting with a dash: an even number of them. */
|
|
2788
|
+
lengths: number[];
|
|
2789
|
+
/** How far into the pattern a line starts. */
|
|
2790
|
+
offset: number;
|
|
2791
|
+
}
|
|
2792
|
+
|
|
2774
2793
|
/**
|
|
2775
2794
|
* A drawing surface the render pipeline paints a map onto. It only describes *how* to
|
|
2776
2795
|
* draw; how the result is handed back is up to the backend, because those differ in
|
|
@@ -2887,14 +2906,15 @@ interface LineStyle {
|
|
|
2887
2906
|
blur: number;
|
|
2888
2907
|
cap: 'butt' | 'round' | 'square';
|
|
2889
2908
|
color: Color;
|
|
2890
|
-
dasharray
|
|
2909
|
+
/** `line-dasharray`, in pixels (see `dashPattern`). */
|
|
2910
|
+
dash?: LineDash;
|
|
2891
2911
|
join: 'bevel' | 'miter' | 'round';
|
|
2892
2912
|
miterLimit: number;
|
|
2893
2913
|
offset: number;
|
|
2894
2914
|
opacity: number;
|
|
2895
2915
|
translate: [number, number];
|
|
2896
2916
|
width: number;
|
|
2897
|
-
/** `line-pattern`: drawn instead of `color` and `
|
|
2917
|
+
/** `line-pattern`: drawn instead of `color` and `dash`. */
|
|
2898
2918
|
pattern?: LinePattern;
|
|
2899
2919
|
}
|
|
2900
2920
|
/**
|
package/dist/index.js
CHANGED
|
@@ -119,6 +119,12 @@ class Feature {
|
|
|
119
119
|
* clipped edges along the tile border are not part of the outline.
|
|
120
120
|
*/
|
|
121
121
|
outline;
|
|
122
|
+
/**
|
|
123
|
+
* For each line of the geometry, how far along the line in its tile it starts, in pixels
|
|
124
|
+
* — where the line was clipped to its tile, which cut off its start. A dash pattern
|
|
125
|
+
* starts where the line in the tile does.
|
|
126
|
+
*/
|
|
127
|
+
lineStarts;
|
|
122
128
|
#bbox;
|
|
123
129
|
constructor(opt) {
|
|
124
130
|
this.type = opt.type;
|
|
@@ -127,6 +133,7 @@ class Feature {
|
|
|
127
133
|
this.patterns = opt.patterns;
|
|
128
134
|
this.geometry = opt.geometry;
|
|
129
135
|
this.outline = opt.outline;
|
|
136
|
+
this.lineStarts = opt.lineStarts;
|
|
130
137
|
}
|
|
131
138
|
getBbox() {
|
|
132
139
|
if (this.#bbox)
|
|
@@ -2133,32 +2140,49 @@ function clipPolygonOutline(rings, min, max) {
|
|
|
2133
2140
|
}
|
|
2134
2141
|
/** Clips a polyline to the square [min, max]², returning the parts inside of it. */
|
|
2135
2142
|
function clipLine(line, min, max) {
|
|
2143
|
+
return clipLineMeasured(line, min, max).map(({ points }) => points);
|
|
2144
|
+
}
|
|
2145
|
+
/**
|
|
2146
|
+
* Clips a polyline to the square [min, max]², like `clipLine`, and measures where each part
|
|
2147
|
+
* starts: its distance from the polyline's start, which is where MapLibre starts a dash
|
|
2148
|
+
* pattern. MapLibre stores that distance in a few bits and starts again from zero at a
|
|
2149
|
+
* vertex further along than `resetAfter` (`LineBucket.addCurrentVertex`).
|
|
2150
|
+
*/
|
|
2151
|
+
function clipLineMeasured(line, min, max, resetAfter = Infinity) {
|
|
2136
2152
|
const parts = [];
|
|
2137
2153
|
let current = [];
|
|
2154
|
+
let start = 0;
|
|
2155
|
+
let distance = 0;
|
|
2156
|
+
const flush = () => {
|
|
2157
|
+
if (current.length > 1)
|
|
2158
|
+
parts.push({ points: current, start });
|
|
2159
|
+
current = [];
|
|
2160
|
+
};
|
|
2138
2161
|
for (let i = 0; i + 1 < line.length; i++) {
|
|
2139
|
-
const
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2162
|
+
const from = line[i];
|
|
2163
|
+
const to = line[i + 1];
|
|
2164
|
+
const segment = clipSegment(from, to, min, max);
|
|
2165
|
+
if (segment) {
|
|
2166
|
+
const [a, b] = segment;
|
|
2167
|
+
const last = current[current.length - 1];
|
|
2168
|
+
if (last?.x !== a.x || last.y !== a.y) {
|
|
2169
|
+
flush();
|
|
2170
|
+
current = [a];
|
|
2171
|
+
start = distance + Math.hypot(a.x - from.x, a.y - from.y);
|
|
2172
|
+
}
|
|
2173
|
+
current.push(b);
|
|
2174
|
+
// The segment left the square: the line continues elsewhere.
|
|
2175
|
+
if (b !== to)
|
|
2176
|
+
flush();
|
|
2152
2177
|
}
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
if (b !== line[i + 1]) {
|
|
2156
|
-
parts.push(current);
|
|
2157
|
-
current = [];
|
|
2178
|
+
else {
|
|
2179
|
+
flush();
|
|
2158
2180
|
}
|
|
2181
|
+
distance += Math.hypot(to.x - from.x, to.y - from.y);
|
|
2182
|
+
if (distance > resetAfter)
|
|
2183
|
+
distance = 0;
|
|
2159
2184
|
}
|
|
2160
|
-
|
|
2161
|
-
parts.push(current);
|
|
2185
|
+
flush();
|
|
2162
2186
|
return parts;
|
|
2163
2187
|
}
|
|
2164
2188
|
/** Liang–Barsky: the part of the segment a→b inside the square, or undefined. */
|
|
@@ -2218,6 +2242,11 @@ function lerpAtY(a, b, y) {
|
|
|
2218
2242
|
}
|
|
2219
2243
|
|
|
2220
2244
|
const TILE_EXTENT = 4096;
|
|
2245
|
+
/**
|
|
2246
|
+
* How far along a line MapLibre measures before it starts again from zero: two tiles
|
|
2247
|
+
* (`MAX_LINE_DISTANCE / 2` in its `line_bucket.ts`, for its tile extent of 8192).
|
|
2248
|
+
*/
|
|
2249
|
+
const MAX_LINE_DISTANCE = 2 * TILE_EXTENT;
|
|
2221
2250
|
const VTFeatureType = { Unknown: 0, Point: 1, LineString: 2, Polygon: 3 };
|
|
2222
2251
|
async function loadVectorSource(source, job, layerFeatures, loadTile = getTile) {
|
|
2223
2252
|
const tiles = source.tiles;
|
|
@@ -2236,7 +2265,7 @@ async function loadVectorSource(source, job, layerFeatures, loadTile = getTile)
|
|
|
2236
2265
|
});
|
|
2237
2266
|
}
|
|
2238
2267
|
/** Decodes one vector tile and adds its features, projected to the screen. */
|
|
2239
|
-
function addTileFeatures(buffer, { project, clipToTile }, layerFeatures, width, height) {
|
|
2268
|
+
function addTileFeatures(buffer, { project, clipToTile, pixelsPerUnit }, layerFeatures, width, height) {
|
|
2240
2269
|
const vectorTile = new VectorTile(new PbfReader(buffer));
|
|
2241
2270
|
for (const [name, layer] of Object.entries(vectorTile.layers)) {
|
|
2242
2271
|
let features = layerFeatures.get(name);
|
|
@@ -2266,6 +2295,7 @@ function addTileFeatures(buffer, { project, clipToTile }, layerFeatures, width,
|
|
|
2266
2295
|
}
|
|
2267
2296
|
let rings = featureSrc.loadGeometry();
|
|
2268
2297
|
let outline;
|
|
2298
|
+
let lineStarts;
|
|
2269
2299
|
// Clip polygons and lines to the tile, like MapLibre's stencil clipping: otherwise
|
|
2270
2300
|
// the parts in the tile buffer are drawn twice, which shows when translucent.
|
|
2271
2301
|
if (clipToTile && type !== 'Point' && exceedsSquare(rings, 0, TILE_EXTENT)) {
|
|
@@ -2274,7 +2304,11 @@ function addTileFeatures(buffer, { project, clipToTile }, layerFeatures, width,
|
|
|
2274
2304
|
rings = clipPolygon(rings, 0, TILE_EXTENT);
|
|
2275
2305
|
}
|
|
2276
2306
|
else {
|
|
2277
|
-
|
|
2307
|
+
const parts = rings.flatMap((line) => clipLineMeasured(line, 0, TILE_EXTENT, MAX_LINE_DISTANCE));
|
|
2308
|
+
rings = parts.map(({ points }) => points);
|
|
2309
|
+
if (pixelsPerUnit !== undefined) {
|
|
2310
|
+
lineStarts = parts.map(({ start }) => start * pixelsPerUnit);
|
|
2311
|
+
}
|
|
2278
2312
|
}
|
|
2279
2313
|
}
|
|
2280
2314
|
const geometry = project(type, rings);
|
|
@@ -2298,6 +2332,7 @@ function addTileFeatures(buffer, { project, clipToTile }, layerFeatures, width,
|
|
|
2298
2332
|
type,
|
|
2299
2333
|
geometry,
|
|
2300
2334
|
outline,
|
|
2335
|
+
lineStarts,
|
|
2301
2336
|
id: featureSrc.id,
|
|
2302
2337
|
properties: featureSrc.properties,
|
|
2303
2338
|
});
|
|
@@ -2352,6 +2387,7 @@ function getTileProjections(source, job) {
|
|
|
2352
2387
|
y,
|
|
2353
2388
|
z: zoomLevel,
|
|
2354
2389
|
clipToTile: true,
|
|
2390
|
+
pixelsPerUnit: scale,
|
|
2355
2391
|
project: (_type, rings) => rings.map((ring) => ring.map((point) => {
|
|
2356
2392
|
const p = new Point2D(point.x, point.y).scale(scale).translate(offset);
|
|
2357
2393
|
return moved ? moved.fromNorthUp(p.x, p.y) : p;
|
|
@@ -11749,6 +11785,55 @@ function stretchZonesToCuts(zones, fixedSize, stretchSize) {
|
|
|
11749
11785
|
return cuts;
|
|
11750
11786
|
}
|
|
11751
11787
|
|
|
11788
|
+
/**
|
|
11789
|
+
* The dashes of a line with `line-dasharray`, shared by every backend, as MapLibre GL JS
|
|
11790
|
+
* draws them (`line_sdf.vertex.glsl`, `LineAtlas`).
|
|
11791
|
+
*/
|
|
11792
|
+
/**
|
|
11793
|
+
* The dash pattern of `dasharray`, or undefined for a line without dashes.
|
|
11794
|
+
*
|
|
11795
|
+
* MapLibre measures a dash array in line widths, but not in the width the line is drawn
|
|
11796
|
+
* with: in its width at the zoom level's integer part (`floorWidth`), scaled with the map
|
|
11797
|
+
* from that integer zoom level up to `zoom`, as its tiles are. And where a dash array has
|
|
11798
|
+
* an odd number of lengths, it is not repeated to an even one, as SVG and the canvas do: its
|
|
11799
|
+
* last and first dash are joined to one.
|
|
11800
|
+
*/
|
|
11801
|
+
function dashPattern(dasharray, floorWidth, zoom) {
|
|
11802
|
+
if (!dasharray || dasharray.length === 0)
|
|
11803
|
+
return undefined;
|
|
11804
|
+
const unit = floorWidth * 2 ** (zoom - Math.floor(zoom));
|
|
11805
|
+
if (!(unit > 0))
|
|
11806
|
+
return undefined;
|
|
11807
|
+
const lengths = dasharray.map((length) => Math.max(length, 0) * unit);
|
|
11808
|
+
let offset = 0;
|
|
11809
|
+
if (lengths.length % 2 === 1) {
|
|
11810
|
+
// A single length is a dash without a gap.
|
|
11811
|
+
if (lengths.length === 1)
|
|
11812
|
+
return undefined;
|
|
11813
|
+
// The last dash goes before the first: the pattern starts that far into their sum.
|
|
11814
|
+
offset = lengths.pop();
|
|
11815
|
+
lengths[0] += offset;
|
|
11816
|
+
}
|
|
11817
|
+
// Without a gap, the line is solid.
|
|
11818
|
+
const hasGap = lengths.some((length, i) => i % 2 === 1 && length > 0);
|
|
11819
|
+
return hasGap ? { lengths, offset } : undefined;
|
|
11820
|
+
}
|
|
11821
|
+
/** `offset` within one period of `dash`, for a line that starts `start` pixels in. */
|
|
11822
|
+
function dashOffset(dash, start) {
|
|
11823
|
+
const period = dash.lengths.reduce((sum, length) => sum + length, 0);
|
|
11824
|
+
const offset = (dash.offset + start) % period;
|
|
11825
|
+
return offset < 0 ? offset + period : offset;
|
|
11826
|
+
}
|
|
11827
|
+
/**
|
|
11828
|
+
* How far along a ring its first vertex is: MapLibre measures a ring from its last vertex,
|
|
11829
|
+
* so by the length of its closing segment (`LineBucket.addLine`).
|
|
11830
|
+
*/
|
|
11831
|
+
function closingLength(ring) {
|
|
11832
|
+
const first = ring[0];
|
|
11833
|
+
const last = ring[ring.length - 1];
|
|
11834
|
+
return first && last ? Math.hypot(first.x - last.x, first.y - last.y) : 0;
|
|
11835
|
+
}
|
|
11836
|
+
|
|
11752
11837
|
/**
|
|
11753
11838
|
* Geometry for drawing a line with `line-pattern`, shared by every backend: the sprite image
|
|
11754
11839
|
* repeated along the line, its height across the line's width, as MapLibre GL JS draws it
|
|
@@ -12219,9 +12304,10 @@ async function renderLineLayer(layer) {
|
|
|
12219
12304
|
if (lineStringFeatures.length === 0)
|
|
12220
12305
|
return;
|
|
12221
12306
|
const { getPaint, getLayout } = evaluateLayer(layer);
|
|
12222
|
-
// A pattern
|
|
12307
|
+
// A pattern and the dashes are scaled to the line's width at the zoom level's integer
|
|
12308
|
+
// part, as in MapLibre.
|
|
12223
12309
|
const { zoom } = layer.job.view;
|
|
12224
|
-
const atFloorZoom = layer.layerStyle.usesPattern
|
|
12310
|
+
const atFloorZoom = layer.layerStyle.usesPattern || getPaint('line-dasharray') !== undefined
|
|
12225
12311
|
? evaluateLayer(layer, Math.floor(zoom))
|
|
12226
12312
|
: undefined;
|
|
12227
12313
|
const sorted = sortByKey(lineStringFeatures, (feature) => getLayout('line-sort-key', feature));
|
|
@@ -12230,21 +12316,26 @@ async function renderLineLayer(layer) {
|
|
|
12230
12316
|
// A pattern whose image is not in the sprite draws nothing, as in MapLibre.
|
|
12231
12317
|
if (found === null)
|
|
12232
12318
|
return [];
|
|
12319
|
+
const width = getPaint('line-width', feature);
|
|
12320
|
+
const floorWidth = atFloorZoom?.getPaint('line-width', feature) ?? width;
|
|
12321
|
+
const dash = dashPattern(getPaint('line-dasharray', feature), floorWidth, zoom);
|
|
12322
|
+
const cap = getLayout('line-cap', feature);
|
|
12233
12323
|
const style = {
|
|
12234
12324
|
blur: getPaint('line-blur', feature),
|
|
12235
12325
|
color: getPaint('line-color', feature),
|
|
12236
12326
|
translate: screenTranslate(layer.job, getPaint('line-translate', feature), getPaint('line-translate-anchor', feature)),
|
|
12237
|
-
|
|
12238
|
-
|
|
12327
|
+
// MapLibre's dashes end flat unless the caps are round: with square caps, an SVG or
|
|
12328
|
+
// a canvas would lengthen every dash. (That leaves out the caps at the line's ends.)
|
|
12329
|
+
cap: dash && cap === 'square' ? 'butt' : cap,
|
|
12330
|
+
dash,
|
|
12239
12331
|
join: getLayout('line-join', feature),
|
|
12240
12332
|
miterLimit: getLayout('line-miter-limit', feature),
|
|
12241
12333
|
offset: getPaint('line-offset', feature),
|
|
12242
12334
|
opacity: getPaint('line-opacity', feature),
|
|
12243
|
-
width
|
|
12335
|
+
width,
|
|
12244
12336
|
};
|
|
12245
12337
|
if (found) {
|
|
12246
12338
|
const { sprite } = found;
|
|
12247
|
-
const floorWidth = atFloorZoom?.getPaint('line-width', feature) ?? style.width;
|
|
12248
12339
|
style.pattern = {
|
|
12249
12340
|
name: found.name,
|
|
12250
12341
|
sprite,
|
|
@@ -14045,6 +14136,10 @@ function strokeAttr(color, width) {
|
|
|
14045
14136
|
function formatScaled(v) {
|
|
14046
14137
|
return formatNum(Math.round(v * 10));
|
|
14047
14138
|
}
|
|
14139
|
+
/** How far into its dash pattern a line starts, in hundredths of a pixel. */
|
|
14140
|
+
function formatDash(v) {
|
|
14141
|
+
return (Math.round(v * 100) / 100).toString();
|
|
14142
|
+
}
|
|
14048
14143
|
function formatUnit(v) {
|
|
14049
14144
|
return (Math.round(v * 100000) / 100000).toString();
|
|
14050
14145
|
}
|
|
@@ -14690,9 +14785,9 @@ class SVGRenderer {
|
|
|
14690
14785
|
? ''
|
|
14691
14786
|
: ` transform="translate(${formatPoint(style.translate)})"`;
|
|
14692
14787
|
const roundedWidth = formatScaled(style.width);
|
|
14693
|
-
const
|
|
14694
|
-
|
|
14695
|
-
|
|
14788
|
+
const { dash } = style;
|
|
14789
|
+
// The lengths are written precisely: what is rounded off adds up along the line.
|
|
14790
|
+
const dasharrayStr = dash ? dash.lengths.map(formatUnit).join(',') : '';
|
|
14696
14791
|
// MapLibre's line-blur fades the line as the blur approaches its width
|
|
14697
14792
|
// (the feather eats into the opaque core). A Gaussian conserves total ink,
|
|
14698
14793
|
// so approximate that fade with an extra opacity factor; it tends to 1 for
|
|
@@ -14715,33 +14810,55 @@ class SVGRenderer {
|
|
|
14715
14810
|
filterAttr,
|
|
14716
14811
|
].join('\0');
|
|
14717
14812
|
const translucent = effectiveOpacity < 1 || color.alpha < 255;
|
|
14718
|
-
|
|
14719
|
-
|
|
14720
|
-
|
|
14721
|
-
|
|
14722
|
-
|
|
14723
|
-
|
|
14724
|
-
|
|
14725
|
-
|
|
14726
|
-
|
|
14727
|
-
|
|
14728
|
-
|
|
14729
|
-
|
|
14730
|
-
|
|
14731
|
-
|
|
14732
|
-
|
|
14813
|
+
// The group of paths with these attributes; `dashOffsetStr` is how far into the dash
|
|
14814
|
+
// pattern they start.
|
|
14815
|
+
const groupFor = (dashOffsetStr) => {
|
|
14816
|
+
const groupKey = key + '\0' + dashOffsetStr;
|
|
14817
|
+
if (translucent || groupKey !== currentKey) {
|
|
14818
|
+
const attrs = [
|
|
14819
|
+
'fill="none"',
|
|
14820
|
+
strokeAttr(color, roundedWidth),
|
|
14821
|
+
`stroke-linecap="${style.cap}"`,
|
|
14822
|
+
`stroke-linejoin="${style.join}"`,
|
|
14823
|
+
`stroke-miterlimit="${String(style.miterLimit)}"`,
|
|
14824
|
+
];
|
|
14825
|
+
if (dasharrayStr)
|
|
14826
|
+
attrs.push(`stroke-dasharray="${dasharrayStr}"`);
|
|
14827
|
+
if (dashOffsetStr !== '0')
|
|
14828
|
+
attrs.push(`stroke-dashoffset="${dashOffsetStr}"`);
|
|
14829
|
+
groups.push({
|
|
14830
|
+
segments: [],
|
|
14831
|
+
rings: [],
|
|
14832
|
+
attrs: attrs.join(' ') + translate + opacityAttr + filterAttr,
|
|
14833
|
+
separate: translucent,
|
|
14834
|
+
dashed: dash !== undefined,
|
|
14835
|
+
});
|
|
14836
|
+
currentKey = translucent ? undefined : groupKey;
|
|
14837
|
+
}
|
|
14838
|
+
return groups[groups.length - 1];
|
|
14839
|
+
};
|
|
14840
|
+
const { open, closed } = strokeLines(feature.geometry, feature.type === 'Polygon', style.offset);
|
|
14841
|
+
if (dash) {
|
|
14842
|
+
// Every part has its dashes where MapLibre draws them: from the start of the line
|
|
14843
|
+
// in its tile, and a ring from the start of its closing segment.
|
|
14844
|
+
open.forEach((line, i) => {
|
|
14845
|
+
const offset = formatDash(dashOffset(dash, feature.lineStarts?.[i] ?? 0));
|
|
14846
|
+
groupFor(offset).segments.push(line.map((p) => roundXY(p.x, p.y)));
|
|
14733
14847
|
});
|
|
14734
|
-
|
|
14848
|
+
for (const ring of closed) {
|
|
14849
|
+
const offset = formatDash(dashOffset(dash, closingLength(ring)));
|
|
14850
|
+
groupFor(offset).rings.push(ring.map((p) => roundXY(p.x, p.y)));
|
|
14851
|
+
}
|
|
14852
|
+
return;
|
|
14735
14853
|
}
|
|
14736
|
-
const group =
|
|
14737
|
-
const { open, closed } = strokeLines(feature.geometry, feature.type === 'Polygon', style.offset);
|
|
14854
|
+
const group = groupFor('0');
|
|
14738
14855
|
for (const line of open)
|
|
14739
14856
|
group.segments.push(line.map((p) => roundXY(p.x, p.y)));
|
|
14740
14857
|
for (const ring of closed)
|
|
14741
14858
|
group.rings.push(ring.map((p) => roundXY(p.x, p.y)));
|
|
14742
14859
|
});
|
|
14743
14860
|
this.#svg.push(`<g id="${escapeXml(id)}">`);
|
|
14744
|
-
for (const { segments, rings, attrs, separate, patterned } of groups) {
|
|
14861
|
+
for (const { segments, rings, attrs, separate, dashed, patterned } of groups) {
|
|
14745
14862
|
if (patterned) {
|
|
14746
14863
|
this.#svg.push(patterned);
|
|
14747
14864
|
continue;
|
|
@@ -14755,7 +14872,7 @@ class SVGRenderer {
|
|
|
14755
14872
|
}
|
|
14756
14873
|
continue;
|
|
14757
14874
|
}
|
|
14758
|
-
const chains = chainSegments(segments);
|
|
14875
|
+
const chains = dashed ? segments : chainSegments(segments);
|
|
14759
14876
|
const d = segmentsToPath(chains) + segmentsToPath(rings, true);
|
|
14760
14877
|
this.#svg.push(`<path d="${d}" ${attrs} />`);
|
|
14761
14878
|
}
|