Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/parse/properties/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ DIR_SOURCES := \
font.c \
font_family.c \
font_weight.c \
gap.c \
list_style.c \
list_style_type.c \
margin.c \
Expand Down
164 changes: 164 additions & 0 deletions src/parse/properties/gap.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
/*
* This file is part of LibCSS.
* Licensed under the MIT License,
* http://www.opensource.org/licenses/mit-license.php
* Copyright 2026 Cyril Hrubis <metan@ucw.cz>
*/

#include <assert.h>
#include <string.h>

#include "bytecode/bytecode.h"
#include "bytecode/opcodes.h"
#include "parse/properties/properties.h"
#include "parse/properties/utils.h"

/**
* Parse a single gap component: normal | <length>
*
* \param c Parsing context
* \param vector Vector of tokens to process
* \param ctx Pointer to vector iteration context
* \param normal Pointer to location to receive "is normal" flag
* \param length Pointer to location to receive the length
* \param unit Pointer to location to receive the length's unit
* \return CSS_OK on success, CSS_INVALID if the input is not a gap component
*
* Post condition: \a *ctx is updated with the next token to process
* If the input is invalid, then \a *ctx remains unchanged.
*/
static css_error parse_gap_component(css_language *c,
const parserutils_vector *vector, int32_t *ctx,
bool *normal, css_fixed *length, uint32_t *unit)
{
int32_t orig_ctx = *ctx;
const css_token *token;
css_error error;
bool match;

token = parserutils_vector_peek(vector, *ctx);
if (token == NULL)
return CSS_INVALID;

if ((token->type == CSS_TOKEN_IDENT) &&
(lwc_string_caseless_isequal(token->idata,
c->strings[NORMAL], &match) == lwc_error_ok &&
match)) {
parserutils_vector_iterate(vector, ctx);
*normal = true;
return CSS_OK;
}

error = css__parse_unit_specifier(c, vector, ctx, UNIT_PX, length, unit);
if (error != CSS_OK) {
*ctx = orig_ctx;
return error;
}

if (*length < 0 || (*unit & UNIT_MASK_COLUMN_GAP) == 0) {
*ctx = orig_ctx;
return CSS_INVALID;
}

*normal = false;

return CSS_OK;
}

/**
* Parse gap shorthand
*
* gap: <row-gap> <column-gap>? — one value sets both axes.
*
* LibCSS has no row-gap property (column-gap comes from multicol), so only
* the column component reaches the cascade. The row component is still
* parsed and consumed, or the whole declaration would be rejected as
* invalid and the column gap lost with it.
*
* \param c Parsing context
* \param vector Vector of tokens to process
* \param ctx Pointer to vector iteration context
* \param result Pointer to location to receive resulting style
* \return CSS_OK on success,
* CSS_NOMEM on memory exhaustion,
* CSS_INVALID if the input is not valid
*
* Post condition: \a *ctx is updated with the next token to process
* If the input is invalid, then \a *ctx remains unchanged.
*/
css_error css__parse_gap(css_language *c,
const parserutils_vector *vector, int32_t *ctx,
css_style *result)
{
int32_t orig_ctx = *ctx;
css_error error;
const css_token *token;
enum flag_value flag_value;
css_fixed length = 0;
uint32_t unit = 0;
bool normal = false;

token = parserutils_vector_peek(vector, *ctx);
if (token == NULL) {
return CSS_INVALID;
}

flag_value = get_css_flag_value(c, token);

if (flag_value != FLAG_VALUE__NONE) {
parserutils_vector_iterate(vector, ctx);

error = css_stylesheet_style_flag_value(result, flag_value,
CSS_PROP_COLUMN_GAP);
if (error != CSS_OK)
*ctx = orig_ctx;

return error;
}

/* Row component — parsed only to consume it. */
error = parse_gap_component(c, vector, ctx, &normal, &length, &unit);
if (error != CSS_OK) {
*ctx = orig_ctx;
return error;
}

consumeWhitespace(vector, ctx);

/* Column component, if any — it overrides the row one. */
token = parserutils_vector_peek(vector, *ctx);
if (token != NULL) {
css_fixed col_length = 0;
uint32_t col_unit = 0;
bool col_normal = false;

if (parse_gap_component(c, vector, ctx, &col_normal,
&col_length, &col_unit) == CSS_OK) {
length = col_length;
unit = col_unit;
normal = col_normal;
}
}

if (normal) {
error = css__stylesheet_style_appendOPV(result,
CSS_PROP_COLUMN_GAP, 0, COLUMN_GAP_NORMAL);
if (error != CSS_OK)
*ctx = orig_ctx;

return error;
}

error = css__stylesheet_style_appendOPV(result, CSS_PROP_COLUMN_GAP,
0, COLUMN_GAP_SET);
if (error != CSS_OK) {
*ctx = orig_ctx;
return error;
}

error = css__stylesheet_style_vappend(result, 2, length, unit);
if (error != CSS_OK)
*ctx = orig_ctx;

return error;
}
1 change: 1 addition & 0 deletions src/parse/properties/properties.c
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ const css_prop_handler property_handlers[LAST_PROP + 1 - FIRST_PROP] =
css__parse_font_style,
css__parse_font_variant,
css__parse_font_weight,
css__parse_gap,
css__parse_height,
css__parse_justify_content,
css__parse_left,
Expand Down
3 changes: 3 additions & 0 deletions src/parse/properties/properties.h
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,9 @@ css_error css__parse_font_variant(css_language *c,
css_error css__parse_font_weight(css_language *c,
const parserutils_vector *vector, int32_t *ctx,
css_style *result);
css_error css__parse_gap(css_language *c,
const parserutils_vector *vector, int32_t *ctx,
css_style *result);
css_error css__parse_height(css_language *c,
const parserutils_vector *vector, int32_t *ctx,
css_style *result);
Expand Down
1 change: 1 addition & 0 deletions src/parse/propstrings.c
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,7 @@ const stringmap_entry stringmap[LAST_KNOWN] = {
SMAP("font-style"),
SMAP("font-variant"),
SMAP("font-weight"),
SMAP("gap"),
SMAP("height"),
SMAP("justify-content"),
SMAP("left"),
Expand Down
2 changes: 1 addition & 1 deletion src/parse/propstrings.h
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ enum {
CURSOR, DIRECTION, DISPLAY, ELEVATION, EMPTY_CELLS, FILL_OPACITY, FLEX,
FLEX_BASIS, FLEX_DIRECTION, FLEX_FLOW, FLEX_GROW, FLEX_SHRINK,
FLEX_WRAP, LIBCSS_FLOAT, FONT, FONT_FAMILY, FONT_SIZE, FONT_STYLE,
FONT_VARIANT, FONT_WEIGHT, HEIGHT, JUSTIFY_CONTENT, LEFT,
FONT_VARIANT, FONT_WEIGHT, GAP, HEIGHT, JUSTIFY_CONTENT, LEFT,
LETTER_SPACING, LINE_HEIGHT, LIST_STYLE, LIST_STYLE_IMAGE,
LIST_STYLE_POSITION, LIST_STYLE_TYPE, MARGIN, MARGIN_BOTTOM,
MARGIN_LEFT, MARGIN_RIGHT, MARGIN_TOP, MAX_HEIGHT, MAX_WIDTH,
Expand Down
105 changes: 105 additions & 0 deletions test/data/parse2/multicol.dat
Original file line number Diff line number Diff line change
Expand Up @@ -1301,3 +1301,108 @@
#reset


#data
* { gap: 2em; }
#errors
#expected
| *
| column-gap: 2em
#reset

#data
* { gap: 1em 2em; }
#errors
#expected
| *
| column-gap: 2em
#reset

#data
* { gap: normal; }
#errors
#expected
| *
| column-gap: normal
#reset

#data
* { gap: normal 2em; }
#errors
#expected
| *
| column-gap: 2em
#reset

#data
* { gap: 1em normal; }
#errors
#expected
| *
| column-gap: normal
#reset

#data
* { gap: 0; }
#errors
#expected
| *
| column-gap: 0px
#reset

#data
* { gap: 1em 2em 3em; }
#errors
#expected
| *
#reset

#data
* { gap: -1em; }
#errors
#expected
| *
#reset

#data
* { gap: 2; }
#errors
#expected
| *
#reset

#data
* { gap: 10%; }
#errors
#expected
| *
#reset

#data
* { gap: auto; }
#errors
#expected
| *
#reset

#data
* { gap: ; }
#errors
#expected
| *
#reset

#data
* { gap: inherit; }
#errors
#expected
| *
| column-gap: inherit
#reset

#data
* { gap: inherit !important; }
#errors
#expected
| *
| column-gap: inherit !important
#reset