lhp: Lightweight HTML Parser

Introduce a very lightweight html5 + css2.1+ stateful stream parser, along
the same lines as the lws json and cbor ones.

This is interesting primarily because of just how low-resource it is for
modest css + html, it uses an lwsac to hold the entirity of the css in
memory at once but the html is parsed in chunks without any need to keep
previous chunks around (chunks may be as small as 1 byte).

A user callback receives element entry and exit callbacks with payload and
all attributes parsed out, CSS related to the active element stack is
parsed to provide a list of active css attributes, which takes heap for the
duration of the parsing.

In effect this provides rich information about the html and css state to
the callback, which has the job of producing the layout in a user-defined
way.

As such, there is no DOM in memory at one time, there is only a stack of
active elements like <html><body><div>xxx with their associated attributes
(like class).  So as it is, it does not support DOM modification such as
JS changing elements after parsing, although elements with interesting IDs
could be kept around by the callback.  There is a corresponding tiny and
relatively flat heap usage regardless of html size.

Default CSS is specified as recommended in the CSS 2.1 standard.

Inline <style></style> elements are supported, but not pre-html5 style= in
element attributes, since these are incompatible with strict CSP.

What the attributes should mean on your system, eg, font-size, font-family
etc is left for the user callback to decide, along with how to lay out the
items using the CSS attributes, and render them.

Fixed point 32.32 constants are used (fraction expressed at parts in 100M)
instead of floating point.

If you have presentation needs, even on a constrained display on a
constrained microcontroller, this makes it feasible to use standardized
markup and styling instead of roll your own.
This commit is contained in:
Andy Green 2021-12-19 19:10:20 +00:00
parent 5d56f7651f
commit 63d2f844db
24 changed files with 6434 additions and 9 deletions

View File

@ -275,6 +275,7 @@ option(LWS_WITH_LEJP "With the Lightweight JSON Parser" ON)
option(LWS_WITH_CBOR "With the Lightweight LECP CBOR Parser" OFF)
option(LWS_WITH_CBOR_FLOAT "Build floating point types if building CBOR LECP" ON)
option(LWS_WITH_SQLITE3 "Require SQLITE3 support" OFF)
option(LWS_WITH_LHP "With the Lightweight HTML5 parser" ON)
option(LWS_WITH_STRUCT_JSON "Generic struct serialization to and from JSON" OFF)
option(LWS_WITH_STRUCT_SQLITE3 "Generic struct serialization to and from SQLITE3" OFF)
option(LWS_WITH_JSONRPC "JSON RPC support" ON)
@ -352,7 +353,10 @@ option(LWS_WITH_ALLOC_METADATA_LWS "Build lws_*alloc() with compressed backtrace
#
option(LWS_WITH_OTA "Build with support for Over The Air update download and validation" OFF)
set(LWS_OTA_VARIANT "set-LWS_OTA_VARIANT" CACHE STRING "Build Variant ID for OTA filtering")
set(LWS_OTA_PUBLIC_JWK "set-LWS_OTA_PUBLIC_JWK" CACHE STRING "Filepath to public JWK used for OTA validation")
set(LWS_OTA_PUBLIC_JWK_FILE "$ENV{HOME}/.lws_ota/libwebsockets.org-ota-v1.public.jwk" CACHE STRING "Filepath of public JWK used to validate packages")
if (LWS_WITH_OTA)
file(READ ${LWS_OTA_PUBLIC_JWK_FILE} LWS_OTA_PUBLIC_JWK)
endif()
if (${CMAKE_SYSTEM_NAME} MATCHES "SunOS")

View File

@ -0,0 +1,294 @@
# lws LHP HTML5 and CSS parser and render pipeline
## Please note this is working end-end, but some parts incomplete and generally pre-alpha... looking for interested parties to help
![overview](../doc-assets/lhp-overview.png)
<figcaption>*LHP Stream-parses HTML and CSS into a DOM and then into DLOs (lws
Display List Objects). Multiple, antialiased, proportional fonts, JPEG and PNGs
are supported. A linewise rasterizer is provided well-suited to resource-
constrained devices with SPI based displays.*</figcaption>
![example](../doc-assets/lhp-acep7.jpg)
<figcaption>*Page fetched from `https://libwebsockets.org/lhp-tests/t1.html` by
an ESP32, and rendered by lws on a 600x448 ACEP 7-colour EPD with 24-bit
composition. The warning symbol at the bottom right is a .png `<img>` in an
absolutely positioned `<div>`. The yellow shapes at the top right are divs with
css-styled rounded corners. The red div is partly transparent. Display only
has a 7 colour palette. Server only sends CSS/HTML/JPEG/PNG, all parsing and
rendering done on the ESP32.*</figcaption>
## Overview
Lws is able to parse **and render** a subset of CSS + HTML5 on very constrained
devices such as ESP32, which have a total of 200KB heap available after boot at
best. There are some technology advances in lws that allow much greater
capability that has previously been possible on those platforms.
The goal is that all system display content is expressed in HTML/CSS by user
code, which may also be dynamically generated, with CSS responsive layout
simplifying managing the same UI over different display dimensions.
There are restrictions - most generic html on the internet are too complex or
want more assets from different hosts than tiny devices can connect to - but
they are quite far beyond what you would expect from a 200KB heap limit. It
is very possible to mix remote and local http content over h2 including large
JPEG and PNG images and express all UI in html/css.
### Features
- Parses common HTML and CSS, somewhat beyond html5 and CSS2.1 (supports
some CSS3)
- Uses Secure Streams to bring in HTML, and references to JPEG and PNG `<img>`,
toplevel async renderer api takes an lws VFS file:// or https:// URL
retrieved via SS. There's easy, customizable lws VFS support at SS for
transparently referencing dynamically-generated or .text, or SD card-stored
HTML, or other assets
- No framebuffer... rendered strictly linewise. RGB framebuffer for 600x448
image above would be 800KB, lws can render it efficiently on an ESP32 with
only the default 200KB memory available.
- Image data is fetched from local or remote as needed during composition,
image buffering just 2 lines (PNG) or 8 or 16 lines (JPEG) plus decompressor
overhead (see below for more details)
- Layout supports DIVs, text wrapping, margin and padding, font colour,
selection and weight supported via CSS; implementation still early on others
- HTML element ID names that the user code cares about can be given, these are
found during parse and layout information for the elements kept
- Rendering is very flexible and lightweight, supports Y (8-) or RGB (24-) bit
line buffer and composition, alpha composing, Gamma correction, RGB565 and
individual palette RGB/Y packing, Dynamic Floyd-Steinberg error diffusion so
the same render can look as good as possible even on B&W display
- EPD Gray levels and spot colour palette (eg, BW-Red) supported
- Compatible with LCD/OLED but understands needs of EPD, display management is
all done via the event loop without any blocking waits.
- LHP, DLO and lws_display use fixed-point int32:8 sig digits, FPU not needed
- SPI DMA supported crossplatform, ESP32 driver provided
- lws_display drivers provided for several common SPI display chips, most
support ping-pong buffering so DMA proceeds while next line rendered
- 9 specific display + ESP32 example combinations provided
- HTML Parse -> 320x240 565 SPI Display update complete possible in under 250ms
on ESP32 WROVER KIT, remote assets slow it down
### Restrictions
- Only quite basic HTML + CSS implemented atm, old `style=` element attributes
not supported.
- Requires correct HTML, not yet tolerant of missing end tags etc
- CSS must be inline in the HTML atm
- lws understands ETAGs but there's no support to cache assets yet, they are
fetched anew in realtime each time
- There is no JS support. Information can be collected from laid-out elements
though by passing a list of IDs to the html parser api at the start.
- There's no CSS Rotation, animation etc
- There's no image scaling, images are presented 1:1
- There is a DOM representation, but for optimal memory usage it is stream-
parsed and destroyed element by element after using it to produce the DLO
layout, so only DOM parent elements that are still open exist at any one time.
This allows the parser to scale to complex html where most of it will be
discarded.
- CSS is parsed and kept for the whole HTML parse, since it doesn't know which
pieces will be needed until it has parsed the html. So giant CSS alone will
overflow available memory on constrained targets
Heap Costs during active decode (while rendering line that includes image)
|Feature|Decoder Cost in heap (600px width)|
|---|---|
|JPEG-Grayscale|6.5KB|
|JPEG-YUV 4:4:4|16.4KB|
|JPEG-YUV 4:4:2v|16.4KB|
|JPEG-YUV 4:4:2h|31KB|
|JPEG-YUV 4:4:0|31KB|
|PNG|36KB|
Connecting to an external tls source costs around 50KB. So for very constrained
targets like ESP32, the only practical way is a single h2 connection that
provides the assets as streams multiplexed inside a single tls tunnel.
### JIT_TRUST
Integrates CA trust bundle dynamic querying into lws, with openssl and mbedtls.
It can support all the typical Mozilla 130+ trusted CAs, using the trust chain
information from the server cert to identify the CA cert required, and just
instantiating that one to validate the server cert, if it trusts it. The
trust CTX is kept around in heap for a little while for the case there are
multiple connections needing it.
No heap is needed for trusted certs that are not actively required. This means
lws can securely connect over tls to arbitrary servers like a browser would
without using up all the memory; without this it's not possible to support
arbitrary connections securely within the memory constraints.
### Display List Objects (DLO)
Lws supports a logical Display List for graphical primitives common in HTML +
CSS, including compressed antialiased fonts, JPEG, PNG and rounded rectangles.
This intermediate representation allows display surface layout without having
all the details to hand, and provides flexibility for how to render the logical
representation of the layout.
### Linewise rendering
There may not be enough heap to hold a framebuffer for even a midrange display
device, eg an RGB buffer for the 600 x 448 display at the top of the page is
800KB. Even if there is, for display devices that hold a framebuffer on the
display, eg, SPI TFT, OLED, or Electrophoretic displays, the display data is
anyway sent linewise (perhaps in two planes, but still linewise) to the display.
In this case, there is no need for a framebuffer at the device, if the software
stack is rewritten to stream-parse all the page elements asynchronously, and
each time enough is buffered, processed and composed to produce the next line's
worth of pixels. Only one or two lines' worth of buffer is required then.
This is the lws approach, rewrite the asset decoders to operate completely
statefully so they can collaborate to provide just the next line's data Just-in-
Time.
### PNG and JPEG stream parsers
Lws includes fully stream-parsed decoders, which can run dry for input or output
at any state safely, and pick up where they left off when more data or space is
next available.
These were rewritten from UPNG and Picojpeg to be wholly stateful. These DLO
are bound to flow-controlled SS so the content can be provided to the composer
Just In Time. The rewrite requires that it can exit the decode at any byte
boundary, due to running out of input, or needing to flush output, and resume
with the same state, this is a complete inversion of the original program flow
where it only returns when it has rendered the whole image into a fullsize
buffer and decode state is spread around stack or filescope variables.
PNG transparency is supported via its A channel and composed by modulating
alpha.
### Compressed, Anti-aliased fonts
Based on mcufont, these are 4-bit antialised fonts produced from arbitrary TTFs.
They are compressed, a set of a dozen different font sizes from 10px thru 32px
and bold sizes only costs 100KB storage. The user can choose their own fonts
and sizes, the encoder is included in lws.
The mcufont decompressor was rewritten to be again completely stateful, glyphs
present on the current line are statefully decoded to produce that line's-worth
of output only and paused until the next line. Only glyphs that appear on the
current line have instantiated decoders.
The anti-alias information is composed into the line buffer as alpha.
### Integration of PNG, JPEG and file:// VFS sources into Secure Streams
Secure Streams and lws VFS now work together via `file://` URLs, a SS can be
directed to a local VFS resource the same way as to an `https://` resource.
Resources from https:// and file:// can refer to each other in CSS or `<img>`
cleanly.
![example](../doc-assets/lhp-ss-unification.png)
<figcaption>*All local and remote resources are fetched using Secure Streams with
a VFS `file://` or `https://` URL. Delivery of enough data to render the next
line from multiple sources without excess buffering is handled by `lws_flow`.*</figcaption>
Dynamic content, such as dynamic HTML, can be registered in a DLO VFS filesystem
and referenced via SS either as the toplevel html document or by URLs inside the
HTML.
`.jpg` and `.png` resources can be used in the html and are fetched using their
own SS, if coming from the same server over h2, these have very modest extra
memory needs since they are sharing the existing h2 connection and tls.
### H2 tx credit buffer management
All of the efforts to make JPEG or PNG stream-parsed are not useful if either
there is an h1 connection requiring a new TLS session that exhausts the heap,
or even if multiplexed into the same h2 session, the whole JPEG or PNG is
dumped too quickly into the device which cannot buffer it.
On constrained devices, the only mix that allows multiple streaming assets that
are decoded as they come, is an h2 server with streaming modulated by h2 tx
credit. The demos stream css, html, JPEG and CSS from libwebsockets.org over h2.
In lws, `lws_flow` provides the link between maximum buffering targets and the
tx_credit flow control management.
The number of assets that can be handled simultaneously on an HTML page is
restricted by the irreducible heap cost of decoding them, about 36KB + an RGB
line buffer for PNGs, and an either 8 (YUV4:4:4) or 16 RGB (4:4:2 or 4:4:0)
line buffer for JPEG.
However, PNG and JPEG decode occurs lazily starting at the render line where the
object starts becoming visible, and all DLO objects are destroyed after the last
line where they are visible. The SS responsible for fetching and regulating the
bufferspace needed is started at layout-time, and the parser is started too up
to the point that the header with the image dimensions is decoded, but not
beyond that where the large decoder allocation is required.
It means only images that appear on the same line have decoders that are
instantiated in memory at the same time; images that don't share any horizontal
common lines do not exist in heap simultaneously; basically multiple vertically
stacked images cost little more than one.
The demo shows that even on ESP32, the images are cheap enough to allow a full
size background JPEG with a partially-transparent PNG composed over it.
### lws_display Composition, palette and dithering
Internally, lws provides either a 8-bit Y (grayscale) or 32-bit RGBA (trucolor)
composition pipeline for all display elements, based on if the display device is
monochrome or not. Alpha (opacity) is supported. This is true regardless of
final the bit depth of the display device, so even B&W devices can approximate
the same output.
Gamma of 2.2 is also applied before palettization, then floyd-steinberg
dithering, all with just a line buffer and no framebuffer needed at the device.
The assets like JPEG can be normal, RGB ones, and the rendering adapts down to
the display palette and capabilities dynamically.
The `lws_display` support in lws has been extended to a variety of common EPD
controllers such as UC8171, supporting B&W, B&W plus a third colour (red or
yellow typically) and 4-level Gray. The ILI9341 driver for the displays found
on WROVER KIT and the ESP32S Kaluga KIT has been enhanced to work with the new
display pipline using native 565.
![overview](../doc-assets/lhp-400-300-red.jpg)
<figcaption>*HTML rendered on the device from file:// VFS-stored normal RGB JPEG and HTML/CSS, by ESP32 with BW-Red palette 400x300 EPD*</figcaption>
![overview](../doc-assets/lhp-rgb-example.png)
<figcaption>*Test html rendered to 24-bit RGB data directly*</figcaption>
![overview](../doc-assets/lhp-example-g4.jpg)
<figcaption>*Test html rendered to 300x240 4-gray palette EPD (from RGB JPEG also fetched from server during render) using Y-only composition... notice effectiveness of error diffusion use of the palette*</figcaption>
![overview](../doc-assets/lhp-104-212-red.jpg)
<figcaption>*Test html rendered to 104 x 212 BW-Red palette EPD, red h1 text set by CSS `color:#f00`, on a lilygo ESP32-based EPD label board*</figcaption>
![overview](../doc-assets/lhp-epd-flex-104.jpg)
<figcaption>*Test html rendered to 104 x 212 BW flexible EPD, notice font legibility, effectiveness of dither and presence of line breaks*</figcaption>
![overview](https://libwebsockets.org/wrover-boot.gif)
<figcaption>*ESP32 WROVER KIT running the example carousel on a 320x200 565 RGB SPI display. 10s delay between tests snipped for brevity, otherwise shown realtime. Moire is artifact of camera. As composition is linewise, the JPEG and other data from libwebsockets.org is arriving and being completely parsed / composed in the time taken to update the display. Interleaved SPI DMA used to send line to display while rendering the next.*</figcaption>
## Top level API
```
int
lws_lhp_ss_browse(struct lws_context *cx, lws_display_render_state_t *rs,
const char *url, sul_cb_t render);
```
You basically give it an `https://` or `file://` URL, a structure for the
render state, and a callback for when the DLOs have been created and lines of
pixels are being emitted. The source fetching, parsing, layout, and finally
rendering proceed asynchronously on the event loop without blocking beyond the
time taken to emit by default 4 lines.
In these examples, the renderer callback passes the lines of pixels to the
`lws_display` blit op.
See `./include/libwebsockets/lws-html.h` for more information.
Also see `./minimal-examples-lowlevel/api-tests/api-test-lhp-dlo/main.c`
example, you can render to 24-bit RGB on stdout by giving it a URL, eg
```
$ ./bin/lws-api-test-lhp-dlo https://libwebsockets.org/lhp-tests/t1.html >/tmp/a.data
```
The raw RGB can be opened in GIMP.

View File

@ -188,6 +188,7 @@
#cmakedefine LWS_WITH_CBOR_FLOAT
#cmakedefine LWS_WITH_JSONRPC
#cmakedefine LWS_WITH_LEJP
#cmakedefine LWS_WITH_LHP
#cmakedefine LWS_WITH_LIBEV
#cmakedefine LWS_WITH_LIBEVENT
#cmakedefine LWS_WITH_LIBUV

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 129 KiB

BIN
doc-assets/lhp-acep7.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 128 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

BIN
doc-assets/lhp-overview.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 270 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 228 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 87 KiB

View File

@ -798,6 +798,8 @@ lws_fx_string(const lws_fx_t *a, char *buf, size_t size);
#include <libwebsockets/lws-netdev.h>
#endif
#include <libwebsockets/lws-html.h>
#ifdef __cplusplus
}
#endif

View File

@ -49,6 +49,11 @@ struct lws_dlo;
/* stores Y in RGBY */
#define PALETTE_RGBY(_r, _g, _b) LWSDC_RGBA(_r, _g, _b, (RGB_TO_Y(_r, _g, _b)))
typedef struct {
lws_fx_t w;
lws_fx_t h;
} lws_dlo_dim_t;
/*
* When using RGBA to describe native greyscale, R is Y and A is A, GB is ignored
*/
@ -98,20 +103,37 @@ typedef struct lws_display_id {
typedef struct lws_dlo {
lws_dll2_t list;
lws_dll2_t col_list; /* lws_dlo_t: column-mates */
lws_dll2_t row_list; /* lws_dlo_t: row-mates */
/* children are rendered "inside" the parent DLO box after allowing
* for parent padding */
lws_dll2_owner_t children;
/* only used for dlo rect representing whole table */
lws_dll2_owner_t table_cols; /* lhp_table_col_t */
lws_dll2_owner_t table_rows; /* lhp_table_row_t */
/* may point to dlo whose width or height decides our x or y */
struct lws_dlo *abut_x;
struct lws_dlo *abut_y;
lws_dlo_destroy_t _destroy; /* dlo-type specific cb */
lws_dlo_renderer_t render; /* dlo-type specific cb */
lws_fx_t margin[4];
lws_fx_t padding[4]; /* child origin */
lws_display_id_t *id; /* only valid until ids destroyed */
lws_box_t box;
lws_display_colour_t dc;
uint8_t flag_runon:1; /* continues same line */
uint8_t flag_done_align:1; /* continues same line */
uint8_t flag_done_align:1;
uint8_t flag_toplevel:1; /* don't scan up with me (different owner) */
/* render-specific members ... */
} lws_dlo_t;
@ -232,7 +254,8 @@ typedef struct lws_dlo_jpeg {
typedef enum {
LWSDLOSS_TYPE_JPEG,
LWSDLOSS_TYPE_PNG
LWSDLOSS_TYPE_PNG,
LWSDLOSS_TYPE_CSS,
} lws_dlo_image_type_t;
typedef struct {
@ -297,6 +320,12 @@ lws_display_render_get_id(lws_display_render_state_t *rs, const char *id);
LWS_VISIBLE LWS_EXTERN void
lws_display_render_dump_ids(lws_dll2_owner_t *ids);
LWS_VISIBLE LWS_EXTERN void
lws_dlo_contents(lws_dlo_t *parent, lws_dlo_dim_t *dim);
LWS_VISIBLE LWS_EXTERN void
lws_display_dlo_adjust_dims(lws_dlo_t *dlo, lws_dlo_dim_t *dim);
/**
* lws_display_dl_init() - init display list object
*
@ -424,17 +453,19 @@ typedef struct {
struct lhp_ctx *lhp;
lws_dlo_image_t *u;
int32_t window;
uint8_t type;
} lws_dlo_ss_create_info_t;
LWS_VISIBLE LWS_EXTERN lws_dlo_t *
lws_dlo_ss_create(lws_dlo_ss_create_info_t *i);
LWS_VISIBLE LWS_EXTERN int
lws_dlo_ss_create(lws_dlo_ss_create_info_t *i, lws_dlo_t **pdlo);
typedef struct lhp_ctx lhp_ctx_t;
LWS_VISIBLE LWS_EXTERN int
lws_dlo_ss_find(struct lws_context *cx, const char *url, lws_dlo_image_t *u);
LWS_VISIBLE LWS_EXTERN signed char
LWS_VISIBLE LWS_EXTERN lws_stateful_ret_t
lhp_displaylist_layout(lhp_ctx_t *ctx, char reason);
#define lws_dlo_image_width(_u) ((_u)->failed ? -1 : \
@ -471,7 +502,7 @@ lws_fonts_destroy(struct lws_context *cx);
* Static blob registry (built-in, name-accessible blobs)
*/
LWS_VISIBLE LWS_EXTERN int
LWS_VISIBLE LWS_EXTERN lws_dlo_filesystem_t *
lws_dlo_file_register(struct lws_context *cx, const lws_dlo_filesystem_t *f);
/* only needed if f dynamically heap-allocated... doesn't free data; data
@ -481,6 +512,9 @@ lws_dlo_file_register(struct lws_context *cx, const lws_dlo_filesystem_t *f);
LWS_VISIBLE LWS_EXTERN void
lws_dlo_file_unregister(lws_dlo_filesystem_t **f);
LWS_VISIBLE LWS_EXTERN void
lws_dlo_file_unregister_by_name(struct lws_context *cx, const char *name);
LWS_VISIBLE LWS_EXTERN const lws_dlo_filesystem_t *
lws_dlo_file_choose(struct lws_context *cx, const char *name);

View File

@ -0,0 +1,659 @@
/*
* libwebsockets - small server side websockets and web server implementation
*
* Copyright (C) 2010 - 2022 Andy Green <andy@warmcat.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*
* Extremely Lightweight HTML5 Stream Parser, same approach as lecp but for
* html5.
*/
#if !defined(LHP_MAX_ELEMS_NEST)
#define LHP_MAX_ELEMS_NEST 32
#endif
#if !defined(LHP_MAX_DEPTH)
#define LHP_MAX_DEPTH 12
#endif
#if !defined(LHP_STRING_CHUNK)
#define LHP_STRING_CHUNK 254
#endif
enum lhp_callbacks {
LHPCB_ERR_ATTRIB_SYNTAX = -5,
LHPCB_ERR_ATTRIB_LEN = -4,
LHPCB_ERR_OOM = -3,
LHPCB_ERR_ELEM_DEPTH = -2,
LHPCB_CONTINUE = -1,
LHPCB_CONSTRUCTED = 0,
LHPCB_DESTRUCTED = 1,
LHPCB_COMPLETE = 2,
LHPCB_FAILED = 3,
LHPCB_ELEMENT_START = 4, /* reported at end of <> */
LHPCB_ELEMENT_END = 5,
LHPCB_CONTENT = 6,
LHPCB_COMMENT = 7,
};
/*
* CSS v2.1 full property set, taken from
*
* https://www.w3.org/TR/CSS21/propidx.html
*/
typedef enum lcsp_props {
LCSP_PROP_AZIMUTH,
LCSP_PROP_BACKGROUND_ATTACHMENT,
LCSP_PROP_BACKGROUND_COLOR,
LCSP_PROP_BACKGROUND_IMAGE,
LCSP_PROP_BACKGROUND_POSITION,
LCSP_PROP_BACKGROUND_REPEAT,
LCSP_PROP_BACKGROUND,
LCSP_PROP_BORDER_COLLAPSE,
LCSP_PROP_BORDER_COLOR,
LCSP_PROP_BORDER_SPACING,
LCSP_PROP_BORDER_STYLE,
LCSP_PROP_BORDER_TOP,
LCSP_PROP_BORDER_RIGHT,
LCSP_PROP_BORDER_BOTTOM,
LCSP_PROP_BORDER_LEFT,
LCSP_PROP_BORDER_TOP_COLOR,
LCSP_PROP_BORDER_RIGHT_COLOR,
LCSP_PROP_BORDER_BOTTOM_COLOR,
LCSP_PROP_BORDER_LEFT_COLOR,
LCSP_PROP_BORDER_TOP_STYLE,
LCSP_PROP_BORDER_RIGHT_STYLE,
LCSP_PROP_BORDER_BOTTOM_STYLE,
LCSP_PROP_BORDER_LEFT_STYLE,
LCSP_PROP_BORDER_TOP_WIDTH,
LCSP_PROP_BORDER_RIGHT_WIDTH,
LCSP_PROP_BORDER_BOTTOM_WIDTH,
LCSP_PROP_BORDER_LEFT_WIDTH,
LCSP_PROP_BORDER_WIDTH,
LCSP_PROP_BORDER_TOP_LEFT_RADIUS,
LCSP_PROP_BORDER_TOP_RIGHT_RADIUS,
LCSP_PROP_BORDER_BOTTOM_LEFT_RADIUS,
LCSP_PROP_BORDER_BOTTOM_RIGHT_RADIUS,
LCSP_PROP_BORDER_RADIUS,
LCSP_PROP_BORDER,
LCSP_PROP_BOTTOM,
LCSP_PROP_CAPTION_SIDE,
LCSP_PROP_CLEAR,
LCSP_PROP_CLIP,
LCSP_PROP_COLOR,
LCSP_PROP_CONTENT,
LCSP_PROP_COUNTER_INCREMENT,
LCSP_PROP_COUNTER_RESET,
LCSP_PROP_CUE_AFTER,
LCSP_PROP_CUE_BEFORE,
LCSP_PROP_CUE,
LCSP_PROP_CURSOR,
LCSP_PROP_DIRECTION,
LCSP_PROP_DISPLAY,
LCSP_PROP_ELEVATION,
LCSP_PROP_EMPTY_CELLS,
LCSP_PROP_FLOAT,
LCSP_PROP_FONT_FAMILY,
LCSP_PROP_FONT_SIZE,
LCSP_PROP_FONT_STYLE,
LCSP_PROP_FONT_VARAIANT,
LCSP_PROP_FONT_WEIGHT,
LCSP_PROP_FONT,
LCSP_PROP_HEIGHT,
LCSP_PROP_LEFT,
LCSP_PROP_LETTER_SPACING,
LCSP_PROP_LINE_HEIGHT,
LCSP_PROP_LIST_STYLE_IMAGE,
LCSP_PROP_LIST_STYLE_POSITION,
LCSP_PROP_LIST_STYLE_TYPE,
LCSP_PROP_LIST_STYLE,
LCSP_PROP_MARGIN_RIGHT,
LCSP_PROP_MARGIN_LEFT,
LCSP_PROP_MARGIN_TOP,
LCSP_PROP_MARGIN_BOTTOM,
LCSP_PROP_MARGIN,
LCSP_PROP_MAX_HEIGHT,
LCSP_PROP_MAX_WIDTH,
LCSP_PROP_MIN_HEIGHT,
LCSP_PROP_MIN_WIDTH,
LCSP_PROP_ORPHANS,
LCSP_PROP_OUTLINE_COLOR,
LCSP_PROP_OUTLINE_STYLE,
LCSP_PROP_OUTLINE_WIDTH,
LCSP_PROP_OUTLINE,
LCSP_PROP_OVERFLOW,
LCSP_PROP_PADDING_TOP,
LCSP_PROP_PADDING_RIGHT,
LCSP_PROP_PADDING_BOTTOM,
LCSP_PROP_PADDING_LEFT,
LCSP_PROP_PADDING,
LCSP_PROP_PAGE_BREAK_AFTER,
LCSP_PROP_PAGE_BREAK_BEFORE,
LCSP_PROP_PAGE_BREAK_INSIDE,
LCSP_PROP_PAUSE_AFTER,
LCSP_PROP_PAUSE_BEFORE,
LCSP_PROP_PAUSE,
LCSP_PROP_PITCH_RANGE,
LCSP_PROP_PITCH,
LCSP_PROP_PLAY_DURING,
LCSP_PROP_POSITION,
LCSP_PROP_QUOTES,
LCSP_PROP_RICHNESS,
LCSP_PROP_RIGHT,
LCSP_PROP_SPEAK_HEADER,
LCSP_PROP_SPEAK_NUMERAL,
LCSP_PROP_SPEAK_PUNCTUATION,
LCSP_PROP_SPEAK,
LCSP_PROP_SPEECH_RATE,
LCSP_PROP_STRESS,
LCSP_PROP_TABLE_LAYOUT,
LCSP_PROP_TEXT_ALIGN,
LCSP_PROP_TEXT_DECORATION,
LCSP_PROP_TEXT_INDENT,
LCSP_PROP_TEXT_TRANSFORM,
LCSP_PROP_TOP,
LCSP_PROP_UNICODE_BIDI,
LCSP_PROP_VERTICAL_ALIGN,
LCSP_PROP_VISIBILITY,
LCSP_PROP_VOICE_FAMILY,
LCSP_PROP_VOLUME,
LCSP_PROP_WHITE_SPACE,
LCSP_PROP_WIDOWS,
LCSP_PROP_WIDTH,
LCSP_PROP_WORD_SPACING,
LCSP_PROP_Z_INDEX,
LCSP_PROP__COUNT /* always last */
} lcsp_props_t;
/*
* Indexes for the well-known property values
*/
typedef enum {
LCSP_PROPVAL_ABOVE,
LCSP_PROPVAL_ABSOLUTE,
LCSP_PROPVAL_ALWAYS,
LCSP_PROPVAL_ARMENIAN,
LCSP_PROPVAL_AUTO,
LCSP_PROPVAL_AVOID,
LCSP_PROPVAL_BASELINE,
LCSP_PROPVAL_BEHIND,
LCSP_PROPVAL_BELOW,
LCSP_PROPVAL_BIDI_OVERRIDE,
LCSP_PROPVAL_BLINK,
LCSP_PROPVAL_BLOCK,
LCSP_PROPVAL_BOLD,
LCSP_PROPVAL_BOLDER,
LCSP_PROPVAL_BOTH,
LCSP_PROPVAL_BOTTOM,
LCSP_PROPVAL_CAPITALIZE,
LCSP_PROPVAL_CAPTION,
LCSP_PROPVAL_CENTER,
LCSP_PROPVAL_CIRCLE,
LCSP_PROPVAL_CLOSE_QUOTE,
LCSP_PROPVAL_CODE,
LCSP_PROPVAL_COLLAPSE,
LCSP_PROPVAL_CONTINUOUS,
LCSP_PROPVAL_CROSSHAIR,
LCSP_PROPVAL_DECIMAL_LEADING_ZERO,
LCSP_PROPVAL_DECIMAL,
LCSP_PROPVAL_DIGITS,
LCSP_PROPVAL_DISC,
LCSP_PROPVAL_EMBED,
LCSP_PROPVAL_E_RESIZE,
LCSP_PROPVAL_FIXED,
LCSP_PROPVAL_GEORGIAN,
LCSP_PROPVAL_HELP,
LCSP_PROPVAL_HIDDEN,
LCSP_PROPVAL_HIDE,
LCSP_PROPVAL_HIGH,
LCSP_PROPVAL_HIGHER,
LCSP_PROPVAL_ICON,
LCSP_PROPVAL_INHERIT,
LCSP_PROPVAL_INLINE,
LCSP_PROPVAL_INLINE_BLOCK,
LCSP_PROPVAL_INLINE_TABLE,
LCSP_PROPVAL_INVERT,
LCSP_PROPVAL_ITALIC,
LCSP_PROPVAL_JUSTIFY,
LCSP_PROPVAL_LEFT,
LCSP_PROPVAL_LIGHTER,
LCSP_PROPVAL_LINE_THROUGH,
LCSP_PROPVAL_LIST_ITEM,
LCSP_PROPVAL_LOW,
LCSP_PROPVAL_LOWER,
LCSP_PROPVAL_LOWER_ALPHA,
LCSP_PROPVAL_LOWERCASE,
LCSP_PROPVAL_LOWER_GREEK,
LCSP_PROPVAL_LOWER_LATIN,
LCSP_PROPVAL_LOWER_ROMAN,
LCSP_PROPVAL_LTR,
LCSP_PROPVAL_MENU,
LCSP_PROPVAL_MESSAGE_BOX,
LCSP_PROPVAL_MIDDLE,
LCSP_PROPVAL_MIX,
LCSP_PROPVAL_MOVE,
LCSP_PROPVAL_NE_RESIZE,
LCSP_PROPVAL_NO_CLOSE_QUOTE,
LCSP_PROPVAL_NONE,
LCSP_PROPVAL_NO_OPEN_QUOTE,
LCSP_PROPVAL_NO_REPEAT,
LCSP_PROPVAL_NORMAL,
LCSP_PROPVAL_NOWRAP,
LCSP_PROPVAL_N_RESIZE,
LCSP_PROPVAL_NW_RESIZE,
LCSP_PROPVAL_OBLIQUE,
LCSP_PROPVAL_ONCE,
LCSP_PROPVAL_OPEN_QUOTE,
LCSP_PROPVAL_OUTSIDE,
LCSP_PROPVAL_OVERLINE,
LCSP_PROPVAL_POINTER,
LCSP_PROPVAL_PRE,
LCSP_PROPVAL_PRE_LINE,
LCSP_PROPVAL_PRE_WRAP,
LCSP_PROPVAL_PROGRESS,
LCSP_PROPVAL_RELATIVE,
LCSP_PROPVAL_REPEAT,
LCSP_PROPVAL_REPEAT_X,
LCSP_PROPVAL_REPEAT_Y,
LCSP_PROPVAL_RIGHT,
LCSP_PROPVAL_RTL,
LCSP_PROPVAL_SCROLL,
LCSP_PROPVAL_SEPARATE,
LCSP_PROPVAL_SE_RESIZE,
LCSP_PROPVAL_SHOW,
LCSP_PROPVAL_SILENT,
LCSP_PROPVAL_SMALL_CAPS,
LCSP_PROPVAL_SMALL_CAPTION,
LCSP_PROPVAL_SPELL_OUT,
LCSP_PROPVAL_SQUARE,
LCSP_PROPVAL_S_RESIZE,
LCSP_PROPVAL_STATIC,
LCSP_PROPVAL_STATUS_BAR,
LCSP_PROPVAL_SUB,
LCSP_PROPVAL_SUPER,
LCSP_PROPVAL_SW_RESIZE,
LCSP_PROPVAL_TABLE,
LCSP_PROPVAL_TABLE_CAPTION,
LCSP_PROPVAL_TABLE_CELL,
LCSP_PROPVAL_TABLE_COLUMN,
LCSP_PROPVAL_TABLE_COLUMN_GROUP,
LCSP_PROPVAL_TABLE_FOOTER_GROUP,
LCSP_PROPVAL_TABLE_HEADER_GROUP,
LCSP_PROPVAL_TABLE_ROW,
LCSP_PROPVAL_TABLE_ROW_GROUP,
LCSP_PROPVAL_TEXT_BOTTOM,
LCSP_PROPVAL_TEXT_TOP,
LCSP_PROPVAL_TEXT,
LCSP_PROPVAL_TOP,
LCSP_PROPVAL_TRANSPARENT,
LCSP_PROPVAL_UNDERLINE,
LCSP_PROPVAL_UPPER_ALPHA,
LCSP_PROPVAL_UPPERCASE,
LCSP_PROPVAL_UPPER_LATIN,
LCSP_PROPVAL_UPPER_ROMAN,
LCSP_PROPVAL_VISIBLE,
LCSP_PROPVAL_WAIT,
LCSP_PROPVAL_W_RESIZE,
LCSP_PROPVAL__COUNT /* always last */
} lcsp_propvals_t;
struct lhp_ctx;
typedef lws_stateful_ret_t (*lhp_callback)(struct lhp_ctx *ctx, char reason);
/* html attribute */
typedef struct lhp_atr {
lws_dll2_t list;
size_t name_len; /* 0 if it is elem tag */
size_t value_len;
/* name+NUL then value+NUL follow */
} lhp_atr_t;
struct lcsp_atr;
#define CCPAS_TOP 0
#define CCPAS_RIGHT 1
#define CCPAS_BOTTOM 2
#define CCPAS_LEFT 3
typedef struct lhp_pstack {
lws_dll2_t list;
void *user; /* private to the stack level */
lhp_callback cb;
/* static: x,y: offset from parent, w,h: surface size of this object */
lws_box_t drt;
/* dynamic cursor inside drt for progressive child placement */
lws_fx_t curx;
lws_fx_t cury;
lws_fx_t widest;
lws_dll2_owner_t atr; /* lhp_atr_t */
const lws_display_font_t *f;
const struct lcsp_atr *css_background_color;
const struct lcsp_atr *css_color;
const struct lcsp_atr *css_position;
const struct lcsp_atr *css_display;
const struct lcsp_atr *css_width;
const struct lcsp_atr *css_height;
const struct lcsp_atr *css_border_radius[4];
const struct lcsp_atr *css_pos[4];
const struct lcsp_atr *css_margin[4];
const struct lcsp_atr *css_padding[4];
uint8_t is_block; /* children use space in our drt */
/* user layout owns these after initial values set */
void *opaque1;
const void *opaque2;
int oi[4];
int positioned[4];
int rel_layout_cursor[4];
uint8_t runon; /* continues same line */
} lhp_pstack_t;
typedef enum lcsp_css_units {
LCSP_UNIT_NONE,
LCSP_UNIT_NUM, /* u.i */
LCSP_UNIT_LENGTH_EM, /* u.i */
LCSP_UNIT_LENGTH_EX, /* u.i */
LCSP_UNIT_LENGTH_IN, /* u.i */
LCSP_UNIT_LENGTH_CM, /* u.i */
LCSP_UNIT_LENGTH_MM, /* u.i */
LCSP_UNIT_LENGTH_PT, /* u.i */
LCSP_UNIT_LENGTH_PC, /* u.i */
LCSP_UNIT_LENGTH_PX, /* u.i */
LCSP_UNIT_LENGTH_PERCENT, /* u.i */
LCSP_UNIT_ANGLE_ABS_DEG, /* u.i */
LCSP_UNIT_ANGLE_REL_DEG, /* u.i */
LCSP_UNIT_FREQ_HZ, /* u.i */
LCSP_UNIT_RGBA, /* u.rgba */
LCSP_UNIT_URL, /* string at end of atr */
LCSP_UNIT_STRING, /* string at end of atr */
LCSP_UNIT_DATA, /* binary data at end of atr */
} lcsp_css_units_t;
typedef struct lcsp_atr {
lws_dll2_t list;
int propval; /* lcsp_propvals_t LCSP_PROPVAL_ */
size_t value_len; /* for string . url */
lcsp_css_units_t unit;
union {
lws_fx_t i;
uint32_t rgba; /* for colours */
} u;
lws_fx_t r;
uint8_t op;
/* .value_len bytes follow (for strings and blobs) */
} lcsp_atr_t;
/* css definitions like font-weight: */
typedef struct lcsp_defs {
lws_dll2_t list;
lws_dll2_owner_t atrs; /* lcsp_atr_t */
lcsp_props_t prop; /* lcsp_props_t, LCSP_PROP_* */
} lcsp_defs_t;
typedef struct lcsp_names {
lws_dll2_t list;
size_t name_len;
/* name + NUL follow */
} lcsp_names_t;
typedef struct lcsp_stanza { /* css stanza, with names and defs */
lws_dll2_t list;
lws_dll2_owner_t names; /* lcsp_names_t */
lws_dll2_owner_t defs; /* lcsp_defs_t */
} lcsp_stanza_t;
/*
* A list of stanza references can easily have to bring in the same stanza
* multiple times, eg, <div><span class=x><div> won't work unless the div
* stanzas are listed twice at different places in the list. It means we can't
* use dll2 directly since the number of references is open-ended.
*
* lcsp_stanza_ptr provides indirection that allows multiple listings.
*/
typedef struct lcsp_stanza_ptr {
lws_dll2_t list;
lcsp_stanza_t *stz;
} lcsp_stanza_ptr_t;
typedef struct lcsp_atr_ptr {
lws_dll2_t list;
lcsp_atr_t *atr;
} lcsp_atr_ptr_t;
#define LHP_FLAG_DOCUMENT_END (1 << 0)
typedef struct lhp_ctx {
lws_dll2_owner_t stack; /* lhp_pstack_t */
struct lwsac *cssac; /* css allocations all in an ac */
struct lwsac *cascadeac; /* active_stanzas ac */
struct lwsac *propatrac; /* prop atr query results ac */
lws_dll2_owner_t css; /* lcsp_stanza_t (all in ac) */
lws_dll2_owner_t *ids;
lws_fx_t tf;
lcsp_css_units_t unit;
lcsp_stanza_t *stz; /* current stanza getting properties */
lcsp_defs_t *def; /* current property getting values */
lws_dll2_owner_t active_stanzas; /* lcsp_stanza_ptr_t allocated
* in cascadeac */
lws_dll2_owner_t active_atr; /* lcsp_atr_ptr_t allocated in
* propatrac */
lws_surface_info_t ic;
const char *base_url; /* strdup of https://x.com/y.html */
sul_cb_t ssevcb; /* callback for ss events */
lws_sorted_usec_list_t *ssevsul; /* sul to use to resume rz */
void *user;
void *user1;
const char *tag; /* private */
size_t tag_len; /* private */
int npos;
int state; /* private */
int state_css_comm; /* private */
int nl_temp;
int temp_count;
uint32_t flags;
uint32_t temp;
int32_t window; /* 0, or ss item flow control limit */
union {
uint32_t s;
struct {
uint32_t first:1;
uint32_t closing:1;
uint32_t void_element:1;
uint32_t doctype:1;
uint32_t inq:1;
uint32_t tag_used:1;
uint32_t arg:1;
uint32_t default_css:1;
#define LHP_CSS_PROPVAL_INT_WHOLE 1
#define LHP_CSS_PROPVAL_INT_FRAC 2
#define LHP_CSS_PROPVAL_INT_UNIT 3
uint32_t integer:2;
uint32_t color:2;
} f;
} u;
int prop; /* lcsp_props_t */
int propval; /* lcsp_propvals_t */
int16_t css_state; /* private */
int16_t cssval_state; /* private */
uint8_t in_body:1;
/* at end so we can memset members above it in one go */
char buf[LHP_STRING_CHUNK + 1];
} lhp_ctx_t;
/*
* lws_lhp_construct() - Construct an lhp context
*
* \param ctx: the lhp context to prepare
* \param cb: the stream parsing callback
* \param user: opaque user pointer available from the lhp context
* \param ic: struct with arguments for lhp context
*
* The lhp context is allocated by the caller (the size is known).
* Prepares an lhp context to parse html. Returns 0 for OK, or nonzero if OOM.
*/
LWS_VISIBLE LWS_EXTERN int
lws_lhp_construct(lhp_ctx_t *ctx, lhp_callback cb, void *user,
const lws_surface_info_t *ic);
/*
* lws_lhp_destruct() - Destroy an lhp context
*
* \param ctx: the lhp context to prepare
*
* Destroys an lhp context. The lhp context is allocated by the caller (the
* size is known). But there are suballocations that must be destroyed with
* this.
*/
LWS_VISIBLE LWS_EXTERN void
lws_lhp_destruct(lhp_ctx_t *ctx);
/**
* lws_lhp_ss_browse() - browse url using SS and parse via lhp to DLOs
*
* \param cx: the lws_context
* \param rs: the user's render state object
* \param url: the https://x.com/y.xyz URL to browse
* \param render: the user's linewise render callback (called from \p rs.sul)
*
* High level network fetch via SS and render html via lhp / DLO
*
* rs->ic must be prepared before calling.
*
* Returns nonzero if an early, fatal problem, else returns 0 and continues
* asynchronously.
*
* If rs->box is (0,0,0,0) on entry, it is set to represent the whole display
* surface. Otherwise if not representing the whole display surface, it
* indicates partial mode should be used.
*/
LWS_VISIBLE LWS_EXTERN int
lws_lhp_ss_browse(struct lws_context *cx, lws_display_render_state_t *rs,
const char *url, sul_cb_t render);
/**
* lws_lhp_parse() - parses a chunk of input HTML
*
* \p ctx: the parsing context
* \p buf: pointer to the start of the chunk of html
* \p len: pointer the number of bytes of html available at *\pbuf
*
* Parses up to *len bytes at *buf. On exit, *buf and *len are adjusted
* according to how much data was used. May return before processing all the
* input.
*
* Returns LWS_SRET_WANT_INPUT if the parsing is stalled on some other async
* event (eg, fetch of image to find out the dimensions).
*
* The lws_lhp_ss_browse() api wraps this.
*/
LWS_VISIBLE LWS_EXTERN lws_stateful_ret_t
lws_lhp_parse(lhp_ctx_t *ctx, const uint8_t **buf, size_t *len);
/**
* lws_css_cascade_get_prop_atr() - create active css atr list for property
*
* \p ctx: the parsing context
* \p prop: the LCSP_PROP_ property to generate the attribute list for
*
* Returns NULL if no atr or OOM.
*
* Otherwise produces a list of active CSS property attributes walkable via
* ctx->active_atr, and returns the tail one. For simple attributes where the
* last definition is the active one, this points to the last definition.
*/
LWS_VISIBLE LWS_EXTERN const lcsp_atr_t *
lws_css_cascade_get_prop_atr(lhp_ctx_t *ctx, lcsp_props_t prop);
LWS_VISIBLE LWS_EXTERN lhp_pstack_t *
lws_css_get_parent_block(lhp_ctx_t *ctx, lhp_pstack_t *ps);
LWS_VISIBLE LWS_EXTERN const char *
lws_css_pstack_name(lhp_pstack_t *ps);
LWS_VISIBLE LWS_EXTERN const char *
lws_html_get_atr(lhp_pstack_t *ps, const char *aname, size_t aname_len);
LWS_VISIBLE LWS_EXTERN const lws_fx_t *
lws_csp_px(const lcsp_atr_t *a, lhp_pstack_t *ps);
LWS_VISIBLE LWS_EXTERN void
lws_lhp_tag_dlo_id(lhp_ctx_t *ctx, lhp_pstack_t *ps, lws_dlo_t *dlo);
#define LWS_LHPREF_WIDTH 0
#define LWS_LHPREF_HEIGHT 1
#define LWS_LHPREF_NONE 2
LWS_VISIBLE LWS_EXTERN int
lhp_prop_axis(const lcsp_atr_t *a);

View File

@ -165,6 +165,16 @@ if (LWS_WITH_CBOR)
misc/lecp.c
misc/ieeehalfprecision.c)
endif()
if (LWS_WITH_LHP)
list(APPEND SOURCES
misc/lhp.c)
if (LWS_WITH_SECURE_STREAMS)
list(APPEND SOURCES
misc/lhp-ss.c)
endif()
endif()
if (UNIX)
if (NOT LWS_HAVE_GETIFADDRS)

View File

@ -0,0 +1,120 @@
azimuth:
background-attachment:
background-color:
background-image:
background-position:
background-repeat:
background:
border-collapse:
border-color:
border-spacing:
border-style:
border-top:
border-right:
border-bottom:
border-left:
border-top-color:
border-right-color:
border-bottom-color:
border-left-color:
border-top-style:
border-right-style:
border-bottom-style:
border-left-style:
border-top-width:
border-right-width:
border-bottom-width:
border-left-width:
border-width:
border-top-left-radius:
border-top-right-radius:
border-bottom-left-radius:
border-bottom-right-radius:
border-radius:
border:
bottom:
caption-side:
clear:
clip:
color:
content:
counter-increment:
counter-reset:
cue-after:
cue-before:
cue:
cursor:
direction:
display:
elevation:
empty-cells:
float:
font-family:
font-size:
font-style:
font-variant:
font-weight:
font:
height:
left:
letter-spacing:
line-height:
list-style-image:
list-style-position:
list-style-type:
list-style:
margin-right:
margin-left:
margin-top:
margin-bottom:
margin:
max-height:
max-width:
min-height:
min-width:
orphans:
outline-color:
outline-style:
outline-width:
outline:
overflow:
padding-top:
padding-right:
padding-bottom:
padding-left:
padding:
page-break-after:
page-break-before:
page-break-inside:
pause-after:
pause-before:
pause:
pitch-range:
pitch:
play-during:
position:
quotes:
richness:
right:
speak-header:
speak-numeral:
speak-punctuation:
speak:
speech-rate:
stress:
table-layout:
text-align:
text-decoration:
text-indent:
text-transform:
top:
unicode-bidi:
vertical-align:
visibility:
voice-family:
volume:
white-space:
widows:
width:
word-spacing:
z-index:

1337
lib/misc/css-lextable.h Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,125 @@
above
absolute
always
armenian
auto
avoid
baseline
behind
below
bidi-override
blink
block
bold
bolder
both
bottom
capitalize
caption
center
circle
close-quote
code
collapse
continuous
crosshair
decimal-leading-zero
decimal
digits
disc
embed
e-resize
fixed
georgian
help
hidden
hide
high
higher
icon
inherit
inline
inline-block
inline-table
invert
italic
justify
left
lighter
line-through
list-item
low
lower
lower-alpha
lowercase
lower-greek
lower-latin
lower-roman
ltr
menu
message-box
middle
mix
move
ne-resize
no-close-quote
none
no-open-quote
no-repeat
normal
nowrap
n-resize
nw-resize
oblique
once
open-quote
outside
overline
pointer
pre
pre-line
pre-wrap
progress
relative
repeat
repeat-x
repeat-y
right
rtl
scroll
separate
se-resize
show
silent
small-caps
small-caption
spell-out
square
s-resize
static
status-bar
sub
super
sw-resize
table
table-caption
table-cell
table-column
table-column-group
table-footer-group
table-header-group
table-row
table-row-group
text-bottom
text-top
text
top
transparent
underline
upper-alpha
uppercase
upper-latin
upper-roman
visible
wait
w-resize

File diff suppressed because it is too large Load Diff

View File

@ -534,8 +534,8 @@ lws_display_render_dump_ids(lws_dll2_owner_t *ids)
lwsl_notice(" id: '%s' (not present)\n", id->id);
else
lwsl_notice(" id: '%s', (%d,%d), %dx%d\n", id->id,
id->box.x.whole, id->box.y.whole,
id->box.w.whole, id->box.h.whole);
(int)id->box.x.whole, (int)id->box.y.whole,
(int)id->box.w.whole, (int)id->box.h.whole);
} lws_end_foreach_dll(d);
}

241
lib/misc/lhp-ss.c Normal file
View File

@ -0,0 +1,241 @@
/*
* libwebsockets - small server side websockets and web server implementation
*
* Copyright (C) 2010 - 2022 Andy Green <andy@warmcat.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*
* SS bindings for html5 parser
*/
#include <private-lib-core.h>
LWS_SS_USER_TYPEDEF
lws_flow_t flow;
lhp_ctx_t lhp; /* html ss owns html parser */
lws_dl_rend_t drt;
lws_sorted_usec_list_t sul;
lws_display_render_state_t *rs;
struct lws_context *cx;
} htmlss_t;
static void
lws_lhp_ss_html_parse(lws_sorted_usec_list_t *sul)
{
htmlss_t *m = lws_container_of(sul, htmlss_t, sul);
lws_stateful_ret_t r;
size_t zero = 0;
do {
if (lws_flow_feed(&m->flow)) {
lwsl_notice("%s: returning from flow_feed\n", __func__);
return;
}
// lwsl_notice("%s: html_parse in len %d\n", __func__, (int)m->flow.len);
/* creates display list objects from html */
r = lws_lhp_parse(&m->lhp, (const uint8_t **)&m->flow.data,
(size_t *)&m->flow.len);
lws_flow_req(&m->flow);
if ((r & LWS_SRET_WANT_INPUT) && !m->flow.len && !m->lhp.await_css_done) {
if (m->flow.state == LWSDLOFLOW_STATE_READ) {
lwsl_warn("%s: returning to await more input\n", __func__);
return;
}
lwsl_warn("%s: inferring we are finished\n", __func__);
break;
}
if (r & LWS_SRET_AWAIT_RETRY) {
if (!m->lhp.await_css_done)
lws_sul_schedule(m->cx, 0, &m->sul, lws_lhp_ss_html_parse, 1);
return;
}
if (r & (LWS_SRET_NO_FURTHER_OUT | LWS_SRET_FATAL)) {
lwsl_warn("%s: r 0x%x\n", __func__, r);
break;
}
} while (1);
/* Finalize the html parse and clean up */
lwsl_notice("%s: DESTROYING the lhp\n", __func__);
m->lhp.flags = LHP_FLAG_DOCUMENT_END;
lws_lhp_parse(&m->lhp, (const uint8_t **)NULL, &zero);
lws_lhp_destruct(&m->lhp);
m->rs->html = 2; /* html completed.. rs outlives the html ss and priv */
lws_display_dl_dump(m->drt.dl);
/* schedule starting the render */
lws_sul_schedule(m->cx, 0, &m->rs->sul, m->lhp.ssevcb, 1);
lws_ss_destroy(&m->ss);
}
void
lws_lhp_ss_html_parse_from_lhp(lhp_ctx_t *lhp)
{
htmlss_t *m = lws_container_of(lhp, htmlss_t, lhp);
lws_lhp_ss_html_parse(&m->sul);
}
/* secure streams payload interface */
static lws_ss_state_return_t
htmlss_rx(void *userobj, const uint8_t *buf, size_t len, int flags)
{
htmlss_t *m = (htmlss_t *)userobj;
lws_ss_state_return_t r = LWSSSSRET_OK;
if (len &&
lws_buflist_append_segment(&m->flow.bl, buf, len) < 0)
return LWSSSSRET_DISCONNECT_ME;
lwsl_notice("%s: buflen size %d\n", __func__,
(int)lws_buflist_total_len(&m->flow.bl));
if (flags & LWSSS_FLAG_EOM) {
m->flow.state = LWSDLOFLOW_STATE_READ_COMPLETED;
r = LWSSSSRET_DISCONNECT_ME;
}
lws_sul_schedule(m->cx, 0, &m->sul, lws_lhp_ss_html_parse, 1);
return r;
}
static lws_ss_state_return_t
htmlss_state(void *userobj, void *sh, lws_ss_constate_t state,
lws_ss_tx_ordinal_t ack)
{
htmlss_t *m = (htmlss_t *)userobj;
switch (state) {
case LWSSSCS_CREATING:
break;
case LWSSSCS_DISCONNECTED:
m->flow.state = LWSDLOFLOW_STATE_READ_COMPLETED;
m->flow.h = NULL;
break;
case LWSSSCS_DESTROYING:
lws_lhp_destruct(&m->lhp);
lws_buflist_destroy_all_segments(&m->flow.bl);
m->drt.dl = NULL;
break;
default:
break;
}
return LWSSSSRET_OK;
}
static LWS_SS_INFO("__default", htmlss_t)
.rx = htmlss_rx,
.state = htmlss_state,
.manual_initial_tx_credit = 1024
};
/* prep rs->displaylist, rs->ic */
int
lws_lhp_ss_browse(struct lws_context *cx, lws_display_render_state_t *rs,
const char *url, sul_cb_t render)
{
struct lws_ss_handle *h = NULL;
lws_ss_info_t ssi;
int32_t w = 64 * 1024;
htmlss_t *m;
/* fetch via SS */
#if defined(LWS_PLAT_BAREMETAL) || defined(LWS_PLAT_FREERTOS)
w = 4096;
#endif
ssi = ssi_htmlss_t;
ssi.manual_initial_tx_credit = w;
if (lws_ss_create(cx, 0, &ssi, NULL, &h, NULL, NULL)) {
lwsl_err("%s: ss create failed\n", __func__);
return 1; /* failed */
}
m = (htmlss_t *)lws_ss_to_user_object(h);
m->cx = cx;
m->flow.h = h;
m->flow.window = w;
m->drt.dl = &rs->displaylist;
m->drt.w = rs->ic->wh_px[0].whole;
m->drt.h = rs->ic->wh_px[1].whole;
m->rs = rs;
m->rs->html = 1; /* render must wait for html to complete */
if (lws_lhp_construct(&m->lhp, lhp_displaylist_layout, &m->drt, rs->ic)) {
lwsl_err("%s: lhp create %s failed\n", __func__, url);
goto bail1;
}
m->lhp.user1 = cx;
m->lhp.base_url = strdup(url);
m->lhp.ssevcb = render;
m->lhp.ssevsul = &rs->sul;
m->lhp.sshtmlevcb = lws_lhp_ss_html_parse;
m->lhp.sshtmlevsul = &m->sul;
m->lhp.ids = &rs->ids;
if (lws_ss_set_metadata(m->ss, "endpoint", url, strlen(url))) {
lwsl_err("%s: failed to use metadata %s\n", __func__, url);
goto bail2;
}
if (lws_ss_set_metadata(m->ss, "ua", "Mozilla/5.0 (X11; Fedora; Linux x86_64; rv:95.0) Gecko/20100101 Firefox/95.0", 76)) {
lwsl_err("%s: failed to use metadata ua\n", __func__);
goto bail2;
}
if (lws_ss_set_metadata(m->ss, "acc", "text/html,image/jpeg,image/png,", 30)) {
lwsl_err("%s: failed to use metadata ua\n", __func__);
goto bail2;
}
if (lws_ss_client_connect(m->ss))
goto bail2;
return 0;
bail2:
lws_lhp_destruct(&m->lhp);
bail1:
lws_ss_destroy(&h);
return 1;
}

2146
lib/misc/lhp.c Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,23 @@
project(lws-api-test-lhp C)
cmake_minimum_required(VERSION 2.8.12)
find_package(libwebsockets CONFIG REQUIRED)
list(APPEND CMAKE_MODULE_PATH ${LWS_CMAKE_DIR})
include(CheckCSourceCompiles)
include(LwsCheckRequirements)
set(requirements 1)
require_lws_config(LWS_WITH_LHP 1 requirements)
require_lws_config(LWS_WITH_SECURE_STREAMS 1 requirements)
if (requirements)
add_executable(${PROJECT_NAME} main.c)
add_test(NAME api-test-lhp COMMAND lws-api-test-lhp)
if (websockets_shared)
target_link_libraries(${PROJECT_NAME} websockets_shared ${LIBWEBSOCKETS_DEP_LIBS})
add_dependencies(${PROJECT_NAME} websockets_shared)
else()
target_link_libraries(${PROJECT_NAME} websockets ${LIBWEBSOCKETS_DEP_LIBS})
endif()
endif()

View File

@ -0,0 +1,319 @@
/*
* lws-api-test-lhp
*
* Written in 2010-2022 by Andy Green <andy@warmcat.com>
*
* This file is made available under the Creative Commons CC0 1.0
* Universal Public Domain Dedication.
*
* sanity tests for lhp
*/
#include <libwebsockets.h>
#include <stdio.h>
static const char * const cb_reasons[] = {
"LHPCB_CONSTRUCTED",
"LHPCB_DESTRUCTED",
"LHPCB_COMPLETE",
"LHPCB_FAILED",
"LHPCB_ELEMENT_START", /* reported at end of <> */
"LHPCB_ELEMENT_END",
"LHPCB_CONTENT",
"LHPCB_COMMENT",
};
static const char * const html_tests[] = {
/* test 1 */
"hello",
/* test 2 */
"<!doctype html>",
/* test 3 */
"<!DOCTYPE html PUBLIC \"-//W3C//DTD HTML 4.0//EN\">",
/* test 4 */
"<!doctype html><html><head></head><body>hello</body></html>",
/* test 5 */
"<!doctype html>\n"
"<html>\n"
"<head>\n"
"<title>An HTML standard template</title>\n"
"<meta charset=\"utf-8\" />\n"
"</head>\n"
"<body>\n"
"<h1>heading</h1>\n"
"<b>bold</b>, normal<br>\n"
"</body>\n"
"</html>\n",
"<>",
"<thing></thing>",
"<thing a></thing>",
"<thing a b=c></thing>",
"<thing a b='d'></thing>",
"<thing a b=\"e\"></thing>",
"<thing ></thing>",
"<thing a ></thing>",
"<thing a b=c ></thing>",
"<thing a b='d' ></thing>",
"<thing a b=\"e\" ></thing>",
"<br/>",
"<br />",
"<br something/>",
"<br something />",
"<!--comment-->",
"<!doctype html>\n"
"<html>\n"
"<head>\n"
"<title>Test html</title>\n"
"<meta charset=\"utf-8\" />\n"
"</head>\n"
"<style>"
"h1 { font-size: 32px },"
"b { font-weight: bold }"
"</style>"
"<body>\n"
"<h1>libwebsockets.org</h1>\n"
"A bunch of normal text, long enough that it is going to want to wrap<br>"
"<b>bold</b>, normal<br>\n"
"<img src=\"something.png\">"
"</body>\n"
"</html>\n",
"&#x20ac;&#x1f44d;",
"<html><head><title>the title</title></head><body><style>"
"<!-- css comment-->"
"/* another css comment */"
"body { font-size: 16px; font-family: default }"
"div { font-size: 16px; display: inline-block }"
"h1 { font-size: 32px; font-family: \"term\" }"
"b { font-weight: bold; color: #f00 }"
".wordy { position: absolute; width: 280px; left: 10px; right: 10px; font-size: 16px; color: #7a7b7c }"
".cat { display: list-item; }"
"</style>"
"<h1>Heading</h1>\n"
"<div class=\"wordy cat\">"
"A bunch of normal <b> and bold</b>text in a div"
"</div>"
"hello</body></html>"
};
static unsigned int m, step;
static int
dump_atr(lws_dll2_t *d, void *user)
{
lhp_atr_t *atr = lws_container_of(d, lhp_atr_t, list);
const char *p = (const char *)&atr[1];
printf("{ \"%.*s\", \"%.*s\" }, ",
(int)atr->name_len, p, (int)atr->value_len, p + atr->name_len + 1);
return 0;
}
#if 0
static int
dump_css_atr(lws_dll2_t *d, void *user)
{
lcsp_atr_ptr_t *pa = lws_container_of(d, lcsp_atr_ptr_t, list);
lcsp_atr_t *a = pa->atr;
if (a->unit == LCSP_UNIT_RGBA)
lwsl_notice("css attr: color 0x%08x\n", a->u.rgba);
else
lwsl_notice("css attr: %d %u.%u %u\n", a->propval, a->u.i.whole, a->u.i.frac, a->unit);
return 0;
}
#endif
static lws_stateful_ret_t
test_cb(lhp_ctx_t *ctx, char reason)
{
lhp_pstack_t *ps = lws_container_of(ctx->stack.tail, lhp_pstack_t, list);
const lcsp_atr_t *a;
printf("{ %s, %u, \"%.*s\", %u, { ", cb_reasons[(unsigned int)reason], ctx->npos, ctx->npos, ctx->buf, ps->atr.count);
if (reason == LHPCB_ELEMENT_START || reason == LHPCB_ELEMENT_END) {
lws_dll2_foreach_safe(&ps->atr, NULL, dump_atr);
a = lws_css_cascade_get_prop_atr(ctx, LCSP_PROP_DISPLAY);
if (a)
lwsl_notice("display: %d\n", a->propval);
a = lws_css_cascade_get_prop_atr(ctx, LCSP_PROP_COLOR);
if (a)
lwsl_notice("color: %d 0x%08X\n", a->propval, a->u.rgba);
//lwsl_notice("color: active_stz %d, atr %d\n", ctx->active_stanzas.count, ctx->active_atr.count);
//lws_dll2_foreach_safe(&ctx->active_atr, NULL, dump_css_atr);
a = ps->css_position;
if (a)
lwsl_notice("position: %d\n", a->propval);
a = ps->css_width;
if (a)
lwsl_notice("width: %d.%u\n", a->u.i.whole, a->u.i.frac);
a = ps->css_height;
if (a)
lwsl_notice("height: %d.%u\n", a->u.i.whole, a->u.i.frac);
a = ps->css_pos[CCPAS_TOP];
if (a)
lwsl_notice("top: %d.%u\n", a->u.i.whole, a->u.i.frac);
a = ps->css_pos[CCPAS_RIGHT];
if (a)
lwsl_notice("right: %d.%u\n", a->u.i.whole, a->u.i.frac);
a = ps->css_pos[CCPAS_BOTTOM];
if (a)
lwsl_notice("bottom: %d.%u\n", a->u.i.whole, a->u.i.frac);
a = ps->css_pos[CCPAS_LEFT];
if (a)
lwsl_notice("left: %d.%u\n", a->u.i.whole, a->u.i.frac);
a = ps->css_margin[CCPAS_TOP];
if (a)
lwsl_notice("margin top: %d.%u\n", a->u.i.whole, a->u.i.frac);
a = ps->css_margin[CCPAS_RIGHT];
if (a)
lwsl_notice("margin right: %d.%u\n", a->u.i.whole, a->u.i.frac);
a = ps->css_margin[CCPAS_BOTTOM];
if (a)
lwsl_notice("margin bottom: %d.%u\n", a->u.i.whole, a->u.i.frac);
a = ps->css_margin[CCPAS_LEFT];
if (a)
lwsl_notice("margin left: %d.%u\n", a->u.i.whole, a->u.i.frac);
a = ps->css_padding[CCPAS_TOP];
if (a)
lwsl_notice("padding top: %d.%u\n", a->u.i.whole, a->u.i.frac);
a = ps->css_padding[CCPAS_RIGHT];
if (a)
lwsl_notice("padding right: %d.%u\n", a->u.i.whole, a->u.i.frac);
a = ps->css_padding[CCPAS_BOTTOM];
if (a)
lwsl_notice("padding bottom: %d.%u\n", a->u.i.whole, a->u.i.frac);
a = ps->css_padding[CCPAS_LEFT];
if (a)
lwsl_notice("padding left: %d.%u\n", a->u.i.whole, a->u.i.frac);
a = lws_css_cascade_get_prop_atr(ctx, LCSP_PROP_FONT_SIZE);
if (a)
lwsl_notice("font-size: %d.%u\n", a->u.i.whole, a->u.i.frac);
a = lws_css_cascade_get_prop_atr(ctx, LCSP_PROP_FONT_FAMILY);
if (a)
lwsl_notice("font-family: %s\n", (const char *)&a[1]);
}
printf(" },\n");
#if 0
if (m < LWS_ARRAY_SIZE(rpkg)) {
if (step < rpkg[m].len) {
// lwsl_notice("test %d, step %d\n", m, step);
if (reason != rpkg[m].r[step].reason) {
lwsl_err("%s: reason mismatch %d vs %d\n", __func__, reason, rpkg[m].r[step].reason);
return -1;
}
if (ctx->ipos != rpkg[m].r[step].ipos) {
lwsl_err("%s: ipos mismatch %d vs %d\n", __func__, ctx->ipos, rpkg[m].r[step].ipos);
return -1;
}
if (ctx->ipos && memcmp(ctx->i, rpkg[m].r[step].indexes, ctx->ipos)) {
lwsl_err("%s: indexes mismatch\n", __func__);
lwsl_hexdump_err(ctx->i, ctx->ipos);
lwsl_hexdump_err(rpkg[m].r[step].indexes, ctx->ipos);
return -1;
}
if (ctx->path_match != rpkg[m].r[step].path_match) {
lwsl_err("%s: path_match mismatch %d vs %d\n", __func__, ctx->path_match, rpkg[m].r[step].path_match);
return -1;
}
if (strcmp(ctx->path, rpkg[m].r[step].path)) {
lwsl_err("%s: path mismatch '%s' vs '%s'\n", __func__, ctx->path, rpkg[m].r[step].path);
return -1;
}
if (strcmp(ctx->buf, rpkg[m].r[step].buf)) {
lwsl_err("%s: buf mismatch '%s' vs '%s'\n", __func__, ctx->buf, rpkg[m].r[step].buf);
return -1;
}
} else {
lwsl_err("%s: extra steps\n", __func__);
return -1;
}
step++;
}
#endif
return 0;
}
static const lws_surface_info_t ic = {
.wh_px = { { 600,0 }, { 448,0 } },
.wh_mm = { { 114,5000000 }, { 82,5000000 } },
};
static lws_displaylist_t displaylist;
int
main(int argc, const char **argv)
{
int e = 0, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE;
lws_stateful_ret_t n;
lws_dl_rend_t drt;
lhp_ctx_t ctx;
const char *p;
memset(&ctx, 0, sizeof(ctx));
if ((p = lws_cmdline_option(argc, argv, "-d")))
logs = atoi(p);
lws_set_log_level(logs, NULL);
lwsl_user("LWS API selftest: lhp HTML5 parser\n");
for (m = 0; m < (int)LWS_ARRAY_SIZE(html_tests); m++) {
const uint8_t *data;
size_t size;
lwsl_user("%s: ++++++++++++++++ test %d\n", __func__, m + 1);
step = 0;
drt.dl = &displaylist;
drt.w = ic.wh_px[0].whole;
drt.h = ic.wh_px[1].whole;
if (lws_lhp_construct(&ctx, test_cb, &drt, &ic)) {
e++;
continue;
}
ctx.flags = LHP_FLAG_DOCUMENT_END;
ctx.base_url = strdup("");
data = (uint8_t *)html_tests[m];
size = strlen(html_tests[m]);
lwsl_hexdump_info(data, size);
n = lws_lhp_parse(&ctx, &data, &size);
lwsl_notice("n = %d\n", (int)n);
if (n & LWS_SRET_FATAL)
e = 1;
lws_lhp_destruct(&ctx);
}
if (e)
goto bail;
lwsl_user("Completed: PASS\n");
return 0;
bail:
lwsl_user("Completed: FAIL\n");
return 1;
}