@@ -203,27 +203,34 @@ float clamp_css_float(T f) { // Clamp to float 0.0 .. 1.0.
203
203
return f < 0 ? 0 : f > 1 ? 1 : f;
204
204
}
205
205
206
- float parseFloat (const char * str) {
207
- return strtof (str, nullptr );
206
+ float parse_float (const std::string& str) {
207
+ return strtof (str. c_str () , nullptr );
208
208
}
209
209
210
- int64_t parseInt (const char * str, uint8_t base = 10 ) {
211
- return strtoll (str, nullptr , base);
210
+ int64_t parse_int (const char * str, int & read, uint8_t base) {
211
+ char *pos = nullptr ;
212
+ int64_t val = strtoll (str, &pos, base);
213
+ read = pos - str;
214
+ return val;
215
+ }
216
+
217
+ int64_t parse_int (const std::string& str) {
218
+ return strtoll (str.c_str (), nullptr , 10 );
212
219
}
213
220
214
221
uint8_t parse_css_int (const std::string& str) { // int or percentage.
215
222
if (str.length () && str.back () == ' %' ) {
216
- return clamp_css_byte (parseFloat (str. c_str () ) / 100 .0f * 255 .0f );
223
+ return clamp_css_byte (parse_float (str) / 100 .0f * 255 .0f );
217
224
} else {
218
- return clamp_css_byte (parseInt (str. c_str () ));
225
+ return clamp_css_byte (parse_int (str));
219
226
}
220
227
}
221
228
222
229
float parse_css_float (const std::string& str) { // float or percentage.
223
230
if (str.length () && str.back () == ' %' ) {
224
- return clamp_css_float (parseFloat (str. c_str () ) / 100 .0f );
231
+ return clamp_css_float (parse_float (str) / 100 .0f );
225
232
} else {
226
- return clamp_css_float (parseFloat (str. c_str () ));
233
+ return clamp_css_float (parse_float (str));
227
234
}
228
235
}
229
236
@@ -246,11 +253,41 @@ float css_hue_to_rgb(float m1, float m2, float h) {
246
253
return m1;
247
254
}
248
255
256
+ bool match (char c, const std::string& text, size_t & pos, size_t end) {
257
+ if (end < pos + 1 )
258
+ return false ;
259
+
260
+ if (c != tolower (text[pos]))
261
+ return false ;
262
+
263
+ pos += 1 ;
264
+ return true ;
265
+ }
249
266
267
+ bool match_prefix (const std::string& prefix, const std::string& text, size_t & pos, size_t end) {
268
+ size_t length = prefix.size ();
269
+ if (end < length + pos)
270
+ return false ;
250
271
251
- std::vector<std::string> split (const std::string& s, char delim) {
272
+ for (size_t i = 0 ; i < length; ++i)
273
+ if (prefix[i] != tolower (text[pos+i]))
274
+ return false ;
275
+
276
+ pos += length;
277
+ return true ;
278
+ }
279
+
280
+ void skip_whitespace (const std::string& text, size_t & pos, size_t end){
281
+ while ((pos < end) && (text[pos] == ' ' )) {
282
+ pos++;
283
+ }
284
+ }
285
+
286
+ std::vector<std::string> split (const std::string& s, char delim, size_t position) {
252
287
std::vector<std::string> elems;
253
288
std::stringstream ss (s);
289
+ ss.seekg (position);
290
+
254
291
std::string item;
255
292
while (std::getline (ss, item, delim)) {
256
293
elems.push_back (item);
@@ -259,57 +296,72 @@ std::vector<std::string> split(const std::string& s, char delim) {
259
296
}
260
297
261
298
Color CSSColorParser::parse (const std::string& css_str) {
262
- int length = css_str.length ();
299
+ bool valid;
300
+ return parse (css_str, valid);
301
+ }
263
302
264
- if (length == 0 ) {
303
+ Color CSSColorParser::parse (const std::string& css_str, bool & valid) {
304
+ valid = false ;
305
+
306
+ size_t pos = 0 ;
307
+ size_t end = css_str.length ();
308
+
309
+ skip_whitespace (css_str, pos, end);
310
+ if (pos == end) {
265
311
return {};
266
312
}
267
313
268
314
// #abc and #abc123 syntax.
269
- if (css_str.front () == ' #' ) {
270
- const char * str = css_str.c_str ()+1 ;
315
+ if (match (' #' , css_str, pos, end)) {
316
+ int read = 0 ;
317
+
318
+ const char * str = css_str.c_str () + pos;
319
+ int64_t iv = parse_int (str, read, 16 );
320
+ if (iv < 0 ) {
321
+ // Invalid: out of range.
322
+ return {};
323
+ }
271
324
272
- if (length == 4 ) { // rgb
273
- int64_t iv = parseInt (str, 16 ); // TODO(deanm): Stricter parsing.
274
- if (!(iv >= 0 && iv <= 0xfff )) {
275
- return {};
276
- } else {
325
+ pos += read;
326
+ skip_whitespace (css_str, pos, end);
327
+ if (pos != end) {
328
+ // Invalid: contains trailing chars.
329
+ return {};
330
+ }
331
+
332
+ if (read == 3 ) { // rgb
333
+ if (iv <= 0xfff ) {
334
+ valid = true ;
277
335
return {
278
336
static_cast <uint8_t >(((iv & 0xf00 ) >> 4 ) | ((iv & 0xf00 ) >> 8 )),
279
337
static_cast <uint8_t >((iv & 0xf0 ) | ((iv & 0xf0 ) >> 4 )),
280
338
static_cast <uint8_t >((iv & 0xf ) | ((iv & 0xf ) << 4 )),
281
339
1
282
340
};
283
341
}
284
- } else if (length == 7 ) { // rrggbb
285
- int64_t iv = parseInt (str, 16 );
286
- if (!(iv >= 0 && iv <= 0xffffff )) {
287
- return {}; // Covers NaN.
288
- } else {
342
+ } else if (read == 6 ) { // rrggbb
343
+ if (iv <= 0xffffff ) {
344
+ valid = true ;
289
345
return {
290
346
static_cast <uint8_t >((iv & 0xff0000 ) >> 16 ),
291
347
static_cast <uint8_t >((iv & 0xff00 ) >> 8 ),
292
348
static_cast <uint8_t >(iv & 0xff ),
293
349
1
294
350
};
295
351
}
296
- } else if (length == 5 ) { // argb
297
- int64_t iv = parseInt (str, 16 );
298
- if (!(iv >= 0 && iv <= 0xffff )) {
299
- return {};
300
- } else {
352
+ } else if (read == 4 ) { // argb
353
+ if (iv <= 0xffff ) {
354
+ valid = true ;
301
355
return {
302
356
static_cast <uint8_t >(((iv & 0xf00 ) >> 4 ) | ((iv & 0xf00 ) >> 8 )),
303
357
static_cast <uint8_t >((iv & 0xf0 ) | ((iv & 0xf0 ) >> 4 )),
304
358
static_cast <uint8_t >((iv & 0xf ) | ((iv & 0xf ) << 4 )),
305
359
static_cast <uint8_t >((iv & 0xf000 ) >> 12 ) / 255 .0f ,
306
360
};
307
361
}
308
- } else if (length == 9 ) { // aarrggbb
309
- int64_t iv = parseInt (str, 16 );
310
- if (!(iv >= 0 && iv <= 0xffffffff )) {
311
- return {}; // Covers NaN.
312
- } else {
362
+ } else if (read == 8 ) { // aarrggbb
363
+ if (iv <= 0xffffffff ) {
364
+ valid = true ;
313
365
return {
314
366
static_cast <uint8_t >((iv & 0xff0000 ) >> 16 ),
315
367
static_cast <uint8_t >((iv & 0xff00 ) >> 8 ),
@@ -318,27 +370,30 @@ Color CSSColorParser::parse(const std::string& css_str) {
318
370
};
319
371
}
320
372
}
373
+
321
374
return {};
322
375
}
323
376
324
- // TODO avoid copy
325
- std::string str = css_str;
377
+ bool rgb, hsl = false ;
378
+ rgb = match_prefix (" rgb" , css_str, pos, end);
379
+ if (!rgb) {
380
+ hsl = match_prefix (" hsl" , css_str, pos, end);
381
+ }
382
+ if (rgb || hsl) {
326
383
327
- // Remove all whitespace, not compliant, but should just be more accepting.
328
- str.erase (std::remove (str.begin (), str.end (), ' ' ), str.end ());
384
+ bool hasAlpha = match (' a' , css_str, pos, end);
329
385
330
- // Convert to lowercase.
331
- std::transform (str.begin (), str.end (), str.begin (), ::tolower);
386
+ if (!match (' (' , css_str, pos, end)) {
387
+ return {};
388
+ }
332
389
333
- size_t op = str.find_first_of (' (' ), ep = str.find_first_of (' )' );
334
- if (op != std::string::npos && ep + 1 == str.length ()) {
335
- const std::string fname = str.substr (0 , op);
336
- const std::vector<std::string> params = split (str.substr (op + 1 , ep - (op + 1 )), ' ,' );
390
+ // TODO: validation
391
+ const std::vector<std::string> params = split (css_str, ' ,' , pos);
337
392
338
393
float alpha = 1 .0f ;
339
394
340
- if (fname == " rgba " || fname == " rgb" ) {
341
- if (fname == " rgba " ) {
395
+ if (rgb) {
396
+ if (hasAlpha ) {
342
397
if (params.size () != 4 ) {
343
398
return {};
344
399
}
@@ -349,15 +404,16 @@ Color CSSColorParser::parse(const std::string& css_str) {
349
404
}
350
405
}
351
406
407
+ valid = true ;
352
408
return {
353
409
parse_css_int (params[0 ]),
354
410
parse_css_int (params[1 ]),
355
411
parse_css_int (params[2 ]),
356
412
alpha
357
413
};
358
414
359
- } else if (fname == " hsla " || fname == " hsl" ) {
360
- if (fname == " hsla " ) {
415
+ } else if (hsl) {
416
+ if (hasAlpha ) {
361
417
if (params.size () != 4 ) {
362
418
return {};
363
419
}
@@ -368,7 +424,7 @@ Color CSSColorParser::parse(const std::string& css_str) {
368
424
}
369
425
}
370
426
371
- float h = parseFloat (params[0 ].c_str ()) / 360 .0f ;
427
+ float h = parse_float (params[0 ].c_str ()) / 360 .0f ;
372
428
while (h < 0 .0f ) h++;
373
429
while (h > 1 .0f ) h--;
374
430
@@ -380,6 +436,7 @@ Color CSSColorParser::parse(const std::string& css_str) {
380
436
float m2 = l <= 0 .5f ? l * (s + 1 .0f ) : l + s - l * s;
381
437
float m1 = l * 2 .0f - m2;
382
438
439
+ valid = true ;
383
440
return {
384
441
clamp_css_byte (css_hue_to_rgb (m1, m2, h + 1 .0f / 3 .0f ) * 255 .0f ),
385
442
clamp_css_byte (css_hue_to_rgb (m1, m2, h) * 255 .0f ),
@@ -389,15 +446,31 @@ Color CSSColorParser::parse(const std::string& css_str) {
389
446
}
390
447
}
391
448
392
- NamedColor v{str.c_str (), {}};
449
+ size_t length = end - pos;
450
+
451
+ // Skip if longer than longest named color
452
+ if (length > 20 )
453
+ return {};
454
+
455
+ // Convert to lowercase.
456
+ char cstr[32 ];
457
+ cstr[length] = ' \0 ' ;
458
+
459
+ for (size_t i = 0 ; i < length; ++i) {
460
+ cstr[i] = std::tolower (css_str[pos++]);
461
+ }
393
462
394
- auto end = (namedColors + namedColorCount);
395
- auto it = std::lower_bound (namedColors, end, v,
396
- [](const NamedColor& a, const NamedColor& b){
397
- return std::strcmp (a.name , b.name ) < 0 ; });
463
+ // Binary search
464
+ auto itEnd = (namedColors + namedColorCount);
465
+ auto it = std::lower_bound (namedColors, itEnd, cstr,
466
+ [](const NamedColor& a, const char * b) {
467
+ return std::strcmp (a.name , b) < 0 ; });
398
468
399
- if (it != end && std::strcmp (it->name , v.name ) == 0 )
469
+ if (it != itEnd && std::strcmp (it->name , cstr) == 0 ) {
470
+ valid = true ;
400
471
return it->color ;
472
+ }
401
473
474
+ // No named color found
402
475
return {};
403
476
}
0 commit comments