@tileflow/dev 0.1.0-alpha.27 → 0.1.0-alpha.29

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -73,6 +73,12 @@ manifest pointer, tracks its own files, and refuses to replace a hosted manifest
73
73
  Production rejects unresolved local PMTiles; publish data explicitly or provide an application-owned
74
74
  production source. Local development snapshots do not publish or redistribute the original dataset.
75
75
 
76
+ Maps whose labels use a line-fitted `text.background` draw text with derived glyph stacks. Local
77
+ previews and captures route such a map's glyph URL through `__glyphs/<map>/…`. Ordinary stacks pass
78
+ through from the declared provider; derived ranges are computed from their source ranges, which are
79
+ cached under `.tileflow/cache/glyphs/v1`. Production output keeps the declared provider, which must
80
+ serve derived stacks.
81
+
76
82
  ## Keep a watched request handler
77
83
 
78
84
  Use one session for repeated work and close it when the owning server stops:
@@ -137,8 +143,9 @@ This is the one normal icon-preparation path. `compileTileflowIconPackages`,
137
143
  `prepareTileflowCatalogIcons`, `inspectTileflowIconCatalogs` and `getTileflowIconWatchPaths` all use
138
144
  it, so `createTileflowBuildArtifacts`, `createTileflowArtifactSession`, and every adapter that
139
145
  enters them accept the same explicit `icons` resolution options: `cacheRoot`, `offline`,
140
- `deliveryOrigins`, `fetch` and `signal`. Nothing selects a cache, an origin, or a transport from
141
- implicit process state, and no build or watch polls a catalog head. `prepareTileflowCatalogIcons`
146
+ `deliveryOrigins`, `fetch` and `signal`. An explicit `cacheRoot` takes precedence over
147
+ `TILEFLOW_ICON_CACHE_DIR`, then the OS cache default. Origins and transport use their documented
148
+ defaults unless supplied explicitly; no build or watch polls a catalog head. `prepareTileflowCatalogIcons`
142
149
  returns `mapIconCompositions` alongside `mapIconSources`, and build-manifest entries carry the
143
150
  receipt as `sourceAssets.iconComposition` with `mapRevisionSchemaVersion: 2`.
144
151
 
@@ -159,8 +166,8 @@ Applications may explicitly configure trusted `deliveryOrigins`; locks cannot ad
159
166
  forbids all fetches and fails on missing or corrupt entries. Complete-map offline rendering still
160
167
  depends on unrelated tiles, glyphs, and other external resources.
161
168
 
162
- The cache lives under the OS user cache with `tileflow/icons/v1/<contentHash>`. `cacheRoot` or
163
- `TILEFLOW_ICON_CACHE_DIR` overrides its parent. Atomic directory installation and exact-entry
169
+ The cache lives under the OS user cache with `tileflow/icons/v1/<contentHash>`. `cacheRoot`, then
170
+ `TILEFLOW_ICON_CACHE_DIR`, overrides its parent. Atomic directory installation and exact-entry
164
171
  retirement support concurrent writers and corruption repair. Symlink entries are rejected. This is
165
172
  a disposable cache, not a security sandbox against another process controlling the same user
166
173
  account. Incomplete temporary entries are never accepted as published cache entries.
@@ -5,6 +5,38 @@ import { TileflowRenderer, TileflowNativeBuildRecord } from '@tileflow/core/nati
5
5
  import { TileflowBuildAsset, TileflowIconResolutionOptions, PreparedTileflowBuildCatalog, TileflowSourceCatalog, PreparedTileflowCatalog } from './icons.js';
6
6
  export { defaultTileflowConfigPath } from './config.js';
7
7
 
8
+ /**
9
+ * Local glyph serving for line-fitted text backgrounds.
10
+ *
11
+ * A style whose text uses a derived `<stack> lines-v1-…` font stack needs a glyph provider that
12
+ * derives those ranges. Local previews and captures route such a map's glyph URL through
13
+ * `__glyphs/<map>/…`: ordinary stacks pass through from the map's declared provider unchanged, and
14
+ * derived stacks are computed from their source stack's range. Production output keeps the
15
+ * declared provider, which must serve derived stacks itself.
16
+ */
17
+ declare const tileflowLineBackgroundGlyphRoute = "__glyphs";
18
+ /** Map name to the upstream glyph URL template its styles declared. Local only; never serialized. */
19
+ type TileflowLineBackgroundGlyphProviders = Readonly<Record<string, string>>;
20
+ type TileflowLineBackgroundGlyphResponderOptions = {
21
+ /** Project root; ranges are cached under `.tileflow/cache/glyphs/v1`. */
22
+ cwd: string;
23
+ fetch?: typeof globalThis.fetch;
24
+ };
25
+ type TileflowLineBackgroundGlyphResponder = (upstream: string, fontStack: string, range: string) => Promise<Response>;
26
+ /** Whether a compiled style draws text with a derived line-background stack. */
27
+ declare function usesTileflowLineBackgroundGlyphs(style: MapLibreStyle): boolean;
28
+ /** Parses `/__glyphs/<map>/<fontstack>/<range>.pbf` from a decoded route path. */
29
+ declare function parseTileflowLineBackgroundGlyphPath(path: string): {
30
+ fontStack: string;
31
+ mapName: string;
32
+ range: string;
33
+ } | undefined;
34
+ /**
35
+ * Serves one glyph range for a declared upstream template. Upstream ranges are cached on disk;
36
+ * derived ranges are recomputed from them and kept in a bounded in-memory cache.
37
+ */
38
+ declare function createTileflowLineBackgroundGlyphResponder(options: TileflowLineBackgroundGlyphResponderOptions): TileflowLineBackgroundGlyphResponder;
39
+
8
40
  type TileflowLocalTilesetFile = Readonly<{
9
41
  byteLength: number;
10
42
  etag: string;
@@ -121,6 +153,12 @@ type TileflowBuildArtifactsOptions = {
121
153
  assetBaseUrl?: string;
122
154
  config?: string;
123
155
  cwd?: string;
156
+ /**
157
+ * Native local preview only: the exact HTTP origin serving these artifacts, such as
158
+ * `http://127.0.0.1:3333`. Native checks accept HTTP resources at this origin only; every other
159
+ * origin still requires HTTPS. It is never inferred, and web or production output rejects it.
160
+ */
161
+ developmentOrigin?: string;
124
162
  /**
125
163
  * Explicit shared Icon Set resolution settings for this build.
126
164
  *
@@ -149,6 +187,8 @@ type TileflowBuildArtifacts = {
149
187
  project: TileflowBuildCatalog;
150
188
  /** Local-only archive ports. Paths never enter serialized build artifacts. */
151
189
  localTilesets?: TileflowLocalTilesetFile[];
190
+ /** Local-only upstream glyph templates for maps whose text uses line-fitted backgrounds. */
191
+ lineBackgroundGlyphs?: TileflowLineBackgroundGlyphProviders;
152
192
  /** Optional local-only provenance. It is never serialized as a build artifact. */
153
193
  styleInspections?: TileflowBuildStyleInspections;
154
194
  styles: TileflowBuildStyles;
@@ -172,7 +212,7 @@ type TileflowArtifactPlan = TileflowBuildArtifacts & {
172
212
  schemaVersion: typeof tileflowArtifactPlanSchemaVersion;
173
213
  sourceProject: TileflowSourceCatalog;
174
214
  };
175
- type CreateTileflowArtifactPlanOptions = Pick<TileflowBuildArtifactsOptions, 'apiBaseUrl' | 'assetBaseUrl' | 'inspection' | 'renderer' | 'styleBaseUrl' | 'target'> & {
215
+ type CreateTileflowArtifactPlanOptions = Pick<TileflowBuildArtifactsOptions, 'apiBaseUrl' | 'assetBaseUrl' | 'developmentOrigin' | 'inspection' | 'renderer' | 'styleBaseUrl' | 'target'> & {
176
216
  inputFiles?: readonly string[];
177
217
  };
178
218
  type WriteTileflowArtifactPlanOptions = {
@@ -222,4 +262,4 @@ declare function assertTileflowSelfHostedManifestTarget(manifestPath: string, op
222
262
  overwriteHostedManifest?: boolean;
223
263
  }): Promise<void>;
224
264
 
225
- export { type CreateTileflowArtifactPlanOptions, PreparedTileflowBuildCatalog, PreparedTileflowCatalog, type TileflowArtifactAcquisition, type TileflowArtifactDiagnostic, type TileflowArtifactFile, type TileflowArtifactInputGraph, type TileflowArtifactPlan, type TileflowArtifactSession, type TileflowArtifactSessionOptions, type TileflowArtifactSessionState, type TileflowBuildArtifacts, type TileflowBuildArtifactsOptions, TileflowBuildAsset, type TileflowBuildStyleInspections, TileflowHostedManifestOverwriteError, TileflowIconResolutionOptions, type TileflowLocalTilesetFile, TileflowLocalTilesetProductionError, type TileflowManifestOptions, TileflowSourceCatalog, type WriteTileflowArtifactPlanOptions, type WriteTileflowBuildArtifactsOptions, assertNoLocalTilesetsForProduction, assertTileflowSelfHostedManifestTarget, createTileflowArtifactDiagnostics, createTileflowArtifactPlan, createTileflowArtifactSession, createTileflowBuildArtifacts, createTileflowBuildProvenance, createTileflowManifest, createTileflowStyle, createTileflowStyles, disposeTileflowBuildArtifacts, getTileflowArtifactFiles, getTileflowAssetBasePath, getTileflowAssetFileName, getTileflowWatchPaths, isTileflowArtifactInputPath, joinTileflowPublicUrl, normalizeTileflowBasePath, prepareTileflowHostedNativeDeployment, prepareTileflowLocalTilesets, refreshTileflowArtifactSession, resolveTileflowArtifactPublicUrls, tileflowArtifactInventoryFileName, tileflowArtifactPlanSchemaVersion, tileflowArtifactSessionSchemaVersion, writeTileflowArtifactPlan, writeTileflowBuildArtifacts };
265
+ export { type CreateTileflowArtifactPlanOptions, PreparedTileflowBuildCatalog, PreparedTileflowCatalog, type TileflowArtifactAcquisition, type TileflowArtifactDiagnostic, type TileflowArtifactFile, type TileflowArtifactInputGraph, type TileflowArtifactPlan, type TileflowArtifactSession, type TileflowArtifactSessionOptions, type TileflowArtifactSessionState, type TileflowBuildArtifacts, type TileflowBuildArtifactsOptions, TileflowBuildAsset, type TileflowBuildStyleInspections, TileflowHostedManifestOverwriteError, TileflowIconResolutionOptions, type TileflowLineBackgroundGlyphProviders, type TileflowLineBackgroundGlyphResponder, type TileflowLocalTilesetFile, TileflowLocalTilesetProductionError, type TileflowManifestOptions, TileflowSourceCatalog, type WriteTileflowArtifactPlanOptions, type WriteTileflowBuildArtifactsOptions, assertNoLocalTilesetsForProduction, assertTileflowSelfHostedManifestTarget, createTileflowArtifactDiagnostics, createTileflowArtifactPlan, createTileflowArtifactSession, createTileflowBuildArtifacts, createTileflowBuildProvenance, createTileflowLineBackgroundGlyphResponder, createTileflowManifest, createTileflowStyle, createTileflowStyles, disposeTileflowBuildArtifacts, getTileflowArtifactFiles, getTileflowAssetBasePath, getTileflowAssetFileName, getTileflowWatchPaths, isTileflowArtifactInputPath, joinTileflowPublicUrl, normalizeTileflowBasePath, parseTileflowLineBackgroundGlyphPath, prepareTileflowHostedNativeDeployment, prepareTileflowLocalTilesets, refreshTileflowArtifactSession, resolveTileflowArtifactPublicUrls, tileflowArtifactInventoryFileName, tileflowArtifactPlanSchemaVersion, tileflowArtifactSessionSchemaVersion, tileflowLineBackgroundGlyphRoute, usesTileflowLineBackgroundGlyphs, writeTileflowArtifactPlan, writeTileflowBuildArtifacts };
package/dist/artifacts.js CHANGED
@@ -8,6 +8,7 @@ import {
8
8
  createTileflowArtifactSession,
9
9
  createTileflowBuildArtifacts,
10
10
  createTileflowBuildProvenance,
11
+ createTileflowLineBackgroundGlyphResponder,
11
12
  createTileflowManifest,
12
13
  createTileflowStyle,
13
14
  createTileflowStyles,
@@ -15,15 +16,18 @@ import {
15
16
  getTileflowArtifactFiles,
16
17
  getTileflowWatchPaths,
17
18
  isTileflowArtifactInputPath,
19
+ parseTileflowLineBackgroundGlyphPath,
18
20
  prepareTileflowHostedNativeDeployment,
19
21
  prepareTileflowLocalTilesets,
20
22
  refreshTileflowArtifactSession,
21
23
  tileflowArtifactInventoryFileName,
22
24
  tileflowArtifactPlanSchemaVersion,
23
25
  tileflowArtifactSessionSchemaVersion,
26
+ tileflowLineBackgroundGlyphRoute,
27
+ usesTileflowLineBackgroundGlyphs,
24
28
  writeTileflowArtifactPlan,
25
29
  writeTileflowBuildArtifacts
26
- } from "./chunk-QAMFAM3S.js";
30
+ } from "./chunk-LXSFLPIU.js";
27
31
  import "./chunk-ONYQNLNA.js";
28
32
  import {
29
33
  getTileflowAssetBasePath,
@@ -49,6 +53,7 @@ export {
49
53
  createTileflowArtifactSession,
50
54
  createTileflowBuildArtifacts,
51
55
  createTileflowBuildProvenance,
56
+ createTileflowLineBackgroundGlyphResponder,
52
57
  createTileflowManifest,
53
58
  createTileflowStyle,
54
59
  createTileflowStyles,
@@ -61,6 +66,7 @@ export {
61
66
  isTileflowArtifactInputPath,
62
67
  joinTileflowPublicUrl,
63
68
  normalizeTileflowBasePath,
69
+ parseTileflowLineBackgroundGlyphPath,
64
70
  prepareTileflowHostedNativeDeployment,
65
71
  prepareTileflowLocalTilesets,
66
72
  refreshTileflowArtifactSession,
@@ -68,6 +74,8 @@ export {
68
74
  tileflowArtifactInventoryFileName,
69
75
  tileflowArtifactPlanSchemaVersion,
70
76
  tileflowArtifactSessionSchemaVersion,
77
+ tileflowLineBackgroundGlyphRoute,
78
+ usesTileflowLineBackgroundGlyphs,
71
79
  writeTileflowArtifactPlan,
72
80
  writeTileflowBuildArtifacts
73
81
  };
@@ -168,7 +168,7 @@ function renderTileflowPreviewHtml(preview, basePath, initialStatus, isSemanticP
168
168
  body { font-family: ui-sans-serif, system-ui, sans-serif; }
169
169
  #map {
170
170
  --tileflow-globe-radius: 220px;
171
- background-color: #2F5070;
171
+ background-color: var(--tileflow-space, #2F5070);
172
172
  background-image:
173
173
  radial-gradient(circle at 18% 24%, rgba(225, 240, 250, 0.34) 0 1px, transparent 1.4px),
174
174
  radial-gradient(circle at 72% 62%, rgba(225, 240, 250, 0.22) 0 1px, transparent 1.3px),
@@ -191,12 +191,12 @@ function renderTileflowPreviewHtml(preview, basePath, initialStatus, isSemanticP
191
191
  top: 50%;
192
192
  width: calc(var(--tileflow-globe-radius) * 2);
193
193
  height: calc(var(--tileflow-globe-radius) * 2);
194
- border: 1px solid rgba(248, 252, 255, 0.88);
194
+ border: 1px solid var(--tileflow-rim-line, rgba(248, 252, 255, 0.88));
195
195
  border-radius: 50%;
196
196
  box-shadow:
197
- 0 0 9px 3px rgba(248, 252, 255, 0.9),
198
- 0 0 28px 10px rgba(176, 220, 246, 0.52),
199
- 0 0 58px 20px rgba(132, 195, 232, 0.2);
197
+ 0 0 9px 3px var(--tileflow-rim-core, rgba(248, 252, 255, 0.9)),
198
+ 0 0 28px 10px var(--tileflow-rim-glow, rgba(176, 220, 246, 0.52)),
199
+ 0 0 58px 20px var(--tileflow-rim-haze, rgba(132, 195, 232, 0.2));
200
200
  pointer-events: none;
201
201
  transform: translate(-50%, -50%);
202
202
  }
@@ -233,7 +233,8 @@ function renderTileflowPreviewHtml(preview, basePath, initialStatus, isSemanticP
233
233
  white-space: pre-wrap;
234
234
  }
235
235
  .maplibregl-ctrl-group .tileflow-3d-toggle,
236
- .maplibregl-ctrl-group .tileflow-tree-toggle {
236
+ .maplibregl-ctrl-group .tileflow-tree-toggle,
237
+ .maplibregl-ctrl-group .tileflow-theme-toggle {
237
238
  width: auto;
238
239
  min-width: 58px;
239
240
  padding: 0 9px;
@@ -247,7 +248,8 @@ function renderTileflowPreviewHtml(preview, basePath, initialStatus, isSemanticP
247
248
  color: #174EA6;
248
249
  }
249
250
  .maplibregl-ctrl-group .tileflow-3d-toggle:hover,
250
- .maplibregl-ctrl-group .tileflow-tree-toggle:hover {
251
+ .maplibregl-ctrl-group .tileflow-tree-toggle:hover,
252
+ .maplibregl-ctrl-group .tileflow-theme-toggle:hover {
251
253
  background-color: #F1F3F4;
252
254
  }
253
255
  </style>
@@ -275,6 +277,8 @@ function renderTileflowPreviewHtml(preview, basePath, initialStatus, isSemanticP
275
277
  const previewLabel = ${JSON.stringify(preview?.label)};
276
278
  const styleUrl = ${JSON.stringify(styleUrl)};
277
279
  const previewMapOptions = ${JSON.stringify(mapOptions)};
280
+ const previewTheme = ${serializeInlineJson(preview?.themeName ?? null)};
281
+ const previewThemes = ${serializeInlineJson(preview?.themeNames ?? [])};
278
282
  const isSemanticPreview = ${JSON.stringify(isSemanticPreview)};
279
283
  const previewFontFaces = ${serializeInlineJson(fontFaces)};
280
284
  const treeSearchParameters = new URL(location.href).searchParams;
@@ -985,6 +989,30 @@ function renderTileflowPreviewHtml(preview, basePath, initialStatus, isSemanticP
985
989
  );
986
990
  container.classList.add("tileflow-globe");
987
991
  container.style.setProperty("--tileflow-globe-radius", radius.toFixed(2) + "px");
992
+ applyAtmosphereBackdrop(map, container);
993
+ }
994
+
995
+ // Uses the map's declared atmosphere for the preview's space and rim, so a themed globe shows
996
+ // its own colours; maps without one keep the default backdrop.
997
+ function applyAtmosphereBackdrop(map, container) {
998
+ const atmosphere = map.getStyle?.()?.metadata?.["tileflow:atmosphere"];
999
+ const rgba = (hex, alpha) => {
1000
+ const match = /^#([0-9a-f]{6})$/i.exec(String(hex ?? ""));
1001
+ if (!match) return undefined;
1002
+ const value = Number.parseInt(match[1], 16);
1003
+ return "rgba(" + (value >> 16) + ", " + ((value >> 8) & 255) + ", " + (value & 255) + ", " + alpha + ")";
1004
+ };
1005
+ const properties = {
1006
+ "--tileflow-space": rgba(atmosphere?.spaceColor, 1),
1007
+ "--tileflow-rim-line": rgba(atmosphere?.horizonColor, 0.88),
1008
+ "--tileflow-rim-core": rgba(atmosphere?.horizonColor, 0.9),
1009
+ "--tileflow-rim-glow": rgba(atmosphere?.horizonColor, 0.52),
1010
+ "--tileflow-rim-haze": rgba(atmosphere?.skyColor, 0.35),
1011
+ };
1012
+ for (const [name, value] of Object.entries(properties)) {
1013
+ if (value) container.style.setProperty(name, value);
1014
+ else container.style.removeProperty(name);
1015
+ }
988
1016
  }
989
1017
 
990
1018
  function createBuildingWireframeLayer(map, styleLayer) {
@@ -3777,6 +3805,38 @@ function renderTileflowPreviewHtml(preview, basePath, initialStatus, isSemanticP
3777
3805
  }
3778
3806
  }
3779
3807
 
3808
+ // Switches a map preview between its themes. The preview reloads with the next theme and keeps
3809
+ // the camera and toggles already written to the URL; scene previews own their theme.
3810
+ class ThemeControl {
3811
+ onAdd(map) {
3812
+ this.map = map;
3813
+ this.container = document.createElement("div");
3814
+ this.container.className = "maplibregl-ctrl maplibregl-ctrl-group";
3815
+ this.button = document.createElement("button");
3816
+ this.button.type = "button";
3817
+ this.button.className = "tileflow-theme-toggle";
3818
+ const next = previewThemes[(previewThemes.indexOf(previewTheme) + 1) % previewThemes.length];
3819
+ this.button.textContent = String(previewTheme).toUpperCase();
3820
+ this.button.title = "Switch to the " + next + " theme";
3821
+ this.button.setAttribute("aria-label", "Switch to the " + next + " theme");
3822
+ this.handleClick = () => {
3823
+ writeCameraToUrl(map);
3824
+ const url = new URL(location.href);
3825
+ url.searchParams.set("theme", next);
3826
+ location.assign(url.href);
3827
+ };
3828
+ this.button.addEventListener("click", this.handleClick);
3829
+ this.container.appendChild(this.button);
3830
+ return this.container;
3831
+ }
3832
+
3833
+ onRemove() {
3834
+ this.button?.removeEventListener("click", this.handleClick);
3835
+ this.container?.remove();
3836
+ this.map = undefined;
3837
+ }
3838
+ }
3839
+
3780
3840
  const previewLayerGroupIds = [
3781
3841
  "labels", "pois", "roads", "transit", "buildings", "landuse", "water"
3782
3842
  ];
@@ -3913,6 +3973,7 @@ function renderTileflowPreviewHtml(preview, basePath, initialStatus, isSemanticP
3913
3973
  map.addControl(threeDimensionalControl, "top-right");
3914
3974
  map.addControl(treeControl, "top-right");
3915
3975
  }
3976
+ if (previewThemes.length > 1) map.addControl(new ThemeControl(), "top-right");
3916
3977
  let ensuringThreeDimensionalLayers;
3917
3978
  const treeRuntimeMinimumZoom = 16;
3918
3979
  const ensureThreeDimensionalLayers = () => {
@@ -4200,7 +4261,8 @@ function resolveTileflowPreview(project, selection = {}) {
4200
4261
  },
4201
4262
  label: `${mapName} / ${selectedTheme.name}`,
4202
4263
  mapName,
4203
- themeName: selectedTheme.name
4264
+ themeName: selectedTheme.name,
4265
+ themeNames: Object.keys(resolvedMap.themes)
4204
4266
  };
4205
4267
  }
4206
4268
  function resolvePreviewTheme(map, requested) {