@mapwhit/glyph-server 1.4.1

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 ADDED
@@ -0,0 +1,56 @@
1
+ [![NPM version][npm-image]][npm-url]
2
+ [![Build Status][build-image]][build-url]
3
+ [![Dependency Status][deps-image]][deps-url]
4
+
5
+ # map-glyph-server
6
+
7
+ Server for glyph/font files compatible with [mapbox-gl-js], [maplibre-gl-js], [mapwhit-gl-js]
8
+
9
+ ## Install
10
+
11
+ ```sh
12
+ $ npm install --global @mapwhit/glyph-server
13
+ ```
14
+
15
+ ## Environment
16
+
17
+ The following environment variables are used by `@mapwhit/glyph-server`
18
+
19
+ - `MAP_GLYPH_SERVER_FONT_PATH` - needs to specify the location of the fonts files
20
+ - `MAP_GLYPH_SERVER_PORT` - local port to which `@mapwhit/glyph-server` binds to default: *3060*
21
+ - `MAP_GLYPH_SERVER_CACHE_MAX_AGE` - if specified used as a `max-age` value of the
22
+ [`Cache-Control` header][cache-control] when serving font/pbf files
23
+ (maximum amount of seconds a resource is considered fresh)
24
+
25
+ `/etc/default/map-glyph-server` can be used to set environment variables
26
+
27
+ ## Usage
28
+
29
+ Prior to starting the server glyphs should be located in `MAP_GLYPH_SERVER_FONT_PATH` directory. This can
30
+ be achieved using [openmaptiles/fonts] or a similar utility.
31
+
32
+ In order to use the fonts edit [Style JSON][glyphs-doc] and change the endpoint for glyphs:
33
+
34
+ "glyphs": "https://fonts.example.com/fonts/{fontstack}/{range}.pbf",
35
+
36
+ ## License
37
+
38
+ MIT © [Damian Krzeminski](https://furkot.com)
39
+
40
+ [npm-image]: https://img.shields.io/npm/v/@mapwhit/glyph-server
41
+ [npm-url]: https://npmjs.org/package/@mapwhit/glyph-server
42
+
43
+ [build-url]: https://github.com/mapwhit/glyph-server/actions/workflows/check.yaml
44
+ [build-image]: https://img.shields.io/github/actions/workflow/status/mapwhit/glyph-server/check.yaml?branch=main
45
+
46
+ [deps-image]: https://img.shields.io/librariesio/release/npm/@mapwhit/glyph-server
47
+ [deps-url]: https://libraries.io/npm/@mapwhit%2Fglyph-server
48
+
49
+
50
+ [openmaptiles/fonts]: https://github.com/openmaptiles/fonts
51
+ [cache-control]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control
52
+ [glyphs-doc]: https://docs.mapbox.com/mapbox-gl-js/style-spec/glyphs/
53
+
54
+ [mapbox-gl-js]: https://github.com/mapbox/mapbox-gl-js
55
+ [maplibre-gl-js]: https://github.com/maplibre/maplibre-gl-js
56
+ [mapwhit-gl-js]: https://github.com/mapwhit/mapwhit-gl-js
package/index.js ADDED
@@ -0,0 +1,25 @@
1
+ #!/usr/bin/env node
2
+
3
+ require('dotenv').config({ path: '/etc/default/map-glyph-server' });
4
+
5
+ const connect = require('@pirxpilot/connect');
6
+ const router = require('./lib/map-glyph-server');
7
+
8
+ const PORT = process.env.MAP_GLYPH_SERVER_PORT || 3060;
9
+ const FONT_PATH = process.env.MAP_GLYPH_SERVER_FONT_PATH;
10
+
11
+ if (!FONT_PATH) {
12
+ console.error('Please configure MAP_GLYPH_SERVER_FONT_PATH');
13
+ process.exit(1);
14
+ }
15
+
16
+ const app = connect();
17
+
18
+ app.use('/fonts', router(FONT_PATH));
19
+
20
+ module.exports = app;
21
+
22
+ if (!module.parent) {
23
+ app.listen(PORT);
24
+ console.log('Listening on port', PORT);
25
+ }
@@ -0,0 +1,35 @@
1
+ const { readdir, stat } = require('node:fs/promises');
2
+ const { join } = require('node:path');
3
+
4
+ const debug = require('debug')('map-glyph-server');
5
+
6
+ module.exports = findFonts;
7
+
8
+ const memo = Object.create(null);
9
+
10
+ function findFonts(fontPath) {
11
+ let p = memo[fontPath];
12
+ if (!p) {
13
+ memo[fontPath] = p = findFontsImplementation(fontPath);
14
+ }
15
+ return p;
16
+ }
17
+
18
+ async function findFontsImplementation(fontPath) {
19
+ debug('Looking for fonts in:', fontPath);
20
+ const dirs = await readdir(fontPath, { withFileTypes: true });
21
+ const fonts = {};
22
+ await Promise.all(
23
+ dirs.filter(d => d.isDirectory() || d.isSymbolicLink()).map(checkDir)
24
+ );
25
+ return fonts;
26
+
27
+ async function checkDir({ name, parentPath }) {
28
+ const filename = join(parentPath, name, '0-255.pbf');
29
+ const st = await stat(filename).catch(() => null);
30
+ if (st?.isFile()) {
31
+ debug('Found font', name);
32
+ fonts[name] = true;
33
+ }
34
+ }
35
+ }
@@ -0,0 +1,51 @@
1
+ const { join } = require('node:path');
2
+ const { readFile } = require('node:fs/promises');
3
+
4
+ const { combine } = require('@furkot/glyph-pbf-composite');
5
+
6
+ const debug = require('debug')('map-glyph-server');
7
+
8
+ module.exports = getFontsPbf;
9
+
10
+ async function getFontPbf(fontPath, choices, range) {
11
+ debug('Looking for', choices[0], range);
12
+ const checked = Object.create(null);
13
+
14
+ for (const name of choices) {
15
+ const pbf = await loadFont(name);
16
+ if (pbf) {
17
+ return pbf;
18
+ }
19
+ }
20
+
21
+ async function loadFont(name) {
22
+ const filename = join(fontPath, name, `${range}.pbf`);
23
+
24
+ if (checked[filename]) {
25
+ return;
26
+ }
27
+ try {
28
+ const data = await readFile(filename);
29
+ debug('Font found:', name);
30
+ return data;
31
+ } catch {
32
+ debug('Font not found:', name);
33
+ checked[filename] = true;
34
+ }
35
+ }
36
+ }
37
+
38
+ async function getFontsPbf(fontPath, fonts, range, fallbacks) {
39
+ let pbfs = await Promise.all(
40
+ fonts.map(font => getFontPbf(fontPath, [font, ...fallbacks], range))
41
+ );
42
+ // only non-empty fonts are OK
43
+ pbfs = pbfs.filter(p => p?.length);
44
+ debug(pbfs);
45
+ if (pbfs.length < fonts.length) {
46
+ console.log('here');
47
+ throw 404;
48
+ }
49
+ const result = pbfs.length > 1 ? combine(pbfs) : pbfs[0];
50
+ return result;
51
+ }
@@ -0,0 +1,66 @@
1
+ const Router = require('@pirxpilot/router');
2
+
3
+ const findFonts = require('./find-fonts');
4
+ const getFontsPbf = require('./get-fonts-pbf');
5
+ const entityTag = require('etag');
6
+ const fresh = require('fresh');
7
+
8
+ const CACHE_MAX_AGE = process.env.MAP_GLYPH_SERVER_CACHE_MAX_AGE;
9
+
10
+ async function find(req, res, next) {
11
+ const fonts = await findFonts(req.fontPath);
12
+ req.fonts = fonts;
13
+ req.fontList = Object.keys(fonts);
14
+ next();
15
+ }
16
+
17
+ function sendFontsList(req, res) {
18
+ const fontList = req.fontList.sort();
19
+ const payload = JSON.stringify(fontList);
20
+ res.setHeader('Content-Type', 'application/json');
21
+ res.setHeader('Content-Length', Buffer.byteLength(payload));
22
+ return res.end(payload);
23
+ }
24
+
25
+ async function sendFontsPbf(req, res) {
26
+ const { fontPath } = req;
27
+ const { fontstack, range } = req.params;
28
+
29
+ const fontstacks = decodeURI(fontstack).split(',');
30
+
31
+ const pbf = await getFontsPbf(fontPath, fontstacks, range, req.fontList);
32
+ if (pbf.length === 0) {
33
+ return res.writeHeader(204).end();
34
+ }
35
+ if (CACHE_MAX_AGE) {
36
+ const etag = entityTag(pbf);
37
+ res.setHeader('Cache-Control', `public, max-age=${CACHE_MAX_AGE}`);
38
+ res.setHeader('ETag', etag);
39
+ if (fresh(req.headers, { etag })) {
40
+ return res.writeHeader(304).end();
41
+ }
42
+ }
43
+ res.setHeader('Content-Type', 'application/x-protobuf');
44
+ res.setHeader('Content-Length', pbf.length);
45
+ res.end(pbf);
46
+ }
47
+
48
+ module.exports = function (fontPath) {
49
+ const router = new Router({
50
+ strict: true,
51
+ caseSensitive: true
52
+ });
53
+
54
+ router.use(function (req, res, next) {
55
+ req.fontPath = fontPath;
56
+ next();
57
+ });
58
+
59
+ router.get(
60
+ /^\/(?<fontstack>[^/]+)\/(?<range>\d+-\d+)\.pbf$/,
61
+ find,
62
+ sendFontsPbf
63
+ );
64
+ router.get('/.json', find, sendFontsList);
65
+ return router;
66
+ };
package/package.json ADDED
@@ -0,0 +1,45 @@
1
+ {
2
+ "name": "@mapwhit/glyph-server",
3
+ "version": "1.4.1",
4
+ "description": "Server for glyph/font files compatible with mapbox-gl-js",
5
+ "author": {
6
+ "name": "Damian Krzeminski",
7
+ "email": "pirxpilot@furkot.com",
8
+ "url": "https://furkot.com"
9
+ },
10
+ "repository": {
11
+ "type": "git",
12
+ "url": "git+https://github.com/mapwhit/glyph-server.git"
13
+ },
14
+ "license": "MIT",
15
+ "keywords": [
16
+ "map-glyph-server",
17
+ "map",
18
+ "mapbox",
19
+ "font",
20
+ "vector tiles"
21
+ ],
22
+ "bin": {
23
+ "map-glyph-server": "index.js"
24
+ },
25
+ "dependencies": {
26
+ "@furkot/glyph-pbf-composite": "~1",
27
+ "@pirxpilot/connect": "~4",
28
+ "@pirxpilot/router": "~1",
29
+ "debug": "~4",
30
+ "dotenv": "~16",
31
+ "etag": "~1",
32
+ "fresh": "~2"
33
+ },
34
+ "devDependencies": {
35
+ "@biomejs/biome": "^1.9.4",
36
+ "supertest-fetch": "~2"
37
+ },
38
+ "scripts": {
39
+ "test": "make check"
40
+ },
41
+ "files": [
42
+ "index.js",
43
+ "lib"
44
+ ]
45
+ }