Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support glyphs #54

Merged
merged 36 commits into from
Aug 8, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
053dd7e
initial build script to generate SDFs
gmaclennan Aug 1, 2022
ca5ed15
add license file to font
achou11 Aug 1, 2022
34125da
install @fastify/static@5
achou11 Aug 1, 2022
e717590
Merge branch 'master' into feat/glyphs
achou11 Aug 1, 2022
ae2f4e6
output pbf files for sdf script
achou11 Aug 1, 2022
c1b3917
set up @fastify/static plugin
achou11 Aug 1, 2022
5954745
update type annotation for createServer
achou11 Aug 1, 2022
fcf8570
add basic route and tests for fonts
achou11 Aug 1, 2022
15f0414
remove unused imports
achou11 Aug 1, 2022
e7f2045
update fonts route to handle fontstack param better
achou11 Aug 1, 2022
ae359ae
replace style's glyphs field with offline url
achou11 Aug 1, 2022
0b995b5
update create style test to check change in glyphs field
achou11 Aug 1, 2022
b425cfd
remove only call
achou11 Aug 1, 2022
20ee781
generate sdfs in ci
achou11 Aug 2, 2022
d43631f
remove console log in test
achou11 Aug 2, 2022
313b33a
basic api implementation of getGlyphs
achou11 Aug 2, 2022
f9cc35f
add more half-baked tests
achou11 Aug 2, 2022
2a8dffe
implement untested upstream glyphs functionality
achou11 Aug 2, 2022
959d904
remove comment
achou11 Aug 2, 2022
0cf0b1b
update naming convention for statically stored fonts
achou11 Aug 3, 2022
ea23bdd
add todo about validating upstream url
achou11 Aug 3, 2022
fd247c8
update variable name
achou11 Aug 3, 2022
8b465b2
fix glyph url generation
achou11 Aug 3, 2022
bc33d47
update access token error status code to match mapbox
achou11 Aug 3, 2022
f38d819
forward upstream responses
achou11 Aug 3, 2022
35abd03
fix mock in test
achou11 Aug 3, 2022
3c1ba7c
fix git naming of open sans dir
achou11 Aug 3, 2022
bf69fc6
update comments in fastify static register
achou11 Aug 3, 2022
9eb6962
set etag from upstream to reply headers
achou11 Aug 3, 2022
7c9ed4b
return error for invalid ranges
achou11 Aug 4, 2022
bd905e6
add test for error response forwarding
achou11 Aug 4, 2022
65272d9
reduce boilerplate in glyphs tests
achou11 Aug 4, 2022
13f0bf3
handle offline case
achou11 Aug 4, 2022
e286bd2
fix comment
achou11 Aug 4, 2022
0a3e82d
move offline error codes to errors file
achou11 Aug 4, 2022
2ced2e4
disable ci for node 16 due to node-fontnik
achou11 Aug 8, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
return error for invalid ranges
  • Loading branch information
achou11 committed Aug 4, 2022
commit 7c9ed4ba9d9178e4f1fa415910b4c3ffc30184ad
6 changes: 6 additions & 0 deletions src/api/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,12 @@ export const ParseError = createError(
500
)

export const InvalidGlyphsRangeError = createError(
'FST_INVALID_GLYPHS_RANGE',
'Invalid range %s-%s',
400
)

export const createForwardedUpstreamError = (statusCode: number) =>
createError(
`FORWARDED_UPSTREAM_${statusCode}`,
Expand Down
15 changes: 13 additions & 2 deletions src/api/glyphs.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,18 @@
import fs from 'fs'
import path from 'path'

import { createStaticGlyphPath, SDF_STATIC_DIR } from '../lib/glyphs'
import {
createStaticGlyphPath,
isValidGlyphsRange,
SDF_STATIC_DIR,
} from '../lib/glyphs'
import { isMapboxURL, normalizeGlyphsURL } from '../lib/mapbox_urls'
import { Context } from '.'
import { MBAccessTokenRequiredError, NotFoundError } from './errors'
import {
InvalidGlyphsRangeError,
MBAccessTokenRequiredError,
NotFoundError,
} from './errors'

export type GlyphsResult =
| { type: 'file'; data: string } // data is an absolute file path
Expand Down Expand Up @@ -44,6 +52,9 @@ function createGlyphsApi({ context }: { context: Context }): GlyphsApi {
return {
// TODO: Should we return always return the offline asset if it exists?
async getGlyphs({ styleId, accessToken, font, start, end }) {
if (!isValidGlyphsRange(start, end))
throw new InvalidGlyphsRangeError(start, end)

try {
// 1. Attempt to get desired offline asset
// Right now this is just a static asset bundled with the module.
Expand Down
15 changes: 15 additions & 0 deletions src/lib/glyphs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,3 +62,18 @@ export function createStaticGlyphPath(
const convertedFontName = font.replace(SPACES_REGEX, '-')
return `${convertedFontName}/${start}-${end}.pbf`
}

const GLYPHS_RANGE_MULTIPLIER = 256
const GLYPHS_RANGE_START_MAX = 65280

// Based on start and end parameter docs here:
// https://docs.mapbox.com/api/maps/fonts/#retrieve-font-glyph-ranges
export function isValidGlyphsRange(start: number, end: number) {
if (start < 0 || end < 0) return false

const validStart =
start % GLYPHS_RANGE_MULTIPLIER === 0 && start <= GLYPHS_RANGE_START_MAX
const validEnd = end - start === GLYPHS_RANGE_MULTIPLIER - 1

return validStart && validEnd
}
6 changes: 5 additions & 1 deletion src/routes/fonts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@ import { FastifyPluginAsync } from 'fastify'
import { HTTPError } from 'got'
import { Static, Type as T } from '@sinclair/typebox'

import { NotFoundError, createForwardedUpstreamError } from '../api/errors'
import {
NotFoundError,
createForwardedUpstreamError,
InvalidGlyphsRangeError,
} from '../api/errors'
import { DEFAULT_STATIC_FONT, createStaticGlyphPath } from '../lib/glyphs'

const GetGlyphsParams = T.Object({
Expand Down
4 changes: 2 additions & 2 deletions test/e2e/glyphs.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ test('GET /fonts/:fontstack/:start-:end.pbf sends fallback when specified font i
t.equal(headers['content-type'], 'application/x-protobuf')
})

test('GET /fonts/:fontstack/:start-:end.pbf returns 404 for requests with non-existent ranges', async (t) => {
test('GET /fonts/:fontstack/:start-:end.pbf returns 400 response for invalid glyph ranges', async (t) => {
const server = createServer(t)

const badStart = 1_000_000
Expand All @@ -86,7 +86,7 @@ test('GET /fonts/:fontstack/:start-:end.pbf returns 404 for requests with non-ex
url: `/fonts/${createFontStack()}/${badStart}-${badStart + 255}.pbf`,
})

t.equal(getGlyphsResponse.statusCode, 404)
t.equal(getGlyphsResponse.statusCode, 400)
})

test(
Expand Down
61 changes: 61 additions & 0 deletions test/unit/glyphs.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
const test = require('tape')
const fs = require('fs')
const path = require('path')

const { isValidGlyphsRange } = require('../../dist/lib/glyphs')

/**
* @param {number} factor
* @returns {[number, number]}
*/
function createRange(factor) {
if (factor < 0) throw new Error('Range factor must be >= 0')

const start = 256 * factor
const end = start + 255

return [start, end]
}

test('isValidGlyphsRange', (t) => {
t.test('returns false for any negative numbers', (st) => {
st.notOk(isValidGlyphsRange(-1, -1), 'when start and end are negative')
st.notOk(isValidGlyphsRange(-1, 1), 'when start is negative')
st.notOk(isValidGlyphsRange(1, -1), 'when end is negative')

st.end()
})

t.test('returns false for invalid positive values', (st) => {
st.notOk(isValidGlyphsRange(10, 255), 'invalid start')
st.notOk(isValidGlyphsRange(0, 10), 'invalid end')

st.end()
})

t.test(
'returns false for valid range that exceeds maximum range of 65280-65535',
(st) => {
const [start, end] = createRange(256)

st.notOk(isValidGlyphsRange(start, end), `${start}-${end} is too large`)

st.end()
}
)

t.test(
'returns true for valid ranges that do not exceed maximum range of 65280-65535',
(st) => {
const allValid = new Array(255).every((_, index) => {
return isValidGlyphsRange(createRange(index))
})

st.ok(allValid, 'all ranges from factor 0 to 255 are valid')

st.end()
}
)

t.end()
})