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
3 changes: 2 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -69,10 +69,11 @@ dmod_add_library(${DMOD_MODULE_NAME} ${DMOD_MODULE_VERSION}
src/style.c
src/layout.c
src/paint.c
src/script.c
)

dmod_link_modules(${DMOD_MODULE_NAME}
dmvsi@>=0.1
dmvsi@>=0.2
)

target_include_directories(${DMOD_MODULE_NAME} PRIVATE
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
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

# The list of C++ sources
DMOD_CXXSOURCES=
Expand Down
50 changes: 45 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ makes a dmview view of it.

It has its own HTML parser, CSS cascade and layout - no browser, no
dependencies - so it runs on a PC at build time and on a device. It is made
for **still UI pages**: the screens a designer (or an AI) draws in HTML with
Tailwind CSS. Scripts are not run: a page is converted as it is when it
loads.
for **UI pages**: the screens a designer (or an AI) draws in HTML with
Tailwind CSS - and the little JavaScript that switches between them, which
it works out at conversion and makes the view's variables and handlers.

## What it does

Expand Down Expand Up @@ -56,6 +56,47 @@ in Tailwind's style sheet.
CSS does; text is measured with them as the view will draw it. Icon fonts
(Font Awesome) work through their CSS: `::before { content: "\f1eb" }`.

## Scripts

A script is not run on the device: what its click handlers do is worked
out at conversion. Understood is what switches screens and toggles things:

```js
const home = document.getElementById('home'); // an element
let current = null; // a variable the handlers set: the view's
function open(id) { // onclick="open('settings')": inlined
const w = document.getElementById(id);
w.classList.add('active'); // classList.add / remove / toggle
home.style.opacity = '0.3'; // style.<property> = ...
current = w;
}
function close() {
if (current) { // if (x), (!x), (a === b), classList.contains()
current.classList.remove('active');
home.style.opacity = '1';
current = null;
}
}
```

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

- how the element **moves and fades** becomes its group's variables, set at
once or **animated by its CSS `transition`** (duration and timing
function) - the view switches its screens as the page does, and slides
them as it does;
- when it **looks different** (a switch on, a light off, play / pause, an
icon hidden, another label) it is painted in each state of its class,
shown on the class's variable;
- `:active` (and Tailwind's `active:`) is its **pressed look**, shown while
its box is pressed.

What is reported and left out: what the page does when it loads (timers,
`Date`, text a script writes), loops, events other than clicks, a style a
script sets that changes how an element looks (but its opacity). A class
changes only the look of its element - what it moves around it stays.

## Resources

Style sheets, fonts and images are files next to the page; a URL is read
Expand All @@ -74,8 +115,7 @@ the view is the viewport, or the element the options name (`-r`).

## What it does not do (yet)

- JavaScript - a page is still; what a script would show (an opened window)
is converted when the HTML says it (e.g. a class added).
- JavaScript beyond the above (see [Scripts](#scripts)).
- Floats, tables (laid out as blocks), multi-line flex (`flex-wrap`),
explicit grid placement (only spans), `position: sticky` (relative).
- Rounded clipping: `overflow: hidden` with `border-radius` clips to the
Expand Down
7 changes: 7 additions & 0 deletions src/css.c
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,11 @@ static bool parse_pseudo(conv_t* c, const char** p, const char* end, compound_t*
out->pseudo |= PC_ROOT;
else if (ieq(name, n, "empty"))
out->pseudo |= PC_EMPTY;
else if (ieq(name, n, "active"))
{
out->pseudo |= PC_ACTIVE;
c->has_active = true;
}
else if (args != NULL && (ieq(name, n, "not") || ieq(name, n, "is") || ieq(name, n, "where") || ieq(name, n, "matches")))
{
uint32_t inner = 0;
Expand Down Expand Up @@ -558,6 +563,8 @@ static bool compound_matches(const compound_t* k, const node_t* n)
return false;
if ((k->pseudo & PC_EMPTY) != 0 && n->first != NULL)
return false;
if ((k->pseudo & PC_ACTIVE) != 0 && !n->active)
return false;
for (uint8_t i = 0; i < k->not_count; i++)
{
if (compound_matches(&k->nots[i], n))
Expand Down
151 changes: 129 additions & 22 deletions src/dmvs_html.c
Original file line number Diff line number Diff line change
Expand Up @@ -478,6 +478,125 @@ dmod_dmvsi_dif_api_declaration(1.0, dmvs_html, bool, _probe, ( const char* path,
return false;
}

/* ---- A page as a script changed it ---- */

static node_t* element_at(node_t* n, uint32_t index, uint32_t depth)
{
for (node_t* k = n->first; k != NULL && depth < 200U; k = k->next)
{
if (k->kind != NODE_ELEMENT)
continue;
if (k->index == index)
return k;
node_t* found = element_at(k, index, depth + 1U);
if (found != NULL)
return found;
}
return NULL;
}

/* "backgroundColor" -> "background-color" */
static void kebab(const char* name, char* out, size_t size)
{
size_t n = 0;
for (const char* s = name; *s != '\0' && n + 2U < size; s++)
{
if (*s >= 'A' && *s <= 'Z')
{
out[n++] = '-';
out[n++] = (char)(*s - 'A' + 'a');
}
else
out[n++] = *s;
}
out[n] = '\0';
}

static void apply_mod(conv_t* c, const mod_t* m)
{
node_t* n = element_at(c->document, m->element, 0);
if (n == NULL)
return;
if (m->kind == MOD_ACTIVE)
{
for (node_t* a = n; a != NULL && a->kind == NODE_ELEMENT; a = a->parent)
a->active = true; /* :active holds for the pressed one and its ancestors */
return;
}
if (m->kind == MOD_STYLE)
{
/* As element.style.<name> = value does: the style attribute, at its end */
char property[64];
kebab(m->name, property, sizeof(property));
attr_t* style = NULL;
for (attr_t* a = n->attrs; a != NULL; a = a->next)
{
if (a->name != NULL && strcmp(a->name, "style") == 0)
style = a;
}
if (style == NULL)
{
style = arena_alloc(&c->arena, sizeof(*style));
if (style == NULL)
return;
style->name = arena_strndup(&c->arena, "style", 5);
style->value = arena_strndup(&c->arena, "", 0);
style->next = n->attrs;
n->attrs = style;
}
size_t length = strlen(style->value) + strlen(property) + strlen(m->value) + 4U;
char* value = arena_alloc(&c->arena, length);
if (value != NULL)
{
Dmod_SnPrintf(value, length, "%s;%s:%s", style->value, property, m->value);
style->value = value;
}
return;
}
/* A class added or removed */
uint32_t at = n->class_count;
for (uint32_t i = 0; i < n->class_count; i++)
{
if (strcmp(n->classes[i], m->name) == 0)
at = i;
}
if (m->kind == MOD_CLASS_REMOVE && at < n->class_count)
{
for (uint32_t i = at + 1U; i < n->class_count; i++)
n->classes[i - 1U] = n->classes[i];
n->class_count--;
}
else if (m->kind == MOD_CLASS_ADD && at == n->class_count)
{
char** classes = arena_alloc(&c->arena, (n->class_count + 1U) * sizeof(char*));
if (classes == NULL)
return;
if (n->class_count > 0)
memcpy(classes, n->classes, n->class_count * sizeof(char*));
classes[n->class_count++] = (char*)m->name;
n->classes = classes;
}
}

int run_layout(conv_t* c)
{
size_t size = 0;
char* text = (c->path != NULL) ? read_resource(c, c->path, &size) : NULL;
if (text == NULL)
return c->arena.failed ? -ENOMEM : -ENOENT;
if ((c->document = html_parse(c, text, size)) == NULL)
return c->arena.failed ? -ENOMEM : -EBADMSG;
for (uint32_t i = 0; i < c->mod_count; i++)
apply_mod(c, &c->mods[i]);
load_sheets(c, c->document, 0);
if (c->tailwind)
css_parse(c, tailwind_preflight, strlen(tailwind_preflight), c->path, LAYER_TAILWIND);
style_compute(c, c->document);
if (!c->arena.failed)
layout_page(c, c->document);
return c->arena.failed ? -ENOMEM : 0;
}

dmod_dmvsi_dif_api_declaration(1.0, dmvs_html, int, _convert, ( const char* path, const dmvsi_options_t* options, dmvsi_doc_t doc ))
{
conv_t* c = Dmod_Malloc(sizeof(*c));
Expand All @@ -490,29 +609,17 @@ dmod_dmvsi_dif_api_declaration(1.0, dmvs_html, int, _convert, ( const char* path
c->vh = ((options->height != 0) ? options->height : DEFAULT_HEIGHT) * U;
c->path = arena_strndup(&c->arena, path, strlen(path));

int status = 0;
size_t size = 0;
char* text = (c->path != NULL) ? read_resource(c, c->path, &size) : NULL;
if (text == NULL)
status = c->arena.failed ? -ENOMEM : -ENOENT;
if (status == 0 && (c->document = html_parse(c, text, size)) == NULL)
status = c->arena.failed ? -ENOMEM : -EBADMSG;
int status = (c->path != NULL) ? run_layout(c) : -ENOMEM;
if (status == 0)
{
load_sheets(c, c->document, 0);
if (c->tailwind)
css_parse(c, tailwind_preflight, strlen(tailwind_preflight), c->path, LAYER_TAILWIND);
style_compute(c, c->document);
if (!c->arena.failed)
layout_page(c, c->document);
if (!c->arena.failed)
status = paint_page(c, c->document);
if (c->arena.failed)
status = -ENOMEM;
const char* dump_path = Dmod_GetEnv("DMVS_HTML_DUMP");
if (status == 0 && dump_path != NULL && dump_path[0] != '\0')
dump(c, dump_path);
}
status = script_compile(c); /* What its scripts do: variables, handlers */
if (status == 0)
status = paint_page(c, c->document);
script_free(c);
if (c->arena.failed)
status = -ENOMEM;
const char* dump_path = Dmod_GetEnv("DMVS_HTML_DUMP");
if (status == 0 && dump_path != NULL && dump_path[0] != '\0')
dump(c, dump_path);
if (c->warnings > 0)
DMOD_LOG_INFO("dmvs_html: %s: %u warnings\n", path, (unsigned)c->warnings);
arena_free(&c->arena);
Expand Down
2 changes: 2 additions & 0 deletions src/html.c
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,7 @@ typedef struct
conv_t* c;
node_t* open[MAX_DEPTH];
uint32_t depth;
uint32_t elements; /* Numbered so far */
} parser_t;

static node_t* current(parser_t* p)
Expand Down Expand Up @@ -264,6 +265,7 @@ static const char* start_tag(parser_t* p, const char* s, const char* end)
if (n == NULL)
return end;
n->kind = NODE_ELEMENT;
n->index = ++p->elements;
n->tag = arena_strndup(&c->arena, name, (size_t)(s - name));
if (n->tag == NULL)
return end;
Expand Down
Loading
Loading