Skip to content

Commit 067a3d4

Browse files
committed
parse: properties: utils: Add support for HWB colours
1 parent fee6d83 commit 067a3d4

File tree

1 file changed

+219
-0
lines changed

1 file changed

+219
-0
lines changed

src/parse/properties/utils.c

Lines changed: 219 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -375,6 +375,40 @@ static void HSL_to_RGB(
375375
*b = FIXTOINT(FDIV(bf, F_100));
376376
}
377377

378+
/**
379+
* Convert Hue Saturation Lightness value to RGB.
380+
*
381+
* \param hue Hue in degrees 0..360
382+
* \param white Whiteness value in percent 0..100
383+
* \param black Blackness value in percent 0..100
384+
* \param r red component
385+
* \param g green component
386+
* \param b blue component
387+
*/
388+
static void HWB_to_RGB(
389+
css_fixed hue, css_fixed white, css_fixed black,
390+
uint8_t *r, uint8_t *g, uint8_t *b)
391+
{
392+
if (FADD(white, black) >= F_100) {
393+
css_fixed grey = (FDIV(FMUL(white, F_255), FADD(white, black)));
394+
uint8_t grey_int = FIXTOINT(grey);
395+
396+
*r = grey_int;
397+
*g = grey_int;
398+
*b = grey_int;
399+
} else {
400+
css_fixed rf, gf, bf; // 0..25500
401+
css_fixed val = FSUB(F_100, FADD(white, black)); // 0..100
402+
403+
HSL_to_RGB_fixed(hue, INTTOFIX(100), INTTOFIX(50),
404+
&rf, &gf, &bf);
405+
406+
*r = FIXTOINT(FDIV(FADD(FMUL(FDIV(rf, F_100), val), FMUL(white, F_255)), F_100));
407+
*g = FIXTOINT(FDIV(FADD(FMUL(FDIV(gf, F_100), val), FMUL(white, F_255)), F_100));
408+
*b = FIXTOINT(FDIV(FADD(FMUL(FDIV(bf, F_100), val), FMUL(white, F_255)), F_100));
409+
}
410+
}
411+
378412
/**
379413
* Parse a RGB(A) colour specifier
380414
*
@@ -706,6 +740,185 @@ static bool parse_hsl(
706740
return true;
707741
}
708742

743+
/**
744+
* Parse a HWB colour specifier (hue, whiteness, blackness)
745+
*
746+
* It's up to the caller to reset the ctx if this fails.
747+
*
748+
* \param vector Vector of tokens to process
749+
* \param ctx Pointer to vector iteration context
750+
* \param result Pointer to location to receive result (AARRGGBB)
751+
* \return true on success, false on error.
752+
*/
753+
static bool parse_hwb(
754+
const parserutils_vector *vector,
755+
int32_t *ctx,
756+
uint32_t *result)
757+
{
758+
const css_token *token;
759+
size_t consumed = 0;
760+
css_fixed hue, white, black;
761+
int32_t alpha = 255;
762+
css_error error;
763+
uint8_t r = 0, g = 0, b = 0, a = 0xff;
764+
765+
/* hue is a number without a unit representing an
766+
* angle (0-360) degrees, or it can be an angle dimension.
767+
*/
768+
consumeWhitespace(vector, ctx);
769+
770+
token = parserutils_vector_iterate(vector, ctx);
771+
if ((token == NULL) ||
772+
(token->type != CSS_TOKEN_NUMBER &&
773+
token->type != CSS_TOKEN_DIMENSION)) {
774+
return false;
775+
}
776+
777+
hue = css__number_from_lwc_string(token->idata, false, &consumed);
778+
779+
switch (token->type) {
780+
case CSS_TOKEN_NUMBER:
781+
if (consumed != lwc_string_length(token->idata)) {
782+
return false; /* failed to consume the whole string as a number */
783+
}
784+
break;
785+
case CSS_TOKEN_DIMENSION:
786+
size_t len = lwc_string_length(token->idata);
787+
const char *data = lwc_string_data(token->idata);
788+
uint32_t unit = UNIT_DEG;
789+
790+
error = css__parse_unit_keyword(
791+
data + consumed,
792+
len - consumed,
793+
&unit);
794+
if (error != CSS_OK) {
795+
return false;
796+
}
797+
798+
switch (unit) {
799+
case UNIT_DEG:
800+
break;
801+
case UNIT_RAD:
802+
hue = FDIV(FMUL(hue, F_180), F_PI);
803+
break;
804+
case UNIT_GRAD:
805+
hue = FMUL(hue, FLTTOFIX(0.9));
806+
break;
807+
case UNIT_TURN:
808+
hue = FMUL(hue, F_360);
809+
break;
810+
default:
811+
return false;
812+
}
813+
break;
814+
default:
815+
return false; /* unexpected token type */
816+
}
817+
818+
/* Normalise hue to the range [0, 360) */
819+
while (hue < 0)
820+
hue += F_360;
821+
while (hue >= F_360)
822+
hue -= F_360;
823+
824+
consumeWhitespace(vector, ctx);
825+
826+
/* whiteness */
827+
token = parserutils_vector_iterate(vector, ctx);
828+
if (token == NULL)
829+
return false;
830+
831+
if ((token->type != CSS_TOKEN_PERCENTAGE) &&
832+
(token->type != CSS_TOKEN_NUMBER)) {
833+
return false;
834+
}
835+
836+
white = css__number_from_lwc_string(token->idata, false, &consumed);
837+
if (consumed != lwc_string_length(token->idata)) {
838+
/* failed to consume the whole string as a number */
839+
return false;
840+
}
841+
842+
/* Normalise whiteness to the range [0, 100] */
843+
if (white < INTTOFIX(0))
844+
white = INTTOFIX(0);
845+
else if (white > INTTOFIX(100))
846+
white = INTTOFIX(100);
847+
848+
consumeWhitespace(vector, ctx);
849+
850+
/* blackness */
851+
token = parserutils_vector_iterate(vector, ctx);
852+
if (token == NULL)
853+
return false;
854+
855+
if ((token->type != CSS_TOKEN_PERCENTAGE) &&
856+
(token->type != CSS_TOKEN_NUMBER)) {
857+
return false;
858+
}
859+
860+
black = css__number_from_lwc_string(token->idata, false, &consumed);
861+
if (consumed != lwc_string_length(token->idata)) {
862+
/* failed to consume the whole string as a number */
863+
return false;
864+
}
865+
866+
/* Normalise blackness to the range [0, 100] */
867+
if (black < INTTOFIX(0))
868+
black = INTTOFIX(0);
869+
else if (black > INTTOFIX(100))
870+
black = INTTOFIX(100);
871+
872+
consumeWhitespace(vector, ctx);
873+
874+
token = parserutils_vector_iterate(vector, ctx);
875+
876+
if (tokenIsChar(token, '/')) {
877+
consumeWhitespace(vector, ctx);
878+
879+
token = parserutils_vector_iterate(vector, ctx);
880+
if ((token == NULL) ||
881+
(token->type != CSS_TOKEN_NUMBER &&
882+
token->type != CSS_TOKEN_PERCENTAGE)) {
883+
return false;
884+
}
885+
886+
alpha = css__number_from_lwc_string(token->idata, false, &consumed);
887+
if (consumed != lwc_string_length(token->idata)) {
888+
/* failed to consume the whole string as a number */
889+
return false;
890+
}
891+
892+
if (token->type != CSS_TOKEN_NUMBER) {
893+
alpha = FIXTOINT(FMUL(alpha, F_255));
894+
} else {
895+
alpha = FIXTOINT(FDIV(FMUL(alpha, F_255), F_100));
896+
}
897+
898+
consumeWhitespace(vector, ctx);
899+
900+
token = parserutils_vector_iterate(vector, ctx);
901+
}
902+
903+
if (!tokenIsChar(token, ')'))
904+
return false;
905+
906+
/* have a valid HSV entry, convert to RGB */
907+
HWB_to_RGB(hue, white, black, &r, &g, &b);
908+
909+
/* apply alpha */
910+
if (alpha > 255) {
911+
a = 255;
912+
} else if (alpha < 0) {
913+
a = 0;
914+
} else {
915+
a = alpha;
916+
}
917+
918+
*result = ((unsigned)a << 24) | (r << 16) | (g << 8) | b;
919+
return true;
920+
}
921+
709922
/**
710923
* Parse a colour specifier
711924
*
@@ -819,6 +1032,12 @@ css_error css__parse_colour_specifier(css_language *c,
8191032
if (!parse_hsl(vector, ctx, result)) {
8201033
goto invalid;
8211034
}
1035+
} else if ((lwc_string_caseless_isequal(
1036+
token->idata, c->strings[HWB],
1037+
&match) == lwc_error_ok && match)) {
1038+
if (!parse_hwb(vector, ctx, result)) {
1039+
goto invalid;
1040+
}
8221041
} else {
8231042
goto invalid;
8241043
}

0 commit comments

Comments
 (0)