@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.
Files changed (51) hide show
  1. package/Readme.md +654 -0
  2. package/binding.gyp +229 -0
  3. package/browser.js +31 -0
  4. package/index.d.ts +507 -0
  5. package/index.js +94 -0
  6. package/lib/DOMMatrix.js +678 -0
  7. package/lib/bindings.js +113 -0
  8. package/lib/canvas.js +113 -0
  9. package/lib/context2d.js +11 -0
  10. package/lib/image.js +97 -0
  11. package/lib/jpegstream.js +41 -0
  12. package/lib/pattern.js +15 -0
  13. package/lib/pdfstream.js +35 -0
  14. package/lib/pngstream.js +42 -0
  15. package/package.json +77 -0
  16. package/scripts/install.js +19 -0
  17. package/src/Backends.h +9 -0
  18. package/src/Canvas.cc +1026 -0
  19. package/src/Canvas.h +128 -0
  20. package/src/CanvasError.h +37 -0
  21. package/src/CanvasGradient.cc +113 -0
  22. package/src/CanvasGradient.h +20 -0
  23. package/src/CanvasPattern.cc +129 -0
  24. package/src/CanvasPattern.h +33 -0
  25. package/src/CanvasRenderingContext2d.cc +3527 -0
  26. package/src/CanvasRenderingContext2d.h +238 -0
  27. package/src/CharData.h +233 -0
  28. package/src/FontParser.cc +605 -0
  29. package/src/FontParser.h +115 -0
  30. package/src/Image.cc +1719 -0
  31. package/src/Image.h +146 -0
  32. package/src/ImageData.cc +138 -0
  33. package/src/ImageData.h +26 -0
  34. package/src/InstanceData.h +12 -0
  35. package/src/JPEGStream.h +157 -0
  36. package/src/PNG.h +292 -0
  37. package/src/Point.h +11 -0
  38. package/src/Util.h +9 -0
  39. package/src/bmp/BMPParser.cc +459 -0
  40. package/src/bmp/BMPParser.h +60 -0
  41. package/src/bmp/LICENSE.md +24 -0
  42. package/src/closure.cc +52 -0
  43. package/src/closure.h +98 -0
  44. package/src/color.cc +796 -0
  45. package/src/color.h +30 -0
  46. package/src/dll_visibility.h +20 -0
  47. package/src/init.cc +114 -0
  48. package/src/register_font.cc +352 -0
  49. package/src/register_font.h +7 -0
  50. package/util/has_lib.js +119 -0
  51. 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
+ }
@@ -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
+ };