Skip to content
Merged
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 CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ dmod_add_library(${DMOD_MODULE_NAME} ${DMOD_MODULE_VERSION}
src/style.c
src/layout.c
src/paint.c
src/svg.c
src/script.c
)

Expand Down
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ DMOD_MODULE_VERSION=0.1
DMOD_AUTHOR_NAME=Patryk Kubiak

# The list of C sources
DMOD_CSOURCES=src/dmvs_html.c src/html.c src/css.c src/tailwind.c src/style.c src/layout.c src/paint.c src/script.c
DMOD_CSOURCES=src/dmvs_html.c src/html.c src/css.c src/tailwind.c src/style.c src/layout.c src/paint.c src/svg.c src/script.c

# The list of C++ sources
DMOD_CXXSOURCES=
Expand Down
24 changes: 22 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,9 @@ it works out at conversion and makes the view's variables and handlers.

**HTML** - elements, attributes, text with character references, the end
tags a page may leave out, `<style>`, `<link rel="stylesheet">`, `style=""`,
`<img>`, `<br>`. `<script>` is not run; one that loads Tailwind's Play CDN
(`cdn.tailwindcss.com`) turns Tailwind on.
`<img>`, inline `<svg>`, `<br>`. `<script>` is not run; one that loads
Tailwind's Play CDN (`cdn.tailwindcss.com`) turns Tailwind on, and its
`tailwind.config` (`theme.extend.fontFamily`) is read.

**CSS** - the cascade (origins, `!important`, specificity, order),
inheritance, custom properties and `var()`, `calc()` / `min()` / `max()` /
Expand All @@ -44,6 +45,17 @@ fixed, `z-index` and stacking contexts, `overflow` (clipping, scrolling),
`border` and `border-radius`, `opacity`, `box-shadow` (outer and inset),
`filter: blur()` (e.g. a glow) and `drop-shadow()`, `visibility`.

**Images** - `<img>` with `object-fit` (`cover`, `contain`, `fill` - its
aspect kept -, `none`, `scale-down`) and `object-position`;
`background-image: url()` (under its gradient layers) with
`background-size` (`cover`, `contain`, `auto`) and `background-position`;
`filter: blur()` blurs the image. Each size and blur an image is drawn at
is converted once (todmvs: a section of the image's `.ini`). An inline
`<svg>` is an image of its own: its subtree is written into an SVG file
of its box's size - `$TMPDIR` (`/tmp`), `<page>-svg<index>.svg`, the
`viewBox` and SVG's camelCase names kept, `currentColor` as its color -
which dmod's dmimg_svg rasterizes.

**Tailwind CSS** (v3, as its Play CDN generates it) - its preflight, the
utilities of layout, flexbox, grid, spacing, sizing, typography,
backgrounds and gradients, borders, effects, filters and transforms, the
Expand Down Expand Up @@ -79,6 +91,14 @@ function close() {
}
```

Handlers are also what the page sets up when it loads:
`addEventListener('click', ...)` and `el.onclick = ...` with arrow and
function expressions (closures over their scope), on elements of
`getElementById()`, `querySelector()` / `querySelectorAll()` (a list's
`forEach()` is unrolled), `getAttribute()`; `el.click()` runs the
element's handlers. Several class changes of an element at once are one
look of it.

`this` in an `onclick` is its element. Every change is laid out - the page
with that class or that style:

Expand Down
43 changes: 43 additions & 0 deletions src/css.c
Original file line number Diff line number Diff line change
Expand Up @@ -618,6 +618,49 @@ bool css_matches(const rule_t* rule, const node_t* n)
return rule->count > 0 && matches_from(rule, (int)rule->count - 1, n);
}

static void select_under(const rule_t* rules, uint32_t count, node_t* n, node_t** out, uint32_t max, uint32_t* found, uint32_t depth)
{
for (node_t* k = n->first; k != NULL && depth < 200U; k = k->next)
{
if (k->kind != NODE_ELEMENT || k->pseudo != PSEUDO_NONE)
continue;
bool match = false;
for (uint32_t i = 0; i < count && !match; i++)
match = rules[i].pseudo_element == PSEUDO_NONE && css_matches(&rules[i], k);
if (match && *found < max)
out[(*found)++] = k;
select_under(rules, count, k, out, max, found, depth + 1U);
}
}

/* What document.querySelectorAll(selector) gives: the elements under n it matches, in the
* document's order - up to max of them */
uint32_t css_select(conv_t* c, const char* selector, node_t* n, node_t** out, uint32_t max)
{
rule_t rules[8];
uint32_t count = 0;
const char* end = selector + strlen(selector);
const char* s = selector;
while (s < end && count < 8U)
{
const char* e = s;
int depth = 0;
while (e < end && !(*e == ',' && depth == 0))
{
depth += (*e == '(' || *e == '[') ? 1 : (*e == ')' || *e == ']') ? -1 : 0;
e++;
}
memset(&rules[count], 0, sizeof(rules[count]));
if (parse_selector(c, s, e, &rules[count]))
count++;
s = (e < end) ? e + 1 : end;
}
uint32_t found = 0;
if (count > 0)
select_under(rules, count, n, out, max, &found, 0);
return found;
}

/* ---- Rules ---- */

uint16_t css_parse_decls(conv_t* c, const char* text, size_t length, decl_t** decls);
Expand Down
22 changes: 22 additions & 0 deletions src/dmvs_html.c
Original file line number Diff line number Diff line change
Expand Up @@ -399,6 +399,8 @@ static void load_sheets(conv_t* c, node_t* n, uint32_t depth)
const char* src = node_attr(k, "src");
if (src != NULL && has_text(src, "tailwindcss"))
c->tailwind = true;
if (src == NULL && k->first != NULL && k->first->kind == NODE_TEXT)
tailwind_config(c, k->first->text, k->first->length);
continue;
}
load_sheets(c, k, depth + 1U);
Expand Down Expand Up @@ -553,6 +555,26 @@ static void apply_mod(conv_t* c, const mod_t* m)
}
return;
}
if (m->kind == MOD_CLASSES)
{
/* "+a -b": each one as its own */
for (const char* q = m->name; *q != '\0'; )
{
while (*q == ' ')
q++;
if (*q != '+' && *q != '-')
break;
bool add = *q++ == '+';
const char* e = q;
while (*e != '\0' && *e != ' ')
e++;
mod_t one = { m->element, add ? MOD_CLASS_ADD : MOD_CLASS_REMOVE, arena_strndup(&c->arena, q, (size_t)(e - q)), NULL };
if (one.name != NULL)
apply_mod(c, &one);
q = e;
}
return;
}
/* A class added or removed */
uint32_t at = n->class_count;
for (uint32_t i = 0; i < n->class_count; i++)
Expand Down
35 changes: 31 additions & 4 deletions src/layout.c
Original file line number Diff line number Diff line change
Expand Up @@ -489,8 +489,8 @@ static frag_t* add_frag(conv_t* c, node_t* block, uint8_t kind)

static bool decorated(const style_t* st)
{
return (st->background >> 24) != 0 || st->background_image != NULL || st->border_w[0] > 0 || st->border_w[1] > 0 ||
st->border_w[2] > 0 || st->border_w[3] > 0 || st->shadow_count > 0;
return (st->background >> 24) != 0 || st->background_image != NULL || st->background_url != NULL ||
st->border_w[0] > 0 || st->border_w[1] > 0 || st->border_w[2] > 0 || st->border_w[3] > 0 || st->shadow_count > 0;
}

/* One line of items [from, to) at y: placed, returns its height */
Expand Down Expand Up @@ -830,9 +830,11 @@ static int32_t clamp_height(const node_t* n, int32_t h, int32_t cb_h)
return max32(h, e);
}

/* <img>: its size from its file (or its width / height attributes) */
/* <img>: its size from its file (or its width / height attributes); <svg>: from its attributes */
static bool replaced_size(conv_t* c, node_t* n, int32_t* w, int32_t* h)
{
if (node_is(n, "svg"))
return svg_size(n, w, h);
if (!node_is(n, "img"))
return false;
int32_t iw = 0, ih = 0;
Expand Down Expand Up @@ -1598,7 +1600,32 @@ static void layout_absolute(conv_t* c, node_t* n)
h = clamp_height(n, h, cbh);
layout_box(c, n, w, (h != AUTO_SIZE) ? h : -1, cbw, cbh);

/* Its static position, where the flow would have put it */
/* Its static position, where the flow would have put it - in a flex container, as
* its only item, aligned by justify-content and align-self (or align-items) */
node_t* fc = n->box.container;
if (fc != NULL && fc == n->parent && fc->style != NULL &&
(fc->style->display == DISPLAY_FLEX || fc->style->display == DISPLAY_INLINE_FLEX))
{
const style_t* fs = fc->style;
bool row = fs->flex_direction == FLEX_ROW || fs->flex_direction == FLEX_ROW_REVERSE;
bool reverse = fs->flex_direction == FLEX_ROW_REVERSE || fs->flex_direction == FLEX_COLUMN_REVERSE;
int32_t free_x = fc->box.w - fc->box.b[1] - fc->box.b[3] - fc->box.p[1] - fc->box.p[3] - n->box.w - mx;
int32_t free_y = fc->box.h - fc->box.b[0] - fc->box.b[2] - fc->box.p[0] - fc->box.p[2] - n->box.h - my;
uint8_t justify = fs->justify_content;
uint8_t cross = (st->align_self != ALIGN_AUTO) ? st->align_self : fs->align_items;
int32_t main_free = row ? free_x : free_y, cross_free = row ? free_y : free_x;
int32_t main_off = 0, cross_off = 0;
if (justify == ALIGN_CENTER || justify == ALIGN_AROUND || justify == ALIGN_EVENLY)
main_off = main_free / 2;
else if ((justify == ALIGN_END) != reverse)
main_off = main_free;
if (cross == ALIGN_CENTER)
cross_off = cross_free / 2;
else if (cross == ALIGN_END)
cross_off = cross_free;
n->box.x = fc->box.b[3] + fc->box.p[3] + (row ? main_off : cross_off);
n->box.y = fc->box.b[0] + fc->box.p[0] + (row ? cross_off : main_off);
}
int32_t sx = n->box.x, sy = n->box.y;
if (n->box.container != NULL)
{
Expand Down
66 changes: 64 additions & 2 deletions src/paint.c
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,48 @@ static bool gradient_paint(const gradient_t* g, int32_t w, int32_t h, dmvsi_pain
return true;
}

/*
* An image file in r, sized as `fit` says - cover (and fill, its aspect kept),
* contain, scale-down, none (its own size) - placed at x, y (%) and clipped
* to r; blurred with the standard deviation `blur`
*/
static void image_in(painter_t* p, const char* path, dmvsi_rect_t r, uint8_t fit, int16_t x, int16_t y, int32_t blur)
{
if (r.w <= 0 || r.h <= 0)
return;
dmvsi_image_t im;
memset(&im, 0, sizeof(im));
im.rect = r;
im.path = path;
im.blur = blur;
int32_t iw = 0, ih = 0;
if (fit != FIT_NONE && image_size(path, &iw, &ih) && iw > 0 && ih > 0)
{
bool wider = (int64_t)r.w * ih > (int64_t)r.h * iw; /* The box is wider than the image */
int64_t w, h;
if ((fit == FIT_COVER || fit == FIT_FILL) == wider)
{
w = r.w;
h = (int64_t)r.w * ih / iw;
}
else
{
h = r.h;
w = (int64_t)r.h * iw / ih;
}
if (fit == FIT_SCALE_DOWN && w > (int64_t)iw * U)
{
w = (int64_t)iw * U;
h = (int64_t)ih * U;
}
im.width = (dmvsi_unit_t)w;
im.height = (dmvsi_unit_t)h;
}
im.flags = (uint8_t)(((x > 75) ? DMVSI_IMAGE_RIGHT : (x >= 25) ? DMVSI_IMAGE_CENTER : 0u) |
((y > 75) ? DMVSI_IMAGE_BOTTOM : (y >= 25) ? DMVSI_IMAGE_MIDDLE : 0u));
check(p, dmvsi_add_image(p->c->doc, &im));
}

static void fill(painter_t* p, dmvsi_rect_t r, int32_t radius, const dmvsi_paint_t* paint, const style_t* st)
{
if (r.w <= 0 || r.h <= 0)
Expand Down Expand Up @@ -384,6 +426,8 @@ static void paint_box(painter_t* p, const style_t* st, dmvsi_rect_t r, const int
paint.color = st->background;
fill(p, r, radius, &paint, st);
}
if (st->background_url != NULL && st != p->canvas && !p->shadow)
image_in(p, st->background_url, r, st->background_size, st->background_x, st->background_y, st->blur);
if (st->background_image != NULL && st != p->canvas && gradient_paint(st->background_image, r.w, r.h, &paint))
fill(p, r, radius, &paint, st);

Expand Down Expand Up @@ -482,13 +526,27 @@ static void paint_image(painter_t* p, const node_t* n)
char* path = (src != NULL) ? resolve_resource(p->c, p->c->path, src, strlen(src)) : NULL;
if (path == NULL || p->shadow || n->style->hidden)
return;
dmvsi_rect_t r = border_box(p, n);
dmvsi_rect_t b = inner(&r, n->box.b);
const style_t* st = n->style;
image_in(p, path, inner(&b, n->box.p), st->object_fit, st->object_x, st->object_y, st->blur);
}

/* An inline <svg>: its subtree as an SVG file of its content box's size */
static void paint_svg(painter_t* p, const node_t* n)
{
if (p->shadow || n->style->hidden)
return;
dmvsi_image_t im;
memset(&im, 0, sizeof(im));
dmvsi_rect_t r = border_box(p, n);
dmvsi_rect_t b = inner(&r, n->box.b);
im.rect = inner(&b, n->box.p);
im.path = path;
check(p, dmvsi_add_image(p->c->doc, &im));
if (im.rect.w <= 0 || im.rect.h <= 0)
return;
im.path = svg_export(p->c, n, (im.rect.w + U - 1) / U, (im.rect.h + U - 1) / U);
if (im.path != NULL)
check(p, dmvsi_add_image(p->c->doc, &im));
}

/* ---- Stacking ---- */
Expand Down Expand Up @@ -561,6 +619,8 @@ static void paint_inner(painter_t* p, node_t* n, uint32_t depth)
paint_decoration(p, st, r, n->box.b); /* The root's is the canvas's (paint_page) */
if (node_is(n, "img"))
paint_image(p, n);
else if (node_is(n, "svg"))
paint_svg(p, n);

bool clip = clips(st);
if (clip)
Expand Down Expand Up @@ -680,6 +740,8 @@ static void paint_flow_element(painter_t* p, node_t* k, uint32_t depth)
paint_box(p, k->style, border_box(p, k), k->box.b, !outer_done);
if (node_is(k, "img"))
paint_image(p, k);
else if (node_is(k, "svg"))
paint_svg(p, k);
paint_lines(p, k);
}
paint_flow(p, k, depth + 1U);
Expand Down
20 changes: 20 additions & 0 deletions src/private.h
Original file line number Diff line number Diff line change
Expand Up @@ -383,6 +383,11 @@ struct style
uint32_t color;
uint32_t background;
gradient_t* background_image;
const char* background_url; /* background-image: url() - the file (its last layer that has one) */
uint8_t background_size; /* FIT_* (FIT_NONE: auto) */
int16_t background_x, background_y; /* background-position, % */
uint8_t object_fit; /* FIT_* */
int16_t object_x, object_y; /* object-position, % */

const char* font_family;
int32_t font_size;
Expand Down Expand Up @@ -451,6 +456,14 @@ struct rule_ref
#define MOD_CLASS_REMOVE 1u
#define MOD_STYLE 2u
#define MOD_ACTIVE 3u /* Pressed: it and its ancestors are :active */
#define MOD_CLASSES 4u /* Classes added and removed at once: name "+a -b" */

/* object-fit, background-size */
#define FIT_FILL 0u
#define FIT_CONTAIN 1u
#define FIT_COVER 2u
#define FIT_NONE 3u /* background-size: auto */
#define FIT_SCALE_DOWN 4u

typedef struct
{
Expand All @@ -475,6 +488,7 @@ typedef struct
uint32_t order;
font_face_t* faces;
bool tailwind; /* The page loads Tailwind CSS (its Play CDN) */
const char* tw_fonts; /* Its tailwind.config's fontFamily: "|sans=Inter, sans-serif|" */
bool has_active; /* A rule of :active (a pressed look) */
int32_t vw, vh; /* The viewport, 1/64 px */
int status;
Expand All @@ -497,9 +511,14 @@ node_t* html_parse(conv_t* c, const char* text, size_t length);
const char* node_attr(const node_t* n, const char* name);
bool node_is(const node_t* n, const char* tag);

/* Inline <svg> (svg.c): its intrinsic size; its subtree written into an SVG file of the box's size (px) */
bool svg_size(const node_t* n, int32_t* w, int32_t* h);
char* svg_export(conv_t* c, const node_t* n, int32_t width, int32_t height);

/* css.c */
void css_parse(conv_t* c, const char* text, size_t length, const char* base, uint32_t layer);
bool css_matches(const rule_t* rule, const node_t* n);
uint32_t css_select(conv_t* c, const char* selector, node_t* n, node_t** out, uint32_t max);
uint32_t css_unescape(const char** p, const char* end);
void css_add_rule(conv_t* c, rule_t* rule);
uint16_t css_parse_decls(conv_t* c, const char* text, size_t length, decl_t** decls);
Expand All @@ -511,6 +530,7 @@ const rule_ref_t* css_bucket(const conv_t* c, const char* prefix, const char* na

/* tailwind.c */
extern const char tailwind_preflight[];
void tailwind_config(conv_t* c, const char* text, size_t length);
uint32_t tailwind_class(conv_t* c, const char* name, decl_t* decls, uint32_t max, uint32_t* rank, bool* active);

/* style.c */
Expand Down
Loading
Loading