webview: vh units, long child selectors, img height %, grid fr fit
Build C-- programs / Build (LANG_RUS) (push) Failing after 41s
Build C-- programs / Build (LANG_ENG) (push) Failing after 43s
Build C-- programs / Build (LANG_SPA) (push) Failing after 32s

- css: support vh (window height, restyle on height change)
- css_match: selectors of up to 16 parts; "a > b > c > d > e" matched nothing
- img "height: 100%; width: auto" in a box of definite height
- grid: fr columns narrower than their items' width wrap into fewer columns
  (theguardian.com carousel), virtualbox.org header fixed
- engine, http and start/error page updates, tests/gridfit.htm

Assisted-by: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Leency committed 2026-09-28 14:46:16 +03:00
1 parent 4ec4f286a7
commit 5a7ba5c952
11 files changed
+1045 -121

No files matched your search

+9 -2
View File
@@ -173,7 +173,7 @@ dword tag_flags(dword _id)
// nodes //
//===================================================//
#define NODE_SIZE 420
#define NODE_SIZE 440
#define BODY_MARGIN 6 // UA margin of <body> (the page margin)
// tree
@@ -252,9 +252,10 @@ dword tag_flags(dword _id)
#define S_MAXH 276 // max-height in px (0 is a real value: a collapsed menu), POS_AUTO none
#define S_ORDER 280 // flex item "order", signed
#define S_GRIDN 284 // grid-template-columns: number of tracks, 0 none; < 0 "repeat(auto-fill, ...)" of tracks at least that many px wide
#define S_GRIDT 288 // the tracks, GRID_TRACKS of them: px > 0, 0 auto, < 0 fr in tenths (-10 = 1fr), GT_PCT + n = n%
#define S_GRIDT 288 // the tracks, GRID_TRACKS of them: px > 0, 0 auto, < 0 fr in tenths (-10 = 1fr), GT_PCT + n = n%, GT_CAP + px
#define GRID_TRACKS 8
#define GT_PCT 0x20000000
#define GT_CAP 0x10000000 // GT_CAP + px: "minmax(0, 96rem)", at most that wide, narrower when the row is short of room
#define N_TAG 320 // T_UNKNOWN: pool offset of the real tag name ("overflow-menu"), 0 = none
#define N_OPEN 324 // <details> the user opened (1) or closed (2); 0: as its "open" attribute says
#define S_Z 328 // z-index of a positioned box, POS_AUTO = auto
@@ -282,6 +283,11 @@ dword tag_flags(dword _id)
#define S_MRPCT 408 // margin-right in percent like S_MLPCT; past style_init[]: style_compute() clears it
#define S_AREAS 412 // grid-template-areas: its place + 1 in style.h css_areas, 0 none; cleared like S_MRPCT
#define S_GAREA 416 // grid-area: css_hash() of the name of the item's area, 0 none; cleared like S_MRPCT
#define S_OUTW 420 // outline: its width in px, 0 = none; cleared like S_MRPCT
#define S_OUTC 424 // outline-color, NO_COLOR = the text colour (currentColor)
#define S_OUTOFF 428 // outline-offset in px
#define S_GCOL 432 // grid-column-start: the line the item starts at (1, 2...), 0 = auto; cleared like S_MRPCT
#define S_BGA 436 // a translucent background: alpha << 24 + its own RGB (S_BG is it blended over the backdrop), 0 = opaque or none; cleared like S_MRPCT
#define OBJPOS_CENTER 12850 // 50% 50%, the initial value
#define GRID_ALL 1000 // S_COLSPAN of "grid-column: 1 / -1": the whole row
@@ -322,6 +328,7 @@ enum { VALIGN_TOP, VALIGN_MIDDLE, VALIGN_BOTTOM, VALIGN_SUPER, VALIGN_SUB }; //
#define SF_BUTTON 32
#define SF_SRONLY 64 // hidden for the eye only (sr-only, parked off-screen): display:none, but its words still name a link (layout.h link_label_text); not inherited
#define SF_ITALIC 128 // no italic font: paint.h shears the drawn run (cv_slant)
#define SF_VMID 256 // vertical-align: middle set on the element itself (S_VALIGN is inherited, for the cells); not inherited
#define SF_COLORSET 1024 // a stylesheet rule set the color on this very node (not inherited)
#define SF_INLBLOCK 4096 // display:inline-block (S_DISPLAY stays D_INLINE), not inherited
#define SF_CLEAR 16384 // clear: left/right/both - starts below the floats, not inherited
+334 -23
View File
@@ -10,7 +10,7 @@
// fragments //
//===================================================//
#define FRAG_SIZE 84
#define FRAG_SIZE 88
#define F_NODE 0 // node the fragment belongs to (text node for text runs)
#define F_KIND 4
#define F_X 8
@@ -34,6 +34,7 @@
#define F_P 72
#define F_IMGY 76 // FR_IMG: first row shown (a picture cut at the top by overflow)
#define F_IMGW 80 // FR_IMG: the width the picture is scaled to (width in %, max-width), 0 = as it is
#define F_AMID 84 // inside an inline-block with "vertical-align: middle": 1, it is centred in its line
enum { FR_TEXT = 1, FR_IMG, FR_BOX, FR_RECT, FR_HLINE, FR_ICON };
// F_TEXT of an FR_ICON that is a checkbox or radio button (form.h draws it)
@@ -89,6 +90,7 @@ dword FRAGS::add()
int lay_measuring; // > 0 while measuring content widths: fragments are discarded
int lay_max_x; // rightmost edge of content emitted so far
int lay_float_depth; // > 0 while a float without a width is measured (float_width)
dword lay_line_id;
bool lay_skip_width; // set by the float row: the width was resolved by the caller
dword lay_page_w;
@@ -348,7 +350,12 @@ int flt_place(int _y, _w, _lo, _hi, dword _side)
void line_wrap_edges()
{
int k;
IF (lay_measuring) RETURN;
// while measuring the lines ignore the floats (a table cell's min-content is the
// widest of them and its words, not the sum), but not in the measure of a float
// without a width: its text goes beside the floats inside it, so its width is
// theirs plus the line (korrespondent.net: "USD" floated left, the rate beside it
// in the floated .index-block went to a line of its own)
IF (lay_measuring) && (!lay_float_depth) RETURN;
IF (flt_n <= flt_base) RETURN;
k = blk_x + blk_w;
flt_room(ln_y, 1, blk_x, k);
@@ -466,6 +473,16 @@ int line_sup_room(int _sink)
RETURN need;
}
// how far down a fragment of an inline-block goes in a line of ln_h (line_close)
int atom_dy(dword _f)
{
int dy;
dy = ln_h - DSDWORD[_f + F_ATOMH];
IF (!DSDWORD[_f + F_AMID]) RETURN dy;
IF (dy < 0) RETURN dy;
RETURN dy / 2;
}
void line_close()
{
dword i, f, last, fw, p;
@@ -518,8 +535,14 @@ void line_close()
IF (frag_is_float(f)) && (!DSDWORD[f + F_ATOMH]) continue;
// a float:right atom stays at the right end whatever the alignment
IF (DSDWORD[f + F_X] < right) DSDWORD[f + F_X] += dx;
// an inline-block moves as one piece: its bottom on the bottom of the line
IF (DSDWORD[f + F_ATOMH]) { dy = ln_h - DSDWORD[f + F_ATOMH]; DSDWORD[f + F_Y] += dy; continue; }
// an inline-block moves as one piece: its bottom on the bottom of the line, or
// with "vertical-align: middle" in the middle of it (korrespondent.net: the
// label "Пошук" and its 30px icon, both middle, sat on the icon's bottom)
if (DSDWORD[f + F_ATOMH]) {
dy = atom_dy(f);
DSDWORD[f + F_Y] += dy;
continue;
}
dy = ln_h - DSDWORD[f + F_H];
dy -= sink;
IF (dy < 0) dy = 0;
@@ -890,6 +913,36 @@ void place_string(dword _tnode, _stnode, _str)
place_run(_tnode, _stnode, off, strlen(_str), false);
}
// _str as page text: a word at a time, the lines break between them (the "[alt]" of a
// picture that did not come: in one piece, a long alt was the narrowest a table column
// could get)
void place_words(dword _tnode, _stnode, _str)
{
dword off = dpool.add(_str);
dword len = strlen(_str);
dword s, p, i, we, fw;
int right;
fw = font_w(node_font(_stnode));
i = 0;
while (i < len) {
s = dpool.get(off); // dpool may move while the runs are placed
p = s + i;
IF (DSBYTE[p] == ' ') {
ln_soft = true;
IF (ln_open) && (ln_has) && (ln_x != ln_flt_x) {
right = ln_right();
IF (ln_x + fw <= right) place_run(_tnode, _stnode, off + i, 1, true);
}
i++;
CONTINUE;
}
we = i;
WHILE (we < len) { p = s + we; IF (DSBYTE[p] == ' ') BREAK; we++; }
place_run(_tnode, _stnode, off + i, we - i, false);
i = we;
}
}
void inline_text(dword _idx, _stnode)
{
dword node = dom_n(_idx);
@@ -1090,6 +1143,30 @@ bool img_cover(dword _node, int _w, _h, _fit)
RETURN true;
}
// "height: 100%; width: auto" on a picture in the flow of a box of definite height
// (lay_def_h): that height, the width by its aspect. virtualbox.org's 2208x552 logo in a
// "height: 5vh" bar took the window. In an absolute box img_cover() does it.
bool img_pct_h(dword _node, int _w, _h)
{
int k, t;
IF (img_cb_h >= 0) RETURN false;
IF (DSDWORD[_node + S_WIDTH]) RETURN false;
k = DSDWORD[_node + S_HPCT];
IF (!k) RETURN false;
IF (lay_def_h <= 0) RETURN false;
IF (_h < 1) RETURN false;
t = k * lay_def_h;
t /= 100;
IF (t < 1) RETURN false;
IF (t == _h) RETURN false;
img_fit_h = t;
k = _w * t;
k /= _h;
IF (k < 1) k = 1;
img_fit_w = k;
RETURN true;
}
// the picture just emitted shows the part img_cover() cut
void img_mark_cut()
{
@@ -1213,6 +1290,12 @@ void inline_image(dword _idx)
}
// the painter scales the picture to the size it gets here (F_IMGW)
fit = 0;
// measured at that size too: the flex row of virtualbox.org gave the logo 689px
if (img_pct_h(node, w, h)) {
w = img_fit_w;
h = img_fit_h;
IF (!lay_measuring) fit = w;
}
IF (!lay_measuring) {
IF (img_fit(node, w, h)) { w = img_fit_w; h = img_fit_h; fit = w; }
IF (img_cover(node, w, h, fit)) {
@@ -1278,7 +1361,7 @@ void inline_image(dword _idx)
}
DSDWORD[node + S_BG] = IMG_PLACEHOLDER_BG;
DSDWORD[node + S_COLOR] = IMG_PLACEHOLDER_TEXT;
place_string(_idx, node, #label);
place_words(_idx, node, #label);
}
void inline_icon(dword _idx)
@@ -1559,7 +1642,7 @@ void inline_control(dword _idx)
IF (css_kw(v, "checkbox|radio")) {
inline_check(_idx);
return;
} else IF (css_kw(v, "submit|button|reset|image")) {
} else IF (css_kw(v, "submit|button|reset")) {
v = dom_attr(_idx, "value");
IF (!v) v = dom_attr(_idx, "alt");
IF (!v) v = "Submit";
@@ -1683,8 +1766,13 @@ void inline_block(dword _idx)
ln_below = s_below;
ln_flt_x = s_flt;
FOR (i = fs; i < frags.count; i++) { f = frags.f(i); DSDWORD[f + F_ATOMH] = h; }
v = DSDWORD[node + S_FLAGS] & SF_VMID;
FOR (i = fs; i < frags.count; i++) { f = frags.f(i); DSDWORD[f + F_ATOMH] = h; DSDWORD[f + F_AMID] = v; }
IF (DSDWORD[node + S_FLOAT] == 2) ln_rw += w; ELSE ln_x += w;
// the measure takes the box in: an empty one draws only its background, which
// widens nothing (korrespondent.net: the 30px icon <span> after "Пошук" in a
// float went to a line of its own)
IF (ln_x > lay_max_x) lay_max_x = ln_x;
IF (h > ln_atom) { ln_atom = h; ln_below = below; }
// a float is out of the flow: a space after it still starts the line and is dropped
// (Slashdot: "<form float:left 50%>\n<ul float:right 50%>" went onto two lines)
@@ -1749,7 +1837,11 @@ void inline_node(dword _idx)
// on a line of its own, as the page asked for
v = DSDWORD[node + S_FLAGS] & SF_OWNLINE;
IF (v) && (ln_has) line_new();
inline_control(_idx);
// <input type=image> is its picture, or "[alt]" that wraps like the one of
// a missing <img>: as a one-line button the long alt of a PayPal button
// that did not come stretched its table cell (publicdomaintorrents.info)
IF (t == T_INPUT) && (is_image_input(_idx)) inline_image(_idx);
ELSE inline_control(_idx);
IF (v) line_new();
return;
case T_Q:
@@ -1947,7 +2039,6 @@ void grid_abs(dword _idx, _top);
void cols_log(dword _idx, _ncols, _total);
#endif
int lay_float_depth;
// what a CSS width does not cover: by default it is the content alone, so the padding
// and the border of the box come on top of it ("box-sizing: border-box" puts them in)
@@ -1966,7 +2057,7 @@ int box_extra(dword _node, _base)
// and leaves here its outer width, which negative margins can bring to 0 or below
int float_outer;
void margin_pct(dword _node, int _avail);
int margin_pct(dword _node, int _avail);
int float_width(dword _idx, _avail)
{
@@ -1987,6 +2078,10 @@ int float_width(dword _idx, _avail)
w = measure_block(_idx, _avail);
lay_float_depth--;
IF (w <= 0) return 0; // margins, padding and border are in the measure
// but a margin in percent was 0 in it (pct_margin_px while measuring): the room
// is known here. hackaday.com's menu, "li { float: left; margin-right: 3.5% }",
// had its items run together
IF (!lay_measuring) w += margin_pct(_node, _avail);
IF (w > _avail) w = _avail;
IF (w < 8) w = 8;
float_outer = w;
@@ -2253,14 +2348,19 @@ bool lay_new_ctx(dword _in_flow, _own)
// that paints its box is put beside it (beside_floats).
// the margin-bottom that came out of the bottom of the block just laid out
int lay_tail;
// the collapsed margin above the block flow_children() lays out next, read (and reset)
// by layout_block: the margin-top of its first child joins it. NO_TOP_M for a block
// placed by anything else, which drops that margin as before
#define NO_TOP_M 0x80000000
int lay_top_m = NO_TOP_M;
void flow_children(dword _idx, _collapse_through)
void flow_children(dword _idx, _collapse_through, _top_m)
{
bool had_fl;
bool had_in;
dword c = dom_nfirst(_idx);
dword cn, disp, t, p, side;
int m, prev_margin = 0;
int m, top, prev_margin = 0;
int fl_w, x, y, fy, k;
bool first = true;
@@ -2331,17 +2431,34 @@ void flow_children(dword _idx, _collapse_through)
continue;
}
m = collapse_margin(DSDWORD[cn + S_MTOP], prev_margin);
IF (first) && (_collapse_through) m = 0;
top = m;
if (first) && (_collapse_through) {
// the margin-top of the first child comes out through the top of a block
// without a border or padding: it joins the margin above that block, only
// what it adds moves the child down (rbc.ua: "#app > .main-menu" 64px under
// the fixed header, laid at 0 when the margin was dropped)
IF (_top_m == NO_TOP_M) {
// dropped, and so is the margin of its own first child: a wrapper <div>
// must not bring back the margin of the <h3> in it
m = 0;
top = NO_TOP_M;
} ELSE {
top = collapse_margin(DSDWORD[cn + S_MTOP], _top_m);
m = top - _top_m;
}
}
y = blk_y + m;
IF (y < 0) y = 0;
had_in = true;
IF (block_may_carry(c)) {
// a plain block: the floats reach into its lines, its own floats may reach out
lay_in_flow = true;
lay_top_m = top;
k = layout_block(c, blk_x, y, blk_w);
} ELSE {
y = beside_floats(c, y);
lay_in_flow = true;
lay_top_m = top;
IF (beside_w) {
IF (beside_fix) lay_skip_width = true;
k = layout_block(c, beside_x, y, beside_w);
@@ -2532,14 +2649,25 @@ int pct_margin_px(int _pct, _avail)
RETURN k;
}
// margin-left / margin-right in percent (S_MLPCT, S_MRPCT) into px of the room _avail
void margin_pct(dword _node, int _avail)
// margin-left / margin-right in percent (S_MLPCT, S_MRPCT) into px of the room _avail:
// their sum in px (float_width adds it to the measure, where they were 0)
int margin_pct(dword _node, int _avail)
{
int v;
int v, sum;
sum = 0;
v = DSDWORD[_node + S_MLPCT];
IF (v) DSDWORD[_node + S_MLEFT] = pct_margin_px(v, _avail);
if (v) {
v = pct_margin_px(v, _avail);
DSDWORD[_node + S_MLEFT] = v;
sum += v;
}
v = DSDWORD[_node + S_MRPCT];
IF (v) DSDWORD[_node + S_MRIGHT] = pct_margin_px(v, _avail);
if (v) {
v = pct_margin_px(v, _avail);
DSDWORD[_node + S_MRIGHT] = v;
sum += v;
}
RETURN sum;
}
// What frag_clip() cuts. By default every fragment: a picture or a box is cut at the
@@ -2579,7 +2707,18 @@ void frag_clip(dword _from, int _l, _t, _r, _b, dword _mode)
IF (m) continue;
d = h / 2;
d += y;
IF (d < _t) || (d >= _b) { DSDWORD[f + F_KIND] = 0; continue; }
IF (d >= _b) { DSDWORD[f + F_KIND] = 0; continue; }
// text across the top edge is cut to the pixel (paint.h keeps the rows above
// F_Y), as a browser does: the title of a card pulled up under the edge of
// its overflow:hidden box (tailwindcss.com, "Browse properties")
if (y < _t) && (k == FR_TEXT) {
d = _t - y;
DSDWORD[f + F_IMGY] += d;
y = _t;
h -= d;
DSDWORD[f + F_Y] = y;
DSDWORD[f + F_H] = h;
} else IF (d < _t) { DSDWORD[f + F_KIND] = 0; continue; }
IF (k == FR_ICON) {
d = w / 2;
d += x;
@@ -2782,6 +2921,45 @@ void col_flex_spread(dword _idx, _from, int _left)
}
}
// one side of an outline: a bar in the outline colour (the text colour for none)
void outline_bar(dword _idx, _node, int _x, _y, _w, _h)
{
dword f;
f = frag_emit(_idx, FR_HLINE, _x, _y, _w, _h, _node);
f = frags.f(f);
IF (DSDWORD[_node + S_OUTC] != NO_COLOR) DSDWORD[f + F_COLOR] = DSDWORD[_node + S_OUTC];
}
// "outline": four bars around the box _x, _y, _w, _h (its border box), outline-offset
// away from it, drawn after its content and taking no room. Only a block or an
// inline-block has one: an inline element broken over lines goes without
void lay_outline(dword _idx, _node, int _x, _y, _w, _h)
{
int t, o, x, y, w, h, k;
t = DSDWORD[_node + S_OUTW];
IF (!t) return;
// "outline: 2px solid transparent" (Tailwind's .outline-none): nothing to draw
IF (DSDWORD[_node + S_OUTC] == CLEAR_COLOR) return;
o = DSDWORD[_node + S_OUTOFF];
o += t;
x = _x - o;
y = _y - o;
w = o * 2;
w += _w;
h = o * 2;
h += _h;
k = t * 2;
IF (w <= k) || (h <= k) return;
outline_bar(_idx, _node, x, y, w, t);
k = y + h;
k -= t;
outline_bar(_idx, _node, x, k, w, t);
outline_bar(_idx, _node, x, y, t, h);
k = x + w;
k -= t;
outline_bar(_idx, _node, k, y, t, h);
}
dword layout_block(dword _idx, _x, _y, _w)
{
dword node = dom_n(_idx);
@@ -2803,9 +2981,12 @@ dword layout_block(dword _idx, _x, _y, _w)
int ps_b, ps_a, cw;
int pct_h, s_def_h;
int tail = 0;
int top_m;
in_flow = lay_in_flow;
lay_in_flow = false;
top_m = lay_top_m;
lay_top_m = NO_TOP_M;
lay_tail = 0;
IF (disp == D_NONE) return 0;
only_floats = false;
@@ -3014,7 +3195,7 @@ dword layout_block(dword _idx, _x, _y, _w)
ELSE IF (DSDWORD[node + S_FLEX] & FLEX_ON) k = flex_children(_idx);
}
lay_only_floats = false;
IF (!k) { flow_children(_idx, collapse_through); IF (!DSDWORD[node + S_HEIGHT]) tail = lay_tail; }
IF (!k) { flow_children(_idx, collapse_through, top_m); IF (!DSDWORD[node + S_HEIGHT]) tail = lay_tail; }
only_floats = lay_only_floats;
}
blk_y += ps_a;
@@ -3110,11 +3291,16 @@ dword layout_block(dword _idx, _x, _y, _w)
IF (DSDWORD[node + S_FLAGS] & SF_CLIP) && (!lay_measuring) {
k = _y + height;
frag_clip(clip_f, CLIP_NEAR, CLIP_NEAR, CLIP_FAR, k, CLIP_BOXES);
// and at its top: a card pulled up by a negative margin (tailwindcss.com "-mt-26"
// in the CSS grid demo) is cut there, not drawn over the text above the box
k = _y + DSDWORD[node + S_BORDT];
frag_clip(clip_f, CLIP_NEAR, k, CLIP_FAR, CLIP_FAR, 0);
}
k = height;
IF (only_floats) && (!own_ctx) { v = edge_t + edge_b; k = floats_only_h(_idx, node, height, v); }
frag_set_h(box_f, k);
frag_set_h(rect_f, k);
IF (DSDWORD[node + S_OUTW]) && (!lay_measuring) lay_outline(_idx, node, _x, _y, _w, k);
DSDWORD[node + L_H] = height;
// position:relative offsets and transform:translate() move the box as drawn,
// with everything inside it; the flow around keeps its place
@@ -3228,6 +3414,28 @@ void abs_clip(dword _cb, _from, int _cy, _ch)
frag_clip(_from, l, t, r, b, 0);
}
// a box from _cb up cuts what overflows it at the bottom: an absolute box inside does
// not make the page longer, the clipping box is in the page's height itself.
// abs_clip() cuts at the bottom of _cb alone, the others are not laid out yet
// (rbc.ua: the times of 400 news, "position:absolute" in their items, in a 1500px
// "overflow:hidden" scroller left meters of empty page under the footer)
bool abs_in_clip(dword _cb)
{
dword p, pn, k;
p = _cb;
while (p) {
// the overflow of <html> and <body> is that of the window: the page scrolls
IF (p == dom.body) || (p == dom.root) RETURN false;
pn = dom_n(p);
IF (DSDWORD[pn + N_TYPE] == T_HTML) RETURN false;
k = DSDWORD[pn + S_FLAGS];
IF (DSDWORD[pn + S_DISPLAY] == D_INLINE) && (!(k & SF_INLBLOCK)) k = 0;
IF (k & SF_CLIP) RETURN true;
p = dom_nparent(p);
}
RETURN false;
}
// the page scrolls down to the lowest fragment of an absolute box
void abs_reach(dword _from)
{
@@ -3471,7 +3679,9 @@ void layout_abs_box(dword _idx, int _sx, _sy, dword _cb, int _cx, _cy, _cw, _ch)
abs_clip(_cb, fs, _cy, _ch);
// out of the flow: it widens nothing, a fixed box does not make the page longer
lay_max_x = s_max;
IF (!(DSDWORD[node + S_FLAGS] & SF_FIXED)) abs_reach(fs);
IF (!(DSDWORD[node + S_FLAGS] & SF_FIXED)) {
IF (!abs_in_clip(_cb)) abs_reach(fs);
}
}
// Place the absolute boxes waiting in the queue from _from on against the containing
@@ -3939,6 +4149,77 @@ bool flex_children(dword _idx)
#define GRID_ITEMS 2048
#define GRID_COLS 16
// The widest width in px an item of a grid asks for itself: its own "width", else that
// of its one element child (through display:contents and plain wrappers, a few levels
// down). 0: none. The cards of theguardian.com's carousel are "width: 280px" inside an
// <li> and a display:contents <div>.
int grid_item_own_w(dword _c)
{
dword c, cn, ch, one;
int d, w, most, k;
most = 0;
c = _c;
FOR (d = 0; d < 4; d++) {
cn = dom_n(c);
w = DSDWORD[cn + S_WIDTH];
IF (w > 0) {
w += box_extra(cn, 0);
k = DSDWORD[cn + S_MLEFT];
IF (k > 0) w += k;
k = DSDWORD[cn + S_MRIGHT];
IF (k > 0) w += k;
RETURN w;
}
// the one child in the flow that is an element
one = 0;
ch = dom_nfirst(c);
WHILE (ch) {
IF (dom_ntype(ch) != T_TEXT) {
cn = dom_n(ch);
IF (DSDWORD[cn + S_DISPLAY] != D_NONE) && (DSDWORD[cn + S_POS] != POS_ABSOLUTE) {
IF (one) RETURN 0;
one = ch;
}
}
ch = dom_nnext(ch);
}
IF (!one) RETURN 0;
c = one;
}
RETURN 0;
}
// "repeat(6, 1fr)" whose items are wider than a sixth of the row: in browsers the fr
// columns grow to their items (an fr column is at least its min-content) and the row
// scrolls sideways - theguardian.com's highlights carousel. There is no horizontal
// scrolling here: fewer columns, as many as hold the widest item, and the rest wraps
// to new rows. Before, six 100px columns squeezed each card's headline to one letter
// per line beside its picture. The new column count, or _ncols when it stays.
int grid_fit_cols(dword _items, int _n, _ncols, _avail, _gap, dword _trk)
{
int i, j, w, most, k, v;
dword p;
// every column an fr one (in an int: a dword is never below 0)
FOR (j = 0; j < _ncols; j++) {
p = j * 4;
p += _trk;
v = DSDWORD[p];
IF (v >= 0) RETURN _ncols;
}
most = 0;
FOR (i = 0; i < _n; i++) {
w = grid_item_own_w(gget(_items, i));
IF (w > most) most = w;
}
IF (most <= 0) RETURN _ncols;
k = _avail + _gap;
v = most + _gap;
k /= v;
IF (k < 1) k = 1;
IF (k >= _ncols) RETURN _ncols;
RETURN k;
}
dword gget(dword _a, _i)
{
dword p;
@@ -4115,6 +4396,8 @@ bool grid_children(dword _idx)
IF (!n) { free(items); grid_abs(_idx, blk_y); return true; }
if (areas) {
IF (!grid_areas_key(areas, items, n)) { free(items); return false; }
} else IF (DSDWORD[node + S_GRIDN] > 1) {
ncols = grid_fit_cols(items, n, ncols, avail, gap, #trk);
}
flex_sort_order(items, n);
buf = malloc(n * 16);
@@ -4143,8 +4426,22 @@ bool grid_children(dword _idx)
cs = DSDWORD[cn + S_COLSPAN];
cs = math.min(cs, ncols);
cs = math.max(cs, 1);
k = col + cs;
IF (k > ncols) { col = 0; row++; }
k = DSDWORD[cn + S_GCOL];
if (k > 0) {
// "grid-column-start: 2": that column, in the next row when the cursor
// is past it (tailwindcss.com: the footer in the middle column between
// two narrow gutters, not in the left gutter)
k--;
v = ncols - 1;
k = math.min(k, v);
IF (k < col) row++;
col = k;
k = ncols - col;
cs = math.min(cs, k);
} else {
k = col + cs;
IF (k > ncols) { col = 0; row++; }
}
}
gset(icol, i, col);
gset(ispan, i, cs);
@@ -4178,6 +4475,8 @@ bool grid_children(dword _idx)
for (j = 0; j < ncols; j++) {
k = trk[j];
IF (cmax[j] < cmin[j]) cmax[j] = cmin[j];
// "minmax(0, 96rem)" starts as 96rem, the end of this loop narrows it
IF (k >= GT_CAP) && (k < GT_PCT) k -= GT_CAP;
IF (k >= GT_PCT) {
v = k - GT_PCT;
v *= room;
@@ -4259,6 +4558,18 @@ bool grid_children(dword _idx)
FOR (j = 0; j < ncols; j++) IF (frz[j] == 2) cw[j] += v;
}
}
// a "minmax(0, 96rem)" column gives back what the row lacks
used = 0;
FOR (j = 0; j < ncols; j++) used += cw[j];
need = used - room;
for (j = 0; j < ncols; j++) {
IF (need <= 0) BREAK;
k = trk[j];
IF (k < GT_CAP) || (k >= GT_PCT) CONTINUE;
v = math.min(need, cw[j]);
cw[j] -= v;
need -= v;
}
// ---- rows ----
rowy = blk_y;
+111 -2
View File
@@ -44,6 +44,112 @@ void cv_bar(int _x, _y, _w, _h, dword _color)
canvas.DrawBar(_x, _y, x2 - _x, y2 - _y, _color);
}
// a bar of the color _argb (alpha << 24 + RGB) mixed into what the canvas holds
void cv_blend_bar(int _x, _y, _w, _h, dword _argb)
{
int x2, y2, y;
dword a, na, p, e, row, n, stride, d, s, v, k, sr, sg, sb;
x2 = _x + _w;
y2 = _y + _h;
IF (_x < 0) _x = 0;
IF (_y < 0) _y = 0;
IF (x2 > canvas.bufw) x2 = canvas.bufw;
IF (y2 >= canvas.bufh) y2 = canvas.bufh - 1;
IF (x2 <= _x) || (y2 <= _y) return;
a = _argb >> 24;
na = 255 - a;
sr = _argb >> 16; sr &= 0xFF; sr *= a;
sg = _argb >> 8; sg &= 0xFF; sg *= a;
sb = _argb & 0xFF; sb *= a;
stride = canvas.bufw * 4;
row = _y * stride;
row += buf_data;
row += 8;
k = _x * 4;
row += k;
n = x2 - _x;
n *= 4;
// a full-window backdrop is blended on every band painted: no division per pixel,
// "x / 255" is (x + 1 + (x >> 8)) >> 8, exact up to 255 * 255
for (y = _y; y < y2; y++) {
p = row;
e = row + n;
while (p < e) {
d = DSDWORD[p];
s = d >> 16 & 0xFF; s *= na; s += sr;
k = s >> 8; s += k; s++; s >>= 8;
v = s << 16;
s = d >> 8 & 0xFF; s *= na; s += sg;
k = s >> 8; s += k; s++; s >>= 8;
s <<= 8;
v |= s;
s = d & 0xFF; s *= na; s += sb;
k = s >> 8; s += k; s++; s >>= 8;
v |= s;
v |= 0xFF000000;
DSDWORD[p] = v;
p += 4;
}
row += stride;
}
}
// a background box: a translucent one of a positioned box (F_P) is mixed into what is
// painted under it, as a browser does; any other is a plain bar of its blended color
void paint_box(dword _f, int _x, _y, _w, _h)
{
dword c, n;
c = DSDWORD[_f + F_BG];
if (c != NO_COLOR) {
n = dom_n(DSDWORD[_f + F_NODE]);
n = DSDWORD[n + S_BGA];
IF (n) && (DSDWORD[_f + F_P]) cv_blend_bar(_x, _y, _w, _h, n);
ELSE cv_bar(_x, _y, _w, _h, c);
}
paint_bg_image(DSDWORD[_f + F_NODE], _x, _y, _w, _h);
}
// canvas rows [_y1, _y2) saved by cv_rows_keep(), put back by cv_rows_back(). One
// buffer for all, it only grows: no malloc per text run on every repaint
dword cv_keep_buf, cv_keep_cap;
int cv_keep_y, cv_keep_n;
void cv_rows_keep(int _y1, _y2)
{
dword a;
IF (_y1 < 0) _y1 = 0;
IF (_y2 > canvas.bufh) _y2 = canvas.bufh;
cv_keep_n = _y2 - _y1;
IF (cv_keep_n <= 0) { cv_keep_n = 0; return; }
cv_keep_n *= canvas.bufw;
cv_keep_n *= 4;
cv_keep_y = _y1;
if (cv_keep_n > cv_keep_cap) {
IF (cv_keep_buf) free(cv_keep_buf);
cv_keep_cap = 0;
cv_keep_buf = malloc(cv_keep_n);
IF (!cv_keep_buf) { cv_keep_n = 0; return; }
cv_keep_cap = cv_keep_n;
}
a = _y1 * canvas.bufw;
a *= 4;
a += buf_data;
a += 8;
memmov(cv_keep_buf, a, cv_keep_n);
}
void cv_rows_back()
{
dword a;
IF (!cv_keep_n) return;
a = cv_keep_y * canvas.bufw;
a *= 4;
a += buf_data;
a += 8;
memmov(a, cv_keep_buf, cv_keep_n);
cv_keep_n = 0;
}
void cv_rect(int _x, _y, _w, _h, _t, dword _color)
{
cv_bar(_x, _y, _w, _t, _color);
@@ -610,12 +716,16 @@ void paint_text_frag(dword _i, _f, int _x, _y)
}
IF (DSDWORD[_f + F_LINK]) && (DSDWORD[_f + F_LINK] == hover_link) color = body_link_active;
// a run cut at the top by an overflow (layout.h frag_clip): its glyphs start above
// _y, the rows there are kept as they are
IF (DSDWORD[_f + F_IMGY]) cv_rows_keep(ty, _y);
cv_text(_x, ty, font, color, str, len);
IF (flags & SF_BOLD) cv_text(_x + 1, ty, font, color, str, len);
IF (flags & SF_ITALIC) { k = len * fw; cv_slant(_x, ty, k, fh); }
IF (flags & SF_UNDER) cv_bar(_x, ty + fh, w, zoom, color);
IF (flags & SF_STRIKE) { k = fh / 2; cv_bar(_x, ty + k, w, zoom, color); }
IF (caret >= 0) { k = caret * fw; cv_bar(_x + k, ty, zoom + 1, fh, color); }
cv_rows_back();
}
// ---- page -------------------------------------------------------------------------
@@ -662,8 +772,7 @@ void paint_area(int _scroll, _view_h)
kind = DSDWORD[f + F_KIND];
switch (kind) {
CASE FR_BOX:
IF (DSDWORD[f + F_BG] != NO_COLOR) cv_bar(fx, fy, fw, fh, DSDWORD[f + F_BG]);
paint_bg_image(DSDWORD[f + F_NODE], fx, fy, fw, fh);
paint_box(f, fx, fy, fw, fh);
break;
CASE FR_RECT:
paint_border(DSDWORD[f + F_NODE], fx, fy, fw, fh, DSDWORD[f + F_COLOR], DSDWORD[f + F_TEXT]);
+351 -40
View File
@@ -2,7 +2,7 @@
// Order of precedence (low -> high): inherited values, UA defaults by tag,
// presentational attributes, <style> rules, style="" attribute.
#define DEFAULT_BG_COL 0xFBE5E4 // peach
#define DEFAULT_BG_COL 0xFFFFFF
#define DEFAULT_TEXT_COL 0x000000
#define DEFAULT_LINK_COL 0x0000FF
#define DEFAULT_LINK_ACTIVE 0xFF0000
@@ -65,7 +65,7 @@ dword line_h(dword f) { RETURN font_hs[f] + LINE_GAP; }
// of its text is bigger: a page set in 10px all over keeps the font the user chose.
// style_compute counts the letters, style_tree decides.
#define TINY_FPX 100
dword text_all, text_tiny;
dword text_all, text_tiny, text_big;
bool node_tiny(dword _node)
{
@@ -74,33 +74,89 @@ bool node_tiny(dword _node)
RETURN true;
}
// Text a page sets in 16px and more gets the medium (from 16px) or the big (from 22px)
// font, but only while most of the page is smaller. When most of its letters are in
// them, the size most of those are in is the body text of the page (rbc.ua: 16px,
// browser.engineering: "html { font-size: 24px }"): it gets the default font, and the
// bigger text steps down against it, so the headings still stand out.
// The headings of the UA style (<h1>..<h3>) keep the em of their parent: not counted.
#define BIG_FPX 160
#define BIG_HIST 101 // letters per px of font-size, 100px and more in the last one
dword text_big_px[BIG_HIST];
bool node_big(dword _node)
{
dword f = DSDWORD[_node + S_FONT];
IF (f != FONT_MEDIUM) && (f != FONT_BIG) RETURN false;
IF (DSDWORD[_node + S_FPX] < BIG_FPX) RETURN false;
RETURN true;
}
void text_big_clear()
{
dword i;
FOR (i = 0; i < BIG_HIST; i++) DSDWORD[i * 4 + #text_big_px] = 0;
}
// the px of font-size of _node as an index of text_big_px
dword node_big_px(dword _node)
{
dword px = DSDWORD[_node + S_FPX] / 10;
IF (px >= BIG_HIST) px = BIG_HIST - 1;
RETURN px;
}
// the letters (not the spaces) of text node _idx, a child of the element node _node
void text_count(dword _idx, _node)
{
dword s;
bool tiny;
dword s, n, h;
IF (dom_ntype(_idx) != T_TEXT) RETURN;
tiny = node_tiny(_node);
s = dom_ntext(_idx);
IF (!s) RETURN;
n = 0;
WHILE (DSBYTE[s]) {
IF (DSBYTE[s] > ' ') {
text_all++;
IF (tiny) text_tiny++;
}
IF (DSBYTE[s] > ' ') n++;
s++;
}
text_all += n;
IF (node_tiny(_node)) text_tiny += n;
if (node_big(_node)) {
text_big += n;
h = node_big_px(_node) * 4;
h += #text_big_px;
DSDWORD[h] += n;
}
}
void style_small_print()
{
dword i, node;
dword i, node, n, most, body, r;
i = text_tiny * 2;
IF (i >= text_all) RETURN;
if (i < text_all) {
FOR (i = 1; i < dom.count; i++) {
IF (dom_ntype(i) == T_TEXT) CONTINUE;
node = dom_n(i);
IF (node_tiny(node)) DSDWORD[node + S_FONT] = FONT_SMALL;
}
}
i = text_big * 2;
IF (i < text_all) return;
// the size of the body text: the one with the most letters
body = BIG_FPX / 10;
most = 0;
FOR (i = 0; i < BIG_HIST; i++) {
n = DSDWORD[i * 4 + #text_big_px];
IF (n > most) { most = n; body = i; }
}
FOR (i = 1; i < dom.count; i++) {
IF (dom_ntype(i) == T_TEXT) CONTINUE;
node = dom_n(i);
IF (node_tiny(node)) DSDWORD[node + S_FONT] = FONT_SMALL;
IF (!node_big(node)) CONTINUE;
// up to a quarter bigger than the body text: the default font; up to 3/4
// bigger: the medium one at most
r = DSDWORD[node + S_FPX] * 10;
r /= body;
IF (r < 125) DSDWORD[node + S_FONT] = FONT_NORMAL;
ELSE IF (r < 175) DSDWORD[node + S_FONT] = FONT_MEDIUM;
}
}
@@ -1076,13 +1132,51 @@ void css_import(dword _p, _end)
css_import_depth--;
}
// "@supports not (...)" between _p and _end: the block is a fallback for what we lack
bool css_supports_not(dword _p, _end)
// a color function css_color() cannot read, between _p and _end (0: the string end).
// Tailwind v4 gives every color as hex, then again as lab() / color-mix() under
// "@supports (color:lab(0% 0 0))": the hex is the value for a browser without them.
// css_apply_decl() runs it on every value it applies: only the name before each "("
// is looked at, most values have none
bool css_fn_unknown(dword _s, _q)
{
dword p, c;
p = _q;
WHILE (p > _s) {
c = DSBYTE[p - 1];
IF (c >= 'a') && (c <= 'z') { p--; CONTINUE; }
IF (c != '-') BREAK;
p--;
}
c = _q - p;
IF (!c) RETURN false;
IF (css_name_idx(p, c, "lab|oklab|lch|oklch|hwb|hsl|hsla|color|color-mix")) RETURN true;
RETURN false;
}
bool css_unknown_color(dword _p, _end)
{
dword p;
p = _p;
WHILE (DSBYTE[p]) {
IF (_end) && (p >= _end) BREAK;
IF (DSBYTE[p] == 0x28) && (css_fn_unknown(_p, p)) RETURN true;
p++;
}
RETURN false;
}
// "@supports ..." between _p and _end holds: "not (...)" is a fallback for what we
// lack (it holds when that is a color we cannot read), a test of such a color fails.
// Anything else is taken to hold.
bool css_supports(dword _p, _end)
{
dword p = _p;
bool bad;
WHILE (p < _end) && (__isWhite(DSBYTE[p])) p++;
IF (!strncmp(p, "not", 3)) RETURN true;
RETURN false;
bad = css_unknown_color(p, _end);
IF (!strncmp(p, "not", 3)) RETURN bad;
IF (bad) RETURN false;
RETURN true;
}
// parse a stylesheet text: "sel, sel { decls } ..."
@@ -1387,9 +1481,9 @@ void css_parse_sheet(dword _text)
IF (!s) return;
IF (!strncmp(p, "@media", 6)) && (css_media_applies(p + 6, s - p - 6)) { p = s + 1; continue; }
// cascade layers hold whole sheets (Tailwind v4): walk in, the order of the
// layers is not kept. "@supports (...)" is taken to hold, "@supports not" not.
// layers is not kept.
IF (!strncmp(p, "@layer", 6)) { p = s + 1; continue; }
IF (!strncmp(p, "@supports", 9)) && (!css_supports_not(p + 9, s)) { p = s + 1; continue; }
IF (!strncmp(p, "@supports", 9)) && (css_supports(p + 9, s)) { p = s + 1; continue; }
// find the matching brace
n = 0; p = s;
WHILE (DSBYTE[p]) {
@@ -1727,21 +1821,27 @@ dword css_prev_element(dword _idx)
// that. Between two parts ">" means the direct parent, "+" the element right before,
// "~" any element before (same parent). "li > span" must not reach a span nested in a
// link; ".wrapper + .static" hides a fallback picture only after its wrapper.
// A combinator is a part of its own: "#ctxtnav > ul > li.first > a > img" of
// virtualbox.org is 9 parts. Over 16 the first ones are dropped and the rest must match;
// a chain that then starts with a combinator matches once its parts up to there do
// (with 8 parts that selector never matched: its icon took its own 232px height).
bool css_match(dword _idx, _sel)
{
dword parts[8];
dword lens[8];
dword parts[16];
dword lens[16];
int np, i, comb;
dword p, s, cur;
bool cut = false;
np = 0;
p = _sel;
s = _sel;
WHILE (1) {
IF (DSBYTE[p] == ' ') || (DSBYTE[p] == 0) {
// over 8 parts: keep the last 8, the subject must stay the real last part
IF (np == 8) {
FOR (i = 0; i < 7; i++) { parts[i] = parts[i+1]; lens[i] = lens[i+1]; }
np = 7;
// over 16 parts: keep the last 16, the subject must stay the real last part
IF (np == 16) {
FOR (i = 0; i < 15; i++) { parts[i] = parts[i+1]; lens[i] = lens[i+1]; }
np = 15;
cut = true;
}
parts[np] = s; lens[np] = p - s; np++;
IF (!DSBYTE[p]) BREAK;
@@ -1761,7 +1861,7 @@ bool css_match(dword _idx, _sel)
IF (DSBYTE[p] == '>') || (DSBYTE[p] == 0x2B) || (DSBYTE[p] == 0x7E) {
comb = DSBYTE[p];
i--;
IF (i < 0) return false;
IF (i < 0) return cut;
}
}
IF (comb == 0x2B) {
@@ -1886,10 +1986,20 @@ dword css_rgb_first(dword _v)
RETURN css_skip_ws(p + 1);
}
// the alpha and the own RGB of the color css_color() read last: 255 for an opaque one
int css_alpha_last;
dword css_rgb_last;
// _r, _g, _b with the alpha _a (1..255) as it looks over the backdrop, as 0xRRGGBB
dword css_blend_rgb(dword _r, _g, _b, _a)
{
dword br, bg, bb, c;
css_alpha_last = _a;
c = _r << 16;
br = _g << 8;
c += br;
c += _b;
css_rgb_last = c;
IF (_a < 255) {
br = css_blend_base >> 16;
br &= 0xFF;
@@ -1912,6 +2022,7 @@ dword css_color(dword _v)
char tmp[32];
dword r, g, b, p, c;
int ii, a;
css_alpha_last = 255;
if (!strncmp(_v, "rgb", 3)) {
// "rgb(1, 2, 3)" or the modern "rgb(1 2 3 / 50%)" (lobste.rs)
p = css_rgb_first(_v);
@@ -2011,7 +2122,7 @@ signed css_em_tenths(dword _v)
}
// "12px" -> 12, "50%" -> -50, "2em" -> 2*CSS_EM_PX, other -> 0.
// "20vw" is a share of the window width (css_vw). Heights in vh have no equivalent.
// "20vw" is a share of the window width (css_vw), "5vh" of its height (css_vh).
// A number with no unit is not a length: browsers drop such a declaration. A
// Neocities page with ".buttons img { width: 88; height: 31 }" would otherwise
// stretch every picture it reaches to 88x31 (cobradile.neocities.org).
@@ -2044,6 +2155,19 @@ signed css_vw(signed _n)
RETURN v;
}
// "5vh": a share of the window height (list.h), restyled when the height changes as for
// height queries. 0 while the height is not known. The bar of virtualbox.org is "height:
// 5vh", its logo "height: 100%" of that: without it the 2208x552 logo filled the window.
signed css_vh(signed _n)
{
int v;
IF (css_view_h <= 0) RETURN 0;
css_media_h = true;
v = _n * css_view_h;
v /= 100;
RETURN v;
}
// the hundredths of a percentage that css_length() drops: "16.66%" -> 66, "12.5%" -> 50,
// anything else 0 (S_WFRAC: layout.h width_px). Six columns of 16.66% were 16% each and
// lost 4% of the row; a strip of six slides drifted 4% more per slide.
@@ -2071,7 +2195,8 @@ signed css_length(dword _v)
IF (css_unitless(_v)) return 0; // "88" instead of "88px": no length at all
IF (strstr(_v, "%")) return -n;
IF (strstr(_v, "vw")) return css_vw(n);
IF (strstr(_v, "vh")) || (strstr(_v, "vmin")) || (strstr(_v, "vmax")) RETURN 0;
IF (strstr(_v, "vmin")) || (strstr(_v, "vmax")) RETURN 0;
IF (strstr(_v, "vh")) return css_vh(n);
IF (strstr(_v, "em")) RETURN css_em(_v);
// "60ch", "2ex": a glyph width / x-height, roughly half an em
IF (strstr(_v, "ch")) || (strstr(_v, "ex")) {
@@ -2479,11 +2604,15 @@ signed css_grid_track(dword _t)
dword p;
int n;
IF (!strncmp(_t, "minmax", 6)) {
// the upper bound decides: "minmax(0, 1fr)" is a share, "minmax(100px, auto)" content
// the upper bound decides: "minmax(0, 1fr)" is a share, "minmax(100px, auto)" content,
// "minmax(0, 96rem)" at most 96rem (tailwindcss.com: the page column between two
// gutters, wider than the window as a plain 96rem)
p = strchr(_t, ',');
IF (!p) return 0;
p = css_skip_sp(p + 1);
RETURN css_grid_track(p);
n = css_grid_track(p);
IF (n > 0) && (n < GT_CAP) RETURN GT_CAP + n;
RETURN n;
}
IF (strstr(_t, "fr")) {
n = css_flex_num(_t);
@@ -2617,6 +2746,17 @@ int css_grid_span(dword _v)
RETURN b;
}
// the line an item starts at: "2" of grid-column-start, "2 / 4" or "1 / -1" of
// grid-column; 0 (auto placement) for "auto", "span 2" and a line counted from the end
int css_grid_line(dword _v)
{
int a;
IF (DSBYTE[_v] < '1') || (DSBYTE[_v] > '9') RETURN 0;
a = atoi(_v);
IF (a > GRID_TRACKS) RETURN 0;
RETURN a;
}
// grid-template: "min-content 1fr / 12.25rem minmax(0,1fr)" - rows / columns; only
// the columns are used
// grid-template-areas as the page wrote them ('"logo about" "copyright copyright"'),
@@ -2988,7 +3128,7 @@ void css_bg_offsets(dword _node, _val)
char w[32];
dword p;
int n, k, v, pc;
bool one;
bool one, open, shut;
DSDWORD[_node + S_BGX] = 0;
DSDWORD[_node + S_BGY] = 0;
css_bg_ppos = 0;
@@ -2998,12 +3138,23 @@ void css_bg_offsets(dword _node, _val)
while (DSBYTE[p]) && (k < 2) {
p = css_skip_sp(p);
n = 0;
WHILE (DSBYTE[p]) && (DSBYTE[p] != ' ') { IF (n < 31) { w[n] = DSBYTE[p]; n++; } p++; }
open = false;
shut = false;
// the brackets of the whole word: a long one is cut in w, and the ")" of an
// absolute "url(https://host/i/icon-sprite.png)" (the urls of a stylesheet file
// are made absolute) was cut off with it, so the position after it was skipped
// as the rest of a function (korrespondent.net: every sprite icon at 0 0)
WHILE (DSBYTE[p]) && (DSBYTE[p] != ' ') {
IF (DSBYTE[p] == 0x28) open = true;
IF (DSBYTE[p] == 0x29) shut = true;
IF (n < 31) { w[n] = DSBYTE[p]; n++; }
p++;
}
w[n] = 0;
IF (!n) break;
IF (w[0] == '/') break; // "center / contain": the size of the shorthand
// "rgb(255, 255, 255)": the rest of a function with spaces is not a position
IF (strchr(#w, '(')) && (!strchr(#w, ')')) {
IF (open) && (!shut) {
WHILE (DSBYTE[p]) && (DSBYTE[p] != ')') p++;
IF (DSBYTE[p]) p++;
continue;
@@ -3232,19 +3383,85 @@ dword css_calc_word(dword _p)
RETURN _p;
}
// an unsigned number in thousandths: "0.25" -> 250, ".875" -> 875, "3" -> 3000
int css_milli(dword _p)
{
int n, k, d;
dword p;
p = _p;
n = atoi(p);
n *= 1000;
WHILE (DSBYTE[p] >= '0') && (DSBYTE[p] <= '9') p++;
IF (DSBYTE[p] != '.') RETURN n;
p++;
k = 100;
WHILE (k) && (DSBYTE[p] >= '0') && (DSBYTE[p] <= '9') {
d = DSBYTE[p];
d -= '0';
d *= k;
n += d;
k /= 10;
p++;
}
RETURN n;
}
// a length term of calc() in hundredths of px, unsigned: an em, rem or px keeps its
// fraction. Tailwind v4 builds every size as "calc(var(--spacing) * 6)" with
// --spacing .25rem: in whole px that was 2 * 6 = 12px for 21px, all its paddings,
// margins and gaps came out little more than half as big.
int css_calc_len_abs(dword _t)
{
int n, b;
IF (strstr(_t, "em")) {
IF (strstr(_t, "rem")) b = CSS_EM_PX * 10; ELSE b = css_em10;
n = css_milli(_t);
n *= b;
n /= 100;
RETURN n;
}
IF (strstr(_t, "px")) {
n = css_milli(_t);
n /= 10;
RETURN n;
}
n = css_length(_t);
n *= 100;
RETURN n;
}
// a term of calc() without its sign: a factor ("1.5" in thousandths) or a length in
// hundredths of px, calc_term_den set to match
int css_calc_abs(dword _p)
{
if (css_unitless(_p)) {
calc_term_den = 1;
IF (!strchr(_p, '.')) RETURN atoi(_p);
calc_term_den = 1000;
RETURN css_milli(_p);
}
calc_term_den = 100;
RETURN css_calc_len_abs(_p);
}
// a term in px, a plain number as it is (a factor), a percentage as its number with
// calc_term_pct set; CALC_BAD for a word (min(, var(, env()
// calc_term_pct set; CALC_BAD for a word (min(, var(, env(). A length comes in
// hundredths (calc_term_den 100), summed as px when its product is closed.
signed css_calc_value(dword _t)
{
dword c;
dword c, p;
int n;
calc_term_pct = false;
calc_term_den = 1;
c = DSBYTE[_t];
IF (!c) RETURN CALC_BAD;
IF (c >= 'a') && (c <= 'z') RETURN CALC_BAD;
IF (strchr(_t, '%')) { calc_term_pct = true; RETURN atoi(_t); }
IF (css_unitless(_t)) RETURN atoi(_t);
RETURN css_length(_t);
// the sign apart, as css_em() does (a stale pointer kept the minus)
IF (c != '-') RETURN css_calc_abs(_t);
p = _t + 1;
n = css_calc_abs(p);
RETURN 0 - n;
}
// a fraction that grows too big for 32 bits is divided out (precision goes, not the
@@ -3639,7 +3856,7 @@ void css_overflow_x(dword _node, _val)
// The properties css_apply_decl() knows: it looks the name up once, CP_* is its place
// in the list. Looked up by css_hash(), then compared: faster than a strcmp per name.
char css_prop_names[] = "background|background-image|background-repeat|background-position|background-size|color|background-color|font-weight|font-style|text-decoration|text-decoration-line|text-align|vertical-align|width|clear|float|height|min-height|max-height|display|grid-template-columns|grid-template-areas|grid-column|grid-template|flex-direction|flex-wrap|flex-flow|justify-content|align-items|flex-grow|flex-basis|flex|grid-template-rows|order|appearance|-webkit-appearance|-moz-appearance|gap|column-gap|grid-gap|grid-column-gap|position|top|left|right|bottom|inset-block-start|inset-inline-start|inset-inline-end|inset-block-end|inset|inset-inline|inset-block|z-index|transform|translate|opacity|visibility|-wv-clearfix|overflow|overflow-y|overflow-x|overflow-wrap|word-wrap|word-break|white-space|line-height|font-size|margin-top|margin-bottom|margin-left|margin-right|max-width|min-width|box-sizing|margin|padding|padding-top|padding-right|padding-bottom|padding-left|border|border-width|border-color|border-style|border-top|border-right|border-bottom|border-left|border-top-width|border-right-width|border-bottom-width|border-left-width|border-top-color|border-right-color|border-bottom-color|border-left-color|list-style|list-style-type|clip|aspect-ratio|object-position|text-indent|grid-area|flex-shrink|mask-image|-webkit-mask-image";
char css_prop_names[] = "background|background-image|background-repeat|background-position|background-size|color|background-color|font-weight|font-style|text-decoration|text-decoration-line|text-align|vertical-align|width|clear|float|height|min-height|max-height|display|grid-template-columns|grid-template-areas|grid-column|grid-template|flex-direction|flex-wrap|flex-flow|justify-content|align-items|flex-grow|flex-basis|flex|grid-template-rows|order|appearance|-webkit-appearance|-moz-appearance|gap|column-gap|grid-gap|grid-column-gap|position|top|left|right|bottom|inset-block-start|inset-inline-start|inset-inline-end|inset-block-end|inset|inset-inline|inset-block|z-index|transform|translate|opacity|visibility|-wv-clearfix|overflow|overflow-y|overflow-x|overflow-wrap|word-wrap|word-break|white-space|line-height|font-size|margin-top|margin-bottom|margin-left|margin-right|max-width|min-width|box-sizing|margin|padding|padding-top|padding-right|padding-bottom|padding-left|border|border-width|border-color|border-style|border-top|border-right|border-bottom|border-left|border-top-width|border-right-width|border-bottom-width|border-left-width|border-top-color|border-right-color|border-bottom-color|border-left-color|list-style|list-style-type|clip|aspect-ratio|object-position|text-indent|grid-area|flex-shrink|mask-image|-webkit-mask-image|outline|outline-width|outline-color|outline-style|outline-offset|grid-column-start";
#define CP_BACKGROUND 1
#define CP_BACKGROUND_IMAGE 2
#define CP_BACKGROUND_REPEAT 3
@@ -3749,7 +3966,13 @@ char css_prop_names[] = "background|background-image|background-repeat|backgroun
#define CP_FLEX_SHRINK 105
#define CP_MASK_IMAGE 106
#define CP_WEBKIT_MASK_IMAGE 107
#define CP_N 107
#define CP_OUTLINE 108
#define CP_OUTLINE_WIDTH 109
#define CP_OUTLINE_COLOR 110
#define CP_OUTLINE_STYLE 111
#define CP_OUTLINE_OFFSET 112
#define CP_GRID_COLUMN_START 113
#define CP_N 113
dword css_prop_h[CP_N + 1]; // css_hash() of each name of the list, filled on the first call
dword css_prop_at[CP_N + 1]; // where the name starts in css_prop_names
bool css_prop_ready;
@@ -4054,6 +4277,9 @@ void css_apply_decl(dword _idx, _prop, _val)
IF (!css_var_expand(_val, #resolved, sizeof(resolved))) return;
_val = #resolved;
}
// a value we cannot read is dropped as by an old browser, the one before it stays:
// "background: #ccc; background: color-mix(in oklab, ...)" keeps #ccc
IF (css_unknown_color(_val, 0)) return;
IF (css_ps) {
strlwr(_val);
css_pseudo_decl(node, _prop, pid, _val);
@@ -4088,6 +4314,15 @@ void css_apply_decl(dword _idx, _prop, _val)
{
// background: may hold "url(...) #fff repeat": the first word that is a color
DSDWORD[node + S_BG] = css_bg_color(_val);
// a translucent one keeps its alpha: a positioned box paints it over what is
// under it (paint.h cv_blend_bar), the "Plus" link of tailwindcss.com under
// its 10% sky tint, not hidden by the tint blended over white
DSDWORD[node + S_BGA] = 0;
IF (DSDWORD[node + S_BG] != NO_COLOR) && (css_alpha_last < 255) && (!strstr(_val, "gradient(")) {
c = css_alpha_last << 24;
c |= css_rgb_last;
DSDWORD[node + S_BGA] = c;
}
}
break;
CASE CP_BACKGROUND_IMAGE:
@@ -4095,7 +4330,8 @@ void css_apply_decl(dword _idx, _prop, _val)
// a gradient over the background color shows its own color instead
IF (strstr(_val, "gradient(")) {
c = css_bg_color(_val);
IF (c != NO_COLOR) DSDWORD[node + S_BG] = c;
// opaque: an rgba() of background-color before it is not blended
IF (c != NO_COLOR) { DSDWORD[node + S_BG] = c; DSDWORD[node + S_BGA] = 0; }
}
}
break;
@@ -4114,6 +4350,19 @@ void css_apply_decl(dword _idx, _prop, _val)
IF (strstr(_val, "underline")) DSDWORD[node + S_FLAGS] |= SF_UNDER;
IF (strstr(_val, "line-through")) DSDWORD[node + S_FLAGS] |= SF_STRIKE;
IF (streq(_val, "none")) sf_clear(node, SF_UNDER);
// "a { text-decoration: inherit }" of Tailwind's reset: a link is underlined
// only when the text around it is (the menu of tailwindcss.com was)
if (streq(_val, "inherit")) {
sf_clear(node, SF_UNDER);
sf_clear(node, SF_STRIKE);
c = dom_nparent(_idx);
IF (c) {
c = dom_n(c);
c = DSDWORD[c + S_FLAGS];
c &= SF_UNDER | SF_STRIKE;
DSDWORD[node + S_FLAGS] |= c;
}
}
}
break;
// the places in the lists are ALIGN_* and VALIGN_* + 1
@@ -4129,6 +4378,8 @@ void css_apply_decl(dword _idx, _prop, _val)
// baseline undoes the super / sub a <sup> passes down
IF (c == 6) c = 1;
IF (c) DSDWORD[node + S_VALIGN] = c - 1;
sf_clear(node, SF_VMID);
IF (c == 2) DSDWORD[node + S_FLAGS] |= SF_VMID;
}
break;
CASE CP_WIDTH:
@@ -4247,6 +4498,10 @@ void css_apply_decl(dword _idx, _prop, _val)
break;
CASE CP_GRID_COLUMN:
DSDWORD[node + S_COLSPAN] = css_grid_span(_val);
DSDWORD[node + S_GCOL] = css_grid_line(_val);
break;
CASE CP_GRID_COLUMN_START:
DSDWORD[node + S_GCOL] = css_grid_line(_val);
break;
CASE CP_GRID_TEMPLATE:
css_grid_template(node, _val);
@@ -4555,6 +4810,41 @@ void css_apply_decl(dword _idx, _prop, _val)
}
}
break;
// outline: a frame drawn outside the box, over what is around, taking no room.
// Solid whatever its style; a focus ring (":focus") never gets here
CASE CP_OUTLINE:
CASE CP_OUTLINE_WIDTH:
{
len = css_border_width(_val);
IF (len >= 0) DSDWORD[node + S_OUTW] = math.min(len, 8);
IF (pid == CP_OUTLINE) {
c = css_border_color(_val);
IF (c != NO_COLOR) DSDWORD[node + S_OUTC] = c;
}
}
break;
CASE CP_OUTLINE_COLOR:
{
c = css_border_color(_val);
IF (c != NO_COLOR) DSDWORD[node + S_OUTC] = c;
}
break;
CASE CP_OUTLINE_STYLE:
{
IF (strstr(_val, "none")) || (strstr(_val, "hidden")) DSDWORD[node + S_OUTW] = 0;
ELSE IF (!DSDWORD[node + S_OUTW]) DSDWORD[node + S_OUTW] = 1;
}
break;
CASE CP_OUTLINE_OFFSET:
{
IF (!strchr(_val, '%')) {
len = css_length(_val);
IF (len > 16) len = 16;
IF (len < -16) len = -16;
DSDWORD[node + S_OUTOFF] = len;
}
}
break;
CASE CP_BORDER_COLOR:
{
c = css_border_color(_val);
@@ -5240,7 +5530,20 @@ bool is_push_button(dword _idx, _t)
// TYPE=SUBMIT: form.h lowercases it too
strlcpy(#t, v, 15);
strlwr(#t);
IF (css_kw(#t, "submit|button|reset|image")) RETURN true;
IF (css_kw(#t, "submit|button|reset")) RETURN true;
RETURN false;
}
// <input type=image>: a picture that submits its form, drawn like an <img>
bool is_image_input(dword _idx)
{
dword v;
char t[16];
v = dom_attr(_idx, "type");
IF (!v) RETURN false;
strlcpy(#t, v, 15);
strlwr(#t);
IF (streq(#t, "image")) RETURN true;
RETURN false;
}
@@ -5444,6 +5747,11 @@ void style_compute(dword _idx, _parent)
DSDWORD[node + S_MRPCT] = 0;
DSDWORD[node + S_AREAS] = 0;
DSDWORD[node + S_GAREA] = 0;
DSDWORD[node + S_OUTW] = 0;
DSDWORD[node + S_OUTC] = NO_COLOR;
DSDWORD[node + S_OUTOFF] = 0;
DSDWORD[node + S_GCOL] = 0;
DSDWORD[node + S_BGA] = 0;
// inherited
DSDWORD[node + S_COLOR] = DSDWORD[pn + S_COLOR];
@@ -5614,6 +5922,7 @@ void style_compute(dword _idx, _parent)
DSDWORD[node + S_FLAGS] |= SF_BUTTON;
v = dom_attr(_idx, "type");
IF (v) && (streq(v, "hidden")) DSDWORD[node + S_DISPLAY] = D_NONE;
IF (t == T_INPUT) && (is_image_input(_idx)) sf_clear(node, SF_BUTTON);
break;
case T_OPTION:
DSDWORD[node + S_DISPLAY] = D_NONE;
@@ -6067,6 +6376,8 @@ void style_tree()
DSDWORD[node + S_DISPLAY] = D_BLOCK;
text_all = 0;
text_tiny = 0;
text_big = 0;
text_big_clear();
css_areas_used = 0;
c = dom_nfirst(dom.root);
WHILE (c) {
+1 -1
View File
@@ -25,7 +25,7 @@
<pre>By the way,
<font color="#555555">&bull; You can check for browser updates from the main menu.
<font color="#555">&bull; You can check for browser updates from the main menu.
&bull; Pressing F6 moves the text cursor to the omnibox.
&bull; You can manually change the encoding of a page by clicking on the label in the bottom right corner.
&bull; Need better web support? Use Netsurf or WebKit.
+3 -3
View File
@@ -17,7 +17,7 @@
<font bg=#F8F15B> web <font bg=#FF5A7E color=#fff> 2.0 <font bg=#47D018> compatible&nbsp;
<font bg=#3CE7FF> </font></font></font></font>
<kosicon n=6><a href="//ftp.kolibrios.org/users/Leency/test_files/showroom.htm">Escaparate</a>
<kosicon n=6><a href=//ftp.kolibrios.org/users/Leency/test_files/showroom.htm>Escaparate</a>
<kosicon n=73><a href=//builds.kolibrios.org/ci/latest/>Compilaciones nocturnas</a>
<td><pre>
@@ -25,7 +25,7 @@
<pre>Por cierto,
<font color="#555555">&bull; Puede buscar actualizaciones del navegador desde el menu principal.
<font color="#555">&bull; Puede buscar actualizaciones del navegador desde el menu principal.
&bull; La tecla F6 mueve el cursor de texto a la barra de direcciones.
&bull; La codificacion de la pagina se cambia pulsando la etiqueta de la esquina inferior derecha.
&bull; Necesita mejor soporte web? Use Netsurf o WebKit.
@@ -40,7 +40,7 @@
' |
() .-.,="``"=. - o -
'=/_ \ |
* | '=.<a href=//ftp.kolibrios.org/users/Leency/test_files/links.htm>&nbsp;</a> |
* | '=._ |
\ `=./`, '
. '=.__.=' `=' *
+ +
+1 -1
View File
@@ -25,7 +25,7 @@
Кстати,
<font color="#555555">&bull; Из главного меню можно проверить наличие обновлений.
<font color="#555">&bull; Из главного меню можно проверить наличие обновлений.
&bull; Клавиша F6 перемещает текстовый курсор в омнибокс.
&bull; Кодировку страницы можно изменить в правом нижнем углу окна.
&bull; Нужна лучшая поддержка вэба? Используйте Netsurf или WebKit.
+1 -1
View File
@@ -1,7 +1,7 @@
<title>Page not found</title>
<body bgcolor=#ccc>
<br>
<table border=2 cellpadding=20 width=80% align=center bgcolor=#fff><tr><td>
<table border=2 cellpadding=20 width=80% align=center bgcolor=#fff>
<h3><kosicon n=67> Webpage Is Not Available</h3>
<pre>
If there is no Internet connection, the easiest way to get one is to plug a phone into this PC/laptop over a USB cable and enable USB tethering (modem mode) on the phone.
+1 -1
View File
@@ -1,7 +1,7 @@
<title>Pagina no encontrada</title>
<body bgcolor=#ccc>
<br>
<table border=2 cellpadding=20 width=80% align=center bgcolor=#fff><tr><td>
<table border=2 cellpadding=20 width=80% align=center bgcolor=#fff>
<h3><kosicon n=67> La pagina no se puede mostrar</h3>
<pre>
Si no hay conexion a Internet, puede conectar un telefono a este PC por USB y activar en el telefono el anclaje a red (modo modem).
+33
View File
@@ -0,0 +1,33 @@
<html><head><title>gridfit</title>
<!-- needs a picture at /hd0/1/pic.png (like noscript.htm) -->
<style>
body { margin:0; background:#eee; }
/* virtualbox.org: a "5vh" bar, the logo "height: 100%" of it, an icon picked by a
selector of four child combinators ("height: 2vw") */
#bar { height:5vh; display:flex; align-items:center; justify-content:space-between; background:#fff; border-bottom:1px solid #bbb; }
#bar li { height:100%; list-style-type:none; margin:0 1vw; }
#bar > ul { display:flex; align-items:center; margin:0 4vw 0 0; padding:0; }
#logo, #logo img { display:block; height:100%; width:auto; }
#bar > ul > li.first > a > img { height:2vw; }
/* theguardian.com: six 280px cards in "repeat(6, 1fr)" of a narrower row */
.wrap { max-width:740px; margin:12px auto; padding:8px 20px; background:#fff; }
.car { display:grid; gap:20px; padding:0; margin:0; list-style:none; grid-auto-flow:column; overflow-x:auto; grid-template-columns:repeat(6, 1fr); }
.card { display:flex; flex-direction:row; justify-content:space-between; column-gap:8px; width:280px; padding:10px 10px 0 10px; background:#fdecea; }
.txt { display:flex; flex-direction:column; padding-bottom:10px; }
.hl { font-size:17px; margin:0; font-weight:normal; }
.media { flex-shrink:0; width:60px; height:60px; margin-bottom:10px; background:#c77a3c; }
</style></head><body>
<div id="bar"><li><a id="logo" href="#"><img alt="logo" src="/hd0/1/pic.png"></a></li>
<ul><li><a href="#">Home</a></li><li><a href="#">Download</a></li><li><a href="#">Documentation</a></li>
<li class="first"><a href="#"><img alt="user" src="/hd0/1/pic.png"></a></li></ul></div>
<p>The bar above is one line: a short blue logo, the links, a small icon.</p>
<div class="wrap"><ul class="car">
<li><div style="display:contents"><div class="card"><div class="txt"><h3 class="hl">One: the headline reads across the card</h3></div><div class="media"></div></div></div></li>
<li><div style="display:contents"><div class="card"><div class="txt"><h3 class="hl">Two: the headline reads across the card</h3></div><div class="media"></div></div></div></li>
<li><div style="display:contents"><div class="card"><div class="txt"><h3 class="hl">Three: the headline reads across the card</h3></div><div class="media"></div></div></div></li>
<li><div style="display:contents"><div class="card"><div class="txt"><h3 class="hl">Four: the headline reads across the card</h3></div><div class="media"></div></div></div></li>
<li><div style="display:contents"><div class="card"><div class="txt"><h3 class="hl">Five: the headline reads across the card</h3></div><div class="media"></div></div></div></li>
<li><div style="display:contents"><div class="card"><div class="txt"><h3 class="hl">Six: the headline reads across the card</h3></div><div class="media"></div></div></div></li>
</ul></div>
<p>Cards above: as many 280px columns as fit (two in a 740px row), not six letter columns.</p>
</body></html>
+200 -47
View File
@@ -325,68 +325,221 @@ void _http::receive()
ESBYTE[d] = 0;
}
:int check_is_the_url_absolute(dword _in)
:bool url_is_alpha(dword _c)
{
if(!strncmp(_in,"file:",5)) return true;
if(!strncmp(_in,"ftp:",4)) return true;
if(!strncmp(_in,"http:",5)) return true;
if(!strncmp(_in,"https:",6)) return true;
if(!strncmp(_in,"mailto:",7)) return true;
if(!strncmp(_in,"tel:",4)) return true;
if(!strncmp(_in,"#",1)) return true;
if(!strncmp(_in,"WebView:",8)) return true;
if(check_is_the_adress_local(_in)) return true;
if (_c >= 'a') && (_c <= 'z') return true;
if (_c >= 'A') && (_c <= 'Z') return true;
return false;
}
// the length of "scheme:" at the start of _u (RFC 3986: a letter, then letters,
// digits, "+", "-" or "."), 0 when there is none. Any case: "HTTPS://" is absolute too
:dword url_scheme_len(dword _u)
{
dword p, c;
if (!url_is_alpha(ESBYTE[_u])) return 0;
p = _u + 1;
loop() {
c = ESBYTE[p];
if (c == ':') break;
if (!url_is_alpha(c)) {
if (c < '0') && (c != '+') && (c != '-') && (c != '.') return 0;
if (c > '9') return 0;
}
p++;
}
p++;
return p - _u;
}
// the length of "scheme:" of an address on the net ("scheme://host"), 0 for any other
:dword url_net_scheme_len(dword _u)
{
dword i, p;
i = url_scheme_len(_u);
if (!i) return 0;
p = _u + i;
if (ESBYTE[p] != '/') return 0;
if (ESBYTE[p+1] != '/') return 0;
return i;
}
// where the path of _u starts: after "scheme://host:port", after "scheme:" for one
// without a host (mailto:), 0 for a path of its own ("/hd0/1/x.htm")
:dword url_path_start(dword _u)
{
dword p, c;
p = url_scheme_len(_u);
if (!p) return 0;
p += _u;
if (ESBYTE[p] != '/') return p - _u;
if (ESBYTE[p+1] != '/') return p - _u;
p += 2;
loop() {
c = ESBYTE[p];
if (!c) break;
if (c == '/') || (c == '?') || (c == '#') break;
p++;
}
return p - _u;
}
// cut the query and the fragment off _u, looking from offset _from on
:void url_cut_query(dword _u, _from)
{
dword p, c;
p = _u + _from;
loop() {
c = ESBYTE[p];
if (!c) return;
if (c == '?') || (c == '#') { ESBYTE[p] = 0; return; }
p++;
}
}
// "&amp;" -> "&" in place, in one pass
:void url_unamp(dword _u)
{
dword s, d;
if (!strstr(_u, "&amp;")) return;
s = _u;
d = _u;
while (ESBYTE[s]) {
ESBYTE[d] = ESBYTE[s];
d++;
if (ESBYTE[s] == '&') && (ESBYTE[s+1] == 'a') && (ESBYTE[s+2] == 'm') && (ESBYTE[s+3] == 'p') && (ESBYTE[s+4] == ';') s += 5;
else s++;
}
ESBYTE[d] = 0;
}
// a path segment of _n chars at _s: 1 for ".", 2 for "..", 0 for a name
:int url_dot_seg(dword _s, _n)
{
if (ESBYTE[_s] != '.') return 0;
if (_n == 1) return 1;
if (_n != 2) return 0;
if (ESBYTE[_s+1] != '.') return 0;
return 2;
}
// the "." and ".." segments of the path of _u (from offset _from to the query) resolved
// (RFC 3986 5.2.4): "a/./b" is "a/b", "a/b/../c" is "a/c", and ".." never climbs above
// the root ("http://a.com/../x" is "http://a.com/x", not "http:/x")
:void url_remove_dots(dword _u, _from)
{
char out[URL_SIZE+1];
dword r, w, e, root, c, n;
int dots;
bool slash;
// root: where ".." stops. Not "root = w" after "if (..) w++": c-- took the old w
r = _u + _from;
w = #out;
root = #out;
if (ESBYTE[r] == '/') {
out[0] = '/';
root = #out + 1;
w = #out + 1;
r = _u + _from + 1;
}
loop() {
c = ESBYTE[r];
if (!c) break;
if (c == '?') || (c == '#') break;
e = r;
loop() {
c = ESBYTE[e];
if (!c) break;
if (c == '/') || (c == '?') || (c == '#') break;
e++;
}
n = e - r;
slash = false;
if (ESBYTE[e] == '/') slash = true;
// a helper, not "if (n == 1) ... else if (n == 2)": c-- dropped the second test
dots = url_dot_seg(r, n);
if (dots == 1) {
r = e;
} else if (dots == 2) {
// drop the segment written last, with its slash
if (w > root) {
w--;
while (w > root) && (ESBYTE[w-1] != '/') w--;
}
r = e;
} else {
while (r < e) { ESBYTE[w] = ESBYTE[r]; w++; r++; }
if (slash) { ESBYTE[w] = '/'; w++; }
}
if (slash) r++;
}
// the query and the fragment as they are
while (ESBYTE[r]) { ESBYTE[w] = ESBYTE[r]; w++; r++; }
ESBYTE[w] = 0;
w = _u + _from;
strcpy(w, #out);
}
:int check_is_the_url_absolute(dword _in)
{
if (url_scheme_len(_in)) return true;
if (ESBYTE[_in] == '#') return true;
if (check_is_the_adress_local(_in)) return true;
return false;
}
// new_URL (a buffer of URL_SIZE+1 bytes) made absolute against base_URL, in place:
// RFC 3986 5.2 for "//host/x", "/x", "?q" and "x", with the dot segments resolved.
// A scheme ("HTTP:", "data:"), a local path ("/hd0/1/x") or "#frag" stays as it is.
:dword get_absolute_url(dword new_URL, base_URL)
{
int i;
dword orig_URL = new_URL;
char newurl[URL_SIZE+1];
strcpy(#newurl, base_URL);
dword ps, i, c;
while (i=strstr(new_URL, "&amp;")) strcpy(i+1, i+5);
url_unamp(new_URL);
strip_file_scheme(new_URL);
url_escape_spaces(new_URL);
if (check_is_the_url_absolute(new_URL)) return new_URL;
switch(ESBYTE[new_URL]) {
case '.':
if (ESBYTE[new_URL+1] == '/') {
new_URL+=2;
}
else if (ESBYTE[new_URL+1] == '.') && (ESBYTE[new_URL+2] == '/') {
_GO_UP:
new_URL+=3;
newurl[strrchr(#newurl, '/')-1] = '\0';
}
goto _DEFAULT;
case '?':
strchr(#newurl+8, '?'); //returns EAX
if (EAX) ESBYTE[EAX] = '\0';
strchr(#newurl+8, '/'); //returns EAX
if (!EAX) chrcat(#newurl, '/');
break;
case '/':
new_URL++;
if (ESBYTE[new_URL] == '/') {
strcpy(#newurl, "http:/");
break;
}
EAX = strchr(#newurl+8, '/');
if (EAX) ESBYTE[EAX] = '\0';
default:
_DEFAULT:
EAX = strrchr(#newurl, '/');
if (newurl[EAX-2]!='/') newurl[EAX] = '\0';
if (!strncmp(new_URL,"../",3)) goto _GO_UP;
if (newurl[strlen(#newurl)-1]!='/') strncat(#newurl, "/", URL_SIZE);
strncpy(#newurl, base_URL, URL_SIZE);
ps = url_path_start(#newurl);
if (!ESBYTE[new_URL]) {
// href="": the page itself, without its fragment
i = strchr(#newurl, '#');
if (i) ESBYTE[i] = 0;
strncpy(new_URL, #newurl, URL_SIZE);
return new_URL;
}
url_cut_query(#newurl, ps);
c = ESBYTE[new_URL];
if (c == '/') && (ESBYTE[new_URL+1] == '/') {
// "//host/x": the scheme of a page on the net (https stays https). An internal
// page ("WebView:home") or a local one has no host: http, as it always was
i = url_net_scheme_len(#newurl);
if (i) newurl[i] = 0;
else strcpy(#newurl, "http:");
} else if (c == '/') {
newurl[ps] = 0;
} else if (c == '?') {
// "http://a.com" + "?q" is "http://a.com/?q"
if (ps) && (!newurl[ps]) chrcat(#newurl, '/');
} else {
// the directory of the page: up to its last slash
i = strlen(#newurl);
while (i > ps) {
if (newurl[i-1] == '/') break;
i--;
}
newurl[i] = 0;
if (i == ps) && (ps) chrcat(#newurl, '/');
}
strncat(#newurl, new_URL, URL_SIZE);
strncpy(orig_URL, #newurl, URL_SIZE);
return orig_URL;
ps = url_path_start(#newurl);
url_remove_dots(#newurl, ps);
strncpy(new_URL, #newurl, URL_SIZE);
return new_URL;
}