@shqld/canvas 3.2.2-rc.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 +654 -0
- package/binding.gyp +229 -0
- package/browser.js +31 -0
- package/index.d.ts +507 -0
- package/index.js +94 -0
- package/lib/DOMMatrix.js +678 -0
- package/lib/bindings.js +113 -0
- package/lib/canvas.js +113 -0
- package/lib/context2d.js +11 -0
- package/lib/image.js +97 -0
- package/lib/jpegstream.js +41 -0
- package/lib/pattern.js +15 -0
- package/lib/pdfstream.js +35 -0
- package/lib/pngstream.js +42 -0
- package/package.json +77 -0
- package/scripts/install.js +19 -0
- package/src/Backends.h +9 -0
- package/src/Canvas.cc +1026 -0
- package/src/Canvas.h +128 -0
- package/src/CanvasError.h +37 -0
- package/src/CanvasGradient.cc +113 -0
- package/src/CanvasGradient.h +20 -0
- package/src/CanvasPattern.cc +129 -0
- package/src/CanvasPattern.h +33 -0
- package/src/CanvasRenderingContext2d.cc +3527 -0
- package/src/CanvasRenderingContext2d.h +238 -0
- package/src/CharData.h +233 -0
- package/src/FontParser.cc +605 -0
- package/src/FontParser.h +115 -0
- package/src/Image.cc +1719 -0
- package/src/Image.h +146 -0
- package/src/ImageData.cc +138 -0
- package/src/ImageData.h +26 -0
- package/src/InstanceData.h +12 -0
- package/src/JPEGStream.h +157 -0
- package/src/PNG.h +292 -0
- package/src/Point.h +11 -0
- package/src/Util.h +9 -0
- package/src/bmp/BMPParser.cc +459 -0
- package/src/bmp/BMPParser.h +60 -0
- package/src/bmp/LICENSE.md +24 -0
- package/src/closure.cc +52 -0
- package/src/closure.h +98 -0
- package/src/color.cc +796 -0
- package/src/color.h +30 -0
- package/src/dll_visibility.h +20 -0
- package/src/init.cc +114 -0
- package/src/register_font.cc +352 -0
- package/src/register_font.h +7 -0
- package/util/has_lib.js +119 -0
- package/util/win_jpeg_lookup.js +21 -0
|
@@ -0,0 +1,605 @@
|
|
|
1
|
+
// This is written to exactly parse the `font` shorthand in CSS2:
|
|
2
|
+
// https://www.w3.org/TR/CSS22/fonts.html#font-shorthand
|
|
3
|
+
// https://www.w3.org/TR/CSS22/syndata.html#tokenization
|
|
4
|
+
//
|
|
5
|
+
// We may want to update it for CSS 3 (e.g. font-stretch, or updated
|
|
6
|
+
// tokenization) but I've only ever seen one or two issues filed in node-canvas
|
|
7
|
+
// due to parsing in my 8 years on the project
|
|
8
|
+
|
|
9
|
+
#include "FontParser.h"
|
|
10
|
+
#include "CharData.h"
|
|
11
|
+
#include <cctype>
|
|
12
|
+
#include <unordered_map>
|
|
13
|
+
|
|
14
|
+
Token::Token(Type type, std::string value) : type_(type), value_(std::move(value)) {}
|
|
15
|
+
|
|
16
|
+
Token::Token(Type type, double value) : type_(type), value_(value) {}
|
|
17
|
+
|
|
18
|
+
Token::Token(Type type) : type_(type), value_(std::string{}) {}
|
|
19
|
+
|
|
20
|
+
const std::string&
|
|
21
|
+
Token::getString() const {
|
|
22
|
+
static const std::string empty;
|
|
23
|
+
auto* str = std::get_if<std::string>(&value_);
|
|
24
|
+
return str ? *str : empty;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
double
|
|
28
|
+
Token::getNumber() const {
|
|
29
|
+
auto* num = std::get_if<double>(&value_);
|
|
30
|
+
return num ? *num : 0.0f;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
Tokenizer::Tokenizer(std::string_view input) : input_(input) {}
|
|
34
|
+
|
|
35
|
+
std::string
|
|
36
|
+
Tokenizer::utf8Encode(uint32_t codepoint) {
|
|
37
|
+
std::string result;
|
|
38
|
+
|
|
39
|
+
if (codepoint < 0x80) {
|
|
40
|
+
result += static_cast<char>(codepoint);
|
|
41
|
+
} else if (codepoint < 0x800) {
|
|
42
|
+
result += static_cast<char>((codepoint >> 6) | 0xc0);
|
|
43
|
+
result += static_cast<char>((codepoint & 0x3f) | 0x80);
|
|
44
|
+
} else if (codepoint < 0x10000) {
|
|
45
|
+
result += static_cast<char>((codepoint >> 12) | 0xe0);
|
|
46
|
+
result += static_cast<char>(((codepoint >> 6) & 0x3f) | 0x80);
|
|
47
|
+
result += static_cast<char>((codepoint & 0x3f) | 0x80);
|
|
48
|
+
} else {
|
|
49
|
+
result += static_cast<char>((codepoint >> 18) | 0xf0);
|
|
50
|
+
result += static_cast<char>(((codepoint >> 12) & 0x3f) | 0x80);
|
|
51
|
+
result += static_cast<char>(((codepoint >> 6) & 0x3f) | 0x80);
|
|
52
|
+
result += static_cast<char>((codepoint & 0x3f) | 0x80);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return result;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
char
|
|
59
|
+
Tokenizer::peek() const {
|
|
60
|
+
return position_ < input_.length() ? input_[position_] : '\0';
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
char
|
|
64
|
+
Tokenizer::advance() {
|
|
65
|
+
return position_ < input_.length() ? input_[position_++] : '\0';
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
Token
|
|
69
|
+
Tokenizer::parseNumber() {
|
|
70
|
+
enum class State {
|
|
71
|
+
Start,
|
|
72
|
+
AfterSign,
|
|
73
|
+
Digits,
|
|
74
|
+
AfterDecimal,
|
|
75
|
+
AfterE,
|
|
76
|
+
AfterESign,
|
|
77
|
+
ExponentDigits
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
size_t start = position_;
|
|
81
|
+
size_t ePosition = 0;
|
|
82
|
+
State state = State::Start;
|
|
83
|
+
bool valid = false;
|
|
84
|
+
|
|
85
|
+
while (position_ < input_.length()) {
|
|
86
|
+
char c = peek();
|
|
87
|
+
uint8_t flags = charData[static_cast<uint8_t>(c)];
|
|
88
|
+
|
|
89
|
+
switch (state) {
|
|
90
|
+
case State::Start:
|
|
91
|
+
if (flags & CharData::Sign) {
|
|
92
|
+
position_++;
|
|
93
|
+
state = State::AfterSign;
|
|
94
|
+
} else if (flags & CharData::Digit) {
|
|
95
|
+
position_++;
|
|
96
|
+
state = State::Digits;
|
|
97
|
+
valid = true;
|
|
98
|
+
} else if (c == '.') {
|
|
99
|
+
position_++;
|
|
100
|
+
state = State::AfterDecimal;
|
|
101
|
+
} else {
|
|
102
|
+
goto done;
|
|
103
|
+
}
|
|
104
|
+
break;
|
|
105
|
+
|
|
106
|
+
case State::AfterSign:
|
|
107
|
+
if (flags & CharData::Digit) {
|
|
108
|
+
position_++;
|
|
109
|
+
state = State::Digits;
|
|
110
|
+
valid = true;
|
|
111
|
+
} else if (c == '.') {
|
|
112
|
+
position_++;
|
|
113
|
+
state = State::AfterDecimal;
|
|
114
|
+
} else {
|
|
115
|
+
goto done;
|
|
116
|
+
}
|
|
117
|
+
break;
|
|
118
|
+
|
|
119
|
+
case State::Digits:
|
|
120
|
+
if (flags & CharData::Digit) {
|
|
121
|
+
position_++;
|
|
122
|
+
} else if (c == '.') {
|
|
123
|
+
position_++;
|
|
124
|
+
state = State::AfterDecimal;
|
|
125
|
+
} else if (c == 'e' || c == 'E') {
|
|
126
|
+
ePosition = position_;
|
|
127
|
+
position_++;
|
|
128
|
+
state = State::AfterE;
|
|
129
|
+
valid = false;
|
|
130
|
+
} else {
|
|
131
|
+
goto done;
|
|
132
|
+
}
|
|
133
|
+
break;
|
|
134
|
+
|
|
135
|
+
case State::AfterDecimal:
|
|
136
|
+
if (flags & CharData::Digit) {
|
|
137
|
+
position_++;
|
|
138
|
+
valid = true;
|
|
139
|
+
state = State::Digits;
|
|
140
|
+
} else {
|
|
141
|
+
goto done;
|
|
142
|
+
}
|
|
143
|
+
break;
|
|
144
|
+
|
|
145
|
+
case State::AfterE:
|
|
146
|
+
if (flags & CharData::Sign) {
|
|
147
|
+
position_++;
|
|
148
|
+
state = State::AfterESign;
|
|
149
|
+
} else if (flags & CharData::Digit) {
|
|
150
|
+
position_++;
|
|
151
|
+
valid = true;
|
|
152
|
+
state = State::ExponentDigits;
|
|
153
|
+
} else {
|
|
154
|
+
position_ = ePosition;
|
|
155
|
+
valid = true;
|
|
156
|
+
goto done;
|
|
157
|
+
}
|
|
158
|
+
break;
|
|
159
|
+
|
|
160
|
+
case State::AfterESign:
|
|
161
|
+
if (flags & CharData::Digit) {
|
|
162
|
+
position_++;
|
|
163
|
+
valid = true;
|
|
164
|
+
state = State::ExponentDigits;
|
|
165
|
+
} else {
|
|
166
|
+
position_ = ePosition;
|
|
167
|
+
valid = true;
|
|
168
|
+
goto done;
|
|
169
|
+
}
|
|
170
|
+
break;
|
|
171
|
+
|
|
172
|
+
case State::ExponentDigits:
|
|
173
|
+
if (flags & CharData::Digit) {
|
|
174
|
+
position_++;
|
|
175
|
+
} else {
|
|
176
|
+
goto done;
|
|
177
|
+
}
|
|
178
|
+
break;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
done:
|
|
183
|
+
if (!valid) {
|
|
184
|
+
position_ = start;
|
|
185
|
+
return Token(Token::Type::Invalid);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
std::string number_str(input_.substr(start, position_ - start));
|
|
189
|
+
double value = std::stod(number_str);
|
|
190
|
+
return Token(Token::Type::Number, value);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Note that identifiers are always lower-case. This helps us make easier/more
|
|
194
|
+
// efficient comparisons, but means that font-families specified as identifiers
|
|
195
|
+
// will be lower-cased. Since font selection isn't case sensitive, this
|
|
196
|
+
// shouldn't ever be a problem.
|
|
197
|
+
Token
|
|
198
|
+
Tokenizer::parseIdentifier() {
|
|
199
|
+
std::string identifier;
|
|
200
|
+
auto flags = CharData::Nmstart;
|
|
201
|
+
auto start = position_;
|
|
202
|
+
|
|
203
|
+
while (position_ < input_.length()) {
|
|
204
|
+
char c = peek();
|
|
205
|
+
|
|
206
|
+
if (c == '\\') {
|
|
207
|
+
advance();
|
|
208
|
+
if (!parseEscape(identifier)) {
|
|
209
|
+
position_ = start;
|
|
210
|
+
return Token(Token::Type::Invalid);
|
|
211
|
+
}
|
|
212
|
+
flags = CharData::Nmchar;
|
|
213
|
+
} else if (charData[static_cast<uint8_t>(c)] & flags) {
|
|
214
|
+
identifier += advance() + (c >= 'A' && c <= 'Z' ? 32 : 0);
|
|
215
|
+
flags = CharData::Nmchar;
|
|
216
|
+
} else {
|
|
217
|
+
break;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
return Token(Token::Type::Identifier, identifier);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
uint32_t
|
|
225
|
+
Tokenizer::parseUnicode() {
|
|
226
|
+
uint32_t value = 0;
|
|
227
|
+
size_t count = 0;
|
|
228
|
+
|
|
229
|
+
while (position_ < input_.length() && count < 6) {
|
|
230
|
+
char c = peek();
|
|
231
|
+
uint32_t digit;
|
|
232
|
+
|
|
233
|
+
if (c >= '0' && c <= '9') {
|
|
234
|
+
digit = c - '0';
|
|
235
|
+
} else if (c >= 'a' && c <= 'f') {
|
|
236
|
+
digit = c - 'a' + 10;
|
|
237
|
+
} else if (c >= 'A' && c <= 'F') {
|
|
238
|
+
digit = c - 'A' + 10;
|
|
239
|
+
} else {
|
|
240
|
+
break;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
value = value * 16 + digit;
|
|
244
|
+
advance();
|
|
245
|
+
count++;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// Optional whitespace after hex escape
|
|
249
|
+
char c = peek();
|
|
250
|
+
if (c == '\r') {
|
|
251
|
+
advance();
|
|
252
|
+
if (peek() == '\n') advance();
|
|
253
|
+
} else if (isWhitespace(c)) {
|
|
254
|
+
advance();
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
return value;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
bool
|
|
261
|
+
Tokenizer::parseEscape(std::string& str) {
|
|
262
|
+
char c = peek();
|
|
263
|
+
auto flags = charData[static_cast<uint8_t>(c)];
|
|
264
|
+
|
|
265
|
+
if (flags & CharData::Hex) {
|
|
266
|
+
str += utf8Encode(parseUnicode());
|
|
267
|
+
return true;
|
|
268
|
+
} else if (!(flags & CharData::Newline) && !(flags & CharData::Hex)) {
|
|
269
|
+
str += advance();
|
|
270
|
+
return true;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
return false;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
Token
|
|
277
|
+
Tokenizer::parseString(char quote) {
|
|
278
|
+
advance();
|
|
279
|
+
std::string value;
|
|
280
|
+
auto start = position_;
|
|
281
|
+
|
|
282
|
+
while (position_ < input_.length()) {
|
|
283
|
+
char c = peek();
|
|
284
|
+
|
|
285
|
+
if (c == quote) {
|
|
286
|
+
advance();
|
|
287
|
+
return Token(Token::Type::QuotedString, value);
|
|
288
|
+
} else if (c == '\\') {
|
|
289
|
+
advance();
|
|
290
|
+
c = peek();
|
|
291
|
+
if (c == '\r') {
|
|
292
|
+
advance();
|
|
293
|
+
if (peek() == '\n') advance();
|
|
294
|
+
} else if (isNewline(c)) {
|
|
295
|
+
advance();
|
|
296
|
+
} else {
|
|
297
|
+
if (!parseEscape(value)) {
|
|
298
|
+
position_ = start;
|
|
299
|
+
return Token(Token::Type::Invalid);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
} else {
|
|
303
|
+
value += advance();
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
position_ = start;
|
|
308
|
+
return Token(Token::Type::Invalid);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
Token
|
|
312
|
+
Tokenizer::nextToken() {
|
|
313
|
+
if (position_ >= input_.length()) {
|
|
314
|
+
return Token(Token::Type::EndOfInput);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
char c = peek();
|
|
318
|
+
auto flags = charData[static_cast<uint8_t>(c)];
|
|
319
|
+
|
|
320
|
+
if (isWhitespace(c)) {
|
|
321
|
+
std::string whitespace;
|
|
322
|
+
while (position_ < input_.length() && isWhitespace(peek())) {
|
|
323
|
+
whitespace += advance();
|
|
324
|
+
}
|
|
325
|
+
return Token(Token::Type::Whitespace, whitespace);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
if (flags & CharData::NumStart) {
|
|
329
|
+
Token token = parseNumber();
|
|
330
|
+
if (token.type() != Token::Type::Invalid) return token;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
if (flags & CharData::Nmstart) {
|
|
334
|
+
Token token = parseIdentifier();
|
|
335
|
+
if (token.type() != Token::Type::Invalid) return token;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
if (c == '"') {
|
|
339
|
+
Token token = parseString('"');
|
|
340
|
+
if (token.type() != Token::Type::Invalid) return token;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
if (c == '\'') {
|
|
344
|
+
Token token = parseString('\'');
|
|
345
|
+
if (token.type() != Token::Type::Invalid) return token;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
switch (advance()) {
|
|
349
|
+
case '/': return Token(Token::Type::Slash);
|
|
350
|
+
case ',': return Token(Token::Type::Comma);
|
|
351
|
+
case '%': return Token(Token::Type::Percent);
|
|
352
|
+
default: return Token(Token::Type::Invalid);
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
FontParser::FontParser(std::string_view input)
|
|
357
|
+
: tokenizer_(input)
|
|
358
|
+
, currentToken_(tokenizer_.nextToken())
|
|
359
|
+
, nextToken_(tokenizer_.nextToken()) {}
|
|
360
|
+
|
|
361
|
+
const std::unordered_map<std::string, uint16_t> FontParser::weightMap = {
|
|
362
|
+
{"normal", 400},
|
|
363
|
+
{"bold", 700},
|
|
364
|
+
{"lighter", 100},
|
|
365
|
+
{"bolder", 700}
|
|
366
|
+
};
|
|
367
|
+
|
|
368
|
+
const std::unordered_map<std::string, double> FontParser::unitMap = {
|
|
369
|
+
{"cm", 37.8f},
|
|
370
|
+
{"mm", 3.78f},
|
|
371
|
+
{"in", 96.0f},
|
|
372
|
+
{"pt", 96.0f / 72.0f},
|
|
373
|
+
{"pc", 96.0f / 6.0f},
|
|
374
|
+
{"em", 16.0f},
|
|
375
|
+
{"px", 1.0f}
|
|
376
|
+
};
|
|
377
|
+
|
|
378
|
+
void
|
|
379
|
+
FontParser::advance() {
|
|
380
|
+
currentToken_ = nextToken_;
|
|
381
|
+
nextToken_ = tokenizer_.nextToken();
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
void
|
|
385
|
+
FontParser::skipWs() {
|
|
386
|
+
while (currentToken_.type() == Token::Type::Whitespace) advance();
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
bool
|
|
390
|
+
FontParser::check(Token::Type type) const {
|
|
391
|
+
return currentToken_.type() == type;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
bool
|
|
395
|
+
FontParser::checkWs() const {
|
|
396
|
+
return nextToken_.type() == Token::Type::Whitespace
|
|
397
|
+
|| nextToken_.type() == Token::Type::EndOfInput;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
bool
|
|
401
|
+
FontParser::parseFontStyle(FontProperties& props) {
|
|
402
|
+
if (check(Token::Type::Identifier)) {
|
|
403
|
+
const auto& value = currentToken_.getString();
|
|
404
|
+
if (value == "italic") {
|
|
405
|
+
props.fontStyle = FontStyle::Italic;
|
|
406
|
+
advance();
|
|
407
|
+
return true;
|
|
408
|
+
} else if (value == "oblique") {
|
|
409
|
+
props.fontStyle = FontStyle::Oblique;
|
|
410
|
+
advance();
|
|
411
|
+
return true;
|
|
412
|
+
} else if (value == "normal") {
|
|
413
|
+
props.fontStyle = FontStyle::Normal;
|
|
414
|
+
advance();
|
|
415
|
+
return true;
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
return false;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
bool
|
|
423
|
+
FontParser::parseFontVariant(FontProperties& props) {
|
|
424
|
+
if (check(Token::Type::Identifier)) {
|
|
425
|
+
const auto& value = currentToken_.getString();
|
|
426
|
+
if (value == "small-caps") {
|
|
427
|
+
props.fontVariant = FontVariant::SmallCaps;
|
|
428
|
+
advance();
|
|
429
|
+
return true;
|
|
430
|
+
} else if (value == "normal") {
|
|
431
|
+
props.fontVariant = FontVariant::Normal;
|
|
432
|
+
advance();
|
|
433
|
+
return true;
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
return false;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
bool
|
|
441
|
+
FontParser::parseFontWeight(FontProperties& props) {
|
|
442
|
+
if (check(Token::Type::Number)) {
|
|
443
|
+
double weightFloat = currentToken_.getNumber();
|
|
444
|
+
int weight = static_cast<int>(weightFloat);
|
|
445
|
+
if (weight < 1 || weight > 1000) return false;
|
|
446
|
+
props.fontWeight = static_cast<uint16_t>(weight);
|
|
447
|
+
advance();
|
|
448
|
+
return true;
|
|
449
|
+
} else if (check(Token::Type::Identifier)) {
|
|
450
|
+
const auto& value = currentToken_.getString();
|
|
451
|
+
|
|
452
|
+
if (auto it = weightMap.find(value); it != weightMap.end()) {
|
|
453
|
+
props.fontWeight = it->second;
|
|
454
|
+
advance();
|
|
455
|
+
return true;
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
return false;
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
bool
|
|
463
|
+
FontParser::parseFontSize(FontProperties& props) {
|
|
464
|
+
if (!check(Token::Type::Number)) return false;
|
|
465
|
+
|
|
466
|
+
props.fontSize = currentToken_.getNumber();
|
|
467
|
+
advance();
|
|
468
|
+
|
|
469
|
+
double multiplier = 1.0f;
|
|
470
|
+
if (check(Token::Type::Identifier)) {
|
|
471
|
+
const auto& unit = currentToken_.getString();
|
|
472
|
+
|
|
473
|
+
if (auto it = unitMap.find(unit); it != unitMap.end()) {
|
|
474
|
+
multiplier = it->second;
|
|
475
|
+
advance();
|
|
476
|
+
} else {
|
|
477
|
+
return false;
|
|
478
|
+
}
|
|
479
|
+
} else if (check(Token::Type::Percent)) {
|
|
480
|
+
multiplier = 16.0f / 100.0f;
|
|
481
|
+
advance();
|
|
482
|
+
} else {
|
|
483
|
+
return false;
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
// Technically if we consumed some tokens but couldn't parse the font-size,
|
|
487
|
+
// we should rewind the tokenizer, but I don't think the grammar allows for
|
|
488
|
+
// any valid alternates in this specific case
|
|
489
|
+
|
|
490
|
+
props.fontSize *= multiplier;
|
|
491
|
+
return true;
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
// line-height is not used by canvas ever, but should still parse
|
|
495
|
+
bool
|
|
496
|
+
FontParser::parseLineHeight(FontProperties& props) {
|
|
497
|
+
if (check(Token::Type::Slash)) {
|
|
498
|
+
advance();
|
|
499
|
+
skipWs();
|
|
500
|
+
if (check(Token::Type::Number)) {
|
|
501
|
+
advance();
|
|
502
|
+
if (check(Token::Type::Percent)) {
|
|
503
|
+
advance();
|
|
504
|
+
} else if (check(Token::Type::Identifier)) {
|
|
505
|
+
auto identifier = currentToken_.getString();
|
|
506
|
+
if (auto it = unitMap.find(identifier); it != unitMap.end()) {
|
|
507
|
+
advance();
|
|
508
|
+
} else {
|
|
509
|
+
return false;
|
|
510
|
+
}
|
|
511
|
+
} else {
|
|
512
|
+
return false;
|
|
513
|
+
}
|
|
514
|
+
} else if (check(Token::Type::Identifier) && currentToken_.getString() == "normal") {
|
|
515
|
+
advance();
|
|
516
|
+
} else {
|
|
517
|
+
return false;
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
return true;
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
bool
|
|
525
|
+
FontParser::parseFontFamily(FontProperties& props) {
|
|
526
|
+
while (!check(Token::Type::EndOfInput)) {
|
|
527
|
+
std::string family = "";
|
|
528
|
+
std::string trailingWs = "";
|
|
529
|
+
bool found = false;
|
|
530
|
+
|
|
531
|
+
while (
|
|
532
|
+
check(Token::Type::QuotedString) ||
|
|
533
|
+
check(Token::Type::Identifier) ||
|
|
534
|
+
check(Token::Type::Whitespace)
|
|
535
|
+
) {
|
|
536
|
+
if (check(Token::Type::Whitespace)) {
|
|
537
|
+
if (found) trailingWs += currentToken_.getString();
|
|
538
|
+
} else { // Identifier, QuotedString
|
|
539
|
+
if (found) {
|
|
540
|
+
family += trailingWs;
|
|
541
|
+
trailingWs.clear();
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
family += currentToken_.getString();
|
|
545
|
+
found = true;
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
advance();
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
if (!found) return false; // only whitespace or non-id/string found
|
|
552
|
+
|
|
553
|
+
props.fontFamily.push_back(family);
|
|
554
|
+
|
|
555
|
+
if (check(Token::Type::Comma)) advance();
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
return true;
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
FontProperties
|
|
562
|
+
FontParser::parse(const std::string& fontString, bool* success) {
|
|
563
|
+
FontParser parser(fontString);
|
|
564
|
+
auto result = parser.parseFont();
|
|
565
|
+
if (success) *success = !parser.hasError_;
|
|
566
|
+
return result;
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
FontProperties
|
|
570
|
+
FontParser::parseFont() {
|
|
571
|
+
FontProperties props;
|
|
572
|
+
uint8_t state = 0b111;
|
|
573
|
+
|
|
574
|
+
skipWs();
|
|
575
|
+
|
|
576
|
+
for (size_t i = 0; i < 3 && checkWs(); i++) {
|
|
577
|
+
if ((state & 0b001) && parseFontStyle(props)) {
|
|
578
|
+
state &= 0b110;
|
|
579
|
+
goto match;
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
if ((state & 0b010) && parseFontVariant(props)) {
|
|
583
|
+
state &= 0b101;
|
|
584
|
+
goto match;
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
if ((state & 0b100) && parseFontWeight(props)) {
|
|
588
|
+
state &= 0b011;
|
|
589
|
+
goto match;
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
break; // all attempts exhausted
|
|
593
|
+
match: skipWs(); // success: move to the next non-ws token
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
if (parseFontSize(props)) {
|
|
597
|
+
skipWs();
|
|
598
|
+
if (parseLineHeight(props) && parseFontFamily(props)) {
|
|
599
|
+
return props;
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
hasError_ = true;
|
|
604
|
+
return props;
|
|
605
|
+
}
|
package/src/FontParser.h
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
#include <string>
|
|
4
|
+
#include <vector>
|
|
5
|
+
#include <optional>
|
|
6
|
+
#include <memory>
|
|
7
|
+
#include <variant>
|
|
8
|
+
#include <unordered_map>
|
|
9
|
+
#include "CharData.h"
|
|
10
|
+
|
|
11
|
+
enum class FontStyle {
|
|
12
|
+
Normal,
|
|
13
|
+
Italic,
|
|
14
|
+
Oblique
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
enum class FontVariant {
|
|
18
|
+
Normal,
|
|
19
|
+
SmallCaps
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
struct FontProperties {
|
|
23
|
+
double fontSize{16.0f};
|
|
24
|
+
std::vector<std::string> fontFamily;
|
|
25
|
+
uint16_t fontWeight{400};
|
|
26
|
+
FontVariant fontVariant{FontVariant::Normal};
|
|
27
|
+
FontStyle fontStyle{FontStyle::Normal};
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
class Token {
|
|
31
|
+
public:
|
|
32
|
+
enum class Type {
|
|
33
|
+
Invalid,
|
|
34
|
+
Number,
|
|
35
|
+
Percent,
|
|
36
|
+
Identifier,
|
|
37
|
+
Slash,
|
|
38
|
+
Comma,
|
|
39
|
+
QuotedString,
|
|
40
|
+
Whitespace,
|
|
41
|
+
EndOfInput
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
Token(Type type, std::string value);
|
|
45
|
+
Token(Type type, double value);
|
|
46
|
+
Token(Type type);
|
|
47
|
+
|
|
48
|
+
Type type() const { return type_; }
|
|
49
|
+
|
|
50
|
+
const std::string& getString() const;
|
|
51
|
+
double getNumber() const;
|
|
52
|
+
|
|
53
|
+
private:
|
|
54
|
+
Type type_;
|
|
55
|
+
std::variant<std::string, double> value_;
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
class Tokenizer {
|
|
59
|
+
public:
|
|
60
|
+
Tokenizer(std::string_view input);
|
|
61
|
+
Token nextToken();
|
|
62
|
+
|
|
63
|
+
private:
|
|
64
|
+
std::string_view input_;
|
|
65
|
+
size_t position_{0};
|
|
66
|
+
|
|
67
|
+
// Util
|
|
68
|
+
std::string utf8Encode(uint32_t codepoint);
|
|
69
|
+
inline bool isWhitespace(char c) const {
|
|
70
|
+
return charData[static_cast<uint8_t>(c)] & CharData::Whitespace;
|
|
71
|
+
}
|
|
72
|
+
inline bool isNewline(char c) const {
|
|
73
|
+
return charData[static_cast<uint8_t>(c)] & CharData::Newline;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Moving through the string
|
|
77
|
+
char peek() const;
|
|
78
|
+
char advance();
|
|
79
|
+
|
|
80
|
+
// Tokenize
|
|
81
|
+
Token parseNumber();
|
|
82
|
+
Token parseIdentifier();
|
|
83
|
+
uint32_t parseUnicode();
|
|
84
|
+
bool parseEscape(std::string& str);
|
|
85
|
+
Token parseString(char quote);
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
class FontParser {
|
|
89
|
+
public:
|
|
90
|
+
static FontProperties parse(const std::string& fontString, bool* success = nullptr);
|
|
91
|
+
|
|
92
|
+
private:
|
|
93
|
+
static const std::unordered_map<std::string, uint16_t> weightMap;
|
|
94
|
+
static const std::unordered_map<std::string, double> unitMap;
|
|
95
|
+
|
|
96
|
+
FontParser(std::string_view input);
|
|
97
|
+
|
|
98
|
+
void advance();
|
|
99
|
+
void skipWs();
|
|
100
|
+
bool check(Token::Type type) const;
|
|
101
|
+
bool checkWs() const;
|
|
102
|
+
|
|
103
|
+
bool parseFontStyle(FontProperties& props);
|
|
104
|
+
bool parseFontVariant(FontProperties& props);
|
|
105
|
+
bool parseFontWeight(FontProperties& props);
|
|
106
|
+
bool parseFontSize(FontProperties& props);
|
|
107
|
+
bool parseLineHeight(FontProperties& props);
|
|
108
|
+
bool parseFontFamily(FontProperties& props);
|
|
109
|
+
FontProperties parseFont();
|
|
110
|
+
|
|
111
|
+
Tokenizer tokenizer_;
|
|
112
|
+
Token currentToken_;
|
|
113
|
+
Token nextToken_;
|
|
114
|
+
bool hasError_{false};
|
|
115
|
+
};
|