@tileflow/core 0.1.0-alpha.29 → 0.1.0-alpha.30

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/build.js CHANGED
@@ -504,9 +504,9 @@ function inferTileflowDataRequirements(style, options = {}) {
504
504
  dynamicAccesses: [...dynamicAccesses].sort(compareCodeUnits),
505
505
  schemaVersion: 1,
506
506
  sourceId,
507
- sourceLayers: [...fieldsByLayer].sort(([left], [right]) => compareCodeUnits(left, right)).map(([id, fields]) => ({
507
+ sourceLayers: [...fieldsByLayer].sort(([left], [right]) => compareCodeUnits(left, right)).map(([id2, fields]) => ({
508
508
  fields: [...fields].sort(([left], [right]) => compareCodeUnits(left, right)).map(([name, type]) => ({ name, ...type === void 0 ? {} : { type } })),
509
- id
509
+ id: id2
510
510
  }))
511
511
  };
512
512
  }
@@ -880,6 +880,47 @@ var tileflowLayerSlots = [
880
880
  "vegetation",
881
881
  "symbols"
882
882
  ];
883
+ var tileflowLayerBands = [
884
+ "land",
885
+ "relief",
886
+ "water",
887
+ "roads",
888
+ "boundaries",
889
+ "buildings",
890
+ "vegetation",
891
+ "labels"
892
+ ];
893
+ var bandSlots = {
894
+ land: ["background", "land"],
895
+ relief: ["terrain", "terrain"],
896
+ water: ["hydro", "hydro"],
897
+ roads: ["transport-areas", "transport-symbols"],
898
+ boundaries: ["boundaries", "boundaries"],
899
+ buildings: ["buildings", "buildings"],
900
+ vegetation: ["vegetation", "vegetation"],
901
+ labels: ["symbols", "symbols"]
902
+ };
903
+ var tileflowBandPlacements = tileflowLayerBands.flatMap(
904
+ (band) => [`below-${band}`, `above-${band}`]
905
+ );
906
+ function tileflowBandPlacementSlot(placement) {
907
+ const [side, band] = splitBandPlacement(placement);
908
+ return bandSlots[band][side === "below" ? 0 : 1];
909
+ }
910
+ function tileflowBandPlacementIndex(slotRanks, placement) {
911
+ const [side, band] = splitBandPlacement(placement);
912
+ const [first, last] = bandSlots[band];
913
+ const threshold = side === "below" ? tileflowLayerSlots.indexOf(first) : tileflowLayerSlots.indexOf(last) + 1;
914
+ const index = slotRanks.findIndex((rank) => rank !== void 0 && rank >= threshold);
915
+ return index < 0 ? slotRanks.length : index;
916
+ }
917
+ function splitBandPlacement(placement) {
918
+ const separator = placement.indexOf("-");
919
+ return [
920
+ placement.slice(0, separator),
921
+ placement.slice(separator + 1)
922
+ ];
923
+ }
883
924
  var tileflowSemanticTargetPattern = /^[a-z][a-z0-9-]*(?:\.[A-Za-z0-9_-]+)*$/u;
884
925
  var tileflowRenderStackOperationNamePattern = /^[a-z][A-Za-z0-9_-]*$/u;
885
926
  var tileflowCompilerMetadataKeys = Object.freeze({
@@ -906,6 +947,98 @@ function typographyTextStyle(typography) {
906
947
  };
907
948
  }
908
949
 
950
+ // src/cartography/layer-style.ts
951
+ import { Color as Color2 } from "@maplibre/maplibre-gl-style-spec";
952
+
953
+ // src/text-background.ts
954
+ var tileflowLineBackgroundDefaults = Object.freeze({
955
+ letterSpacing: 0,
956
+ lineHeight: 1.2,
957
+ padding: 0.33
958
+ });
959
+ var tileflowLineBackgroundLimits = Object.freeze({
960
+ letterSpacing: Object.freeze({ max: 0.5, min: 0 }),
961
+ lineHeight: Object.freeze({ max: 3, min: 0.8 }),
962
+ padding: Object.freeze({ max: 1, min: 0 })
963
+ });
964
+ var tileflowLineBackgroundHaloRatio = 1 / 6;
965
+ var STACK_PATTERN = /^(.+) lines-v1-t(\d{1,3})-l(\d{3,4})-p(\d{1,4})$/u;
966
+ var SOURCE_PATTERN = /^[\p{L}\p{N} _.-]+$/u;
967
+ var MAX_STACK_LENGTH = 256;
968
+ function thousandths(value) {
969
+ return Math.round(value * 1e3);
970
+ }
971
+ function normalizeTileflowLineBackgroundMetrics(metrics = {}) {
972
+ const resolved = { ...tileflowLineBackgroundDefaults, ...metrics };
973
+ const normalized = {};
974
+ for (const key of ["letterSpacing", "lineHeight", "padding"]) {
975
+ const value = resolved[key];
976
+ const { max, min } = tileflowLineBackgroundLimits[key];
977
+ if (typeof value !== "number" || !Number.isFinite(value) || value < min || value > max) {
978
+ throw new RangeError(
979
+ `Line-fitted text background ${key} must be a constant number from ${min} to ${max} ems.`
980
+ );
981
+ }
982
+ normalized[key] = thousandths(value) / 1e3;
983
+ }
984
+ return normalized;
985
+ }
986
+ function createTileflowLineBackgroundFontStack(source, metrics = {}) {
987
+ if (!SOURCE_PATTERN.test(source) || source !== source.trim()) {
988
+ throw new TypeError(
989
+ `Line-fitted text backgrounds need one exact font stack of letters, numbers, spaces, underscores, dots or hyphens; received "${source}".`
990
+ );
991
+ }
992
+ if (parseTileflowLineBackgroundFontStack(source)) {
993
+ throw new TypeError(`Font stack "${source}" is already a line-fitted background stack.`);
994
+ }
995
+ const { letterSpacing, lineHeight, padding } = normalizeTileflowLineBackgroundMetrics(metrics);
996
+ const stack = `${source} lines-v1-t${thousandths(letterSpacing)}-l${thousandths(lineHeight)}-p${thousandths(padding)}`;
997
+ if (stack.length > MAX_STACK_LENGTH) {
998
+ throw new RangeError(
999
+ `Line-fitted text background font stack exceeds ${MAX_STACK_LENGTH} characters.`
1000
+ );
1001
+ }
1002
+ return stack;
1003
+ }
1004
+ function parseTileflowLineBackgroundFontStack(stack) {
1005
+ const match2 = STACK_PATTERN.exec(stack);
1006
+ if (!match2 || stack.length > MAX_STACK_LENGTH || !SOURCE_PATTERN.test(match2[1]))
1007
+ return void 0;
1008
+ const metrics = {
1009
+ letterSpacing: Number(match2[2]) / 1e3,
1010
+ lineHeight: Number(match2[3]) / 1e3,
1011
+ padding: Number(match2[4]) / 1e3
1012
+ };
1013
+ try {
1014
+ const normalized = normalizeTileflowLineBackgroundMetrics(metrics);
1015
+ if (`lines-v1-t${thousandths(normalized.letterSpacing)}-l${thousandths(normalized.lineHeight)}-p${thousandths(normalized.padding)}` !== stack.slice(match2[1].length + 1)) {
1016
+ return void 0;
1017
+ }
1018
+ return { metrics: normalized, source: match2[1] };
1019
+ } catch {
1020
+ return void 0;
1021
+ }
1022
+ }
1023
+ function scaleTileflowTextSizeForLineBackground(size) {
1024
+ const ratio = tileflowLineBackgroundHaloRatio;
1025
+ const scale = (output) => typeof output === "number" ? output * ratio : ["*", ratio, output];
1026
+ if (size === void 0) return 16 * ratio;
1027
+ if (typeof size === "number") return size * ratio;
1028
+ if (Array.isArray(size) && size[0] === "interpolate" && isZoomInput(size[2])) {
1029
+ return size.map((entry, index) => index >= 4 && index % 2 === 0 ? scale(entry) : entry);
1030
+ }
1031
+ if (Array.isArray(size) && size[0] === "step" && isZoomInput(size[1])) {
1032
+ return size.map(
1033
+ (entry, index) => index === 2 || index >= 4 && index % 2 === 0 ? scale(entry) : entry
1034
+ );
1035
+ }
1036
+ return ["*", ratio, size];
1037
+ }
1038
+ function isZoomInput(value) {
1039
+ return Array.isArray(value) && value.length === 1 && value[0] === "zoom";
1040
+ }
1041
+
909
1042
  // src/cartography/expression-operators.ts
910
1043
  var mapLibreExpressionOperators = /* @__PURE__ */ new Set([
911
1044
  "!",
@@ -1216,8 +1349,52 @@ function applyTextStyle(layer, style) {
1216
1349
  setPaint(paint, "text-halo-color", style.haloColor);
1217
1350
  setPaint(paint, "text-halo-width", style.haloWidth);
1218
1351
  setPaint(paint, "text-opacity", style.opacity);
1352
+ applyLineBackground(layer.id, layout, paint, style, backedFontStack(asRecord(layer.layout)));
1219
1353
  return applyLayerRange(withLayerParts(layer, layout, paint), style);
1220
1354
  }
1355
+ function applyLineBackground(id2, layout, paint, style, previous) {
1356
+ const fonts = layout["text-font"];
1357
+ const single = Array.isArray(fonts) && fonts.length === 1 && typeof fonts[0] === "string";
1358
+ const current = backedFontStack(layout);
1359
+ const background = style.background;
1360
+ if (!background && !current && !previous) return;
1361
+ if (!single) {
1362
+ throw new Error(
1363
+ `Layer "${id2}" needs exactly one text font without fallbacks for a line-fitted background.`
1364
+ );
1365
+ }
1366
+ const padding = background?.padding === void 0 ? (current ?? previous)?.metrics.padding ?? tileflowLineBackgroundDefaults.padding : constantNumber(id2, toMapLibreStyleValue(background.padding), "background padding");
1367
+ const stack = createTileflowLineBackgroundFontStack(current?.source ?? fonts[0], {
1368
+ letterSpacing: constantNumber(id2, layout["text-letter-spacing"] ?? 0, "letterSpacing"),
1369
+ lineHeight: constantNumber(id2, layout["text-line-height"] ?? 1.2, "lineHeight"),
1370
+ padding
1371
+ });
1372
+ const { metrics } = parseTileflowLineBackgroundFontStack(stack);
1373
+ layout["text-font"] = [stack];
1374
+ layout["text-letter-spacing"] = metrics.letterSpacing;
1375
+ layout["text-line-height"] = metrics.lineHeight;
1376
+ if (background) {
1377
+ const color2 = toMapLibreStyleValue(background.color);
1378
+ if (typeof color2 === "string" && (Color2.parse(color2)?.a ?? 1) < 1) {
1379
+ throw new Error(
1380
+ `Layer "${id2}" needs an opaque line-fitted background colour; "${color2}" is translucent.`
1381
+ );
1382
+ }
1383
+ paint["text-halo-color"] = color2;
1384
+ }
1385
+ paint["text-halo-width"] = scaleTileflowTextSizeForLineBackground(layout["text-size"]);
1386
+ paint["text-halo-blur"] = 0;
1387
+ }
1388
+ function backedFontStack(layout) {
1389
+ const fonts = layout["text-font"];
1390
+ return Array.isArray(fonts) && fonts.length === 1 && typeof fonts[0] === "string" ? parseTileflowLineBackgroundFontStack(fonts[0]) : void 0;
1391
+ }
1392
+ function constantNumber(id2, value, property) {
1393
+ if (typeof value === "number" && Number.isFinite(value)) return value;
1394
+ throw new Error(
1395
+ `Layer "${id2}" uses a line-fitted text background, so its ${property} must be a constant number.`
1396
+ );
1397
+ }
1221
1398
  function applyIconStyle(layer, style) {
1222
1399
  const paint = { ...asRecord(layer.paint) };
1223
1400
  const layout = { ...asRecord(layer.layout) };
@@ -1360,12 +1537,12 @@ function setLayout(layout, property, value) {
1360
1537
  function invertPriority(value) {
1361
1538
  const resolved = toMapLibreStyleValue(value);
1362
1539
  if (typeof resolved === "number") return -resolved;
1363
- if (Array.isArray(resolved) && resolved[0] === "interpolate" && isZoomInput(resolved[2])) {
1540
+ if (Array.isArray(resolved) && resolved[0] === "interpolate" && isZoomInput2(resolved[2])) {
1364
1541
  return resolved.map(
1365
1542
  (entry, index) => index >= 4 && index % 2 === 0 ? negateExpressionOutput(entry) : entry
1366
1543
  );
1367
1544
  }
1368
- if (Array.isArray(resolved) && resolved[0] === "step" && isZoomInput(resolved[1])) {
1545
+ if (Array.isArray(resolved) && resolved[0] === "step" && isZoomInput2(resolved[1])) {
1369
1546
  return resolved.map(
1370
1547
  (entry, index) => index === 2 || index >= 4 && index % 2 === 0 ? negateExpressionOutput(entry) : entry
1371
1548
  );
@@ -1378,7 +1555,7 @@ function resolvePriority(value, order) {
1378
1555
  function negateExpressionOutput(value) {
1379
1556
  return typeof value === "number" ? -value : ["*", -1, value];
1380
1557
  }
1381
- function isZoomInput(value) {
1558
+ function isZoomInput2(value) {
1382
1559
  return Array.isArray(value) && value.length === 1 && value[0] === "zoom";
1383
1560
  }
1384
1561
  function asRecord(value) {
@@ -1569,7 +1746,7 @@ function compileAeroways(request, context) {
1569
1746
  ["taxiway", config.taxiway, 10],
1570
1747
  ["runway", config.runway, 20]
1571
1748
  ]) {
1572
- for (const [phase, style, phaseOrder2] of [
1749
+ for (const [phase, style, phaseOrder] of [
1573
1750
  ["shadow", target?.shadow, 0],
1574
1751
  ["casing", target?.casing, 1],
1575
1752
  ["fill", target?.fill, 2]
@@ -1591,7 +1768,7 @@ function compileAeroways(request, context) {
1591
1768
  },
1592
1769
  style
1593
1770
  ),
1594
- localOrder: order + phaseOrder2,
1771
+ localOrder: order + phaseOrder,
1595
1772
  owner: "aeroways",
1596
1773
  slot: "aeroways",
1597
1774
  target: `aeroways.${name}.${phase}`
@@ -2373,14 +2550,14 @@ function compileLabels(request, roadRequest, context) {
2373
2550
  for (const placeClass of visiblePlaceClasses) {
2374
2551
  const style = styles.places?.[placeClass];
2375
2552
  if (!style || style.visible === false || style.text?.visible === false) continue;
2376
- const id = `tileflow-label-place-${placeClass}`;
2553
+ const id2 = `tileflow-label-place-${placeClass}`;
2377
2554
  result.push(
2378
2555
  symbolContribution(
2379
2556
  `labels.places.${placeClass}`,
2380
2557
  (navigationPriority ? 1300 : 800) + order++,
2381
2558
  applySymbolStyle(
2382
2559
  {
2383
- id,
2560
+ id: id2,
2384
2561
  type: "symbol",
2385
2562
  source,
2386
2563
  "source-layer": schema.layers.place,
@@ -2405,14 +2582,14 @@ function compileLabels(request, roadRequest, context) {
2405
2582
  for (const roadClass of orderedRoads) {
2406
2583
  const style = styles.roads?.[roadClass];
2407
2584
  if (!style || style.visible === false || style.text?.visible === false) continue;
2408
- const id = `tileflow-label-road-${roadClass}`;
2585
+ const id2 = `tileflow-label-road-${roadClass}`;
2409
2586
  result.push(
2410
2587
  symbolContribution(
2411
2588
  `labels.roads.${roadClass}`,
2412
2589
  (navigationPriority ? 1200 : 200) + order++,
2413
2590
  applySymbolStyle(
2414
2591
  {
2415
- id,
2592
+ id: id2,
2416
2593
  type: "symbol",
2417
2594
  source,
2418
2595
  "source-layer": schema.layers.roadName,
@@ -2424,7 +2601,7 @@ function compileLabels(request, roadRequest, context) {
2424
2601
  },
2425
2602
  mergeTileflowDesign(style, { text: { field: field2 } })
2426
2603
  ),
2427
- navigationPriority ? "symbols" : "transport-symbols",
2604
+ "symbols",
2428
2605
  { group: "roads", kind: "road-label", member: roadClass }
2429
2606
  )
2430
2607
  );
@@ -2516,8 +2693,7 @@ function compileLabels(request, roadRequest, context) {
2516
2693
  ]
2517
2694
  },
2518
2695
  style
2519
- ),
2520
- "transport-symbols"
2696
+ )
2521
2697
  )
2522
2698
  );
2523
2699
  }
@@ -2555,8 +2731,7 @@ function compileLabels(request, roadRequest, context) {
2555
2731
  ])
2556
2732
  }
2557
2733
  })
2558
- ),
2559
- "transport-symbols"
2734
+ )
2560
2735
  )
2561
2736
  );
2562
2737
  }
@@ -6608,10 +6783,10 @@ function isExpressionOutput(value) {
6608
6783
  }
6609
6784
  function rewriteZoomOutputs(value, rewrite) {
6610
6785
  if (!Array.isArray(value)) return rewrite(value);
6611
- if (value[0] === "interpolate" && isZoomInput2(value[2])) {
6786
+ if (value[0] === "interpolate" && isZoomInput3(value[2])) {
6612
6787
  return value.map((entry, index) => index >= 4 && index % 2 === 0 ? rewrite(entry) : entry);
6613
6788
  }
6614
- if (value[0] === "step" && isZoomInput2(value[1])) {
6789
+ if (value[0] === "step" && isZoomInput3(value[1])) {
6615
6790
  return value.map(
6616
6791
  (entry, index) => index === 2 || index >= 4 && index % 2 === 0 ? rewrite(entry) : entry
6617
6792
  );
@@ -6621,7 +6796,7 @@ function rewriteZoomOutputs(value, rewrite) {
6621
6796
  function expressionOutput(value) {
6622
6797
  return Array.isArray(value) && typeof value[0] !== "string" ? ["literal", value] : value;
6623
6798
  }
6624
- function isZoomInput2(value) {
6799
+ function isZoomInput3(value) {
6625
6800
  return Array.isArray(value) && value.length === 1 && value[0] === "zoom";
6626
6801
  }
6627
6802
  function restrictedAccessFilter(field2) {
@@ -8048,7 +8223,7 @@ function insertTileflowOverlays(input) {
8048
8223
  });
8049
8224
  groups.set(overlay.placement, [...groups.get(overlay.placement) ?? [], ...layers2]);
8050
8225
  identities[overlayId] = Object.freeze({
8051
- layers: Object.freeze(overlay.layers.map(({ id }) => id)),
8226
+ layers: Object.freeze(overlay.layers.map(({ id: id2 }) => id2)),
8052
8227
  placement: overlay.placement,
8053
8228
  source: overlay.source
8054
8229
  });
@@ -8085,22 +8260,13 @@ function cloneJson18(value) {
8085
8260
  );
8086
8261
  }
8087
8262
  function tileflowOverlayInsertionIndex(layers, placement) {
8088
- const threshold = {
8089
- "above-water": tileflowLayerSlots.indexOf("building-areas"),
8090
- "below-roads": tileflowLayerSlots.indexOf("transport-areas"),
8091
- "above-roads": tileflowLayerSlots.indexOf("boundaries"),
8092
- "above-buildings": tileflowLayerSlots.indexOf("vegetation"),
8093
- "below-labels": tileflowLayerSlots.indexOf("symbols"),
8094
- "above-labels": Number.POSITIVE_INFINITY
8095
- }[placement];
8096
- if (!Number.isFinite(threshold)) return layers.length;
8097
- const index = layers.findIndex((layer) => {
8098
- const ranks = readTileflowCompilerProvenance(layer).map(
8263
+ const ranks = layers.map((layer) => {
8264
+ const slots = readTileflowCompilerProvenance(layer).map(
8099
8265
  ({ slot }) => tileflowLayerSlots.indexOf(slot)
8100
8266
  );
8101
- return ranks.length > 0 && Math.min(...ranks) >= threshold;
8267
+ return slots.length > 0 ? Math.min(...slots) : void 0;
8102
8268
  });
8103
- return index < 0 ? layers.length : index;
8269
+ return tileflowBandPlacementIndex(ranks, placement);
8104
8270
  }
8105
8271
  function compareCodeUnits4(left, right) {
8106
8272
  return left < right ? -1 : left > right ? 1 : 0;
@@ -8112,7 +8278,7 @@ function isPlainRecord2(value) {
8112
8278
  }
8113
8279
 
8114
8280
  // src/resolved-map-schema.ts
8115
- import { Color as Color2 } from "@maplibre/maplibre-gl-style-spec";
8281
+ import { Color as Color3 } from "@maplibre/maplibre-gl-style-spec";
8116
8282
  import { z as z5 } from "zod";
8117
8283
 
8118
8284
  // src/cartography/data-expression.ts
@@ -8130,6 +8296,9 @@ function literal(value) {
8130
8296
  function zoom2() {
8131
8297
  return make(["zoom"]);
8132
8298
  }
8299
+ function id() {
8300
+ return make(["id"]);
8301
+ }
8133
8302
  function coalesce(first, ...rest) {
8134
8303
  if (rest.length === 0) throw new Error("expr.coalesce requires at least two values.");
8135
8304
  validateOperandCount(1 + rest.length, "expr.coalesce");
@@ -8180,6 +8349,9 @@ function subtract(first, second) {
8180
8349
  function divide(dividend, divisor) {
8181
8350
  return make(["/", node(dividend), node(divisor)]);
8182
8351
  }
8352
+ function modulo(dividend, divisor) {
8353
+ return make(["%", node(dividend), node(divisor)]);
8354
+ }
8183
8355
  function multiply(first, second, ...rest) {
8184
8356
  validateOperandCount(2 + rest.length, "expr.multiply");
8185
8357
  return make(["*", node(first), node(second), ...rest.map(node)]);
@@ -8385,6 +8557,7 @@ function visitExpression(value, path, variables, issues) {
8385
8557
  }
8386
8558
  return;
8387
8559
  }
8560
+ case "id":
8388
8561
  case "zoom": {
8389
8562
  exact(1);
8390
8563
  return;
@@ -8434,6 +8607,7 @@ function visitExpression(value, path, variables, issues) {
8434
8607
  }
8435
8608
  case "-":
8436
8609
  case "/":
8610
+ case "%":
8437
8611
  case "!=":
8438
8612
  case "<":
8439
8613
  case "<=":
@@ -8658,6 +8832,7 @@ var expr = Object.freeze({
8658
8832
  gt: (left, right) => compare(">", left, right),
8659
8833
  gte: (left, right) => compare(">=", left, right),
8660
8834
  has,
8835
+ id,
8661
8836
  featureState,
8662
8837
  interpolate: interpolate2,
8663
8838
  let: letValue,
@@ -8667,6 +8842,7 @@ var expr = Object.freeze({
8667
8842
  match,
8668
8843
  max: maximum,
8669
8844
  min: minimum,
8845
+ modulo,
8670
8846
  multiply,
8671
8847
  ne: (left, right) => compare("!=", left, right),
8672
8848
  not,
@@ -8799,7 +8975,7 @@ function createTileflowLayerFamilyIR(contribution) {
8799
8975
  function createTileflowLayerTemplateIR(layer, semanticKey) {
8800
8976
  const {
8801
8977
  filter,
8802
- id,
8978
+ id: id2,
8803
8979
  layout,
8804
8980
  maxzoom,
8805
8981
  metadata,
@@ -8810,7 +8986,7 @@ function createTileflowLayerTemplateIR(layer, semanticKey) {
8810
8986
  type,
8811
8987
  ...properties2
8812
8988
  } = layer;
8813
- if (typeof id !== "string" || !id.trim()) {
8989
+ if (typeof id2 !== "string" || !id2.trim()) {
8814
8990
  throw new TileflowDomainIRError(
8815
8991
  "TILEFLOW_DOMAIN_IR_INVALID_KEY",
8816
8992
  "Layer key must not be empty."
@@ -8825,7 +9001,7 @@ function createTileflowLayerTemplateIR(layer, semanticKey) {
8825
9001
  if (typeof type !== "string" || !type.trim()) {
8826
9002
  throw new TileflowDomainIRError(
8827
9003
  "TILEFLOW_DOMAIN_IR_INVALID_RENDERER",
8828
- `Layer family ${id} requires a renderer.`
9004
+ `Layer family ${id2} requires a renderer.`
8829
9005
  );
8830
9006
  }
8831
9007
  const annotations = publicMetadata(metadata);
@@ -9107,13 +9283,6 @@ function isRecord5(value) {
9107
9283
  }
9108
9284
 
9109
9285
  // src/cartography/render-stack.ts
9110
- var tileflowRenderStackPhases = [
9111
- "underlay",
9112
- "overlay",
9113
- "postRelief",
9114
- "annotation",
9115
- "finish"
9116
- ];
9117
9286
  var tileflowRenderStackRenderers = [
9118
9287
  "background",
9119
9288
  "circle",
@@ -9309,7 +9478,6 @@ var geometryTypes = {
9309
9478
  point: "Point",
9310
9479
  polygon: "Polygon"
9311
9480
  };
9312
- var phaseOrder = new Map(tileflowRenderStackPhases.map((phase, index) => [phase, index]));
9313
9481
  function compileRenderStack(module, data) {
9314
9482
  if (!tileflowLayerDomains.includes(module.type)) {
9315
9483
  throw renderStackError("invalid-owner", `Unknown render-stack owner: ${String(module.type)}.`);
@@ -9327,71 +9495,118 @@ function compileRenderSelector(selector, data) {
9327
9495
  }
9328
9496
  function applyCompiledRenderStacks(input, operations) {
9329
9497
  let layers = input.map(cloneJson21);
9330
- const activeOwners = new Set(layers.map(({ owner }) => owner));
9331
- const layerOperations = operations.filter(
9498
+ const passes = operations.filter(
9332
9499
  (operation) => operation.kind === "layer"
9333
9500
  );
9334
- const suppressedTargets = new Set(
9335
- layerOperations.filter(
9336
- (operation) => operation.requirements.some((requirement) => !activeOwners.has(requirement))
9337
- ).map((operation) => operation.target)
9501
+ const refinements = operations.filter(
9502
+ (operation) => operation.kind === "refinement"
9338
9503
  );
9339
- let changed = true;
9340
- while (changed) {
9504
+ requireUniqueCompiledLayers(layers, passes);
9505
+ const passTargets = new Set(passes.map(({ target }) => target));
9506
+ const knownTargets = /* @__PURE__ */ new Set([...layers.map(({ target }) => target), ...passTargets]);
9507
+ const activeOwners = new Set(layers.map(({ owner }) => owner));
9508
+ const drawn = /* @__PURE__ */ new Set();
9509
+ for (let changed = true; changed; ) {
9341
9510
  changed = false;
9342
- for (const operation of layerOperations) {
9343
- if (!suppressedTargets.has(operation.target) && suppressedTargets.has(operation.attachTo)) {
9344
- suppressedTargets.add(operation.target);
9345
- changed = true;
9346
- }
9511
+ for (const pass of passes) {
9512
+ if (drawn.has(pass.target)) continue;
9513
+ const anchor = placementAnchor(pass.placement);
9514
+ if (pass.requirements.some((requirement) => !activeOwners.has(requirement))) continue;
9515
+ if (anchor !== void 0 && passTargets.has(anchor) && !drawn.has(anchor)) continue;
9516
+ drawn.add(pass.target);
9517
+ activeOwners.add(pass.owner);
9518
+ changed = true;
9519
+ }
9520
+ }
9521
+ const activeRefinements = refinements.filter(
9522
+ (refinement) => refinement.requirements.every((requirement) => activeOwners.has(requirement))
9523
+ ).sort(compareCompiledOperations);
9524
+ for (const refinement of activeRefinements) {
9525
+ const index = findTargetIndex(layers, refinement.target);
9526
+ if (index < 0 && !passTargets.has(refinement.target)) {
9527
+ throw renderStackError(
9528
+ "unknown-refinement-target",
9529
+ `Render refinement targets unknown semantic contribution: ${refinement.target}.`
9530
+ );
9347
9531
  }
9348
- }
9349
- const pending = layerOperations.filter((operation) => !suppressedTargets.has(operation.target)).sort(compareCompiledOperations);
9350
- requireUniqueCompiledLayers(layers, pending);
9351
- while (pending.length > 0) {
9352
- const ready = pending.find((operation) => findTargetIndex(layers, operation.attachTo) >= 0);
9353
- if (!ready) {
9532
+ if (refinement.placement !== void 0 && placementAnchor(refinement.placement) === refinement.target) {
9354
9533
  throw renderStackError(
9355
9534
  "unknown-or-circular-anchor",
9356
- `Render passes target unknown or circular anchors: ${pending.map((operation) => `${operation.target}->${operation.attachTo}`).join(", ")}.`
9535
+ `Render refinement ${refinement.target} cannot be placed beside itself.`
9357
9536
  );
9358
9537
  }
9359
- const anchorTarget = ready.attachTo;
9360
- const group = pending.filter((operation) => operation.attachTo === anchorTarget).sort(compareCompiledOperations);
9361
- const anchorIndex = findTargetIndex(layers, anchorTarget);
9362
- const anchor = layers[anchorIndex];
9363
- const before = group.filter((operation) => operation.phase === "underlay").map((operation) => materializeRenderLayer(operation, anchor));
9364
- const after = group.filter((operation) => operation.phase !== "underlay").map((operation) => materializeRenderLayer(operation, anchor));
9365
- layers = [
9366
- ...layers.slice(0, anchorIndex),
9367
- ...before,
9368
- anchor,
9369
- ...after,
9370
- ...layers.slice(anchorIndex + 1)
9371
- ];
9372
- for (const operation of group) pending.splice(pending.indexOf(operation), 1);
9373
9538
  }
9374
- const refinements = operations.filter(
9375
- (operation) => operation.kind === "refinement"
9376
- ).filter(
9377
- (operation) => operation.requirements.every((requirement) => activeOwners.has(requirement))
9378
- ).sort(compareCompiledOperations);
9379
- for (const refinement of refinements) {
9380
- const index = findTargetIndex(layers, refinement.target);
9381
- if (index < 0) {
9539
+ const isDrawn = (target) => !passTargets.has(target) || drawn.has(target);
9540
+ const pending = [
9541
+ ...passes.filter(({ target }) => drawn.has(target)),
9542
+ ...activeRefinements.filter(({ placement, target }) => {
9543
+ if (placement === void 0 || !isDrawn(target)) return false;
9544
+ const anchor = placementAnchor(placement);
9545
+ return anchor === void 0 || isDrawn(anchor);
9546
+ })
9547
+ ].sort(compareCompiledOperations);
9548
+ for (const operation of pending) {
9549
+ const anchor = placementAnchor(operation.placement);
9550
+ if (anchor !== void 0 && !knownTargets.has(anchor)) {
9382
9551
  throw renderStackError(
9383
- "unknown-refinement-target",
9384
- `Render refinement targets unknown semantic contribution: ${refinement.target}.`
9552
+ "unknown-or-circular-anchor",
9553
+ `${operation.target} is placed beside unknown semantic target ${anchor}.`
9385
9554
  );
9386
9555
  }
9387
- const previous = layers[index];
9388
- const actualOwner = previous.owner;
9389
- if (actualOwner !== refinement.owner) {
9556
+ }
9557
+ const lastPlaced = /* @__PURE__ */ new Map();
9558
+ const isMovePending = (target) => pending.some((operation) => operation.kind === "refinement" && operation.target === target);
9559
+ const isReady = (operation) => {
9560
+ if (operation.kind === "refinement" && findTargetIndex(layers, operation.target) < 0) {
9561
+ return false;
9562
+ }
9563
+ const anchor = placementAnchor(operation.placement);
9564
+ return anchor === void 0 || findTargetIndex(layers, anchor) >= 0 && !isMovePending(anchor);
9565
+ };
9566
+ while (pending.length > 0) {
9567
+ const first = pending.find(isReady);
9568
+ if (first === void 0) {
9390
9569
  throw renderStackError(
9391
- "refinement-owner-mismatch",
9392
- `Render refinement ${refinement.target} belongs to ${actualOwner ?? "no owner"}, not ${refinement.owner}.`
9570
+ "unknown-or-circular-anchor",
9571
+ `Render operations are placed beside circular anchors: ${pending.map((operation) => `${operation.target}->${placementKey(operation.placement)}`).join(", ")}.`
9393
9572
  );
9394
9573
  }
9574
+ const key = placementKey(first.placement);
9575
+ const group = pending.filter(
9576
+ (operation) => placementKey(operation.placement) === key && isReady(operation)
9577
+ );
9578
+ for (const operation of group) pending.splice(pending.indexOf(operation), 1);
9579
+ const moved = /* @__PURE__ */ new Map();
9580
+ for (const operation of group) {
9581
+ if (operation.kind !== "refinement") continue;
9582
+ const index2 = findTargetIndex(layers, operation.target);
9583
+ moved.set(operation.target, requireRefinementOwner(layers[index2], operation));
9584
+ layers = [...layers.slice(0, index2), ...layers.slice(index2 + 1)];
9585
+ for (const [placed, target] of lastPlaced) {
9586
+ if (target === operation.target) lastPlaced.delete(placed);
9587
+ }
9588
+ }
9589
+ const placement = first.placement;
9590
+ const anchor = placementAnchor(placement);
9591
+ const anchorLayer = anchor === void 0 ? void 0 : layers[findTargetIndex(layers, anchor)];
9592
+ const slot = anchorLayer === void 0 ? tileflowBandPlacementSlot(placement) : requireSemanticSlot(anchorLayer, anchor);
9593
+ const block = group.map(
9594
+ (operation) => operation.kind === "layer" ? materializeRenderLayer(operation, anchorLayer, slot) : moveLayer(moved.get(operation.target), operation, slot, anchorLayer)
9595
+ );
9596
+ const previous = lastPlaced.get(key);
9597
+ const previousIndex = previous === void 0 ? -1 : findTargetIndex(layers, previous);
9598
+ const index = anchorLayer !== void 0 ? findTargetIndex(layers, anchor) + (key.startsWith("above:") ? 1 : 0) : previousIndex >= 0 ? previousIndex + 1 : tileflowBandPlacementIndex(
9599
+ layers.map((candidate) => tileflowLayerSlots.indexOf(candidate.slot)),
9600
+ placement
9601
+ );
9602
+ layers = [...layers.slice(0, index), ...block, ...layers.slice(index)];
9603
+ lastPlaced.set(key, block.at(-1).target);
9604
+ }
9605
+ for (const refinement of activeRefinements) {
9606
+ if (refinement.patch === void 0) continue;
9607
+ const index = findTargetIndex(layers, refinement.target);
9608
+ if (index < 0) continue;
9609
+ const previous = requireRefinementOwner(layers[index], refinement);
9395
9610
  const expectedType = rendererLayerType(refinement.renderer);
9396
9611
  if (previous.renderer !== expectedType) {
9397
9612
  throw renderStackError(
@@ -9399,26 +9614,48 @@ function applyCompiledRenderStacks(input, operations) {
9399
9614
  `Render refinement ${refinement.target} expects ${expectedType}, found ${String(previous.renderer)}.`
9400
9615
  );
9401
9616
  }
9402
- layers[index] = applyRefinement(previous, refinement);
9617
+ layers[index] = applyRefinement(previous, { ...refinement, patch: refinement.patch });
9403
9618
  }
9404
9619
  return layers;
9405
9620
  }
9621
+ function requireRefinementOwner(layer, refinement) {
9622
+ if (layer.owner !== refinement.owner) {
9623
+ throw renderStackError(
9624
+ "refinement-owner-mismatch",
9625
+ `Render refinement ${refinement.target} belongs to ${layer.owner ?? "no owner"}, not ${refinement.owner}.`
9626
+ );
9627
+ }
9628
+ return layer;
9629
+ }
9630
+ function placementAnchor(placement) {
9631
+ if (placement === void 0 || typeof placement === "string") return void 0;
9632
+ return placement.above ?? placement.below;
9633
+ }
9634
+ function placementKey(placement) {
9635
+ if (typeof placement === "string") return placement;
9636
+ return placement.above === void 0 ? `below:${placement.below}` : `above:${placement.above}`;
9637
+ }
9406
9638
  function compileOperation(owner, name, operation, order, data) {
9407
9639
  requireOperationName(name);
9408
9640
  if (operation.kind === "render-pass") {
9409
9641
  validateRenderPassOperation(operation);
9410
9642
  const requirements2 = normalizeRequirements(operation.requirements);
9411
- requireAttachTarget(owner, operation.attachTo, requirements2);
9643
+ requirePlacementAnchor(owner, operation.placement, requirements2);
9412
9644
  const target = `${owner}.render.${name}`;
9413
9645
  requirePortableTarget(target, "generated render target");
9646
+ if (typeof operation.placement === "string" && operation.renderer !== "background" && operation.feature === void 0) {
9647
+ throw renderStackError(
9648
+ "missing-inherited-feature",
9649
+ `Render pass ${target} is placed at ${operation.placement} and must declare its feature.`
9650
+ );
9651
+ }
9414
9652
  const template2 = compileRenderLayer(target, operation, data);
9415
9653
  return {
9416
- attachTo: operation.attachTo,
9417
9654
  kind: "layer",
9418
9655
  name,
9419
9656
  order,
9420
9657
  owner,
9421
- phase: operation.phase,
9658
+ placement: cloneJson21(operation.placement),
9422
9659
  renderer: operation.renderer,
9423
9660
  requirements: requirements2,
9424
9661
  target,
@@ -9428,6 +9665,21 @@ function compileOperation(owner, name, operation, order, data) {
9428
9665
  validateRefinementOperation(operation);
9429
9666
  requireOwnedTarget(owner, operation.target, "refinement target");
9430
9667
  const requirements = normalizeRequirements(operation.requirements);
9668
+ if (operation.placement !== void 0) {
9669
+ requirePlacementAnchor(owner, operation.placement, requirements);
9670
+ }
9671
+ const placement = operation.placement === void 0 ? {} : { placement: cloneJson21(operation.placement) };
9672
+ if (operation.renderer === void 0) {
9673
+ return {
9674
+ kind: "refinement",
9675
+ name,
9676
+ order,
9677
+ owner,
9678
+ ...placement,
9679
+ requirements,
9680
+ target: operation.target
9681
+ };
9682
+ }
9431
9683
  const template = applyRendererStyle(
9432
9684
  { id: "tileflow-render-refinement-template", type: rendererLayerType(operation.renderer) },
9433
9685
  operation.renderer,
@@ -9441,6 +9693,7 @@ function compileOperation(owner, name, operation, order, data) {
9441
9693
  order,
9442
9694
  owner,
9443
9695
  patch,
9696
+ ...placement,
9444
9697
  renderer: operation.renderer,
9445
9698
  requirements,
9446
9699
  target: operation.target
@@ -9618,26 +9871,42 @@ function requireFieldBinding(fieldName, data) {
9618
9871
  }
9619
9872
  return field(fieldName);
9620
9873
  }
9621
- function materializeRenderLayer(operation, anchor) {
9874
+ function materializeRenderLayer(operation, anchor, slot) {
9622
9875
  let template = cloneJson21(operation.template);
9623
9876
  if (operation.renderer !== "background" && template.feature?.dataSource === void 0) {
9624
- if (anchor.feature?.dataSource === void 0 || anchor.feature.dataLayer === void 0) {
9877
+ if (anchor?.feature?.dataSource === void 0 || anchor.feature.dataLayer === void 0) {
9625
9878
  throw renderStackError(
9626
9879
  "missing-inherited-feature",
9627
- `Render pass ${operation.target} has no feature and anchor ${operation.attachTo} has no vector feature to inherit.`
9880
+ `Render pass ${operation.target} has no feature and ${placementKey(operation.placement)} provides no vector feature to inherit.`
9628
9881
  );
9629
9882
  }
9630
9883
  template = { ...template, feature: cloneJson21(anchor.feature) };
9631
9884
  }
9632
- const slot = requireSemanticSlot(anchor, operation.attachTo);
9633
9885
  return materializeTileflowLayerFamilyIR(template, {
9634
9886
  operations: [{ kind: "pass", owner: operation.owner, target: operation.target }],
9635
- order: anchor.order,
9887
+ order: anchor?.order ?? 0,
9636
9888
  owner: operation.owner,
9637
9889
  slot,
9638
9890
  target: operation.target
9639
9891
  });
9640
9892
  }
9893
+ function moveLayer(layer, refinement, slot, anchor) {
9894
+ return {
9895
+ ...layer,
9896
+ order: anchor?.order ?? layer.order,
9897
+ origins: layer.origins.map(
9898
+ (origin) => origin.owner === refinement.owner && origin.target === refinement.target ? {
9899
+ ...origin,
9900
+ operations: [
9901
+ ...origin.operations,
9902
+ { kind: "refinement", owner: refinement.owner, target: refinement.target }
9903
+ ],
9904
+ slot
9905
+ } : origin
9906
+ ),
9907
+ slot
9908
+ };
9909
+ }
9641
9910
  function applyRefinement(previous, refinement) {
9642
9911
  const patch = refinement.patch;
9643
9912
  const operation = {
@@ -9653,7 +9922,8 @@ function applyRefinement(previous, refinement) {
9653
9922
  ...patch.range === void 0 ? {} : { range: { ...previous.range ?? {}, ...patch.range } },
9654
9923
  ...!Object.hasOwn(patch, "selector") ? {} : { selector: cloneJson21(patch.selector) },
9655
9924
  origins: previous.origins.map(
9656
- (origin) => origin.owner === refinement.owner && origin.target === refinement.target ? { ...origin, operations: [...origin.operations, operation] } : origin
9925
+ (origin) => origin.owner === refinement.owner && origin.target === refinement.target && // A move already recorded this refinement on the target.
9926
+ refinement.placement === void 0 ? { ...origin, operations: [...origin.operations, operation] } : origin
9657
9927
  ),
9658
9928
  style: {
9659
9929
  ...previous.style,
@@ -9684,10 +9954,34 @@ function requireRenderer(value) {
9684
9954
  throw renderStackError("invalid-renderer", `Unknown render-stack renderer: ${String(value)}.`);
9685
9955
  }
9686
9956
  }
9687
- function requirePhase(value) {
9688
- if (!tileflowRenderStackPhases.includes(value)) {
9689
- throw renderStackError("invalid-phase", `Unknown render-stack phase: ${String(value)}.`);
9957
+ function requirePlacement(value) {
9958
+ if (typeof value === "string") {
9959
+ if (tileflowBandPlacements.includes(value)) return;
9960
+ } else if (isRecord6(value)) {
9961
+ const keys = Object.keys(value);
9962
+ if (keys.length === 1 && (keys[0] === "above" || keys[0] === "below")) {
9963
+ requirePortableTarget(value[keys[0]], "render placement target");
9964
+ return;
9965
+ }
9966
+ }
9967
+ throw renderStackError(
9968
+ "invalid-placement",
9969
+ `A render placement is a band edge (${tileflowBandPlacements.join(", ")}) or {above} / {below} one semantic target: ${JSON.stringify(value)}.`
9970
+ );
9971
+ }
9972
+ function validateRefinementShape(operation) {
9973
+ if (operation.placement !== void 0) requirePlacement(operation.placement);
9974
+ if (operation.renderer === void 0) {
9975
+ if (operation.placement === void 0 || "style" in operation || "selector" in operation) {
9976
+ throw renderStackError(
9977
+ "invalid-refinement",
9978
+ "A render refinement declares a renderer with its style, a placement, or both."
9979
+ );
9980
+ }
9981
+ return;
9690
9982
  }
9983
+ requireRenderer(operation.renderer);
9984
+ validateStyle(operation.renderer, operation.style);
9691
9985
  }
9692
9986
  function validateStyle(renderer, style) {
9693
9987
  if (!isRecord6(style)) {
@@ -9807,13 +10101,15 @@ function requireRenderStackEntries(stack) {
9807
10101
  }
9808
10102
  return entries;
9809
10103
  }
9810
- function requireAttachTarget(owner, target, requirements) {
9811
- requirePortableTarget(target, "render attachment target");
10104
+ function requirePlacementAnchor(owner, placement, requirements) {
10105
+ requirePlacement(placement);
10106
+ const target = placementAnchor(placement);
10107
+ if (target === void 0) return;
9812
10108
  const targetOwner = target.split(".")[0];
9813
10109
  if (targetOwner !== owner && !requirements.includes(targetOwner)) {
9814
10110
  throw renderStackError(
9815
10111
  "cross-owner-attachment",
9816
- `Render pass owner ${owner} may attach to ${target} only with an explicit ${targetOwner} requirement.`
10112
+ `Render operation owner ${owner} may be placed beside ${target} only with an explicit ${targetOwner} requirement.`
9817
10113
  );
9818
10114
  }
9819
10115
  }
@@ -9860,18 +10156,16 @@ function requireUniqueCompiledLayers(layers, operations) {
9860
10156
  }
9861
10157
  }
9862
10158
  function compareCompiledOperations(left, right) {
9863
- const leftPhase = left.kind === "layer" ? phaseOrder.get(left.phase) : Number.MAX_SAFE_INTEGER;
9864
- const rightPhase = right.kind === "layer" ? phaseOrder.get(right.phase) : Number.MAX_SAFE_INTEGER;
9865
- return leftPhase - rightPhase || left.order - right.order || compareCodeUnits6(left.owner, right.owner) || compareCodeUnits6(left.name, right.name);
10159
+ return left.order - right.order || compareCodeUnits6(left.owner, right.owner) || compareCodeUnits6(left.name, right.name);
9866
10160
  }
9867
10161
  function validateRenderPassOperation(operation) {
9868
10162
  assertOnlyKeys(
9869
10163
  operation,
9870
- ["attachTo", "feature", "kind", "phase", "renderer", "requirements", "selector", "style"],
10164
+ ["feature", "kind", "placement", "renderer", "requirements", "selector", "style"],
9871
10165
  "render pass operation"
9872
10166
  );
9873
10167
  requireRenderer(operation.renderer);
9874
- requirePhase(operation.phase);
10168
+ requirePlacement(operation.placement);
9875
10169
  validateStyle(operation.renderer, operation.style);
9876
10170
  if (operation.renderer === "background" && ("feature" in operation || "selector" in operation)) {
9877
10171
  throw renderStackError(
@@ -9883,11 +10177,10 @@ function validateRenderPassOperation(operation) {
9883
10177
  function validateRefinementOperation(operation) {
9884
10178
  assertOnlyKeys(
9885
10179
  operation,
9886
- ["kind", "renderer", "requirements", "selector", "style", "target"],
10180
+ ["kind", "placement", "renderer", "requirements", "selector", "style", "target"],
9887
10181
  "render refinement operation"
9888
10182
  );
9889
- requireRenderer(operation.renderer);
9890
- validateStyle(operation.renderer, operation.style);
10183
+ validateRefinementShape(operation);
9891
10184
  if (operation.renderer === "background" && "selector" in operation) {
9892
10185
  throw renderStackError(
9893
10186
  "invalid-background-refinement",
@@ -10522,6 +10815,11 @@ var textStyleSchema = z5.object({
10522
10815
  "top-left",
10523
10816
  "top-right"
10524
10817
  ]).optional(),
10818
+ background: z5.object({
10819
+ color: colorValueSchema,
10820
+ fit: z5.literal("lines"),
10821
+ padding: themeNumberValueSchema.optional()
10822
+ }).strict().optional(),
10525
10823
  color: colorValueSchema.optional(),
10526
10824
  fallbacks: moduleFontFallbacksSchema.optional(),
10527
10825
  field: stringValueSchema.optional(),
@@ -10768,12 +11066,16 @@ var renderRequirementsSchema = z5.array(z5.enum(tileflowLayerDomains)).min(1).ma
10768
11066
  seen.add(requirement);
10769
11067
  }
10770
11068
  });
11069
+ var renderPlacementSchema = z5.union([
11070
+ z5.enum(tileflowBandPlacements),
11071
+ z5.object({ above: renderTargetSchema }).strict(),
11072
+ z5.object({ below: renderTargetSchema }).strict()
11073
+ ]);
10771
11074
  var vectorRenderPassSchemas = tileflowRenderStackRenderers.filter((renderer) => renderer !== "background").map(
10772
11075
  (renderer) => z5.object({
10773
- attachTo: renderTargetSchema,
10774
11076
  feature: z5.enum(tileflowSemanticLayerNames).optional(),
10775
11077
  kind: z5.literal("render-pass"),
10776
- phase: z5.enum(tileflowRenderStackPhases),
11078
+ placement: renderPlacementSchema,
10777
11079
  renderer: z5.literal(renderer),
10778
11080
  requirements: renderRequirementsSchema.optional(),
10779
11081
  selector: renderSelectorSchema.optional(),
@@ -10782,9 +11084,8 @@ var vectorRenderPassSchemas = tileflowRenderStackRenderers.filter((renderer) =>
10782
11084
  );
10783
11085
  var renderPassSchema = z5.discriminatedUnion("renderer", [
10784
11086
  z5.object({
10785
- attachTo: renderTargetSchema,
10786
11087
  kind: z5.literal("render-pass"),
10787
- phase: z5.enum(tileflowRenderStackPhases),
11088
+ placement: renderPlacementSchema,
10788
11089
  renderer: z5.literal("background"),
10789
11090
  requirements: renderRequirementsSchema.optional(),
10790
11091
  style: renderStyleSchemas.background
@@ -10794,6 +11095,7 @@ var renderPassSchema = z5.discriminatedUnion("renderer", [
10794
11095
  var renderRefinementSchemas = tileflowRenderStackRenderers.map(
10795
11096
  (renderer) => z5.object({
10796
11097
  kind: z5.literal("refine-render-target"),
11098
+ placement: renderPlacementSchema.optional(),
10797
11099
  requirements: renderRequirementsSchema.optional(),
10798
11100
  renderer: z5.literal(renderer),
10799
11101
  ...renderer === "background" ? {} : { selector: renderSelectorSchema.optional() },
@@ -10801,7 +11103,17 @@ var renderRefinementSchemas = tileflowRenderStackRenderers.map(
10801
11103
  target: renderTargetSchema
10802
11104
  }).strict()
10803
11105
  );
10804
- var renderStackOperationSchema = z5.union([renderPassSchema, ...renderRefinementSchemas]);
11106
+ var renderPlacementRefinementSchema = z5.object({
11107
+ kind: z5.literal("refine-render-target"),
11108
+ placement: renderPlacementSchema,
11109
+ requirements: renderRequirementsSchema.optional(),
11110
+ target: renderTargetSchema
11111
+ }).strict();
11112
+ var renderStackOperationSchema = z5.union([
11113
+ renderPassSchema,
11114
+ ...renderRefinementSchemas,
11115
+ renderPlacementRefinementSchema
11116
+ ]);
10805
11117
  var renderStackSchema = z5.record(renderStackOperationNameSchema, renderStackOperationSchema).superRefine((stack, context) => {
10806
11118
  const size = Object.keys(stack).length;
10807
11119
  if (size === 0) {
@@ -11643,7 +11955,7 @@ var terrainSchema = z5.union([
11643
11955
  url: z5.string().trim().min(1).optional()
11644
11956
  }).strict()
11645
11957
  ]);
11646
- var atmosphereColorLiteralSchema = z5.string().refine((value) => Color2.parse(value)?.a === 1, "Expected an opaque color");
11958
+ var atmosphereColorLiteralSchema = z5.string().refine((value) => Color3.parse(value)?.a === 1, "Expected an opaque color");
11647
11959
  var atmosphereColorSchema = z5.union([
11648
11960
  atmosphereColorLiteralSchema,
11649
11961
  fixedStringSchema.extend({ value: atmosphereColorLiteralSchema }),
@@ -12166,7 +12478,7 @@ function cloneJson22(value) {
12166
12478
  }
12167
12479
 
12168
12480
  // src/icon-appearance.ts
12169
- import { Color as Color3 } from "@maplibre/maplibre-gl-style-spec";
12481
+ import { Color as Color4 } from "@maplibre/maplibre-gl-style-spec";
12170
12482
  import { z as z7 } from "zod";
12171
12483
 
12172
12484
  // src/icon-layout.ts
@@ -12224,7 +12536,7 @@ function assertTileflowIconLayoutBounds(layout, width, height) {
12224
12536
  }
12225
12537
 
12226
12538
  // src/icon-appearance.ts
12227
- var color = z7.string().max(128).refine((value) => Color3.parse(value) !== void 0, "Expected a MapLibre color");
12539
+ var color = z7.string().max(128).refine((value) => Color4.parse(value) !== void 0, "Expected a MapLibre color");
12228
12540
  var tileflowIconAppearanceSchema = z7.object({
12229
12541
  representation: z7.literal("sdf"),
12230
12542
  defaults: z7.object({
@@ -12257,9 +12569,9 @@ function applyTileflowIconCapabilities(style, icons) {
12257
12569
  if (!icons?.appearances || !Object.keys(icons.appearances).length) return;
12258
12570
  const ids = [...icons.ids];
12259
12571
  const appearances = icons.appearances;
12260
- for (const [id, appearance] of Object.entries(appearances)) {
12261
- if (!ids.includes(id))
12262
- throw new Error(`Icon capability metadata references unknown icon "${id}".`);
12572
+ for (const [id2, appearance] of Object.entries(appearances)) {
12573
+ if (!ids.includes(id2))
12574
+ throw new Error(`Icon capability metadata references unknown icon "${id2}".`);
12263
12575
  tileflowIconAppearanceSchema.parse(appearance);
12264
12576
  }
12265
12577
  for (const layer of style.layers) {
@@ -12270,7 +12582,7 @@ function applyTileflowIconCapabilities(style, icons) {
12270
12582
  "background-pattern"
12271
12583
  ]) {
12272
12584
  const pattern = record(layer.paint)[property];
12273
- if (pattern !== void 0 && [...outputs(pattern, ids)].some((id) => appearances[id]?.representation === "sdf")) {
12585
+ if (pattern !== void 0 && [...outputs(pattern, ids)].some((id2) => appearances[id2]?.representation === "sdf")) {
12274
12586
  throw Object.assign(new Error(`Layer "${layer.id}" cannot use an SDF icon as a pattern.`), {
12275
12587
  code: "TF_ICON_PATTERN_SDF",
12276
12588
  path: `layers.${layer.id}.paint.${property}`,
@@ -12288,7 +12600,7 @@ function applyTileflowIconCapabilities(style, icons) {
12288
12600
  });
12289
12601
  }
12290
12602
  const names = [...outputs(image, ids)];
12291
- const sdf = names.filter((id) => appearances[id]?.representation === "sdf");
12603
+ const sdf = names.filter((id2) => appearances[id2]?.representation === "sdf");
12292
12604
  if (!sdf.length) continue;
12293
12605
  if (sdf.length !== names.length) {
12294
12606
  throw Object.assign(
@@ -12400,7 +12712,7 @@ function numericBounds(value) {
12400
12712
  ];
12401
12713
  }
12402
12714
  function defaultPaint(image, key, names, appearances, ids) {
12403
- const values = names.map((id) => appearances[id].defaults[key]);
12715
+ const values = names.map((id2) => appearances[id2].defaults[key]);
12404
12716
  if (new Set(values).size === 1) return values[0];
12405
12717
  if (Array.isArray(image) && (image[0] === "step" || image[0] === "interpolate")) {
12406
12718
  const copy = [...image];
@@ -12411,7 +12723,7 @@ function defaultPaint(image, key, names, appearances, ids) {
12411
12723
  return [
12412
12724
  "match",
12413
12725
  ["to-string", selection(image, ids)],
12414
- ...names.flatMap((id, i) => [id, values[i]]),
12726
+ ...names.flatMap((id2, i) => [id2, values[i]]),
12415
12727
  values[0]
12416
12728
  ];
12417
12729
  }
@@ -12421,18 +12733,18 @@ var tileflowInteractionManifestMetadataKey = "tileflow:interaction-manifest";
12421
12733
  var tileflowInteractionManifestVersion = 2;
12422
12734
  function createTileflowInteractionManifest(layers, fields) {
12423
12735
  const poiLayers = layers.flatMap((layer, priority) => {
12424
- const id = typeof layer.id === "string" ? layer.id : void 0;
12736
+ const id2 = typeof layer.id === "string" ? layer.id : void 0;
12425
12737
  const source = typeof layer.source === "string" ? layer.source : void 0;
12426
12738
  const sourceLayer = typeof layer["source-layer"] === "string" ? layer["source-layer"] : void 0;
12427
12739
  const target = semanticTarget(layer);
12428
- if (!id || !source || !sourceLayer || !target?.startsWith("poi.")) return [];
12740
+ if (!id2 || !source || !sourceLayer || !target?.startsWith("poi.")) return [];
12429
12741
  const parsed = parsePoiTarget(target);
12430
12742
  if (!parsed) return [];
12431
12743
  return [
12432
12744
  {
12433
12745
  ...parsed,
12434
12746
  anchor: "pointer-coordinate",
12435
- layerId: id,
12747
+ layerId: id2,
12436
12748
  priority,
12437
12749
  source,
12438
12750
  sourceLayer
@@ -12531,11 +12843,11 @@ function createTileflowOverlayPlacementManifest(layers) {
12531
12843
  const anchors = Object.fromEntries(
12532
12844
  tileflowOverlayPlacements.map((placement) => {
12533
12845
  const index = tileflowOverlayInsertionIndex(layers, placement);
12534
- const id = index < layers.length ? layers[index]?.id : void 0;
12535
- if (id !== void 0 && typeof id !== "string") {
12846
+ const id2 = index < layers.length ? layers[index]?.id : void 0;
12847
+ if (id2 !== void 0 && typeof id2 !== "string") {
12536
12848
  throw new Error(`Tileflow overlay placement ${placement} resolved an invalid layer ID.`);
12537
12849
  }
12538
- return [placement, id ?? null];
12850
+ return [placement, id2 ?? null];
12539
12851
  })
12540
12852
  );
12541
12853
  return {
@@ -13759,10 +14071,10 @@ function compileSemanticStyleInternal(input, options, inspect, onReport) {
13759
14071
  function finalizeTileflowLayers(input) {
13760
14072
  const ids = /* @__PURE__ */ new Set();
13761
14073
  return input.map((layer, index) => {
13762
- const id = typeof layer.id === "string" ? layer.id : "";
13763
- if (!id) throw new Error(`Compiled Tileflow layer at index ${index} has no ID.`);
13764
- if (ids.has(id)) throw new Error(`Duplicate compiled Tileflow layer ID: ${id}`);
13765
- ids.add(id);
14074
+ const id2 = typeof layer.id === "string" ? layer.id : "";
14075
+ if (!id2) throw new Error(`Compiled Tileflow layer at index ${index} has no ID.`);
14076
+ if (ids.has(id2)) throw new Error(`Duplicate compiled Tileflow layer ID: ${id2}`);
14077
+ ids.add(id2);
13766
14078
  const minimum2 = layer.minzoom;
13767
14079
  const maximum2 = layer.maxzoom;
13768
14080
  for (const [name, value] of [
@@ -13771,11 +14083,11 @@ function finalizeTileflowLayers(input) {
13771
14083
  ]) {
13772
14084
  if (value === void 0) continue;
13773
14085
  if (typeof value !== "number" || !Number.isFinite(value) || value < 0 || value > 24) {
13774
- throw new Error(`Compiled Tileflow layer ${id} has invalid ${name}: ${String(value)}`);
14086
+ throw new Error(`Compiled Tileflow layer ${id2} has invalid ${name}: ${String(value)}`);
13775
14087
  }
13776
14088
  }
13777
14089
  if (typeof minimum2 === "number" && typeof maximum2 === "number" && minimum2 > maximum2) {
13778
- throw new Error(`Compiled Tileflow layer ${id} requires minzoom <= maxzoom.`);
14090
+ throw new Error(`Compiled Tileflow layer ${id2} requires minzoom <= maxzoom.`);
13779
14091
  }
13780
14092
  const metadata = isRecord8(layer.metadata) ? { ...layer.metadata } : void 0;
13781
14093
  if (metadata) {
@@ -13819,7 +14131,7 @@ function assertPreparedIconReferences(style, config, prepared) {
13819
14131
  const references = collectStyleImageReferences(style);
13820
14132
  if (references.size === 0) return;
13821
14133
  const available = new Set(prepared?.icons?.ids ?? []);
13822
- const missing = [...references].filter((id) => !available.has(id)).sort();
14134
+ const missing = [...references].filter((id2) => !available.has(id2)).sort();
13823
14135
  if (missing.length === 0 && prepared?.icons?.sprite) return;
13824
14136
  const authoring = config.icons?.length ? "Run the Node build so map.icons directories are prepared." : "Declare map.icons with directories containing those canonical filenames.";
13825
14137
  throw new Error(
@@ -13923,7 +14235,8 @@ function assertGlyphFontStacks(style, config) {
13923
14235
  const font = layout?.["text-font"];
13924
14236
  if (!Array.isArray(font) || !font.every((entry) => typeof entry === "string")) continue;
13925
14237
  const stack = font.join(",");
13926
- if (stack && !declared.has(stack)) missing.add(stack);
14238
+ const source = parseTileflowLineBackgroundFontStack(stack)?.source ?? stack;
14239
+ if (stack && !declared.has(source)) missing.add(source);
13927
14240
  }
13928
14241
  if (missing.size > 0) {
13929
14242
  throw new Error(
@@ -13946,6 +14259,15 @@ function assertTextAssets(style, config) {
13946
14259
  }
13947
14260
  if (textLayers.length === 0) return;
13948
14261
  if (config.fonts !== void 0) {
14262
+ const backed = style.layers.find((layer) => {
14263
+ const font = isRecord8(layer.layout) ? layer.layout["text-font"] : void 0;
14264
+ return Array.isArray(font) && font.length === 1 && typeof font[0] === "string" && parseTileflowLineBackgroundFontStack(font[0]) !== void 0;
14265
+ });
14266
+ if (backed) {
14267
+ throw new Error(
14268
+ `Tileflow map "${config.id}" layer "${String(backed.id)}" uses a line-fitted text background, which needs a glyphs provider; browser-rendered fonts cannot draw its cells.`
14269
+ );
14270
+ }
13949
14271
  if (config.fonts.length > 0) return;
13950
14272
  throw new Error(
13951
14273
  `Tileflow map "${config.id}" contains text but declares an empty fonts directory array.`
@@ -14525,10 +14847,10 @@ function resolveMap(map, options = {}) {
14525
14847
  }
14526
14848
  }
14527
14849
  const leaf = lineage[0];
14528
- const id = parseMapId(leaf.id);
14850
+ const id2 = parseMapId(leaf.id);
14529
14851
  const resolved = {
14530
- id,
14531
- name: leaf.name ?? id,
14852
+ id: id2,
14853
+ name: leaf.name ?? id2,
14532
14854
  version: leaf.version,
14533
14855
  ...design,
14534
14856
  ...modules === void 0 ? {} : { modules }
@@ -15036,7 +15358,7 @@ function containsControlCharacter(value) {
15036
15358
  // src/icon-composition.ts
15037
15359
  var tileflowIconCompositionMaximumBytes = 1024 * 1024;
15038
15360
  var idsSchema = z10.array(tileflowIconIdSchema).max(tileflowIconPackageLimits.maxIconCount).refine(
15039
- (ids) => new Set(ids).size === ids.length && ids.every((id, index) => index === 0 || compareCodeUnits8(ids[index - 1], id) < 0),
15361
+ (ids) => new Set(ids).size === ids.length && ids.every((id2, index) => index === 0 || compareCodeUnits8(ids[index - 1], id2) < 0),
15040
15362
  "Icon exports must be unique and sorted by code unit"
15041
15363
  );
15042
15364
  var localContributorSchema = z10.object({ kind: z10.literal("local"), iconIds: idsSchema }).strict();
@@ -15131,7 +15453,7 @@ var tileflowIconCompositionSchema = z10.object({
15131
15453
  teams.add(contributor.teamId);
15132
15454
  teamSlugs.add(contributor.reference.split("/")[0]);
15133
15455
  }
15134
- for (const id of contributor.iconIds) expected.set(id, ordinal);
15456
+ for (const id2 of contributor.iconIds) expected.set(id2, ordinal);
15135
15457
  }
15136
15458
  if (teams.size !== 1 || teamSlugs.size !== 1)
15137
15459
  context.addIssue({
@@ -24375,9 +24697,9 @@ function validateNativeStyle(input, options, stage) {
24375
24697
  if (Object.keys(sources).length > tileflowNativeProfileLimits.maximumSources) {
24376
24698
  add2("NATIVE_UNSUPPORTED_SOURCE", "/sources");
24377
24699
  }
24378
- for (const id of Object.keys(sources).sort().slice(0, tileflowNativeProfileLimits.maximumSources)) {
24379
- const source = sources[id];
24380
- const path = nativePointer("/sources", id);
24700
+ for (const id2 of Object.keys(sources).sort().slice(0, tileflowNativeProfileLimits.maximumSources)) {
24701
+ const source = sources[id2];
24702
+ const path = nativePointer("/sources", id2);
24381
24703
  if (!record2(source) || typeof source.type !== "string" || !sourceKinds.has(source.type)) {
24382
24704
  add2("NATIVE_UNSUPPORTED_SOURCE", path);
24383
24705
  continue;
@@ -24672,8 +24994,8 @@ var teamSourcesSchema = z14.record(
24672
24994
  const entries = Object.entries(sources);
24673
24995
  const tilesets = /* @__PURE__ */ new Set();
24674
24996
  if (entries.length > 16) context.addIssue({ code: "custom", message: "Too many Team sources" });
24675
- for (const [id, value] of entries) {
24676
- if (id === "tileflow" || id.startsWith("tileflow-") || ["world", "terrain"].includes(value.tileset) || tilesets.has(value.tileset))
24997
+ for (const [id2, value] of entries) {
24998
+ if (id2 === "tileflow" || id2.startsWith("tileflow-") || ["world", "terrain"].includes(value.tileset) || tilesets.has(value.tileset))
24677
24999
  context.addIssue({ code: "custom", message: "Invalid logical Team source binding" });
24678
25000
  tilesets.add(value.tileset);
24679
25001
  }
@@ -24784,11 +25106,11 @@ async function parseTileflowRendererDeploymentArtifact(input) {
24784
25106
  function nativeValidationStyle(style, bindings) {
24785
25107
  const copy = JSON.parse(serializeCanonicalJson(style));
24786
25108
  const sources = object(copy.sources);
24787
- for (const [id, binding] of Object.entries(bindings)) {
24788
- const source = object(sources?.[id]);
24789
- if (!source || source.type !== binding.type || source.url !== `tileflow://hosted-sources/${id}` || source.tiles !== void 0)
25109
+ for (const [id2, binding] of Object.entries(bindings)) {
25110
+ const source = object(sources?.[id2]);
25111
+ if (!source || source.type !== binding.type || source.url !== `tileflow://hosted-sources/${id2}` || source.tiles !== void 0)
24790
25112
  throw new Error();
24791
- source.url = `https://artifacts.invalid/tiles/${id}/tiles.json`;
25113
+ source.url = `https://artifacts.invalid/tiles/${id2}/tiles.json`;
24792
25114
  }
24793
25115
  return copy;
24794
25116
  }
@@ -24942,7 +25264,7 @@ function createStyleFromCatalog(catalog, mapName, options = {}) {
24942
25264
  };
24943
25265
  return compileSemanticStyle(resolved, {
24944
25266
  ...options,
24945
- map: { ...metadata, lineage: metadata.lineage.map(({ id }) => id) }
25267
+ map: { ...metadata, lineage: metadata.lineage.map(({ id: id2 }) => id2) }
24946
25268
  });
24947
25269
  }
24948
25270
  function createStyleFromCatalogWithInspection(catalog, mapName, options = {}) {
@@ -24957,7 +25279,7 @@ function createStyleFromCatalogWithInspection(catalog, mapName, options = {}) {
24957
25279
  };
24958
25280
  return compileSemanticStyleWithInspection(resolved, {
24959
25281
  ...options,
24960
- map: { ...metadata, lineage: metadata.lineage.map(({ id }) => id) }
25282
+ map: { ...metadata, lineage: metadata.lineage.map(({ id: id2 }) => id2) }
24961
25283
  });
24962
25284
  }
24963
25285
  function createStylesFromCatalog(catalog, options = {}) {