Skip to content

Rendering

Generated from crates/niso-core/include/rendering.h when the site was built at 7aa23e2.

This is the rendering section of niso_runtime.h, owned by niso-core and kept in crates/niso-core/include/rendering.h. The build composes it with the other sections in the order crates/niso-compose/sections.txt gives.

Functions: niso_render_frame, niso_render_version, niso_render_surfaces, niso_render_template, niso_dom_tap, niso_dom_activate, niso_dom_scroll, niso_dom_pointer, niso_dom_touch_action, niso_dom_wheel, niso_dom_key, niso_dom_set_control_size, niso_dom_set_control_font_size, niso_dom_set_field_style, niso_dom_set_button_style, niso_dom_set_button_dark_colors, niso_dom_text_edit, niso_dom_text_input, niso_dom_text_select, niso_dom_text_commit, niso_dom_focus_input, niso_dom_control_input, niso_render_reset, niso_render_font, niso_render_image

The render stream.

niso_render_frame runs the rendering update (scroll events, then animation frame callbacks, then style and layout) and returns what changed in the rendered box tree since the previous call, as a byte stream valid until the next call into the DOM. The first frame, and the first after niso_render_reset, describes the whole tree. Hosts keep one view per render node and apply the ops in order.

Values are little-endian. Geometry is in CSS pixels; colors are non-premultiplied sRGB packed as 0xRRGGBBAA. Render node ids are the DOM handles of the elements (or anonymous boxes) that generate the boxes.

1 FONT u32 font, u32 collection index
A font later TEXT ops use. niso_render_font returns its file
bytes, valid for the life of the DOM. Hosts may call it while
reading the frame: it leaves the frame's bytes alone.
2 CREATE u64 node
3 DESTROY u64 node
4 CHILDREN u64 node, u32 n, n x u64 child
The node's children in paint order (back to front).
5 FRAME u64 node, f32 x, y, width, height
The border box, relative to the parent's border box origin.
6 BOX u64 node, u32 background, f32 opacity,
f32 transform a b c d e f (about the border box origin),
u8 overflow: 0 visible; 1 clip (text and children are
clipped to the padding box); 2 scroll (clipped, and the host
scrolls the children in a native scroll view, see SCROLL),
f32 border widths top right bottom left,
u32 border colors top right bottom left,
u8 border styles top right bottom left (0 solid, 1 dashed,
2 dotted, 3 double, 4 two halves: groove and ridge),
f32 corner radii top-left x y, top-right x y,
bottom-right x y, bottom-left x y,
u32 inner border colors top right bottom left
A double border is two lines a third of the width each, at
its edges. A side in two halves is drawn solid in its color
from the outer edge to the middle of its width, and in its
inner color from there in. Inset and outset borders come as
solid ones; their colors and groove's and ridge's are shaded
already, darker or lighter by side as Chrome shades them. A
side's inner color is its color unless it is in two halves.
7 TEXT u64 node, u32 runs, then per run:
u32 font, f32 size, u32 color, f32 synthetic skew degrees,
u8 synthetic bold, u8 n, n x (u32 axis tag, f32 value),
u32 glyphs, glyphs x (u32 glyph id, f32 x, f32 baseline y),
u8 flags (1 right to left), f32 baseline y, f32 advance,
u32 n, n x u16 text, u32 pieces, pieces x (u32 start, u32 end,
f32 x)
Glyph positions are relative to the node's border box origin.
The text is what the run was shaped in (UTF-16): its shaped
run on the line, so hosts that draw text instead of glyphs
(Android's drawTextRun, which gets outline text) shape it the
same way, kerning across a color change included. The pieces
are ranges of it to draw, each from pen x on the baseline:
the glyph run's own text, or one piece per cluster where
letter-spacing moved clusters apart and per word (spaces
left out) where justification or word-spacing moved words.
A run whose text is unknown has none. The advance is the
glyph run's width, which a host's own shaping of a single
piece has to match for its positions to be the core's.
Then u32 lines, per line: u8 style, u8 flags, u32 color,
f32 x, y, width, thickness, f32 a, b, u32 gaps,
gaps x (f32 from x, to x)
The text decorations (underlines, overlines and
line-throughs), each a line from x to x + width whose middle
is at y, positioned like the glyphs. Styles: 0 solid; 1
dashed, dashes a long with gaps b between them, starting
with a dash at x; 2 dotted, round dots as wide as the line
every b, the first centered half a thickness in from x,
none past the end; 3 wavy, a stroke as thick as the line
that leaves y downward, swinging a below and above it, in
quadratic curves each b long (controls at 2a). A double line
comes as two solid ones. Flag bit 0: the line paints over
the text (a line-through), else under it, after the box's
own background and before its glyphs. The gaps are ranges
of x the line leaves out, where it would cross glyphs' ink
(text-decoration-skip-ink, always auto: stylo parses the
property only for Gecko); they may overlap.
8 ROOT u64 node, u32 canvas color, u8 flags, u8 scroll axes,
f32 scroll width, height, f32 scroll x, y,
u32 n, n x u64 fixed node
The root of the tree (0 when nothing renders), the color the
host fills the whole viewport with behind it (the canvas
background: the root element's, or the body's, transparent
when neither has one, over the host's base color, white as
browsers paint it unless the embedder made the host's view
transparent, like an overlay's), the flags, and the
viewport's scrolling area and offset: the host scrolls the
root natively. Fixed nodes are boxes fixed to the viewport,
in paint order, painted above the root without scrolling;
their frames are relative to the root's border box origin.
Flag bit 0, cover: the page's viewport meta asks for
viewport-fit=cover. The host then makes the viewport the
whole window, under the status bar, cutouts and the home
indicator or navigation bar, and gives the page the safe
area as --niso-safe-area-inset-top, -right, -bottom and
-left in its platform style (niso_dom_set_platform_style),
since env(safe-area-inset-*) is 0 in stylo. Otherwise the
viewport is the safe area and the host leaves them unset.
The viewport units' variants, once stylo resolves them (it
gives sv*, lv* and dv* the viewport size for now): all the
viewport without cover; with it, large is the whole window,
small the window less the system bars that can appear, and
dynamic whichever is on screen, differing from large only
while bars are transient (Android's immersive mode). The
keyboard changes none of them, as in browsers by default.
9 SCROLL u64 node, u8 scroll axes, f32 scroll width, height,
f32 scroll x, y
A scroll container's scrolling area, measured from its
padding box origin, and the offset to show. Scroll axes are
those the user may scroll (bit 0 x, bit 1 y, from overflow
auto or scroll); script may scroll either, so hosts show the
offset on both but let gestures move only these. Children's frames
are unscrolled: the native scroll view moves them. Sent when
it changes, including when script scrolls.

10 CONTROL u64 node, u8 kind, u8 state, f32 x, y, width, height,

f64 value, min, max, step; for kind 5, u32 n, n x (u8 flags,
u32 n, n x u8 label (UTF-8))
The node is a form control the host draws natively in the
given content box, relative to the border box origin (the
UA stylesheet sizes it as niso_dom_set_control_size says,
so the control fits): kind 1 a
switch (input type=checkbox switch), 2 a slider (input
type=range), 3 a linear progress bar (progress), 4 a push
button. Unlike the others a button keeps its TEXT and
children, which the host shows as the button's label, in
the content box. The button fills the border box, and its
BOX's fill, border and radii are the native button's own,
which the host draws it with instead of drawing the box: the
stock style when they are the stock button's (see
niso_dom_set_button_style), else a filled one, or a
borderless one without a fill; 5 a radio group: a fieldset
or role=radiogroup element whose radio buttons (two or more,
outside groups nested in it) share a name and that has no
other form controls. It is one native control with an
option per radio button, labeled with the text of the label
around or for it (else its value), flag 1 when that radio
button is disabled: a row of up to 5 options of up to 15
characters (the element is a flex container in a row) is a
segmented control (iOS's segmented Picker, Android's
connected button group), any other group a list (state
bit 16: a menu Picker on iOS, a column of radio button rows
on Android). Its box takes the control's height, see
niso_dom_set_control_size, and loses its padding and border,
so the control fills it; page styles on the radio buttons
and labels never show. A group that holds more than its
radio buttons and their labels, such as a legend or a hint,
keeps it (state bit 32): the control takes only their area,
its children from the first holding a radio button or a
label for one to the last, which becomes one box of the
control's height as wide as the group's content (sharing a
row with what is beside it), and the group's box, its TEXT
and its other children are drawn as any others, its fill no
tint. Kind 0 when
it stops being one (appearance: none, or a type change).
State bits: 1 checked, 2 disabled, 4 indeterminate, 8 dark
(the element's used color scheme is dark, as its
color-scheme and niso_dom_set_color_scheme pick it: the host
draws the control in its dark appearance), 16 a list, 32 a
radio group's control that takes only part of its box. The
value is a switch's checkedness (0 or 1), a slider's value
between min and max in multiples of step from min (step 0:
any), a progress bar's value between 0 and max, a radio
group's selected option between 0 and max (n - 1), or -1
for none. A control other than a button or a radio group
with state bit 32 has no TEXT or children. Sent when any of
it changes.

11 TEXT_FIELD u64 node, u8 kind, u8 state, u8 keyboard,

f32 x, y, width, height, f32 font size, u16 weight,
u8 italic, u8 align, u32 color, u32 caret color,
u32 n, n x u8 value, u32 n, n x u8 placeholder (UTF-8),
u32 selection start, end, u8 direction
The node is a text field the host edits natively: unless
plain, its own stock field filling the border box (and
drawn instead of the node's BOX), else a bare editor in the
given content box. Kind 1 single-line (input types text,
search, email, url, tel, password, number), 2 multi-line
(textarea); 0 when it stops being one, with nothing after
the keyboard byte. State bits: 1 disabled, 2 read-only,
4 secure (password), 8 plain (the page gave it a box other
than the stock field's, see niso_dom_set_field_style, which
the host draws as the BOX says around a bare editor), 16
focused, 32 caret (the page set caret-color: the caret is
the caret color, even transparent), 64 dark (as for
CONTROL). Keyboard: 0 text, 1 email,
2 url, 3 phone, 4 decimal, 5 search. The text uses the
system font at this size (CSS px), weight (100-900), slant,
alignment (0 start, 1 center, 2 end) and color, with a
caret of the caret color when the caret bit is set, else
the platform's. The value is
what the field shows: the host replaces its text with it
only when it differs from what the host last reported. The
selection is the field's, in UTF-16 code units of the value
(start <= end, equal for a caret; direction 0 none, 1
forward, 2 backward): the host selects it when it differs
from what the host last reported, so script's select(),
setSelectionRange() and value changes (which put the caret
at the end) reach the native field. An edit the page
refuses (a beforeinput listener canceled it) leaves the
field as it was, and the next frame sends this op again,
though nothing in it changed: the host shows the value and
selection it sends, putting its native field back. Hosts report the
user's selection with niso_dom_text_edit and
niso_dom_text_select. (The selection came after the other
fields: hosts written before it must now read its 9
bytes.) A text field has no TEXT or
children. Sent when any of it changes.

12 IMAGE u64 node, u32 image, f32 x, y, width, height,

f32 clip x, y, width, height, u8 flags,
u32 n, n x (u32 x, y, width, height)
The node's picture (an img element's decoded image, or a
canvas's bitmap), drawn scaled into the rect, which
object-fit and object-position place, clipped to the clip
rect (the content box), both relative to the border box
origin, above the BOX and below the TEXT and children.
niso_render_image returns the image's pixels; the same image
keeps its id. Image u32 max when the node stops having one.
Flags: 1 opaque (ignore the pixels' alpha: a canvas whose
context has no alpha), 2 pixelated (image-rendering:
pixelated or crisp-edges: scale with nearest-neighbor
filtering, else smoothly), 4 live (a canvas's bitmap: its
pixels change and its size may, so hosts update their copy
rather than keep it). The rectangles are a live image's
pixels that changed since the node's previous IMAGE, all of
them when the host hasn't had the image at this size; the
host copies those from niso_render_image. None for other
images. Sent when any of it changes, including a live
image's pixels. A canvas no script has drawn into has no
image (it is transparent), and its fallback content does
not render.

13 MEDIA u64 node, u32 load, u32 n, n x u8 source URL (UTF-8),

u8 flags, f32 volume, f32 playback rate, f64 seek
An audio element's player, which the host keeps whether or
not the element renders. It plays the source when flag 1 is
set and pauses otherwise; flag 2 mutes it and flag 4 loops
it. A new load number means load the source again from the
start. Seek is a position in seconds to jump to once, NaN
for none. An empty source means stop and drop the player.
Hosts report the player with niso_dom_media_update. Sent
when any of it changes.

14 NOW_PLAYING u32 n, n x u8 title, then artist, album and artwork URL

the same way, u8 playback state (0 none, 1 paused,
2 playing), u32 actions, f64 duration, f64 position,
f64 rate
What the platform shows as now playing (lock screen,
notification, control center) and the controls it offers:
bit n of actions is niso_dom_media_session_action's action
n. Sent when any of it changes. duration, position and rate
are the page's position state (setPositionState), NaN
without one: seconds, the duration infinite for a live
stream, the position as of this frame, from which hosts
advance it at the rate while the state is playing. Without
a seekto action (bit 7) hosts offer no seeking.

15 EFFECTS u64 node, u32 n, n x shadow (u32 color, f32 x, y, blur,

spread, u8 inset), u32 n, n x filter (u8 kind, f32 amount),
u32 n, n x backdrop filter (the same)
The box's box-shadow (in paint order, first on top, with
the blur radius as CSS gives it, twice the Gaussian's
standard deviation), filter and backdrop-filter. Filter
kinds: 1 blur (the standard deviation, CSS pixels),
2 brightness, 3 contrast, 4 grayscale, 5 hue-rotate
(degrees), 6 invert, 7 opacity, 8 saturate, 9 sepia, each
with its CSS amount (1 is 100%) and applied in order;
drop-shadow() and url() are left out. A filter applies to
the box with its text and children, as one layer; a backdrop
filter to what is behind the box, inside its border box's
curve. Sent when any of it changes; all three empty when the
box stops having them. Boxes that never have any get none.

Versions: ops carry no length, so a host reads every op and field of the stream. Ops and their fields only grow at the end: a new op takes the next number and a new field goes after an op’s last, and niso_render_version goes up with each, so a host updates by reading the additions. A host checks niso_render_version() against the NISO_RENDER_VERSION it was written for and reads no other version’s stream.

Hosts report the user’s scrolling with niso_dom_scroll; the next niso_render_frame fires the scroll events. They report the user’s changes to native controls with niso_dom_control_input and niso_dom_text_edit, and show the state the next CONTROL or TEXT_FIELD op sends, which script may have changed or refused. Native views that take focus report it with niso_dom_focus_input; script’s focus comes back in the focused bit.

int32_t niso_render_frame(niso_dom* dom, niso_bytes* out);

The version of the render stream niso_render_frame writes: the ops as this header describes them are NISO_RENDER_VERSION.

#define NISO_RENDER_VERSION 2
uint32_t niso_render_version(void);

Surfaces and templates.

Template targets (home screen widgets, Live Activities and the Dynamic Island, ongoing notifications; later CarPlay and Android Auto) take only native views and lay text out themselves, often in another process that runs no niso. A page renders content for them as surfaces: elements with a data-niso-surface attribute naming the surface, like

<div data-niso-surface="widget.small" style="width: 170px; height: 170px">

A surface is laid out with the page, at the size its CSS gives it, but is never part of the render stream and takes no taps (the UA stylesheet makes it position: fixed at the viewport’s top left, pointer-events: none). The names hosts know:

widget.small, widget.medium home screen widgets, by size class
activity iOS Live Activity on the Lock Screen and
the expanded Dynamic Island; Android's
ongoing notification. Present = running.
island.leading, island.trailing, island.minimal
the compact and minimal Dynamic Island

niso_render_surfaces lays the document out and returns the surfaces whose template changed since the previous call, and those that went away (removed, display: none or renamed), valid until the next call into the DOM; the first call after niso_render_reset returns every surface. Hosts call it after each niso_render_frame. It is a list of

name_len:u32 name:utf8[name_len] len:u32 template:u8[len]

sorted by name, where len 0 means the surface is gone. When two surfaces share a name the first in tree order wins.

niso_render_template returns the template of any element, as the document lays it out, in a buffer valid until the next call into the DOM: a server rendering content for push updates calls it on a headless DOM.

A template is the surface’s box tree as the render stream would describe it, reduced to what native template views take: boxes, text as strings, timers, progress bars and images. Little-endian, lengths in CSS pixels (points on iOS, dp on Android), colors non-premultiplied sRGB 0xRRGGBBAA:

template := 'N' 'T' version:u8 width:f32 height:f32 node
node := kind:u8 x:f32 y:f32 w:f32 h:f32 len:u16 body:u8[len]
count:u8 node[count]
body := flags:u8 [background:u32] flags & 1
[border_width:f32 border_color:u32] flags & 2 (the top edge's)
[radius:f32] flags & 4 (top left)
[opacity:f32] flags & 8
kind's fields flags & 16: clips children

The root node is the surface, at (0, 0) and the template’s size. A node’s frame is its border box in its parent’s; children are in paint order. Boxes that draw nothing are flattened away, their children moved to their parent, so a template stays shallow. Kinds and their fields:

1 BOX none
2 TEXT text_style string: an element's inline content, as one string
in the style of its first run (whitespace collapsed, inline
boxes dropped). A box with text may also have children.
3 TIMER text_style start:f64 end:f64: a <time> element with a
data-timer-start (counting up from) or data-timer-end
(counting down to) attribute, in ms since the Unix epoch, 0
when absent. Targets show the platform's self-updating timer
(SwiftUI Text(date, style: .timer), Android Chronometer)
instead of the text the page shows, so the template does not
change while it runs. A timer must be a box of its own, like
a block or a flex item, to be one; inline, its text is part
of its paragraph's. A <time> without these attributes (a
paused timer) is TEXT.
4 PROGRESS value:f32 max:f32 tint:u32 track:u32 flags:u8 start:f64
end:f64 period:f64: a <progress> element, tint its CSS
background (0 for the platform's), track 0 for the
platform's, flags 1 when indeterminate. With data-timer-start
and data-timer-end (ms since the Unix epoch) the bar fills
from empty at start to full at end by itself; with
data-timer-start and data-timer-period (ms) it fills every
period from start, then starts over; its value is then 0,
for the target to compute. Times are 0 when absent.
5 IMAGE string: an <img>'s src, which targets resolve to an image
they bundle or have stored.
6 BUTTON node:u64 flags:u8: a <button> (flags 1 when disabled), its
own box its only child at (0, 0). Targets that can act on
it (Android's ongoing notification, as its actions) show
its text and report the user's action with
niso_dom_activate(node), node meaning something only to the
DOM that rendered it; targets that cannot leave it out (the
iOS widgets and Live Activity, which run no page), and
decoders older than it draw it as a box.
text_style := x:f32 y:f32 w:f32 h:f32 size:f32 weight:u16 color:u32
align:u8 font:u8 lines:u8
string := len:u16 utf8[len]

text_style’s rect is where the text’s lines sit in the node: the content box’s left and width, the lines’ top and height. size is the font size, weight 1-1000, align 0 start, 1 center, 2 end; font’s low two bits are the design (0 the system font, 1 monospaced: monospace or ui-monospace, 2 rounded: ui-rounded, 3 serif: serif or ui-serif), 4 italic, 8 tabular figures (font-variant-numeric: tabular-nums). The rect and lines are as the core laid the text out at the user’s text size (niso_dom_set_text_scale), but size is the CSS font size: targets draw it at the platform’s own text scale (sp, Dynamic Type), which matches the core’s when the template is shown at the text size it was made at. lines is how many lines the rect holds: all of them, or those that fit whole in a box that clips its overflow (overflow other than visible, as with a max-height). Targets keep to that many and end text that still does not fit with an ellipsis, never shrinking or cutting it.

Compatibility: an app and its widget extension update separately, and a template from a newer app (or a server) can reach an older extension. A decoder rejects a template whose magic or version differs from its own, and shows nothing rather than guessing. Within a version, newer cores only append fields to a body and add kinds; decoders read the fields they know, skip the rest of the body by its length, and draw a node of an unknown kind as a BOX (its children included). A change that cannot be expressed that way is a new version. One change within version 1 could not be: text lines used to be every line the core laid out, which targets kept to by shrinking the text, and are now those a clipping box holds, which targets keep to with an ellipsis. A target from before it shrinks a newer template’s clipped text to fit instead of ending it with an ellipsis.

int32_t niso_render_surfaces(niso_dom* dom, niso_bytes* out);
int32_t niso_render_template(niso_dom* dom, niso_handle element, niso_bytes* out);

A user’s tap at viewport point (x, y) in CSS pixels: fires trusted mousedown, mouseup and click (with its activation behavior) at the topmost element there, which *target receives (0 outside the viewport). A native control’s content box gets no events, a button’s aside: the control handles its own touches, reported with niso_dom_control_input. Taps on a button are taps like any other, so they reach what its label holds.

int32_t niso_dom_tap(niso_dom* dom, float x, float y, niso_handle* target);

The user acted on a template’s BUTTON outside the page (a notification’s action): element is its node. Fires the trusted click a tap on it would, with its activation behavior; none if it is disabled.

int32_t niso_dom_activate(niso_dom* dom, niso_handle element);

The user scrolled a native scroll view: node is the scroll container’s render node (0 for the viewport), scrolled to (x, y) CSS pixels.

int32_t niso_dom_scroll(niso_dom* dom, niso_handle node, float x, float y);

Pointer input for pages that handle their own (a canvas a page draws and drives, like a remote desktop, and touches where touch-action is none, see niso_dom_touch_action). kind: NISO_POINTER_DOWN, MOVE, UP or CANCEL; id: the pointer, stable from down to up (a finger), and 1 for the mouse, which is always active, as in Chrome (setPointerCapture(1) never throws NotFoundError); type: NISO_POINTER_MOUSE, TOUCH or PEN; primary: the first finger down or the mouse; (x, y): the viewport point in CSS pixels; button: the button that changed (0 main, 1 middle, 2 secondary; -1 for moves); buttons: the pressed buttons as MouseEvent.buttons bits; pressure 0-1 (touch and pen); modifiers: NISO_MOD_* bits. Fires the Pointer Events (over, enter, down, move, up, cancel, out, leave) at the element there or the one capturing the pointer (touches are captured by what they land on), with the compatibility mouse events and click for the primary mouse, and a tap’s mouse events and click when a primary touch or pen lifts. *not_canceled is false if a listener canceled the pointer event: a host that would also scroll or zoom for the gesture should not. Hosts that report pointers this way report no niso_dom_tap for the same touches.

enum {
NISO_POINTER_DOWN = 0,
NISO_POINTER_MOVE = 1,
NISO_POINTER_UP = 2,
NISO_POINTER_CANCEL = 3,
NISO_POINTER_MOUSE = 0,
NISO_POINTER_TOUCH = 1,
NISO_POINTER_PEN = 2,
NISO_MOD_SHIFT = 1,
NISO_MOD_CTRL = 2,
NISO_MOD_ALT = 4,
NISO_MOD_META = 8,
};
int32_t niso_dom_pointer(niso_dom* dom, uint8_t kind, int32_t id, uint8_t type, uint8_t primary, float x, float y,
int16_t button, uint16_t buttons, float pressure, uint32_t modifiers, bool* not_canceled);

The native gestures a touch starting at viewport point (x, y) may begin, as NISO_TOUCH_* bits in *actions: what touch-action (Pointer Events) allows there, for the element at the point and its ancestors (a scroll container allows panning what it holds again, as in Chromium). Hosts ask when a touch goes down. Where it is 0 (touch-action: none) the page handles the touch itself: the host starts no scrolling, zooming or tap of its own for it and reports it with niso_dom_pointer, which also gives the tap’s click when it lifts.

enum {
NISO_TOUCH_PAN_X = 1,
NISO_TOUCH_PAN_Y = 2,
NISO_TOUCH_PINCH_ZOOM = 4,
NISO_TOUCH_DOUBLE_TAP_ZOOM = 8,
};
int32_t niso_dom_touch_action(niso_dom* dom, float x, float y, uint8_t* actions);

A mouse wheel or trackpad scroll at viewport point (x, y): a WheelEvent there with the deltas in delta_mode units (0 pixels, 1 lines, 2 pages). *not_canceled is false if a listener canceled it: don’t scroll.

int32_t niso_dom_wheel(niso_dom* dom, float x, float y, double delta_x, double delta_y, double delta_z,
uint32_t delta_mode, uint32_t modifiers, bool* not_canceled);

A hardware key pressed (down 1) or released (0): keydown or keyup at the focused element, or the body, with UI Events key and code values (UTF-8) and a KeyboardEvent location, then what the key does in the page: a key that types a character (or Enter) fires keypress; Tab moves focus to the next (with shift, the previous) element in sequential focus order; Enter clicks a focused link, button or details summary, or submits a focused input’s form implicitly; Space released where it was pressed clicks a focused button, checkbox, radio button or summary.

*host_default is false if a listener canceled the key or the page did something with it: the host must not do its own default for it. True means the host does what it would for the key:

  • A native text field keeps typing natively: the host inserts the
character and reports the edit with niso_dom_text_input, and commits a
single line on Enter with niso_dom_text_commit. False here means the
page canceled the keydown or keypress, so nothing is typed.
  • Focus is the DOM’s: when Tab, a click or a script moves it, the host
makes the focused element's native view first responder (or resigns it
when nothing has focus), rather than moving native focus itself. Tab
with nothing after (or, with shift, before) the focused element is true:
the host decides whether to wrap round or leave the page.
  • Space on a checkbox or radio button drawn as a native switch toggles it
in the page (false here), so the host must not toggle the switch too;
the next CONTROL op shows the new state.
int32_t niso_dom_key(niso_dom* dom, uint8_t down, niso_str key, niso_str code, uint32_t location, uint8_t repeat,
uint32_t modifiers, bool* host_default);

The size in CSS pixels of the host’s native control of a CONTROL op kind, which the UA stylesheet gives those elements (until set: 51x31 switches, 160x31 sliders, 160x4 progress bars). Sliders and progress bars stretch, so their width is only a default. Kinds 6, 7 and 8 give radio groups, which stretch too, their height when the page leaves it auto (and the area of their radio buttons theirs, see CONTROL): 6 a segmented control’s (until set: 32), and a list’s, 8 its own part (a menu’s button, until set: 34) plus 7 a row per option (until set: 0). Hosts measure their stock controls and set each before running script.

int32_t niso_dom_set_control_size(niso_dom* dom, uint8_t kind, float width, float height);

The host’s text fields’ font size in CSS pixels (until set: 17), which text fields get from the UA stylesheet. Kinds 4 (single-line) and 5 (multi-line) of niso_dom_set_control_size size text fields (until set: 200x34 and 200x80).

int32_t niso_dom_set_control_font_size(niso_dom* dom, float size);

The host’s stock text field’s box, in CSS pixels: the inset of its text from its sides (and a multi-line field’s from its top), its fill, border width and color and corner radius, colors 0xRRGGBBAA. The UA stylesheet gives text fields this box, so an unstyled field is the stock one: a field whose box differs is plain (see TEXT_FIELD). Until set: 7, 0xffffffff, 1/3, 0x00000033, 6.

int32_t niso_dom_set_field_style(niso_dom* dom, float padding, uint32_t background, float border_width,
uint32_t border_color, float radius);

The host’s stock push button, in CSS pixels: the padding beside its label, its height with one line of label, the label’s line height, font size, weight (100-900) and 0xRRGGBBAA colors (enabled and disabled), and the button’s fill (enabled and disabled) and corner radius, at least half its height for a capsule. The UA stylesheet gives these to

The stock push button’s label colors and fills (enabled and disabled) where a button’s used color scheme is dark, which the UA stylesheet gives with light-dark(). niso_dom_set_button_style sets them to its light ones; call this after it.

int32_t niso_dom_set_button_dark_colors(niso_dom* dom, uint32_t color, uint32_t disabled_color,
uint32_t background, uint32_t disabled_background);

The user edited the text field drawn for node to the UTF-8 value, leaving the selection from start to end (UTF-16 code units of the value, start <= end, equal for a caret) with direction 0 none, 1 forward or 2 backward: the field takes the value and the selection, then input fires, so input listeners see where the caret is. Disabled fields ignore it. A read-only field, or an unchanged value, takes only the selection, as niso_dom_text_select.

int32_t niso_dom_text_edit(niso_dom* dom, niso_handle node, const char* value, size_t len, uint32_t start,
uint32_t end, uint8_t direction);

niso_dom_text_edit for a host that can’t read its field’s selection: the caret goes to the end of the value. Read-only fields ignore it.

int32_t niso_dom_text_input(niso_dom* dom, niso_handle node, const char* value, size_t len);

The user moved the selection of the text field drawn for node without editing it, as niso_dom_text_edit gives it. select fires when it selects text. Disabled fields ignore it.

int32_t niso_dom_text_select(niso_dom* dom, niso_handle node, uint32_t start, uint32_t end, uint8_t direction);

The user committed the text field (return on a single line): change fires if input did since the last change. Losing focus does the same.

int32_t niso_dom_text_commit(niso_dom* dom, niso_handle node);

The native view drawn for node gained (focused 1) or lost (0) focus: the document’s focus follows, with blur, focusout, focus and focusin.

int32_t niso_dom_focus_input(niso_dom* dom, niso_handle node, uint8_t focused);

The user changed the native control drawn for node to value: a button pressed (any value) is a tap in its middle (mousedown, mouseup, focus, click); a radio group to option value checks that option’s radio button as a click on it does (input and change fire), unless it is disabled. A host whose page taps (niso_dom_tap) also see touches on its button lets those be the press, and reports only presses no tap saw, such as accessibility activations; a switch to 0 or 1 (a trusted click toggles it, with input and change events, unless a listener cancels the click), a slider to a value it sanitizes and fires input for. done means the user let go of the slider: change fires if input did since the last change. Ignored for disabled controls.

int32_t niso_dom_control_input(niso_dom* dom, niso_handle node, double value, uint8_t done);
int32_t niso_render_reset(niso_dom* dom);
int32_t niso_render_font(niso_dom* dom, uint32_t font, niso_bytes* data, uint32_t* index);

An IMAGE op’s image: *width x *height pixels as non-premultiplied RGBA8, row by row, valid for the life of the DOM; a live image’s (flag 4) are its pixels now, valid until the next call into the DOM other than this and niso_render_font. Hosts may call it while reading the frame.

int32_t niso_render_image(niso_dom* dom, uint32_t image, niso_bytes* pixels, uint32_t* width, uint32_t* height);