Skip to main content

Glyph services and label fonts

Switch glyph providers with shared getGlyphs/setGlyphs APIs and distinguish label language from UI locale

Identical portable script / Complete HTML/CSS and button handlers.

Common API

The web SDK, web scripts, desktop scripts and native-library scripts expose await map.getGlyphs() and chainable map.setGlyphs(url, options?). Await queries; confirm script writes with await map.sqs.flush() and web SDK writes with await map.flush(). Names follow the MapLibre public API; execution belongs to the SqsMap shared Rust core.

The getter returns the parsed style's complete URL template, or undefined when unset/unparsed. Null, undefined or an empty string unsets the service. Empty maps can set the service; changing glyphs while a replacement retains a rollback map returns CONFLICT. A template is bounded to 4096 UTF-8 bytes and requires HTTP(S), no credentials or fragment, and exactly one {fontstack} and one {range} token. Tokens cannot occur in the authority. {validate:true} is optional. Shared validation is mandatory; validate:false reports CAPABILITY_UNAVAILABLE.

// Run on a map with an already parsed style. Same web/desktop/native script bytes.
import {map} from 'sqsmap';
const original = await map.getGlyphs();
map.setGlyphs(null);
await map.sqs.flush();
map.setGlyphs(original);
await map.sqs.flush();

Resources, cancellation and fallback

A switch retains ready tiles, source identities and registered images while rebuilding glyph caches, the atlas and label placement. All outstanding style resources become obsolete by generation and unresolved resources are rescheduled; late replies cannot affect the new revision. Input/output preparation failure retains the previous configuration and requests. SDK main/Worker entries abort old HTTP; desktop threads observe the shared cancellation generation. The native library performs no network I/O: its host must stop obsolete downloads and pump new resource descriptors/responses.

The whole ordered text-font array is URL encoded and requested as one font stack. Ranges cover 256 codepoints with a 4 MiB response budget; the per-map atlas is 2048×2048. Matched distributed SDK/WASM, default desktop builds and the locally validated native library include HarmonyOS Sans SC Regular. Missing/failed remote glyphs, or an unset service, use the same shared SDF rasterizer for supported Chinese/Latin characters. Characters absent from that font may still be missing. Custom Rust builds disabling bundled-font have no local fallback and must not claim the distributed capability. Embedding the font increases WASM size; font/rasterizer licenses accompany THIRD_PARTY_NOTICES.

Label language comes from style data and text-field, for example ['coalesce',['get','name:en'],['get','name']]. UI setLocale changes controls only and never translates feature names.

This contract implements codepoint PBF and single-character SDF fallback. Font-face/file registration, full shaping, variable fonts and complex grapheme-cluster layout remain pending in the parent font task. Local results do not replace mobile-device, Windows or macOS acceptance.

Native library

Check SQSM_MAP_GLYPH_SERVICE (8388608). Use the existing sqsm_map_image_json method/args channel for getGlyphs/setGlyphs; the ABI export set is unchanged. Getter JSON null maps to script undefined. Free owned output with sqsm_map_bytes_free; failed output allocation cannot mutate the service. Script users retain the familiar method names rather than the transport envelope.

Complete HTML/CSS/script

Serve this file next to a matched standalone SDK's dist/ and wasm/ directories. OpenFreeMap credits come from the actual attribution API and subscription. Every button handler and page cleanup is included.

<!doctype html>
<html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>SqsMap Glyph service and bundled font</title>
<style>html,body{margin:0;height:100%;font-family:system-ui}#map{height:100%}.tools{position:absolute;top:12px;left:12px;background:white;padding:10px;border-radius:8px;max-width:340px;box-shadow:0 2px 8px #0003}button{padding:6px;margin:2px}pre{white-space:pre-wrap;word-break:break-all}#credits{position:absolute;bottom:0;right:0;background:#ffffffdd;padding:4px;font-size:12px}</style></head>
<body><div id="map"></div><section class="tools"><strong>Glyph service and bundled font</strong><div><button id="remote" disabled>Remote glyphs</button><button id="bundled" disabled>Bundled font</button><button id="read" disabled>Read URL</button></div><pre id="log" role="status">Loading map</pre></section><div id="credits"></div>
<script type="module">
// Serve this file over HTTP next to a matched standalone SqsMap SDK (dist/ + wasm/).
import {MapView} from './dist/index.js';
const log=document.querySelector('#log'),buttons=[...document.querySelectorAll('button')];let map,remoteUrl;const subscriptions=[];
async function run(action){buttons.forEach(b=>b.disabled=true);try{await action();}catch(error){log.textContent=String(error);}finally{if(map)buttons.forEach(b=>b.disabled=false);}}
try{
 map=await MapView.create(document.querySelector('#map'),{worker:false});
 if(!map.capabilities.glyphService||!map.capabilities.bundledGlyphFallback)throw Error('Matched glyph-capable SDK/WASM required');
 subscriptions.push(map.on('sqs.attributionschange',event=>document.querySelector('#credits').textContent=event.attributions.text));
 map.jumpTo({center:[116.4,39.9],zoom:10});map.setStyle('https://tiles.openfreemap.org/styles/liberty');await map.flush();
 const deadline=Date.now()+30000;
 while(!['ready','degraded'].includes(map.basemapStatus.state)){if(Date.now()>deadline)throw Error('Style loading timed out');await new Promise(r=>setTimeout(r,50));}
 remoteUrl=await map.getGlyphs();if(!remoteUrl)throw Error('Style has no glyph provider');
 document.querySelector('#credits').textContent=(await map.getAttributions()).text;
 document.querySelector('#remote').onclick=()=>run(async()=>{map.setGlyphs(remoteUrl);await map.flush();log.textContent='Remote glyph service restored\n'+await map.getGlyphs();});
 document.querySelector('#bundled').onclick=()=>run(async()=>{map.setGlyphs(null);await map.flush();log.textContent='Bundled fallback enabled. Label language still comes from style text-field.';});
 document.querySelector('#read').onclick=()=>run(async()=>{log.textContent=(await map.getGlyphs())??'No glyph service; bundled font fallback';});
 log.textContent='Map ready; switch the glyph provider';buttons.forEach(b=>b.disabled=false);
}catch(error){log.textContent=String(error);map?.destroy();map=null;}
window.addEventListener('pagehide',()=>{for(const subscription of subscriptions)subscription.unsubscribe();map?.destroy();map=null;},{once:true});
</script></body></html>

Empty maps can now configure the service. External glyphs for common text share the request/decode/cache path with URL styles.