Skip to content

Commit e0b8a84

Browse files
authored
Add validation for font-faces property (#1351)
* Add validation for font-faces property * improve test coverage * use const * fix markdown link * PR review comments
1 parent 4255ad2 commit e0b8a84

18 files changed

+174
-3
lines changed

build/generate-docs.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,8 @@ function typeToMarkdownLink(type: string): string {
208208
case 'paint':
209209
case 'layout':
210210
return ` [${type}](layers.md#${type.toLocaleLowerCase()})`;
211+
case 'fontfaces':
212+
return ` [${type}](font-faces.md)`;
211213
default:
212214
// top level types have their own file
213215
return ` [${type}](${type}.md)`;

src/reference/v8.json

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -219,8 +219,7 @@
219219
}
220220
},
221221
"font-faces": {
222-
"type": "array",
223-
"value": "fontFaces",
222+
"type": "fontFaces",
224223
"doc": "The `font-faces` property can be used to specify what font files to use for rendering text. Font faces contain information needed to render complex texts such as [Devanagari](https://en.wikipedia.org/wiki/Devanagari), [Khmer](https://en.wikipedia.org/wiki/Khmer_script) among many others.<h2>Unicode range</h2>The optional `unicode-range` property can be used to only use a particular font file for characters within the specified unicode range(s). Its value should be an array of strings, each indicating a start and end of a unicode range, similar to the [CSS descriptor with the same name](https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/unicode-range). This allows specifying multiple non-consecutive unicode ranges. When not specified, the default value is `U+0-10FFFF`, meaning the font file will be used for all unicode characters.\n\nRefer to the [Unicode Character Code Charts](https://www.unicode.org/charts/) to see ranges for scripts supported by Unicode. To see what unicode code-points are available in a font, use a tool like [FontDrop](https://fontdrop.info/).\n\n<h2>Font Resolution</h2>For every name in a symbol layer’s [`text-font`](./layers.md/#text-font) array, characters are matched if they are covered one of the by the font files in the corresponding entry of the `font-faces` map. Any still-unmatched characters then fall back to the [`glyphs`](./glyphs.md) URL if provided.\n\n<h2>Supported Fonts</h2>What type of fonts are supported is implementation-defined. Unsupported fonts are ignored.",
225224
"example": {
226225
"Noto Sans Regular": [{

src/validate/validate.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import {ValidationError} from '../error/validation_error';
3131
import {validateProjection} from './validate_projection';
3232
import {validateProjectionDefinition} from './validate_projectiondefinition';
3333
import {validateState} from './validate_state';
34+
import {validateFontFaces} from './validate_font_faces';
3435

3536
const VALIDATORS = {
3637
'*'() {
@@ -60,7 +61,8 @@ const VALIDATORS = {
6061
'colorArray': validateColorArray,
6162
'variableAnchorOffsetCollection': validateVariableAnchorOffsetCollection,
6263
'sprite': validateSprite,
63-
'state': validateState
64+
'state': validateState,
65+
'fontFaces': validateFontFaces
6466
};
6567

6668
/**
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
import {ValidationError} from '../error/validation_error';
2+
import {getType} from '../util/get_type';
3+
import {isObjectLiteral} from '../util/is_object_literal';
4+
import {validateObject} from './validate_object';
5+
import {validateString} from './validate_string';
6+
import v8 from '../reference/v8.json' with {type: 'json'};
7+
import type {StyleSpecification} from '../types.g';
8+
9+
interface ValidateFontFacesOptions {
10+
key: string;
11+
value: unknown;
12+
styleSpec: typeof v8;
13+
style: StyleSpecification;
14+
validateSpec: Function;
15+
}
16+
17+
export function validateFontFaces(options: ValidateFontFacesOptions): ValidationError[] {
18+
const key = options.key;
19+
const value = options.value;
20+
const validateSpec = options.validateSpec;
21+
const styleSpec = options.styleSpec;
22+
const style = options.style;
23+
24+
if (!isObjectLiteral(value)) {
25+
return [
26+
new ValidationError(
27+
key,
28+
value,
29+
`object expected, ${getType(value)} found`
30+
),
31+
];
32+
}
33+
34+
const errors: ValidationError[] = [];
35+
36+
for (const fontName in value) {
37+
const fontValue = value[fontName];
38+
const fontValueType = getType(fontValue);
39+
40+
if (fontValueType === 'string') {
41+
// Validate as a string URL
42+
errors.push(...validateString({
43+
key: `${key}.${fontName}`,
44+
value: fontValue,
45+
}));
46+
} else if (fontValueType === 'array') {
47+
// Validate as an array of font face objects
48+
const fontFaceSpec = {
49+
url: {
50+
type: 'string',
51+
required: true,
52+
},
53+
'unicode-range': {
54+
type: 'array',
55+
value: 'string',
56+
}
57+
};
58+
59+
for (const [i, fontFace] of (fontValue as any[]).entries()) {
60+
errors.push(...validateObject({
61+
key: `${key}.${fontName}[${i}]`,
62+
value: fontFace,
63+
valueSpec: fontFaceSpec,
64+
styleSpec,
65+
style,
66+
validateSpec,
67+
}));
68+
}
69+
} else {
70+
errors.push(new ValidationError(
71+
`${key}.${fontName}`,
72+
fontValue,
73+
`string or array expected, ${fontValueType} found`
74+
));
75+
}
76+
}
77+
78+
return errors;
79+
}
80+
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"version": 8,
3+
"sources": {},
4+
"layers": [],
5+
"font-faces": []
6+
}
7+
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
[
2+
{
3+
"message": "font-faces: object expected, array found",
4+
"line": 5
5+
}
6+
]
7+
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"version": 8,
3+
"sources": {},
4+
"layers": [],
5+
"font-faces": true
6+
}
7+
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
[
2+
{
3+
"message": "font-faces: object expected, boolean found",
4+
"line": 5
5+
}
6+
]
7+
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"version": 8,
3+
"sources": {},
4+
"layers": [],
5+
"font-faces": 123
6+
}
7+
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
[
2+
{
3+
"message": "font-faces: object expected, number found",
4+
"line": 5
5+
}
6+
]
7+

0 commit comments

Comments
 (0)