@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/index.js CHANGED
@@ -2721,16 +2721,16 @@ function validateTileflowWorldV1Tilejson(tilejson, schema = tileflowWorldV1Schem
2721
2721
  }
2722
2722
  return issues;
2723
2723
  }
2724
- function requireTilejsonLayer(layers, id, issues) {
2724
+ function requireTilejsonLayer(layers, id2, issues) {
2725
2725
  const matches = layers.filter(
2726
- (layer) => typeof layer === "object" && layer !== null && layer.id === id
2726
+ (layer) => typeof layer === "object" && layer !== null && layer.id === id2
2727
2727
  );
2728
2728
  if (matches.length === 0) {
2729
- issues.push(`Tileflow World V1 requires ${id}.`);
2729
+ issues.push(`Tileflow World V1 requires ${id2}.`);
2730
2730
  return void 0;
2731
2731
  }
2732
2732
  if (matches.length !== 1) {
2733
- issues.push(`Tileflow World V1 requires exactly one ${id} layer.`);
2733
+ issues.push(`Tileflow World V1 requires exactly one ${id2} layer.`);
2734
2734
  return void 0;
2735
2735
  }
2736
2736
  return matches[0];
@@ -3100,9 +3100,9 @@ function inferTileflowDataRequirements(style, options = {}) {
3100
3100
  dynamicAccesses: [...dynamicAccesses].sort(compareCodeUnits),
3101
3101
  schemaVersion: 1,
3102
3102
  sourceId,
3103
- sourceLayers: [...fieldsByLayer].sort(([left], [right]) => compareCodeUnits(left, right)).map(([id, fields]) => ({
3103
+ sourceLayers: [...fieldsByLayer].sort(([left], [right]) => compareCodeUnits(left, right)).map(([id2, fields]) => ({
3104
3104
  fields: [...fields].sort(([left], [right]) => compareCodeUnits(left, right)).map(([name, type]) => ({ name, ...type === void 0 ? {} : { type } })),
3105
- id
3105
+ id: id2
3106
3106
  }))
3107
3107
  };
3108
3108
  }
@@ -3479,6 +3479,47 @@ var tileflowLayerSlots = [
3479
3479
  "vegetation",
3480
3480
  "symbols"
3481
3481
  ];
3482
+ var tileflowLayerBands = [
3483
+ "land",
3484
+ "relief",
3485
+ "water",
3486
+ "roads",
3487
+ "boundaries",
3488
+ "buildings",
3489
+ "vegetation",
3490
+ "labels"
3491
+ ];
3492
+ var bandSlots = {
3493
+ land: ["background", "land"],
3494
+ relief: ["terrain", "terrain"],
3495
+ water: ["hydro", "hydro"],
3496
+ roads: ["transport-areas", "transport-symbols"],
3497
+ boundaries: ["boundaries", "boundaries"],
3498
+ buildings: ["buildings", "buildings"],
3499
+ vegetation: ["vegetation", "vegetation"],
3500
+ labels: ["symbols", "symbols"]
3501
+ };
3502
+ var tileflowBandPlacements = tileflowLayerBands.flatMap(
3503
+ (band) => [`below-${band}`, `above-${band}`]
3504
+ );
3505
+ function tileflowBandPlacementSlot(placement) {
3506
+ const [side, band] = splitBandPlacement(placement);
3507
+ return bandSlots[band][side === "below" ? 0 : 1];
3508
+ }
3509
+ function tileflowBandPlacementIndex(slotRanks, placement) {
3510
+ const [side, band] = splitBandPlacement(placement);
3511
+ const [first, last] = bandSlots[band];
3512
+ const threshold = side === "below" ? tileflowLayerSlots.indexOf(first) : tileflowLayerSlots.indexOf(last) + 1;
3513
+ const index = slotRanks.findIndex((rank) => rank !== void 0 && rank >= threshold);
3514
+ return index < 0 ? slotRanks.length : index;
3515
+ }
3516
+ function splitBandPlacement(placement) {
3517
+ const separator = placement.indexOf("-");
3518
+ return [
3519
+ placement.slice(0, separator),
3520
+ placement.slice(separator + 1)
3521
+ ];
3522
+ }
3482
3523
  var tileflowSemanticTargetPattern = /^[a-z][a-z0-9-]*(?:\.[A-Za-z0-9_-]+)*$/u;
3483
3524
  var tileflowRenderStackOperationNamePattern = /^[a-z][A-Za-z0-9_-]*$/u;
3484
3525
  var tileflowCompilerMetadataKeys = Object.freeze({
@@ -3505,6 +3546,332 @@ function typographyTextStyle(typography) {
3505
3546
  };
3506
3547
  }
3507
3548
 
3549
+ // src/cartography/layer-style.ts
3550
+ import { Color as Color2 } from "@maplibre/maplibre-gl-style-spec";
3551
+
3552
+ // src/text-background.ts
3553
+ var tileflowLineBackgroundDefaults = Object.freeze({
3554
+ letterSpacing: 0,
3555
+ lineHeight: 1.2,
3556
+ padding: 0.33
3557
+ });
3558
+ var tileflowLineBackgroundLimits = Object.freeze({
3559
+ letterSpacing: Object.freeze({ max: 0.5, min: 0 }),
3560
+ lineHeight: Object.freeze({ max: 3, min: 0.8 }),
3561
+ padding: Object.freeze({ max: 1, min: 0 })
3562
+ });
3563
+ var tileflowLineBackgroundHaloRatio = 1 / 6;
3564
+ var STACK_PATTERN = /^(.+) lines-v1-t(\d{1,3})-l(\d{3,4})-p(\d{1,4})$/u;
3565
+ var SOURCE_PATTERN = /^[\p{L}\p{N} _.-]+$/u;
3566
+ var MAX_STACK_LENGTH = 256;
3567
+ var BORDER = 3;
3568
+ var TOP_OF_BITMAP = -20;
3569
+ var STRIP_CENTER = 0.5;
3570
+ var CELL_VALUE = 128;
3571
+ var CELL_EDGE = 64;
3572
+ var CELL_SLOPE = 64;
3573
+ var CELL_OVERLAP = 0.75;
3574
+ var MAX_GLYPH_SIDE = 255;
3575
+ function thousandths(value) {
3576
+ return Math.round(value * 1e3);
3577
+ }
3578
+ function normalizeTileflowLineBackgroundMetrics(metrics = {}) {
3579
+ const resolved = { ...tileflowLineBackgroundDefaults, ...metrics };
3580
+ const normalized = {};
3581
+ for (const key of ["letterSpacing", "lineHeight", "padding"]) {
3582
+ const value = resolved[key];
3583
+ const { max, min } = tileflowLineBackgroundLimits[key];
3584
+ if (typeof value !== "number" || !Number.isFinite(value) || value < min || value > max) {
3585
+ throw new RangeError(
3586
+ `Line-fitted text background ${key} must be a constant number from ${min} to ${max} ems.`
3587
+ );
3588
+ }
3589
+ normalized[key] = thousandths(value) / 1e3;
3590
+ }
3591
+ return normalized;
3592
+ }
3593
+ function createTileflowLineBackgroundFontStack(source, metrics = {}) {
3594
+ if (!SOURCE_PATTERN.test(source) || source !== source.trim()) {
3595
+ throw new TypeError(
3596
+ `Line-fitted text backgrounds need one exact font stack of letters, numbers, spaces, underscores, dots or hyphens; received "${source}".`
3597
+ );
3598
+ }
3599
+ if (parseTileflowLineBackgroundFontStack(source)) {
3600
+ throw new TypeError(`Font stack "${source}" is already a line-fitted background stack.`);
3601
+ }
3602
+ const { letterSpacing, lineHeight, padding } = normalizeTileflowLineBackgroundMetrics(metrics);
3603
+ const stack = `${source} lines-v1-t${thousandths(letterSpacing)}-l${thousandths(lineHeight)}-p${thousandths(padding)}`;
3604
+ if (stack.length > MAX_STACK_LENGTH) {
3605
+ throw new RangeError(
3606
+ `Line-fitted text background font stack exceeds ${MAX_STACK_LENGTH} characters.`
3607
+ );
3608
+ }
3609
+ return stack;
3610
+ }
3611
+ function parseTileflowLineBackgroundFontStack(stack) {
3612
+ const match2 = STACK_PATTERN.exec(stack);
3613
+ if (!match2 || stack.length > MAX_STACK_LENGTH || !SOURCE_PATTERN.test(match2[1]))
3614
+ return void 0;
3615
+ const metrics = {
3616
+ letterSpacing: Number(match2[2]) / 1e3,
3617
+ lineHeight: Number(match2[3]) / 1e3,
3618
+ padding: Number(match2[4]) / 1e3
3619
+ };
3620
+ try {
3621
+ const normalized = normalizeTileflowLineBackgroundMetrics(metrics);
3622
+ if (`lines-v1-t${thousandths(normalized.letterSpacing)}-l${thousandths(normalized.lineHeight)}-p${thousandths(normalized.padding)}` !== stack.slice(match2[1].length + 1)) {
3623
+ return void 0;
3624
+ }
3625
+ return { metrics: normalized, source: match2[1] };
3626
+ } catch {
3627
+ return void 0;
3628
+ }
3629
+ }
3630
+ function scaleTileflowTextSizeForLineBackground(size) {
3631
+ const ratio = tileflowLineBackgroundHaloRatio;
3632
+ const scale = (output) => typeof output === "number" ? output * ratio : ["*", ratio, output];
3633
+ if (size === void 0) return 16 * ratio;
3634
+ if (typeof size === "number") return size * ratio;
3635
+ if (Array.isArray(size) && size[0] === "interpolate" && isZoomInput(size[2])) {
3636
+ return size.map((entry, index) => index >= 4 && index % 2 === 0 ? scale(entry) : entry);
3637
+ }
3638
+ if (Array.isArray(size) && size[0] === "step" && isZoomInput(size[1])) {
3639
+ return size.map(
3640
+ (entry, index) => index === 2 || index >= 4 && index % 2 === 0 ? scale(entry) : entry
3641
+ );
3642
+ }
3643
+ return ["*", ratio, size];
3644
+ }
3645
+ function isZoomInput(value) {
3646
+ return Array.isArray(value) && value.length === 1 && value[0] === "zoom";
3647
+ }
3648
+ function deriveTileflowLineBackgroundGlyphs(range2, metrics = {}) {
3649
+ const normalized = normalizeTileflowLineBackgroundMetrics(metrics);
3650
+ const stacks = decodeGlyphStacks(range2);
3651
+ return encodeGlyphStacks(
3652
+ stacks.map((stack) => ({
3653
+ ...stack,
3654
+ glyphs: stack.glyphs.map((glyph) => deriveGlyph(glyph, normalized))
3655
+ }))
3656
+ );
3657
+ }
3658
+ function deriveGlyph(glyph, metrics) {
3659
+ const letterSpacing = metrics.letterSpacing * 24;
3660
+ const padding = metrics.padding * 24;
3661
+ const height = metrics.lineHeight * 24;
3662
+ const cell = {
3663
+ x0: -letterSpacing / 2 - padding - CELL_OVERLAP,
3664
+ x1: glyph.advance + letterSpacing / 2 + padding + CELL_OVERLAP,
3665
+ y0: STRIP_CENTER - height / 2 - CELL_OVERLAP,
3666
+ y1: STRIP_CENTER + height / 2 + CELL_OVERLAP
3667
+ };
3668
+ const ramp = Math.ceil(CELL_EDGE / CELL_SLOPE) + 1;
3669
+ const hasBitmap = glyph.width > 0 && glyph.height > 0 && glyph.bitmap.length > 0;
3670
+ const sourceWidth = glyph.width + 2 * BORDER;
3671
+ const sourceHeight = glyph.height + 2 * BORDER;
3672
+ const sourceX = glyph.left - BORDER;
3673
+ const sourceY = TOP_OF_BITMAP - glyph.top;
3674
+ if (hasBitmap && glyph.bitmap.length !== sourceWidth * sourceHeight) {
3675
+ throw new RangeError(`Glyph ${glyph.id} has a bitmap that does not match its metrics.`);
3676
+ }
3677
+ let x0 = Math.floor(cell.x0 - ramp);
3678
+ let x1 = Math.ceil(cell.x1 + ramp);
3679
+ let y0 = Math.floor(cell.y0 - ramp);
3680
+ let y1 = Math.ceil(cell.y1 + ramp);
3681
+ if (hasBitmap) {
3682
+ x0 = Math.min(x0, sourceX);
3683
+ x1 = Math.max(x1, sourceX + sourceWidth);
3684
+ y0 = Math.min(y0, sourceY);
3685
+ y1 = Math.max(y1, sourceY + sourceHeight);
3686
+ }
3687
+ const width = x1 - x0;
3688
+ const rows = y1 - y0;
3689
+ if (width - 2 * BORDER > MAX_GLYPH_SIDE || rows - 2 * BORDER > MAX_GLYPH_SIDE) {
3690
+ throw new RangeError(`Glyph ${glyph.id} would exceed the derived glyph size limit.`);
3691
+ }
3692
+ const bitmap = new Uint8Array(width * rows);
3693
+ for (let row = 0; row < rows; row++) {
3694
+ const y = y0 + row + 0.5;
3695
+ for (let column = 0; column < width; column++) {
3696
+ const x = x0 + column + 0.5;
3697
+ const distance = Math.min(x - cell.x0, cell.x1 - x, y - cell.y0, cell.y1 - y);
3698
+ const background = Math.min(CELL_VALUE, Math.max(0, CELL_EDGE + CELL_SLOPE * distance));
3699
+ let letter = 0;
3700
+ if (hasBitmap) {
3701
+ const sourceColumn = x0 + column - sourceX;
3702
+ const sourceRow = y0 + row - sourceY;
3703
+ if (sourceColumn >= 0 && sourceColumn < sourceWidth && sourceRow >= 0 && sourceRow < sourceHeight) {
3704
+ letter = glyph.bitmap[sourceRow * sourceWidth + sourceColumn];
3705
+ }
3706
+ }
3707
+ bitmap[row * width + column] = Math.round(Math.max(letter, background));
3708
+ }
3709
+ }
3710
+ return {
3711
+ advance: glyph.advance,
3712
+ bitmap,
3713
+ height: rows - 2 * BORDER,
3714
+ id: glyph.id,
3715
+ left: x0 + BORDER,
3716
+ top: TOP_OF_BITMAP - y0,
3717
+ width: width - 2 * BORDER
3718
+ };
3719
+ }
3720
+ var GlyphReader = class {
3721
+ constructor(bytes) {
3722
+ this.bytes = bytes;
3723
+ }
3724
+ bytes;
3725
+ position = 0;
3726
+ fields(read) {
3727
+ while (this.position < this.bytes.length) {
3728
+ const key = this.varint();
3729
+ if (!read(Math.floor(key / 8), this)) this.skip(key % 8);
3730
+ }
3731
+ }
3732
+ varint() {
3733
+ let result = 0;
3734
+ let shift = 0;
3735
+ let byte;
3736
+ do {
3737
+ if (this.position >= this.bytes.length || shift > 49) {
3738
+ throw new RangeError("Invalid glyph PBF varint.");
3739
+ }
3740
+ byte = this.bytes[this.position++];
3741
+ result += (byte & 127) * 2 ** shift;
3742
+ shift += 7;
3743
+ } while (byte & 128);
3744
+ return result;
3745
+ }
3746
+ signed() {
3747
+ const value = this.varint();
3748
+ return value % 2 === 1 ? (value + 1) / -2 : value / 2;
3749
+ }
3750
+ message() {
3751
+ const length = this.varint();
3752
+ const end = this.position + length;
3753
+ if (end > this.bytes.length) throw new RangeError("Truncated glyph PBF field.");
3754
+ const value = this.bytes.subarray(this.position, end);
3755
+ this.position = end;
3756
+ return value;
3757
+ }
3758
+ skip(wire) {
3759
+ if (wire === 0) this.varint();
3760
+ else if (wire === 1) this.position += 8;
3761
+ else if (wire === 2) this.message();
3762
+ else if (wire === 5) this.position += 4;
3763
+ else throw new RangeError(`Unsupported glyph PBF wire type ${wire}.`);
3764
+ if (this.position > this.bytes.length) throw new RangeError("Truncated glyph PBF field.");
3765
+ }
3766
+ };
3767
+ function decodeGlyphStacks(bytes) {
3768
+ const stacks = [];
3769
+ const text = new TextDecoder();
3770
+ new GlyphReader(bytes).fields((tag, reader) => {
3771
+ if (tag !== 1) return false;
3772
+ const stack = { glyphs: [], name: "", range: "" };
3773
+ new GlyphReader(reader.message()).fields((stackTag, stackReader) => {
3774
+ if (stackTag === 1) stack.name = text.decode(stackReader.message());
3775
+ else if (stackTag === 2) stack.range = text.decode(stackReader.message());
3776
+ else if (stackTag === 3) stack.glyphs.push(decodeGlyph(stackReader.message()));
3777
+ else return false;
3778
+ return true;
3779
+ });
3780
+ stacks.push(stack);
3781
+ return true;
3782
+ });
3783
+ return stacks;
3784
+ }
3785
+ function decodeGlyph(bytes) {
3786
+ const glyph = {
3787
+ advance: 0,
3788
+ bitmap: new Uint8Array(0),
3789
+ height: 0,
3790
+ id: 0,
3791
+ left: 0,
3792
+ top: 0,
3793
+ width: 0
3794
+ };
3795
+ new GlyphReader(bytes).fields((tag, reader) => {
3796
+ if (tag === 1) glyph.id = reader.varint();
3797
+ else if (tag === 2) glyph.bitmap = reader.message().slice();
3798
+ else if (tag === 3) glyph.width = reader.varint();
3799
+ else if (tag === 4) glyph.height = reader.varint();
3800
+ else if (tag === 5) glyph.left = reader.signed();
3801
+ else if (tag === 6) glyph.top = reader.signed();
3802
+ else if (tag === 7) glyph.advance = reader.varint();
3803
+ else return false;
3804
+ return true;
3805
+ });
3806
+ return glyph;
3807
+ }
3808
+ var GlyphWriter = class {
3809
+ parts = [];
3810
+ length = 0;
3811
+ varint(value) {
3812
+ const bytes = [];
3813
+ let remaining = value;
3814
+ while (remaining >= 128) {
3815
+ bytes.push(remaining % 128 | 128);
3816
+ remaining = Math.floor(remaining / 128);
3817
+ }
3818
+ bytes.push(remaining);
3819
+ this.raw(Uint8Array.from(bytes));
3820
+ }
3821
+ signed(value) {
3822
+ this.varint(value < 0 ? -2 * value - 1 : 2 * value);
3823
+ }
3824
+ key(tag, wire) {
3825
+ this.varint(tag * 8 + wire);
3826
+ }
3827
+ message(tag, bytes) {
3828
+ this.key(tag, 2);
3829
+ this.varint(bytes.length);
3830
+ this.raw(bytes);
3831
+ }
3832
+ finish() {
3833
+ const output = new Uint8Array(this.length);
3834
+ let offset = 0;
3835
+ for (const part of this.parts) {
3836
+ output.set(part, offset);
3837
+ offset += part.length;
3838
+ }
3839
+ return output;
3840
+ }
3841
+ raw(bytes) {
3842
+ this.parts.push(bytes);
3843
+ this.length += bytes.length;
3844
+ }
3845
+ };
3846
+ function encodeGlyphStacks(stacks) {
3847
+ const text = new TextEncoder();
3848
+ const root = new GlyphWriter();
3849
+ for (const stack of stacks) {
3850
+ const writer = new GlyphWriter();
3851
+ writer.message(1, text.encode(stack.name));
3852
+ writer.message(2, text.encode(stack.range));
3853
+ for (const glyph of stack.glyphs) {
3854
+ const glyphWriter = new GlyphWriter();
3855
+ glyphWriter.key(1, 0);
3856
+ glyphWriter.varint(glyph.id);
3857
+ if (glyph.bitmap.length > 0) glyphWriter.message(2, glyph.bitmap);
3858
+ glyphWriter.key(3, 0);
3859
+ glyphWriter.varint(glyph.width);
3860
+ glyphWriter.key(4, 0);
3861
+ glyphWriter.varint(glyph.height);
3862
+ glyphWriter.key(5, 0);
3863
+ glyphWriter.signed(glyph.left);
3864
+ glyphWriter.key(6, 0);
3865
+ glyphWriter.signed(glyph.top);
3866
+ glyphWriter.key(7, 0);
3867
+ glyphWriter.varint(glyph.advance);
3868
+ writer.message(3, glyphWriter.finish());
3869
+ }
3870
+ root.message(1, writer.finish());
3871
+ }
3872
+ return root.finish();
3873
+ }
3874
+
3508
3875
  // src/cartography/layer-style.ts
3509
3876
  init_values();
3510
3877
  function applyBackgroundStyle(layer, style) {
@@ -3584,8 +3951,52 @@ function applyTextStyle(layer, style) {
3584
3951
  setPaint(paint, "text-halo-color", style.haloColor);
3585
3952
  setPaint(paint, "text-halo-width", style.haloWidth);
3586
3953
  setPaint(paint, "text-opacity", style.opacity);
3954
+ applyLineBackground(layer.id, layout, paint, style, backedFontStack(asRecord2(layer.layout)));
3587
3955
  return applyLayerRange(withLayerParts(layer, layout, paint), style);
3588
3956
  }
3957
+ function applyLineBackground(id2, layout, paint, style, previous) {
3958
+ const fonts = layout["text-font"];
3959
+ const single = Array.isArray(fonts) && fonts.length === 1 && typeof fonts[0] === "string";
3960
+ const current = backedFontStack(layout);
3961
+ const background = style.background;
3962
+ if (!background && !current && !previous) return;
3963
+ if (!single) {
3964
+ throw new Error(
3965
+ `Layer "${id2}" needs exactly one text font without fallbacks for a line-fitted background.`
3966
+ );
3967
+ }
3968
+ const padding = background?.padding === void 0 ? (current ?? previous)?.metrics.padding ?? tileflowLineBackgroundDefaults.padding : constantNumber(id2, toMapLibreStyleValue(background.padding), "background padding");
3969
+ const stack = createTileflowLineBackgroundFontStack(current?.source ?? fonts[0], {
3970
+ letterSpacing: constantNumber(id2, layout["text-letter-spacing"] ?? 0, "letterSpacing"),
3971
+ lineHeight: constantNumber(id2, layout["text-line-height"] ?? 1.2, "lineHeight"),
3972
+ padding
3973
+ });
3974
+ const { metrics } = parseTileflowLineBackgroundFontStack(stack);
3975
+ layout["text-font"] = [stack];
3976
+ layout["text-letter-spacing"] = metrics.letterSpacing;
3977
+ layout["text-line-height"] = metrics.lineHeight;
3978
+ if (background) {
3979
+ const color3 = toMapLibreStyleValue(background.color);
3980
+ if (typeof color3 === "string" && (Color2.parse(color3)?.a ?? 1) < 1) {
3981
+ throw new Error(
3982
+ `Layer "${id2}" needs an opaque line-fitted background colour; "${color3}" is translucent.`
3983
+ );
3984
+ }
3985
+ paint["text-halo-color"] = color3;
3986
+ }
3987
+ paint["text-halo-width"] = scaleTileflowTextSizeForLineBackground(layout["text-size"]);
3988
+ paint["text-halo-blur"] = 0;
3989
+ }
3990
+ function backedFontStack(layout) {
3991
+ const fonts = layout["text-font"];
3992
+ return Array.isArray(fonts) && fonts.length === 1 && typeof fonts[0] === "string" ? parseTileflowLineBackgroundFontStack(fonts[0]) : void 0;
3993
+ }
3994
+ function constantNumber(id2, value, property) {
3995
+ if (typeof value === "number" && Number.isFinite(value)) return value;
3996
+ throw new Error(
3997
+ `Layer "${id2}" uses a line-fitted text background, so its ${property} must be a constant number.`
3998
+ );
3999
+ }
3589
4000
  function applyIconStyle(layer, style) {
3590
4001
  const paint = { ...asRecord2(layer.paint) };
3591
4002
  const layout = { ...asRecord2(layer.layout) };
@@ -3728,12 +4139,12 @@ function setLayout(layout, property, value) {
3728
4139
  function invertPriority(value) {
3729
4140
  const resolved = toMapLibreStyleValue(value);
3730
4141
  if (typeof resolved === "number") return -resolved;
3731
- if (Array.isArray(resolved) && resolved[0] === "interpolate" && isZoomInput(resolved[2])) {
4142
+ if (Array.isArray(resolved) && resolved[0] === "interpolate" && isZoomInput2(resolved[2])) {
3732
4143
  return resolved.map(
3733
4144
  (entry, index) => index >= 4 && index % 2 === 0 ? negateExpressionOutput(entry) : entry
3734
4145
  );
3735
4146
  }
3736
- if (Array.isArray(resolved) && resolved[0] === "step" && isZoomInput(resolved[1])) {
4147
+ if (Array.isArray(resolved) && resolved[0] === "step" && isZoomInput2(resolved[1])) {
3737
4148
  return resolved.map(
3738
4149
  (entry, index) => index === 2 || index >= 4 && index % 2 === 0 ? negateExpressionOutput(entry) : entry
3739
4150
  );
@@ -3746,7 +4157,7 @@ function resolvePriority(value, order) {
3746
4157
  function negateExpressionOutput(value) {
3747
4158
  return typeof value === "number" ? -value : ["*", -1, value];
3748
4159
  }
3749
- function isZoomInput(value) {
4160
+ function isZoomInput2(value) {
3750
4161
  return Array.isArray(value) && value.length === 1 && value[0] === "zoom";
3751
4162
  }
3752
4163
  function asRecord2(value) {
@@ -3940,7 +4351,7 @@ function compileAeroways(request, context) {
3940
4351
  ["taxiway", config.taxiway, 10],
3941
4352
  ["runway", config.runway, 20]
3942
4353
  ]) {
3943
- for (const [phase, style, phaseOrder2] of [
4354
+ for (const [phase, style, phaseOrder] of [
3944
4355
  ["shadow", target?.shadow, 0],
3945
4356
  ["casing", target?.casing, 1],
3946
4357
  ["fill", target?.fill, 2]
@@ -3962,7 +4373,7 @@ function compileAeroways(request, context) {
3962
4373
  },
3963
4374
  style
3964
4375
  ),
3965
- localOrder: order + phaseOrder2,
4376
+ localOrder: order + phaseOrder,
3966
4377
  owner: "aeroways",
3967
4378
  slot: "aeroways",
3968
4379
  target: `aeroways.${name}.${phase}`
@@ -4748,14 +5159,14 @@ function compileLabels(request, roadRequest, context) {
4748
5159
  for (const placeClass of visiblePlaceClasses) {
4749
5160
  const style = styles.places?.[placeClass];
4750
5161
  if (!style || style.visible === false || style.text?.visible === false) continue;
4751
- const id = `tileflow-label-place-${placeClass}`;
5162
+ const id2 = `tileflow-label-place-${placeClass}`;
4752
5163
  result.push(
4753
5164
  symbolContribution(
4754
5165
  `labels.places.${placeClass}`,
4755
5166
  (navigationPriority ? 1300 : 800) + order++,
4756
5167
  applySymbolStyle(
4757
5168
  {
4758
- id,
5169
+ id: id2,
4759
5170
  type: "symbol",
4760
5171
  source,
4761
5172
  "source-layer": schema.layers.place,
@@ -4780,14 +5191,14 @@ function compileLabels(request, roadRequest, context) {
4780
5191
  for (const roadClass of orderedRoads) {
4781
5192
  const style = styles.roads?.[roadClass];
4782
5193
  if (!style || style.visible === false || style.text?.visible === false) continue;
4783
- const id = `tileflow-label-road-${roadClass}`;
5194
+ const id2 = `tileflow-label-road-${roadClass}`;
4784
5195
  result.push(
4785
5196
  symbolContribution(
4786
5197
  `labels.roads.${roadClass}`,
4787
5198
  (navigationPriority ? 1200 : 200) + order++,
4788
5199
  applySymbolStyle(
4789
5200
  {
4790
- id,
5201
+ id: id2,
4791
5202
  type: "symbol",
4792
5203
  source,
4793
5204
  "source-layer": schema.layers.roadName,
@@ -4799,7 +5210,7 @@ function compileLabels(request, roadRequest, context) {
4799
5210
  },
4800
5211
  mergeTileflowDesign(style, { text: { field: field2 } })
4801
5212
  ),
4802
- navigationPriority ? "symbols" : "transport-symbols",
5213
+ "symbols",
4803
5214
  { group: "roads", kind: "road-label", member: roadClass }
4804
5215
  )
4805
5216
  );
@@ -4891,8 +5302,7 @@ function compileLabels(request, roadRequest, context) {
4891
5302
  ]
4892
5303
  },
4893
5304
  style
4894
- ),
4895
- "transport-symbols"
5305
+ )
4896
5306
  )
4897
5307
  );
4898
5308
  }
@@ -4930,8 +5340,7 @@ function compileLabels(request, roadRequest, context) {
4930
5340
  ])
4931
5341
  }
4932
5342
  })
4933
- ),
4934
- "transport-symbols"
5343
+ )
4935
5344
  )
4936
5345
  );
4937
5346
  }
@@ -7734,10 +8143,10 @@ function isExpressionOutput(value) {
7734
8143
  }
7735
8144
  function rewriteZoomOutputs(value, rewrite) {
7736
8145
  if (!Array.isArray(value)) return rewrite(value);
7737
- if (value[0] === "interpolate" && isZoomInput2(value[2])) {
8146
+ if (value[0] === "interpolate" && isZoomInput3(value[2])) {
7738
8147
  return value.map((entry, index) => index >= 4 && index % 2 === 0 ? rewrite(entry) : entry);
7739
8148
  }
7740
- if (value[0] === "step" && isZoomInput2(value[1])) {
8149
+ if (value[0] === "step" && isZoomInput3(value[1])) {
7741
8150
  return value.map(
7742
8151
  (entry, index) => index === 2 || index >= 4 && index % 2 === 0 ? rewrite(entry) : entry
7743
8152
  );
@@ -7747,7 +8156,7 @@ function rewriteZoomOutputs(value, rewrite) {
7747
8156
  function expressionOutput(value) {
7748
8157
  return Array.isArray(value) && typeof value[0] !== "string" ? ["literal", value] : value;
7749
8158
  }
7750
- function isZoomInput2(value) {
8159
+ function isZoomInput3(value) {
7751
8160
  return Array.isArray(value) && value.length === 1 && value[0] === "zoom";
7752
8161
  }
7753
8162
  function restrictedAccessFilter(field2) {
@@ -9164,7 +9573,7 @@ function insertTileflowOverlays(input) {
9164
9573
  });
9165
9574
  groups.set(overlay.placement, [...groups.get(overlay.placement) ?? [], ...layers2]);
9166
9575
  identities[overlayId] = Object.freeze({
9167
- layers: Object.freeze(overlay.layers.map(({ id }) => id)),
9576
+ layers: Object.freeze(overlay.layers.map(({ id: id2 }) => id2)),
9168
9577
  placement: overlay.placement,
9169
9578
  source: overlay.source
9170
9579
  });
@@ -9201,22 +9610,13 @@ function cloneJson18(value) {
9201
9610
  );
9202
9611
  }
9203
9612
  function tileflowOverlayInsertionIndex(layers, placement) {
9204
- const threshold = {
9205
- "above-water": tileflowLayerSlots.indexOf("building-areas"),
9206
- "below-roads": tileflowLayerSlots.indexOf("transport-areas"),
9207
- "above-roads": tileflowLayerSlots.indexOf("boundaries"),
9208
- "above-buildings": tileflowLayerSlots.indexOf("vegetation"),
9209
- "below-labels": tileflowLayerSlots.indexOf("symbols"),
9210
- "above-labels": Number.POSITIVE_INFINITY
9211
- }[placement];
9212
- if (!Number.isFinite(threshold)) return layers.length;
9213
- const index = layers.findIndex((layer) => {
9214
- const ranks = readTileflowCompilerProvenance(layer).map(
9613
+ const ranks = layers.map((layer) => {
9614
+ const slots = readTileflowCompilerProvenance(layer).map(
9215
9615
  ({ slot }) => tileflowLayerSlots.indexOf(slot)
9216
9616
  );
9217
- return ranks.length > 0 && Math.min(...ranks) >= threshold;
9617
+ return slots.length > 0 ? Math.min(...slots) : void 0;
9218
9618
  });
9219
- return index < 0 ? layers.length : index;
9619
+ return tileflowBandPlacementIndex(ranks, placement);
9220
9620
  }
9221
9621
  function compareCodeUnits4(left, right) {
9222
9622
  return left < right ? -1 : left > right ? 1 : 0;
@@ -9228,7 +9628,7 @@ function isPlainRecord2(value) {
9228
9628
  }
9229
9629
 
9230
9630
  // src/resolved-map-schema.ts
9231
- import { Color as Color2 } from "@maplibre/maplibre-gl-style-spec";
9631
+ import { Color as Color3 } from "@maplibre/maplibre-gl-style-spec";
9232
9632
  import { z as z6 } from "zod";
9233
9633
 
9234
9634
  // src/cartography/data-expression.ts
@@ -9247,6 +9647,9 @@ function literal(value) {
9247
9647
  function zoom2() {
9248
9648
  return make(["zoom"]);
9249
9649
  }
9650
+ function id() {
9651
+ return make(["id"]);
9652
+ }
9250
9653
  function coalesce(first, ...rest) {
9251
9654
  if (rest.length === 0) throw new Error("expr.coalesce requires at least two values.");
9252
9655
  validateOperandCount(1 + rest.length, "expr.coalesce");
@@ -9297,6 +9700,9 @@ function subtract(first, second) {
9297
9700
  function divide(dividend, divisor) {
9298
9701
  return make(["/", node(dividend), node(divisor)]);
9299
9702
  }
9703
+ function modulo(dividend, divisor) {
9704
+ return make(["%", node(dividend), node(divisor)]);
9705
+ }
9300
9706
  function multiply(first, second, ...rest) {
9301
9707
  validateOperandCount(2 + rest.length, "expr.multiply");
9302
9708
  return make(["*", node(first), node(second), ...rest.map(node)]);
@@ -9502,6 +9908,7 @@ function visitExpression(value, path, variables, issues) {
9502
9908
  }
9503
9909
  return;
9504
9910
  }
9911
+ case "id":
9505
9912
  case "zoom": {
9506
9913
  exact(1);
9507
9914
  return;
@@ -9551,6 +9958,7 @@ function visitExpression(value, path, variables, issues) {
9551
9958
  }
9552
9959
  case "-":
9553
9960
  case "/":
9961
+ case "%":
9554
9962
  case "!=":
9555
9963
  case "<":
9556
9964
  case "<=":
@@ -9775,6 +10183,7 @@ var expr = Object.freeze({
9775
10183
  gt: (left, right) => compare(">", left, right),
9776
10184
  gte: (left, right) => compare(">=", left, right),
9777
10185
  has,
10186
+ id,
9778
10187
  featureState,
9779
10188
  interpolate: interpolate2,
9780
10189
  let: letValue,
@@ -9784,6 +10193,7 @@ var expr = Object.freeze({
9784
10193
  match,
9785
10194
  max: maximum,
9786
10195
  min: minimum,
10196
+ modulo,
9787
10197
  multiply,
9788
10198
  ne: (left, right) => compare("!=", left, right),
9789
10199
  not,
@@ -9916,7 +10326,7 @@ function createTileflowLayerFamilyIR(contribution) {
9916
10326
  function createTileflowLayerTemplateIR(layer, semanticKey) {
9917
10327
  const {
9918
10328
  filter,
9919
- id,
10329
+ id: id2,
9920
10330
  layout,
9921
10331
  maxzoom,
9922
10332
  metadata,
@@ -9927,7 +10337,7 @@ function createTileflowLayerTemplateIR(layer, semanticKey) {
9927
10337
  type,
9928
10338
  ...properties
9929
10339
  } = layer;
9930
- if (typeof id !== "string" || !id.trim()) {
10340
+ if (typeof id2 !== "string" || !id2.trim()) {
9931
10341
  throw new TileflowDomainIRError(
9932
10342
  "TILEFLOW_DOMAIN_IR_INVALID_KEY",
9933
10343
  "Layer key must not be empty."
@@ -9942,7 +10352,7 @@ function createTileflowLayerTemplateIR(layer, semanticKey) {
9942
10352
  if (typeof type !== "string" || !type.trim()) {
9943
10353
  throw new TileflowDomainIRError(
9944
10354
  "TILEFLOW_DOMAIN_IR_INVALID_RENDERER",
9945
- `Layer family ${id} requires a renderer.`
10355
+ `Layer family ${id2} requires a renderer.`
9946
10356
  );
9947
10357
  }
9948
10358
  const annotations = publicMetadata(metadata);
@@ -10224,13 +10634,6 @@ function isRecord5(value) {
10224
10634
  }
10225
10635
 
10226
10636
  // src/cartography/render-stack.ts
10227
- var tileflowRenderStackPhases = [
10228
- "underlay",
10229
- "overlay",
10230
- "postRelief",
10231
- "annotation",
10232
- "finish"
10233
- ];
10234
10637
  var tileflowRenderStackRenderers = [
10235
10638
  "background",
10236
10639
  "circle",
@@ -10438,15 +10841,14 @@ var geometryTypes = {
10438
10841
  point: "Point",
10439
10842
  polygon: "Polygon"
10440
10843
  };
10441
- var phaseOrder = new Map(tileflowRenderStackPhases.map((phase, index) => [phase, index]));
10442
10844
  function renderPass(input) {
10443
10845
  assertOnlyKeys(
10444
10846
  input,
10445
- ["attachTo", "feature", "phase", "renderer", "requirements", "selector", "style"],
10847
+ ["feature", "placement", "renderer", "requirements", "selector", "style"],
10446
10848
  "renderPass"
10447
10849
  );
10448
10850
  requireRenderer(input.renderer);
10449
- requirePhase(input.phase);
10851
+ requirePlacement(input.placement);
10450
10852
  validateStyle(input.renderer, input.style);
10451
10853
  if (input.renderer === "background" && ("feature" in input || "selector" in input)) {
10452
10854
  throw renderStackError(
@@ -10461,11 +10863,10 @@ function renderPass(input) {
10461
10863
  function refineRenderTarget(input) {
10462
10864
  assertOnlyKeys(
10463
10865
  input,
10464
- ["renderer", "requirements", "selector", "style", "target"],
10866
+ ["placement", "renderer", "requirements", "selector", "style", "target"],
10465
10867
  "refineRenderTarget"
10466
10868
  );
10467
- requireRenderer(input.renderer);
10468
- validateStyle(input.renderer, input.style);
10869
+ validateRefinementShape(input);
10469
10870
  if (input.renderer === "background" && "selector" in input) {
10470
10871
  throw renderStackError(
10471
10872
  "invalid-background-refinement",
@@ -10515,71 +10916,118 @@ function compileRenderSelector(selector, data) {
10515
10916
  }
10516
10917
  function applyCompiledRenderStacks(input, operations2) {
10517
10918
  let layers = input.map(cloneJson21);
10518
- const activeOwners = new Set(layers.map(({ owner }) => owner));
10519
- const layerOperations = operations2.filter(
10919
+ const passes = operations2.filter(
10520
10920
  (operation) => operation.kind === "layer"
10521
10921
  );
10522
- const suppressedTargets = new Set(
10523
- layerOperations.filter(
10524
- (operation) => operation.requirements.some((requirement) => !activeOwners.has(requirement))
10525
- ).map((operation) => operation.target)
10922
+ const refinements = operations2.filter(
10923
+ (operation) => operation.kind === "refinement"
10526
10924
  );
10527
- let changed = true;
10528
- while (changed) {
10925
+ requireUniqueCompiledLayers(layers, passes);
10926
+ const passTargets = new Set(passes.map(({ target }) => target));
10927
+ const knownTargets = /* @__PURE__ */ new Set([...layers.map(({ target }) => target), ...passTargets]);
10928
+ const activeOwners = new Set(layers.map(({ owner }) => owner));
10929
+ const drawn = /* @__PURE__ */ new Set();
10930
+ for (let changed = true; changed; ) {
10529
10931
  changed = false;
10530
- for (const operation of layerOperations) {
10531
- if (!suppressedTargets.has(operation.target) && suppressedTargets.has(operation.attachTo)) {
10532
- suppressedTargets.add(operation.target);
10533
- changed = true;
10534
- }
10932
+ for (const pass of passes) {
10933
+ if (drawn.has(pass.target)) continue;
10934
+ const anchor = placementAnchor(pass.placement);
10935
+ if (pass.requirements.some((requirement) => !activeOwners.has(requirement))) continue;
10936
+ if (anchor !== void 0 && passTargets.has(anchor) && !drawn.has(anchor)) continue;
10937
+ drawn.add(pass.target);
10938
+ activeOwners.add(pass.owner);
10939
+ changed = true;
10940
+ }
10941
+ }
10942
+ const activeRefinements = refinements.filter(
10943
+ (refinement) => refinement.requirements.every((requirement) => activeOwners.has(requirement))
10944
+ ).sort(compareCompiledOperations);
10945
+ for (const refinement of activeRefinements) {
10946
+ const index = findTargetIndex(layers, refinement.target);
10947
+ if (index < 0 && !passTargets.has(refinement.target)) {
10948
+ throw renderStackError(
10949
+ "unknown-refinement-target",
10950
+ `Render refinement targets unknown semantic contribution: ${refinement.target}.`
10951
+ );
10535
10952
  }
10536
- }
10537
- const pending = layerOperations.filter((operation) => !suppressedTargets.has(operation.target)).sort(compareCompiledOperations);
10538
- requireUniqueCompiledLayers(layers, pending);
10539
- while (pending.length > 0) {
10540
- const ready = pending.find((operation) => findTargetIndex(layers, operation.attachTo) >= 0);
10541
- if (!ready) {
10953
+ if (refinement.placement !== void 0 && placementAnchor(refinement.placement) === refinement.target) {
10542
10954
  throw renderStackError(
10543
10955
  "unknown-or-circular-anchor",
10544
- `Render passes target unknown or circular anchors: ${pending.map((operation) => `${operation.target}->${operation.attachTo}`).join(", ")}.`
10956
+ `Render refinement ${refinement.target} cannot be placed beside itself.`
10545
10957
  );
10546
10958
  }
10547
- const anchorTarget = ready.attachTo;
10548
- const group = pending.filter((operation) => operation.attachTo === anchorTarget).sort(compareCompiledOperations);
10549
- const anchorIndex = findTargetIndex(layers, anchorTarget);
10550
- const anchor = layers[anchorIndex];
10551
- const before = group.filter((operation) => operation.phase === "underlay").map((operation) => materializeRenderLayer(operation, anchor));
10552
- const after = group.filter((operation) => operation.phase !== "underlay").map((operation) => materializeRenderLayer(operation, anchor));
10553
- layers = [
10554
- ...layers.slice(0, anchorIndex),
10555
- ...before,
10556
- anchor,
10557
- ...after,
10558
- ...layers.slice(anchorIndex + 1)
10559
- ];
10560
- for (const operation of group) pending.splice(pending.indexOf(operation), 1);
10561
10959
  }
10562
- const refinements = operations2.filter(
10563
- (operation) => operation.kind === "refinement"
10564
- ).filter(
10565
- (operation) => operation.requirements.every((requirement) => activeOwners.has(requirement))
10566
- ).sort(compareCompiledOperations);
10567
- for (const refinement of refinements) {
10568
- const index = findTargetIndex(layers, refinement.target);
10569
- if (index < 0) {
10960
+ const isDrawn = (target) => !passTargets.has(target) || drawn.has(target);
10961
+ const pending = [
10962
+ ...passes.filter(({ target }) => drawn.has(target)),
10963
+ ...activeRefinements.filter(({ placement, target }) => {
10964
+ if (placement === void 0 || !isDrawn(target)) return false;
10965
+ const anchor = placementAnchor(placement);
10966
+ return anchor === void 0 || isDrawn(anchor);
10967
+ })
10968
+ ].sort(compareCompiledOperations);
10969
+ for (const operation of pending) {
10970
+ const anchor = placementAnchor(operation.placement);
10971
+ if (anchor !== void 0 && !knownTargets.has(anchor)) {
10570
10972
  throw renderStackError(
10571
- "unknown-refinement-target",
10572
- `Render refinement targets unknown semantic contribution: ${refinement.target}.`
10973
+ "unknown-or-circular-anchor",
10974
+ `${operation.target} is placed beside unknown semantic target ${anchor}.`
10573
10975
  );
10574
10976
  }
10575
- const previous = layers[index];
10576
- const actualOwner = previous.owner;
10577
- if (actualOwner !== refinement.owner) {
10977
+ }
10978
+ const lastPlaced = /* @__PURE__ */ new Map();
10979
+ const isMovePending = (target) => pending.some((operation) => operation.kind === "refinement" && operation.target === target);
10980
+ const isReady = (operation) => {
10981
+ if (operation.kind === "refinement" && findTargetIndex(layers, operation.target) < 0) {
10982
+ return false;
10983
+ }
10984
+ const anchor = placementAnchor(operation.placement);
10985
+ return anchor === void 0 || findTargetIndex(layers, anchor) >= 0 && !isMovePending(anchor);
10986
+ };
10987
+ while (pending.length > 0) {
10988
+ const first = pending.find(isReady);
10989
+ if (first === void 0) {
10578
10990
  throw renderStackError(
10579
- "refinement-owner-mismatch",
10580
- `Render refinement ${refinement.target} belongs to ${actualOwner ?? "no owner"}, not ${refinement.owner}.`
10991
+ "unknown-or-circular-anchor",
10992
+ `Render operations are placed beside circular anchors: ${pending.map((operation) => `${operation.target}->${placementKey(operation.placement)}`).join(", ")}.`
10581
10993
  );
10582
10994
  }
10995
+ const key = placementKey(first.placement);
10996
+ const group = pending.filter(
10997
+ (operation) => placementKey(operation.placement) === key && isReady(operation)
10998
+ );
10999
+ for (const operation of group) pending.splice(pending.indexOf(operation), 1);
11000
+ const moved = /* @__PURE__ */ new Map();
11001
+ for (const operation of group) {
11002
+ if (operation.kind !== "refinement") continue;
11003
+ const index2 = findTargetIndex(layers, operation.target);
11004
+ moved.set(operation.target, requireRefinementOwner(layers[index2], operation));
11005
+ layers = [...layers.slice(0, index2), ...layers.slice(index2 + 1)];
11006
+ for (const [placed, target] of lastPlaced) {
11007
+ if (target === operation.target) lastPlaced.delete(placed);
11008
+ }
11009
+ }
11010
+ const placement = first.placement;
11011
+ const anchor = placementAnchor(placement);
11012
+ const anchorLayer = anchor === void 0 ? void 0 : layers[findTargetIndex(layers, anchor)];
11013
+ const slot = anchorLayer === void 0 ? tileflowBandPlacementSlot(placement) : requireSemanticSlot(anchorLayer, anchor);
11014
+ const block = group.map(
11015
+ (operation) => operation.kind === "layer" ? materializeRenderLayer(operation, anchorLayer, slot) : moveLayer(moved.get(operation.target), operation, slot, anchorLayer)
11016
+ );
11017
+ const previous = lastPlaced.get(key);
11018
+ const previousIndex = previous === void 0 ? -1 : findTargetIndex(layers, previous);
11019
+ const index = anchorLayer !== void 0 ? findTargetIndex(layers, anchor) + (key.startsWith("above:") ? 1 : 0) : previousIndex >= 0 ? previousIndex + 1 : tileflowBandPlacementIndex(
11020
+ layers.map((candidate) => tileflowLayerSlots.indexOf(candidate.slot)),
11021
+ placement
11022
+ );
11023
+ layers = [...layers.slice(0, index), ...block, ...layers.slice(index)];
11024
+ lastPlaced.set(key, block.at(-1).target);
11025
+ }
11026
+ for (const refinement of activeRefinements) {
11027
+ if (refinement.patch === void 0) continue;
11028
+ const index = findTargetIndex(layers, refinement.target);
11029
+ if (index < 0) continue;
11030
+ const previous = requireRefinementOwner(layers[index], refinement);
10583
11031
  const expectedType = rendererLayerType(refinement.renderer);
10584
11032
  if (previous.renderer !== expectedType) {
10585
11033
  throw renderStackError(
@@ -10587,26 +11035,48 @@ function applyCompiledRenderStacks(input, operations2) {
10587
11035
  `Render refinement ${refinement.target} expects ${expectedType}, found ${String(previous.renderer)}.`
10588
11036
  );
10589
11037
  }
10590
- layers[index] = applyRefinement(previous, refinement);
11038
+ layers[index] = applyRefinement(previous, { ...refinement, patch: refinement.patch });
10591
11039
  }
10592
11040
  return layers;
10593
11041
  }
11042
+ function requireRefinementOwner(layer, refinement) {
11043
+ if (layer.owner !== refinement.owner) {
11044
+ throw renderStackError(
11045
+ "refinement-owner-mismatch",
11046
+ `Render refinement ${refinement.target} belongs to ${layer.owner ?? "no owner"}, not ${refinement.owner}.`
11047
+ );
11048
+ }
11049
+ return layer;
11050
+ }
11051
+ function placementAnchor(placement) {
11052
+ if (placement === void 0 || typeof placement === "string") return void 0;
11053
+ return placement.above ?? placement.below;
11054
+ }
11055
+ function placementKey(placement) {
11056
+ if (typeof placement === "string") return placement;
11057
+ return placement.above === void 0 ? `below:${placement.below}` : `above:${placement.above}`;
11058
+ }
10594
11059
  function compileOperation(owner, name, operation, order, data) {
10595
11060
  requireOperationName(name);
10596
11061
  if (operation.kind === "render-pass") {
10597
11062
  validateRenderPassOperation(operation);
10598
11063
  const requirements2 = normalizeRequirements(operation.requirements);
10599
- requireAttachTarget(owner, operation.attachTo, requirements2);
11064
+ requirePlacementAnchor(owner, operation.placement, requirements2);
10600
11065
  const target = `${owner}.render.${name}`;
10601
11066
  requirePortableTarget(target, "generated render target");
11067
+ if (typeof operation.placement === "string" && operation.renderer !== "background" && operation.feature === void 0) {
11068
+ throw renderStackError(
11069
+ "missing-inherited-feature",
11070
+ `Render pass ${target} is placed at ${operation.placement} and must declare its feature.`
11071
+ );
11072
+ }
10602
11073
  const template2 = compileRenderLayer(target, operation, data);
10603
11074
  return {
10604
- attachTo: operation.attachTo,
10605
11075
  kind: "layer",
10606
11076
  name,
10607
11077
  order,
10608
11078
  owner,
10609
- phase: operation.phase,
11079
+ placement: cloneJson21(operation.placement),
10610
11080
  renderer: operation.renderer,
10611
11081
  requirements: requirements2,
10612
11082
  target,
@@ -10616,6 +11086,21 @@ function compileOperation(owner, name, operation, order, data) {
10616
11086
  validateRefinementOperation(operation);
10617
11087
  requireOwnedTarget(owner, operation.target, "refinement target");
10618
11088
  const requirements = normalizeRequirements(operation.requirements);
11089
+ if (operation.placement !== void 0) {
11090
+ requirePlacementAnchor(owner, operation.placement, requirements);
11091
+ }
11092
+ const placement = operation.placement === void 0 ? {} : { placement: cloneJson21(operation.placement) };
11093
+ if (operation.renderer === void 0) {
11094
+ return {
11095
+ kind: "refinement",
11096
+ name,
11097
+ order,
11098
+ owner,
11099
+ ...placement,
11100
+ requirements,
11101
+ target: operation.target
11102
+ };
11103
+ }
10619
11104
  const template = applyRendererStyle(
10620
11105
  { id: "tileflow-render-refinement-template", type: rendererLayerType(operation.renderer) },
10621
11106
  operation.renderer,
@@ -10629,6 +11114,7 @@ function compileOperation(owner, name, operation, order, data) {
10629
11114
  order,
10630
11115
  owner,
10631
11116
  patch,
11117
+ ...placement,
10632
11118
  renderer: operation.renderer,
10633
11119
  requirements,
10634
11120
  target: operation.target
@@ -10806,26 +11292,42 @@ function requireFieldBinding(fieldName, data) {
10806
11292
  }
10807
11293
  return field(fieldName);
10808
11294
  }
10809
- function materializeRenderLayer(operation, anchor) {
11295
+ function materializeRenderLayer(operation, anchor, slot) {
10810
11296
  let template = cloneJson21(operation.template);
10811
11297
  if (operation.renderer !== "background" && template.feature?.dataSource === void 0) {
10812
- if (anchor.feature?.dataSource === void 0 || anchor.feature.dataLayer === void 0) {
11298
+ if (anchor?.feature?.dataSource === void 0 || anchor.feature.dataLayer === void 0) {
10813
11299
  throw renderStackError(
10814
11300
  "missing-inherited-feature",
10815
- `Render pass ${operation.target} has no feature and anchor ${operation.attachTo} has no vector feature to inherit.`
11301
+ `Render pass ${operation.target} has no feature and ${placementKey(operation.placement)} provides no vector feature to inherit.`
10816
11302
  );
10817
11303
  }
10818
11304
  template = { ...template, feature: cloneJson21(anchor.feature) };
10819
11305
  }
10820
- const slot = requireSemanticSlot(anchor, operation.attachTo);
10821
11306
  return materializeTileflowLayerFamilyIR(template, {
10822
11307
  operations: [{ kind: "pass", owner: operation.owner, target: operation.target }],
10823
- order: anchor.order,
11308
+ order: anchor?.order ?? 0,
10824
11309
  owner: operation.owner,
10825
11310
  slot,
10826
11311
  target: operation.target
10827
11312
  });
10828
11313
  }
11314
+ function moveLayer(layer, refinement, slot, anchor) {
11315
+ return {
11316
+ ...layer,
11317
+ order: anchor?.order ?? layer.order,
11318
+ origins: layer.origins.map(
11319
+ (origin) => origin.owner === refinement.owner && origin.target === refinement.target ? {
11320
+ ...origin,
11321
+ operations: [
11322
+ ...origin.operations,
11323
+ { kind: "refinement", owner: refinement.owner, target: refinement.target }
11324
+ ],
11325
+ slot
11326
+ } : origin
11327
+ ),
11328
+ slot
11329
+ };
11330
+ }
10829
11331
  function applyRefinement(previous, refinement) {
10830
11332
  const patch = refinement.patch;
10831
11333
  const operation = {
@@ -10841,7 +11343,8 @@ function applyRefinement(previous, refinement) {
10841
11343
  ...patch.range === void 0 ? {} : { range: { ...previous.range ?? {}, ...patch.range } },
10842
11344
  ...!Object.hasOwn(patch, "selector") ? {} : { selector: cloneJson21(patch.selector) },
10843
11345
  origins: previous.origins.map(
10844
- (origin) => origin.owner === refinement.owner && origin.target === refinement.target ? { ...origin, operations: [...origin.operations, operation] } : origin
11346
+ (origin) => origin.owner === refinement.owner && origin.target === refinement.target && // A move already recorded this refinement on the target.
11347
+ refinement.placement === void 0 ? { ...origin, operations: [...origin.operations, operation] } : origin
10845
11348
  ),
10846
11349
  style: {
10847
11350
  ...previous.style,
@@ -10872,10 +11375,34 @@ function requireRenderer(value) {
10872
11375
  throw renderStackError("invalid-renderer", `Unknown render-stack renderer: ${String(value)}.`);
10873
11376
  }
10874
11377
  }
10875
- function requirePhase(value) {
10876
- if (!tileflowRenderStackPhases.includes(value)) {
10877
- throw renderStackError("invalid-phase", `Unknown render-stack phase: ${String(value)}.`);
11378
+ function requirePlacement(value) {
11379
+ if (typeof value === "string") {
11380
+ if (tileflowBandPlacements.includes(value)) return;
11381
+ } else if (isRecord6(value)) {
11382
+ const keys = Object.keys(value);
11383
+ if (keys.length === 1 && (keys[0] === "above" || keys[0] === "below")) {
11384
+ requirePortableTarget(value[keys[0]], "render placement target");
11385
+ return;
11386
+ }
11387
+ }
11388
+ throw renderStackError(
11389
+ "invalid-placement",
11390
+ `A render placement is a band edge (${tileflowBandPlacements.join(", ")}) or {above} / {below} one semantic target: ${JSON.stringify(value)}.`
11391
+ );
11392
+ }
11393
+ function validateRefinementShape(operation) {
11394
+ if (operation.placement !== void 0) requirePlacement(operation.placement);
11395
+ if (operation.renderer === void 0) {
11396
+ if (operation.placement === void 0 || "style" in operation || "selector" in operation) {
11397
+ throw renderStackError(
11398
+ "invalid-refinement",
11399
+ "A render refinement declares a renderer with its style, a placement, or both."
11400
+ );
11401
+ }
11402
+ return;
10878
11403
  }
11404
+ requireRenderer(operation.renderer);
11405
+ validateStyle(operation.renderer, operation.style);
10879
11406
  }
10880
11407
  function validateStyle(renderer, style) {
10881
11408
  if (!isRecord6(style)) {
@@ -10995,13 +11522,15 @@ function requireRenderStackEntries(stack) {
10995
11522
  }
10996
11523
  return entries;
10997
11524
  }
10998
- function requireAttachTarget(owner, target, requirements) {
10999
- requirePortableTarget(target, "render attachment target");
11525
+ function requirePlacementAnchor(owner, placement, requirements) {
11526
+ requirePlacement(placement);
11527
+ const target = placementAnchor(placement);
11528
+ if (target === void 0) return;
11000
11529
  const targetOwner = target.split(".")[0];
11001
11530
  if (targetOwner !== owner && !requirements.includes(targetOwner)) {
11002
11531
  throw renderStackError(
11003
11532
  "cross-owner-attachment",
11004
- `Render pass owner ${owner} may attach to ${target} only with an explicit ${targetOwner} requirement.`
11533
+ `Render operation owner ${owner} may be placed beside ${target} only with an explicit ${targetOwner} requirement.`
11005
11534
  );
11006
11535
  }
11007
11536
  }
@@ -11048,18 +11577,16 @@ function requireUniqueCompiledLayers(layers, operations2) {
11048
11577
  }
11049
11578
  }
11050
11579
  function compareCompiledOperations(left, right) {
11051
- const leftPhase = left.kind === "layer" ? phaseOrder.get(left.phase) : Number.MAX_SAFE_INTEGER;
11052
- const rightPhase = right.kind === "layer" ? phaseOrder.get(right.phase) : Number.MAX_SAFE_INTEGER;
11053
- return leftPhase - rightPhase || left.order - right.order || compareCodeUnits6(left.owner, right.owner) || compareCodeUnits6(left.name, right.name);
11580
+ return left.order - right.order || compareCodeUnits6(left.owner, right.owner) || compareCodeUnits6(left.name, right.name);
11054
11581
  }
11055
11582
  function validateRenderPassOperation(operation) {
11056
11583
  assertOnlyKeys(
11057
11584
  operation,
11058
- ["attachTo", "feature", "kind", "phase", "renderer", "requirements", "selector", "style"],
11585
+ ["feature", "kind", "placement", "renderer", "requirements", "selector", "style"],
11059
11586
  "render pass operation"
11060
11587
  );
11061
11588
  requireRenderer(operation.renderer);
11062
- requirePhase(operation.phase);
11589
+ requirePlacement(operation.placement);
11063
11590
  validateStyle(operation.renderer, operation.style);
11064
11591
  if (operation.renderer === "background" && ("feature" in operation || "selector" in operation)) {
11065
11592
  throw renderStackError(
@@ -11071,11 +11598,10 @@ function validateRenderPassOperation(operation) {
11071
11598
  function validateRefinementOperation(operation) {
11072
11599
  assertOnlyKeys(
11073
11600
  operation,
11074
- ["kind", "renderer", "requirements", "selector", "style", "target"],
11601
+ ["kind", "placement", "renderer", "requirements", "selector", "style", "target"],
11075
11602
  "render refinement operation"
11076
11603
  );
11077
- requireRenderer(operation.renderer);
11078
- validateStyle(operation.renderer, operation.style);
11604
+ validateRefinementShape(operation);
11079
11605
  if (operation.renderer === "background" && "selector" in operation) {
11080
11606
  throw renderStackError(
11081
11607
  "invalid-background-refinement",
@@ -11725,6 +12251,11 @@ var textStyleSchema = z6.object({
11725
12251
  "top-left",
11726
12252
  "top-right"
11727
12253
  ]).optional(),
12254
+ background: z6.object({
12255
+ color: colorValueSchema,
12256
+ fit: z6.literal("lines"),
12257
+ padding: themeNumberValueSchema.optional()
12258
+ }).strict().optional(),
11728
12259
  color: colorValueSchema.optional(),
11729
12260
  fallbacks: moduleFontFallbacksSchema.optional(),
11730
12261
  field: stringValueSchema.optional(),
@@ -11971,12 +12502,16 @@ var renderRequirementsSchema = z6.array(z6.enum(tileflowLayerDomains)).min(1).ma
11971
12502
  seen.add(requirement);
11972
12503
  }
11973
12504
  });
12505
+ var renderPlacementSchema = z6.union([
12506
+ z6.enum(tileflowBandPlacements),
12507
+ z6.object({ above: renderTargetSchema }).strict(),
12508
+ z6.object({ below: renderTargetSchema }).strict()
12509
+ ]);
11974
12510
  var vectorRenderPassSchemas = tileflowRenderStackRenderers.filter((renderer) => renderer !== "background").map(
11975
12511
  (renderer) => z6.object({
11976
- attachTo: renderTargetSchema,
11977
12512
  feature: z6.enum(tileflowSemanticLayerNames).optional(),
11978
12513
  kind: z6.literal("render-pass"),
11979
- phase: z6.enum(tileflowRenderStackPhases),
12514
+ placement: renderPlacementSchema,
11980
12515
  renderer: z6.literal(renderer),
11981
12516
  requirements: renderRequirementsSchema.optional(),
11982
12517
  selector: renderSelectorSchema.optional(),
@@ -11985,9 +12520,8 @@ var vectorRenderPassSchemas = tileflowRenderStackRenderers.filter((renderer) =>
11985
12520
  );
11986
12521
  var renderPassSchema = z6.discriminatedUnion("renderer", [
11987
12522
  z6.object({
11988
- attachTo: renderTargetSchema,
11989
12523
  kind: z6.literal("render-pass"),
11990
- phase: z6.enum(tileflowRenderStackPhases),
12524
+ placement: renderPlacementSchema,
11991
12525
  renderer: z6.literal("background"),
11992
12526
  requirements: renderRequirementsSchema.optional(),
11993
12527
  style: renderStyleSchemas.background
@@ -11997,6 +12531,7 @@ var renderPassSchema = z6.discriminatedUnion("renderer", [
11997
12531
  var renderRefinementSchemas = tileflowRenderStackRenderers.map(
11998
12532
  (renderer) => z6.object({
11999
12533
  kind: z6.literal("refine-render-target"),
12534
+ placement: renderPlacementSchema.optional(),
12000
12535
  requirements: renderRequirementsSchema.optional(),
12001
12536
  renderer: z6.literal(renderer),
12002
12537
  ...renderer === "background" ? {} : { selector: renderSelectorSchema.optional() },
@@ -12004,7 +12539,17 @@ var renderRefinementSchemas = tileflowRenderStackRenderers.map(
12004
12539
  target: renderTargetSchema
12005
12540
  }).strict()
12006
12541
  );
12007
- var renderStackOperationSchema = z6.union([renderPassSchema, ...renderRefinementSchemas]);
12542
+ var renderPlacementRefinementSchema = z6.object({
12543
+ kind: z6.literal("refine-render-target"),
12544
+ placement: renderPlacementSchema,
12545
+ requirements: renderRequirementsSchema.optional(),
12546
+ target: renderTargetSchema
12547
+ }).strict();
12548
+ var renderStackOperationSchema = z6.union([
12549
+ renderPassSchema,
12550
+ ...renderRefinementSchemas,
12551
+ renderPlacementRefinementSchema
12552
+ ]);
12008
12553
  var renderStackSchema = z6.record(renderStackOperationNameSchema, renderStackOperationSchema).superRefine((stack, context) => {
12009
12554
  const size = Object.keys(stack).length;
12010
12555
  if (size === 0) {
@@ -12846,7 +13391,7 @@ var terrainSchema = z6.union([
12846
13391
  url: z6.string().trim().min(1).optional()
12847
13392
  }).strict()
12848
13393
  ]);
12849
- var atmosphereColorLiteralSchema = z6.string().refine((value) => Color2.parse(value)?.a === 1, "Expected an opaque color");
13394
+ var atmosphereColorLiteralSchema = z6.string().refine((value) => Color3.parse(value)?.a === 1, "Expected an opaque color");
12850
13395
  var atmosphereColorSchema = z6.union([
12851
13396
  atmosphereColorLiteralSchema,
12852
13397
  fixedStringSchema.extend({ value: atmosphereColorLiteralSchema }),
@@ -13343,7 +13888,7 @@ function cloneJson22(value) {
13343
13888
  }
13344
13889
 
13345
13890
  // src/icon-appearance.ts
13346
- import { Color as Color3 } from "@maplibre/maplibre-gl-style-spec";
13891
+ import { Color as Color4 } from "@maplibre/maplibre-gl-style-spec";
13347
13892
  import { z as z8 } from "zod";
13348
13893
 
13349
13894
  // src/icon-layout.ts
@@ -13417,7 +13962,7 @@ function scaleTileflowIconLayout(layout, scale) {
13417
13962
  }
13418
13963
 
13419
13964
  // src/icon-appearance.ts
13420
- var color2 = z8.string().max(128).refine((value) => Color3.parse(value) !== void 0, "Expected a MapLibre color");
13965
+ var color2 = z8.string().max(128).refine((value) => Color4.parse(value) !== void 0, "Expected a MapLibre color");
13421
13966
  var tileflowIconAppearanceSchema = z8.object({
13422
13967
  representation: z8.literal("sdf"),
13423
13968
  defaults: z8.object({
@@ -13491,9 +14036,9 @@ function applyTileflowIconCapabilities(style, icons) {
13491
14036
  if (!icons?.appearances || !Object.keys(icons.appearances).length) return;
13492
14037
  const ids = [...icons.ids];
13493
14038
  const appearances = icons.appearances;
13494
- for (const [id, appearance] of Object.entries(appearances)) {
13495
- if (!ids.includes(id))
13496
- throw new Error(`Icon capability metadata references unknown icon "${id}".`);
14039
+ for (const [id2, appearance] of Object.entries(appearances)) {
14040
+ if (!ids.includes(id2))
14041
+ throw new Error(`Icon capability metadata references unknown icon "${id2}".`);
13497
14042
  tileflowIconAppearanceSchema.parse(appearance);
13498
14043
  }
13499
14044
  for (const layer of style.layers) {
@@ -13504,7 +14049,7 @@ function applyTileflowIconCapabilities(style, icons) {
13504
14049
  "background-pattern"
13505
14050
  ]) {
13506
14051
  const pattern = record(layer.paint)[property];
13507
- if (pattern !== void 0 && [...outputs(pattern, ids)].some((id) => appearances[id]?.representation === "sdf")) {
14052
+ if (pattern !== void 0 && [...outputs(pattern, ids)].some((id2) => appearances[id2]?.representation === "sdf")) {
13508
14053
  throw Object.assign(new Error(`Layer "${layer.id}" cannot use an SDF icon as a pattern.`), {
13509
14054
  code: "TF_ICON_PATTERN_SDF",
13510
14055
  path: `layers.${layer.id}.paint.${property}`,
@@ -13522,7 +14067,7 @@ function applyTileflowIconCapabilities(style, icons) {
13522
14067
  });
13523
14068
  }
13524
14069
  const names = [...outputs(image, ids)];
13525
- const sdf = names.filter((id) => appearances[id]?.representation === "sdf");
14070
+ const sdf = names.filter((id2) => appearances[id2]?.representation === "sdf");
13526
14071
  if (!sdf.length) continue;
13527
14072
  if (sdf.length !== names.length) {
13528
14073
  throw Object.assign(
@@ -13634,7 +14179,7 @@ function numericBounds(value) {
13634
14179
  ];
13635
14180
  }
13636
14181
  function defaultPaint(image, key, names, appearances, ids) {
13637
- const values = names.map((id) => appearances[id].defaults[key]);
14182
+ const values = names.map((id2) => appearances[id2].defaults[key]);
13638
14183
  if (new Set(values).size === 1) return values[0];
13639
14184
  if (Array.isArray(image) && (image[0] === "step" || image[0] === "interpolate")) {
13640
14185
  const copy = [...image];
@@ -13645,7 +14190,7 @@ function defaultPaint(image, key, names, appearances, ids) {
13645
14190
  return [
13646
14191
  "match",
13647
14192
  ["to-string", selection(image, ids)],
13648
- ...names.flatMap((id, i) => [id, values[i]]),
14193
+ ...names.flatMap((id2, i) => [id2, values[i]]),
13649
14194
  values[0]
13650
14195
  ];
13651
14196
  }
@@ -13655,18 +14200,18 @@ var tileflowInteractionManifestMetadataKey = "tileflow:interaction-manifest";
13655
14200
  var tileflowInteractionManifestVersion = 2;
13656
14201
  function createTileflowInteractionManifest(layers, fields) {
13657
14202
  const poiLayers = layers.flatMap((layer, priority) => {
13658
- const id = typeof layer.id === "string" ? layer.id : void 0;
14203
+ const id2 = typeof layer.id === "string" ? layer.id : void 0;
13659
14204
  const source = typeof layer.source === "string" ? layer.source : void 0;
13660
14205
  const sourceLayer = typeof layer["source-layer"] === "string" ? layer["source-layer"] : void 0;
13661
14206
  const target = semanticTarget(layer);
13662
- if (!id || !source || !sourceLayer || !target?.startsWith("poi.")) return [];
14207
+ if (!id2 || !source || !sourceLayer || !target?.startsWith("poi.")) return [];
13663
14208
  const parsed = parsePoiTarget(target);
13664
14209
  if (!parsed) return [];
13665
14210
  return [
13666
14211
  {
13667
14212
  ...parsed,
13668
14213
  anchor: "pointer-coordinate",
13669
- layerId: id,
14214
+ layerId: id2,
13670
14215
  priority,
13671
14216
  source,
13672
14217
  sourceLayer
@@ -13765,11 +14310,11 @@ function createTileflowOverlayPlacementManifest(layers) {
13765
14310
  const anchors = Object.fromEntries(
13766
14311
  tileflowOverlayPlacements.map((placement) => {
13767
14312
  const index = tileflowOverlayInsertionIndex(layers, placement);
13768
- const id = index < layers.length ? layers[index]?.id : void 0;
13769
- if (id !== void 0 && typeof id !== "string") {
14313
+ const id2 = index < layers.length ? layers[index]?.id : void 0;
14314
+ if (id2 !== void 0 && typeof id2 !== "string") {
13770
14315
  throw new Error(`Tileflow overlay placement ${placement} resolved an invalid layer ID.`);
13771
14316
  }
13772
- return [placement, id ?? null];
14317
+ return [placement, id2 ?? null];
13773
14318
  })
13774
14319
  );
13775
14320
  return {
@@ -15013,10 +15558,10 @@ function compileSemanticStyleInternal(input, options, inspect, onReport) {
15013
15558
  function finalizeTileflowLayers(input) {
15014
15559
  const ids = /* @__PURE__ */ new Set();
15015
15560
  return input.map((layer, index) => {
15016
- const id = typeof layer.id === "string" ? layer.id : "";
15017
- if (!id) throw new Error(`Compiled Tileflow layer at index ${index} has no ID.`);
15018
- if (ids.has(id)) throw new Error(`Duplicate compiled Tileflow layer ID: ${id}`);
15019
- ids.add(id);
15561
+ const id2 = typeof layer.id === "string" ? layer.id : "";
15562
+ if (!id2) throw new Error(`Compiled Tileflow layer at index ${index} has no ID.`);
15563
+ if (ids.has(id2)) throw new Error(`Duplicate compiled Tileflow layer ID: ${id2}`);
15564
+ ids.add(id2);
15020
15565
  const minimum2 = layer.minzoom;
15021
15566
  const maximum2 = layer.maxzoom;
15022
15567
  for (const [name, value] of [
@@ -15025,11 +15570,11 @@ function finalizeTileflowLayers(input) {
15025
15570
  ]) {
15026
15571
  if (value === void 0) continue;
15027
15572
  if (typeof value !== "number" || !Number.isFinite(value) || value < 0 || value > 24) {
15028
- throw new Error(`Compiled Tileflow layer ${id} has invalid ${name}: ${String(value)}`);
15573
+ throw new Error(`Compiled Tileflow layer ${id2} has invalid ${name}: ${String(value)}`);
15029
15574
  }
15030
15575
  }
15031
15576
  if (typeof minimum2 === "number" && typeof maximum2 === "number" && minimum2 > maximum2) {
15032
- throw new Error(`Compiled Tileflow layer ${id} requires minzoom <= maxzoom.`);
15577
+ throw new Error(`Compiled Tileflow layer ${id2} requires minzoom <= maxzoom.`);
15033
15578
  }
15034
15579
  const metadata = isRecord8(layer.metadata) ? { ...layer.metadata } : void 0;
15035
15580
  if (metadata) {
@@ -15073,7 +15618,7 @@ function assertPreparedIconReferences(style, config, prepared) {
15073
15618
  const references = collectStyleImageReferences(style);
15074
15619
  if (references.size === 0) return;
15075
15620
  const available = new Set(prepared?.icons?.ids ?? []);
15076
- const missing = [...references].filter((id) => !available.has(id)).sort();
15621
+ const missing = [...references].filter((id2) => !available.has(id2)).sort();
15077
15622
  if (missing.length === 0 && prepared?.icons?.sprite) return;
15078
15623
  const authoring = config.icons?.length ? "Run the Node build so map.icons directories are prepared." : "Declare map.icons with directories containing those canonical filenames.";
15079
15624
  throw new Error(
@@ -15177,7 +15722,8 @@ function assertGlyphFontStacks(style, config) {
15177
15722
  const font = layout?.["text-font"];
15178
15723
  if (!Array.isArray(font) || !font.every((entry) => typeof entry === "string")) continue;
15179
15724
  const stack = font.join(",");
15180
- if (stack && !declared.has(stack)) missing.add(stack);
15725
+ const source = parseTileflowLineBackgroundFontStack(stack)?.source ?? stack;
15726
+ if (stack && !declared.has(source)) missing.add(source);
15181
15727
  }
15182
15728
  if (missing.size > 0) {
15183
15729
  throw new Error(
@@ -15200,6 +15746,15 @@ function assertTextAssets(style, config) {
15200
15746
  }
15201
15747
  if (textLayers.length === 0) return;
15202
15748
  if (config.fonts !== void 0) {
15749
+ const backed = style.layers.find((layer) => {
15750
+ const font = isRecord8(layer.layout) ? layer.layout["text-font"] : void 0;
15751
+ return Array.isArray(font) && font.length === 1 && typeof font[0] === "string" && parseTileflowLineBackgroundFontStack(font[0]) !== void 0;
15752
+ });
15753
+ if (backed) {
15754
+ throw new Error(
15755
+ `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.`
15756
+ );
15757
+ }
15203
15758
  if (config.fonts.length > 0) return;
15204
15759
  throw new Error(
15205
15760
  `Tileflow map "${config.id}" contains text but declares an empty fonts directory array.`
@@ -15416,10 +15971,10 @@ function resolveMap(map, options = {}) {
15416
15971
  }
15417
15972
  }
15418
15973
  const leaf = lineage[0];
15419
- const id = parseMapId(leaf.id);
15974
+ const id2 = parseMapId(leaf.id);
15420
15975
  const resolved = {
15421
- id,
15422
- name: leaf.name ?? id,
15976
+ id: id2,
15977
+ name: leaf.name ?? id2,
15423
15978
  version: leaf.version,
15424
15979
  ...design,
15425
15980
  ...modules === void 0 ? {} : { modules }
@@ -15802,12 +16357,12 @@ function validateTileflowMap(input) {
15802
16357
  }
15803
16358
 
15804
16359
  // src/icon-preview.ts
15805
- import { Color as Color4 } from "@maplibre/maplibre-gl-style-spec";
16360
+ import { Color as Color5 } from "@maplibre/maplibre-gl-style-spec";
15806
16361
  function resolveTileflowIconPreviewPaint(appearance) {
15807
16362
  return {
15808
16363
  ...appearance.defaults,
15809
- color: Color4.parse(appearance.defaults.color),
15810
- haloColor: Color4.parse(appearance.defaults.haloColor)
16364
+ color: Color5.parse(appearance.defaults.color),
16365
+ haloColor: Color5.parse(appearance.defaults.haloColor)
15811
16366
  };
15812
16367
  }
15813
16368
  function createTileflowIconPreviewRuntime() {
@@ -15928,13 +16483,13 @@ var operations = [
15928
16483
  },
15929
16484
  {
15930
16485
  api: "withRenderStack(module, { name: renderPass(...) })",
15931
- description: "Add an owner-local semantic pass around a semantic target.",
16486
+ description: "Add an owner-local semantic pass at a band edge or beside a semantic target.",
15932
16487
  name: "render-pass",
15933
16488
  scope: "render-stack"
15934
16489
  },
15935
16490
  {
15936
16491
  api: "withRenderStack(module, { name: refineRenderTarget(...) })",
15937
- description: "Refine an owner-local semantic target without a physical layer patch.",
16492
+ description: "Refine or move an owner-local semantic target without a physical layer patch.",
15938
16493
  name: "refine-render-target",
15939
16494
  scope: "render-stack"
15940
16495
  }
@@ -16081,6 +16636,7 @@ var expressionBuilders = [
16081
16636
  returns: "boolean",
16082
16637
  serializedOperator: "has"
16083
16638
  },
16639
+ { arguments: [], name: "id", returns: "string | number | null", serializedOperator: "id" },
16084
16640
  {
16085
16641
  arguments: [
16086
16642
  "interpolation: linear | exponential(base) | cubic-bezier(x1,y1,x2,y2)",
@@ -16137,6 +16693,12 @@ var expressionBuilders = [
16137
16693
  returns: "number",
16138
16694
  serializedOperator: "min"
16139
16695
  },
16696
+ {
16697
+ arguments: ["dividend: number", "divisor: number"],
16698
+ name: "modulo",
16699
+ returns: "number",
16700
+ serializedOperator: "%"
16701
+ },
16140
16702
  {
16141
16703
  arguments: ["first: number", "second: number", "...rest: number[]"],
16142
16704
  name: "multiply",
@@ -16286,7 +16848,10 @@ var tileflowAuthoringManifest = deepFreeze2({
16286
16848
  geometries: [...tileflowRenderSelectorGeometries],
16287
16849
  limits: tileflowRenderStackLimits,
16288
16850
  operationNamePattern: tileflowRenderStackOperationNamePattern.source,
16289
- phases: [...tileflowRenderStackPhases],
16851
+ placements: {
16852
+ bands: [...tileflowBandPlacements],
16853
+ targets: ["{ above: target }", "{ below: target }"]
16854
+ },
16290
16855
  renderers: [...tileflowRenderStackRenderers],
16291
16856
  selectorKinds: [...tileflowRenderSelectorKinds],
16292
16857
  targetPattern: tileflowSemanticTargetPattern.source
@@ -16984,6 +17549,30 @@ function validateTileflowRuntimeSource(source) {
16984
17549
  function validateTileflowThemeSelection(value) {
16985
17550
  return value === "system" || isTileflowThemeName(value);
16986
17551
  }
17552
+ var tileflowThemeBlendLimits = Object.freeze({ maximumThemes: 8, minimumThemes: 2 });
17553
+ function validateTileflowThemeBlendSelection(value) {
17554
+ if (!value || typeof value !== "object") return false;
17555
+ const { position, themes } = value;
17556
+ return Array.isArray(themes) && themes.length >= tileflowThemeBlendLimits.minimumThemes && themes.length <= tileflowThemeBlendLimits.maximumThemes && themes.every((theme) => isTileflowThemeName(theme)) && typeof position === "number" && Number.isFinite(position) && position >= 0 && position <= themes.length - 1;
17557
+ }
17558
+ function nearestTileflowBlendTheme(blend) {
17559
+ const index = Math.min(blend.themes.length - 1, Math.max(0, Math.round(blend.position)));
17560
+ return blend.themes[index];
17561
+ }
17562
+ function resolveTileflowRuntimeThemeBlend(options) {
17563
+ if (!validateTileflowThemeBlendSelection(options.blend)) {
17564
+ throw new TypeError(
17565
+ "A Tileflow theme blend requires two to eight concrete themes and a position."
17566
+ );
17567
+ }
17568
+ return options.blend.themes.map(
17569
+ (theme) => resolveTileflowRuntimeStyle({
17570
+ manifestMap: options.manifestMap,
17571
+ source: options.source,
17572
+ theme
17573
+ })
17574
+ );
17575
+ }
16987
17576
  function assertValidTileflowRuntimeSource(source) {
16988
17577
  const validation = validateTileflowRuntimeSource(source);
16989
17578
  if (!validation.ok) {
@@ -17936,7 +18525,7 @@ async function serializeTileflowIconsLockfile(input) {
17936
18525
  import { z as z12 } from "zod";
17937
18526
  var tileflowIconCompositionMaximumBytes = 1024 * 1024;
17938
18527
  var idsSchema = z12.array(tileflowIconIdSchema).max(tileflowIconPackageLimits.maxIconCount).refine(
17939
- (ids) => new Set(ids).size === ids.length && ids.every((id, index) => index === 0 || compareCodeUnits11(ids[index - 1], id) < 0),
18528
+ (ids) => new Set(ids).size === ids.length && ids.every((id2, index) => index === 0 || compareCodeUnits11(ids[index - 1], id2) < 0),
17940
18529
  "Icon exports must be unique and sorted by code unit"
17941
18530
  );
17942
18531
  var localContributorSchema = z12.object({ kind: z12.literal("local"), iconIds: idsSchema }).strict();
@@ -18031,7 +18620,7 @@ var tileflowIconCompositionSchema = z12.object({
18031
18620
  teams.add(contributor.teamId);
18032
18621
  teamSlugs.add(contributor.reference.split("/")[0]);
18033
18622
  }
18034
- for (const id of contributor.iconIds) expected.set(id, ordinal);
18623
+ for (const id2 of contributor.iconIds) expected.set(id2, ordinal);
18035
18624
  }
18036
18625
  if (teams.size !== 1 || teamSlugs.size !== 1)
18037
18626
  context.addIssue({
@@ -18123,12 +18712,14 @@ export {
18123
18712
  createStyle,
18124
18713
  createStyleResult,
18125
18714
  createTileflowIconPreviewRuntime,
18715
+ createTileflowLineBackgroundFontStack,
18126
18716
  createTileflowSessionController,
18127
18717
  createTileflowSessionId,
18128
18718
  defaultTileflowManifestUrl,
18129
18719
  defaultTileflowRuntimeView,
18130
18720
  defineMap,
18131
18721
  defineTheme,
18722
+ deriveTileflowLineBackgroundGlyphs,
18132
18723
  diffTileflowIconPackageManifests,
18133
18724
  diffTileflowMaps,
18134
18725
  disable,
@@ -18161,8 +18752,10 @@ export {
18161
18752
  maplibreOverlay,
18162
18753
  mergeTileflowAnalytics,
18163
18754
  nautical,
18755
+ nearestTileflowBlendTheme,
18164
18756
  normalizeTileflowCaptureId,
18165
18757
  normalizeTileflowCaptureScene,
18758
+ normalizeTileflowLineBackgroundMetrics,
18166
18759
  normalizeTileflowRuntimeCenter,
18167
18760
  normalizeTileflowStaticImageSize,
18168
18761
  normalizeTileflowUrl,
@@ -18172,6 +18765,7 @@ export {
18172
18765
  parseTileflowIconComposition,
18173
18766
  parseTileflowIconJson,
18174
18767
  parseTileflowIconsLockfile,
18768
+ parseTileflowLineBackgroundFontStack,
18175
18769
  parseTileflowMap,
18176
18770
  parseTileflowRuntimeManifest,
18177
18771
  poi,
@@ -18193,12 +18787,14 @@ export {
18193
18787
  resolveTileflowMapMode,
18194
18788
  resolveTileflowRuntimeStyle,
18195
18789
  resolveTileflowRuntimeTheme,
18790
+ resolveTileflowRuntimeThemeBlend,
18196
18791
  resolveTileflowRuntimeView,
18197
18792
  resolveTileflowStaticImageUrl,
18198
18793
  resolveTileflowTheme,
18199
18794
  roads,
18200
18795
  safeParseTileflowRuntimeManifest,
18201
18796
  scaleTileflowIconLayout,
18797
+ scaleTileflowTextSizeForLineBackground,
18202
18798
  serializeCanonicalJson,
18203
18799
  serializeTileflowFontBundleManifest,
18204
18800
  serializeTileflowIconPackageManifest,
@@ -18208,6 +18804,7 @@ export {
18208
18804
  startTileflowSession,
18209
18805
  tileflowAuthoringManifest,
18210
18806
  tileflowAuthoringManifestSchemaVersion,
18807
+ tileflowBandPlacements,
18211
18808
  tileflowBathymetryDemSourceId,
18212
18809
  tileflowBathymetrySourceId,
18213
18810
  tileflowBathymetryV1Schema,
@@ -18259,6 +18856,10 @@ export {
18259
18856
  tileflowIconsLockfileName,
18260
18857
  tileflowIconsLockfileSchema,
18261
18858
  tileflowLandformClasses,
18859
+ tileflowLayerBands,
18860
+ tileflowLineBackgroundDefaults,
18861
+ tileflowLineBackgroundHaloRatio,
18862
+ tileflowLineBackgroundLimits,
18262
18863
  tileflowLocalDirectoryMaximumLength,
18263
18864
  tileflowLocalDirectoryMessage,
18264
18865
  tileflowMapDefaultMaxDepth,
@@ -18275,7 +18876,6 @@ export {
18275
18876
  tileflowRenderSelectorGeometries,
18276
18877
  tileflowRenderSelectorKinds,
18277
18878
  tileflowRenderStackLimits,
18278
- tileflowRenderStackPhases,
18279
18879
  tileflowRenderStackRenderers,
18280
18880
  tileflowRenderedIconIdentitySchema,
18281
18881
  tileflowRenderedIconManifestEntrySchema,
@@ -18286,6 +18886,7 @@ export {
18286
18886
  tileflowSemanticDiffSchemaVersion,
18287
18887
  tileflowStyleFontFaceLimits,
18288
18888
  tileflowStyleFontFacesMetadataKey,
18889
+ tileflowThemeBlendLimits,
18289
18890
  tileflowThemeLimits,
18290
18891
  tileflowThemeNameSchema,
18291
18892
  tileflowThemeTokenCategories,
@@ -18304,6 +18905,7 @@ export {
18304
18905
  validateTileflowDataCompatibility,
18305
18906
  validateTileflowMap,
18306
18907
  validateTileflowRuntimeSource,
18908
+ validateTileflowThemeBlendSelection,
18307
18909
  validateTileflowThemeSelection,
18308
18910
  validateTileflowWorldV1Tilejson,
18309
18911
  vectorTiles,