Skip to content

Commit 178b1a4

Browse files
committed
parse: properties: utils: Add support for HWB colours
1 parent abb3e3e commit 178b1a4

File tree

1 file changed

+220
-0
lines changed

1 file changed

+220
-0
lines changed

src/parse/properties/utils.c

Lines changed: 220 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,186 @@ 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+
}
814+
break;
815+
default:
816+
return false; /* unexpected token type */
817+
}
818+
819+
/* Normalise hue to the range [0, 360) */
820+
while (hue < 0)
821+
hue += F_360;
822+
while (hue >= F_360)
823+
hue -= F_360;
824+
825+
consumeWhitespace(vector, ctx);
826+
827+
/* whiteness */
828+
token = parserutils_vector_iterate(vector, ctx);
829+
if (token == NULL)
830+
return false;
831+
832+
if ((token->type != CSS_TOKEN_PERCENTAGE) &&
833+
(token->type != CSS_TOKEN_NUMBER)) {
834+
return false;
835+
}
836+
837+
white = css__number_from_lwc_string(token->idata, false, &consumed);
838+
if (consumed != lwc_string_length(token->idata)) {
839+
/* failed to consume the whole string as a number */
840+
return false;
841+
}
842+
843+
/* Normalise whiteness to the range [0, 100] */
844+
if (white < INTTOFIX(0))
845+
white = INTTOFIX(0);
846+
else if (white > INTTOFIX(100))
847+
white = INTTOFIX(100);
848+
849+
consumeWhitespace(vector, ctx);
850+
851+
/* blackness */
852+
token = parserutils_vector_iterate(vector, ctx);
853+
if (token == NULL)
854+
return false;
855+
856+
if ((token->type != CSS_TOKEN_PERCENTAGE) &&
857+
(token->type != CSS_TOKEN_NUMBER)) {
858+
return false;
859+
}
860+
861+
black = css__number_from_lwc_string(token->idata, false, &consumed);
862+
if (consumed != lwc_string_length(token->idata)) {
863+
/* failed to consume the whole string as a number */
864+
return false;
865+
}
866+
867+
/* Normalise blackness to the range [0, 100] */
868+
if (black < INTTOFIX(0))
869+
black = INTTOFIX(0);
870+
else if (black > INTTOFIX(100))
871+
black = INTTOFIX(100);
872+
873+
consumeWhitespace(vector, ctx);
874+
875+
token = parserutils_vector_iterate(vector, ctx);
876+
877+
if (tokenIsChar(token, '/')) {
878+
consumeWhitespace(vector, ctx);
879+
880+
token = parserutils_vector_iterate(vector, ctx);
881+
if ((token == NULL) ||
882+
(token->type != CSS_TOKEN_NUMBER &&
883+
token->type != CSS_TOKEN_PERCENTAGE)) {
884+
return false;
885+
}
886+
887+
alpha = css__number_from_lwc_string(token->idata, false, &consumed);
888+
if (consumed != lwc_string_length(token->idata)) {
889+
/* failed to consume the whole string as a number */
890+
return false;
891+
}
892+
893+
if (token->type != CSS_TOKEN_NUMBER) {
894+
alpha = FIXTOINT(FMUL(alpha, F_255));
895+
} else {
896+
alpha = FIXTOINT(FDIV(FMUL(alpha, F_255), F_100));
897+
}
898+
899+
consumeWhitespace(vector, ctx);
900+
901+
token = parserutils_vector_iterate(vector, ctx);
902+
}
903+
904+
if (!tokenIsChar(token, ')'))
905+
return false;
906+
907+
/* have a valid HSV entry, convert to RGB */
908+
HWB_to_RGB(hue, white, black, &r, &g, &b);
909+
910+
/* apply alpha */
911+
if (alpha > 255) {
912+
a = 255;
913+
} else if (alpha < 0) {
914+
a = 0;
915+
} else {
916+
a = alpha;
917+
}
918+
919+
*result = ((unsigned)a << 24) | (r << 16) | (g << 8) | b;
920+
return true;
921+
}
922+
709923
/**
710924
* Parse a colour specifier
711925
*
@@ -819,6 +1033,12 @@ css_error css__parse_colour_specifier(css_language *c,
8191033
if (!parse_hsl(vector, ctx, result)) {
8201034
goto invalid;
8211035
}
1036+
} else if ((lwc_string_caseless_isequal(
1037+
token->idata, c->strings[HWB],
1038+
&match) == lwc_error_ok && match)) {
1039+
if (!parse_hwb(vector, ctx, result)) {
1040+
goto invalid;
1041+
}
8221042
} else {
8231043
goto invalid;
8241044
}

0 commit comments

Comments
 (0)