code wiki / _hdl_build / nx_srcset_lib.nx

nx_srcset_lib.nx source

↩ module page · 500 lines · 26533 B

1// nx_srcset_lib.nx -- BR14: CHOOSE THE ONE IMAGE URL A RENDER SHOULD FETCH FOR THIS ELEMENT AT THIS 2// VIEWPORT. The compare board's browser rung BR14 (symbol bi_srcset) and the named debt row 3// `images-lazy`: "Lazy-load / srcset / data-src images are not fetched ... so media-heavy pages render 4// most figures blank even though the decoders are byte-exact." The decoders were never the problem -- 5// br_extract only ever looked at the `src` attribute, so a lazy-loaded <img> handed the renderer a 6// `data:` placeholder (or nothing at all) and the real URL sat unread in srcset / data-src. 7// 8// WHAT THIS IS NOT: nx_img_harvest / nx_media_harvest COLLECT every image URL on a page (scraping -- 9// they want ALL srcset candidates). This SELECTS exactly one, for one element, at one viewport. Same 10// attribute, opposite question, so it is a second ruler for a second job, not a duplicate of the first. 11// nx_cms_image's img_srcset_widths GENERATES a srcset ladder for publishing -- the inverse direction. 12// 13// NO ALLOCATION. The selection needs no candidate list: "smallest candidate that still covers the 14// target, else the largest available" is two running extrema, computable in ONE pass over the 15// attribute bytes. Scratch is PASSED IN (BI_SCRATCH_SLOTS) so nothing here allocates per <img>. 16// 17// NO NEW BYTES. Every URL this returns is a byte range INTO THE CALLER'S OWN BUFFER (off,len), which 18// is exactly the contract br_extract/page.bsrc_off already speaks. A srcset candidate is a contiguous 19// substring of the attribute value, so selection is a pair of integers -- no copy, no arena, no cap. 20// 21// INTEGER ONLY. A pixel-density descriptor is written as a decimal ("1.5x"), so every density here is 22// PERMIL (1x = 1000, 1.5x = 1500, 2x = 2000). Permil is the estate's standing fixed-point unit and 23// three fractional digits covers every density any shipping site writes. 24// 25// DELIBERATE DIVERGENCES FROM THE HTML SPEC LETTER, each named so the next reader does not have to 26// rediscover which are bugs and which are choices: 27// * descriptor suffix accepted in EITHER case (`2X` as well as `2x`). The spec's parser is 28// lowercase-only. There is no other meaning for `2X`, and the divergence can only ever ADD a 29// picture, never remove one -- it fails in the direction of rendering more, not less. 30// * `sizes` is evaluated only in the two forms decidable WITHOUT a media-query engine (a bare 31// `<n>px` and a bare `<n>vw`). Anything carrying a media condition or a list REFUSES BY NAME 32// (BI_SIZES_UNPARSED) and falls back to HTML's own default source size of 100vw, so a caller can 33// report that it did not honour the author's sizes instead of silently pretending it did. 34// * fractional digits past permil resolution are TRUNCATED toward zero, never rounded, so a 35// density can only ever select a LARGER candidate than the exact value would -- again the 36// direction that renders more. 37import "nx_syscalls.nx" 38import "nx_dom_query.nx" // nx_dom_find_attr -- the estate's ONE attribute reader. Composed, not re-written. 39 40// ---- ASCII bytes, NAMED. A string literal spelled as character codes is unreadable and ungreppable; 41// ---- the estate has eaten that defect once already, so every byte this parser tests has a name. 42const BI_TAB: i64 = 9 43const BI_LF: i64 = 10 44const BI_FF: i64 = 12 45const BI_CR: i64 = 13 46const BI_SPACE: i64 = 32 47const BI_COMMA: i64 = 44 48const BI_DOT: i64 = 46 49const BI_ZERO: i64 = 48 50const BI_NINE: i64 = 57 51const BI_UC_A: i64 = 65 52const BI_UC_Z: i64 = 90 53const BI_LC_P: i64 = 112 54const BI_LC_V: i64 = 118 55const BI_LC_W: i64 = 119 56const BI_LC_X: i64 = 120 57const BI_CASE_DELTA: i64 = 32 58 59// ---- fixed point ---------------------------------------------------------------------------------- 60const BI_PERMIL_DIGITS: i64 = 3 61const BI_PERMIL_ONE: i64 = 1000 62const BI_B10: i64 = 10 63// i64 represents every 18-digit decimal without exception (10^18 - 1 < 2^63 - 1); the 19th digit does 64// not. A parsed value is scaled by BI_PERMIL_ONE = 10^BI_PERMIL_DIGITS, so its integer part may carry 65// at most BI_SAFE_DIGITS - BI_PERMIL_DIGITS digits and still scale without overflow. THE BOUND IS 66// DERIVED FROM THE TYPE AND THE SCALE -- no ceiling was guessed, and exceeding it REFUSES the 67// candidate (announced by the DROP count) rather than wrapping in silence. 68const BI_SAFE_DIGITS: i64 = 18 69const BI_NUM_MAX_DIGITS: i64 = BI_SAFE_DIGITS - BI_PERMIL_DIGITS 70// 1vw is DEFINED as one hundredth of the viewport width. This is the unit's definition, not a tuned 71// number, and it is the only place a hundred appears in this file. 72const BI_VW_PER_VIEWPORT: i64 = 100 73 74// ---- scratch contract ----------------------------------------------------------------------------- 75// The caller owns the scratch and allocates it ONCE (br_extract already allocates its attribute 76// scratch outside its element loop). Slot map, so two functions can never silently share one: 77// sc[0] number/descriptor value out sc[1] attribute value offset out 78// sc[2] attribute value length out sc[3] bi_sizes_px verdict code out 79const BI_SCRATCH_SLOTS: i64 = 4 80const BI_SC_NUM: i64 = 0 81const BI_SC_AOFF: i64 = 1 82const BI_SC_ALEN: i64 = 2 83const BI_SC_SIZECODE: i64 = 3 84 85// ---- bi_srcset verdicts (>0 = a pick was made; <=0 = a NAMED refusal, caller falls back) ------------ 86const BI_NO_CANDIDATES: i64 = 0 87const BI_PICK_W: i64 = 1 // chosen by width descriptor against the derived target 88const BI_PICK_X: i64 = 2 // chosen by pixel-density descriptor against the device ratio 89const BI_MIXED_DESCRIPTORS: i64 = 0 - 2 // w and x in one list: invalid HTML, the WHOLE attribute is ignored 90const BI_BAD_TARGET: i64 = 0 - 3 // caller passed a non-positive size or density -- refuse, never guess 91 92// ---- descriptor kinds ------------------------------------------------------------------------------- 93const BI_DESC_DROP: i64 = 0 94const BI_DESC_W: i64 = 1 95const BI_DESC_X: i64 = 2 96 97// ---- bi_img_source verdicts: WHICH RULE FIRED. A feature that announces itself is a feature whose 98// ---- ABSENCE is visible in normal output, not only its failure. 99const BI_SRC_NONE: i64 = 0 // this element carries no usable source at all 100const BI_SRC_SRCSET: i64 = 1 101const BI_SRC_SRC: i64 = 2 // the plain eager path -- byte-for-byte what br_extract chose before 102const BI_SRC_DATA_SRCSET: i64 = 3 103const BI_SRC_DATA_ATTR: i64 = 4 104const BI_SRC_PLACEHOLDER: i64 = 5 // ONLY a data: src existed; returned unchanged, exactly as before 105 106// ---- bi_sizes_px verdicts --------------------------------------------------------------------------- 107const BI_SIZES_DEFAULT: i64 = 0 // no sizes attribute -- HTML's own default source size is 100vw 108const BI_SIZES_PX: i64 = 1 109const BI_SIZES_VW: i64 = 2 110const BI_SIZES_UNPARSED: i64 = 3 // a media condition or list this lib does not evaluate -> 100vw 111 112func bi_lc(c: i64) -> i64 { if c >= BI_UC_A { if c <= BI_UC_Z { return c + BI_CASE_DELTA } } return c } 113 114// HTML whitespace is exactly these five bytes (tab, LF, FF, CR, space). Not "anything below space": 115// a NUL or a control byte inside an attribute is NOT a separator, and treating it as one would split 116// one URL into two silently. 117func bi_is_ws(c: i64) -> i64 { 118 if c == BI_SPACE { return 1 } 119 if c == BI_TAB { return 1 } 120 if c == BI_LF { return 1 } 121 if c == BI_FF { return 1 } 122 if c == BI_CR { return 1 } 123 return 0 124} 125 126func bi_is_digit(c: i64) -> i64 { if c >= BI_ZERO { if c <= BI_NINE { return 1 } } return 0 } 127 128func bi_has_dot(buf: *u8, a: i64, b: i64) -> i64 { 129 var i: i64 = a 130 while i < b { 131 if (buf[i] as i64) == BI_DOT { return 1 } 132 i = i + 1 133 } 134 return 0 135} 136 137// Parse buf[a..b) as a non-negative decimal, into PERMIL. Returns 1 only if the WHOLE span is a 138// number -- trailing junk is a refusal, never a partial read, because a partial read of "400wide" 139// would silently mean 400. The loop carries an explicit `stop` flag rather than exiting by clobbering 140// its own cursor: a loop that breaks by destroying `i` cannot also report where it stopped, and this 141// one has to (the fractional part resumes from exactly there). 142func bi_num_permil(buf: *u8, a: i64, b: i64, out: *i64) -> i64 { 143 out[0] = 0 144 if b <= a { return 0 } 145 var i: i64 = a 146 var whole: i64 = 0 147 var nwhole: i64 = 0 148 var stop: i64 = 0 149 while stop == 0 { 150 if i >= b { stop = 1 } 151 if stop == 0 { 152 let c: i64 = buf[i] as i64 153 if bi_is_digit(c) == 1 { 154 if nwhole >= BI_NUM_MAX_DIGITS { return 0 } 155 whole = whole * BI_B10 + (c - BI_ZERO) 156 nwhole = nwhole + 1 157 i = i + 1 158 } else { stop = 1 } 159 } 160 } 161 if nwhole == 0 { return 0 } 162 var frac: i64 = 0 163 var scale: i64 = BI_PERMIL_ONE 164 var dotted: i64 = 0 165 if i < b { if (buf[i] as i64) == BI_DOT { dotted = 1; i = i + 1 } } 166 if dotted == 1 { 167 var ndig: i64 = 0 168 var stop2: i64 = 0 169 while stop2 == 0 { 170 if i >= b { stop2 = 1 } 171 if stop2 == 0 { 172 let c2: i64 = buf[i] as i64 173 if bi_is_digit(c2) == 1 { 174 if scale > 1 { scale = scale / BI_B10; frac = frac + (c2 - BI_ZERO) * scale } 175 // digits past permil resolution are BELOW what this fixed point can express and 176 // are dropped -- truncation toward zero, never rounding. 177 ndig = ndig + 1 178 i = i + 1 179 } else { stop2 = 1 } 180 } 181 } 182 if ndig == 0 { return 0 } // "1." is not a number 183 } 184 if i != b { return 0 } 185 out[0] = whole * BI_PERMIL_ONE + frac 186 return 1 187} 188 189// Classify one descriptor token buf[a..b). out[0] = value: WHOLE PIXELS for w, PERMIL for x. 190// An EMPTY descriptor is not an error -- HTML gives such a candidate a density of exactly 1x, and 191// that default is what makes "a.jpg 400w, b.jpg" a MIXED list rather than a two-width one. 192// An `h` descriptor (removed from the spec years ago) and anything else DROPS the candidate. 193func bi_desc(buf: *u8, a: i64, b: i64, out: *i64) -> i64 { 194 out[0] = 0 195 if b <= a { out[0] = BI_PERMIL_ONE; return BI_DESC_X } 196 let last: i64 = bi_lc(buf[b - 1] as i64) 197 let nb: i64 = b - 1 198 if last == BI_LC_W { 199 // a width descriptor is a valid non-negative INTEGER; a fractional width is a parse error. 200 if bi_has_dot(buf, a, nb) == 1 { return BI_DESC_DROP } 201 if bi_num_permil(buf, a, nb, out) == 0 { return BI_DESC_DROP } 202 out[0] = out[0] / BI_PERMIL_ONE 203 if out[0] <= 0 { return BI_DESC_DROP } 204 return BI_DESC_W 205 } 206 if last == BI_LC_X { 207 if bi_num_permil(buf, a, nb, out) == 0 { return BI_DESC_DROP } 208 if out[0] <= 0 { return BI_DESC_DROP } 209 return BI_DESC_X 210 } 211 return BI_DESC_DROP 212} 213 214// ---- bi_srcset: THE RULER BR14 NAMES ----------------------------------------------------------------- 215// Parse the srcset attribute VALUE at buf[off..off+len) and select ONE candidate for a box that will 216// occupy `size_px` CSS pixels on a display of `dpr_permil` device pixels per CSS pixel. 217// 218// THE SELECTION RULE IS DERIVED, NOT A BREAKPOINT TABLE: 219// target_device_px = size_px * dpr_permil / BI_PERMIL_ONE 220// and then, within whichever descriptor kind the list uses, THE SMALLEST CANDIDATE THAT STILL COVERS 221// THE TARGET WINS; if every candidate is smaller than the target, the LARGEST one wins. That is the 222// whole rule: never fetch more pixels than the target needs, never leave the best available unused. 223// Nothing here knows what 640 or 1280 mean -- change the viewport and the answer changes with it. 224// 225// TIES ARE DEFINED: on equal descriptor values the FIRST candidate in document order wins, so the 226// result is stable under list order and two runs over the same bytes cannot disagree. 227// 228// out_off/out_len are a byte range INTO buf. Returns BI_PICK_W / BI_PICK_X on a pick, or a NAMED 229// refusal <= 0 with out_off = -1 so a caller that ignores the code still cannot use a bad range. 230func bi_srcset(buf: *u8, off: i64, len: i64, size_px: i64, dpr_permil: i64, sc: *i64, out_off: *i64, out_len: *i64) -> i64 { 231 out_off[0] = 0 - 1 232 out_len[0] = 0 233 if len <= 0 { return BI_NO_CANDIDATES } 234 if size_px <= 0 { return BI_BAD_TARGET } 235 if dpr_permil <= 0 { return BI_BAD_TARGET } 236 let end: i64 = off + len 237 let target_px: i64 = (size_px * dpr_permil) / BI_PERMIL_ONE 238 var has_w: i64 = 0 239 var has_x: i64 = 0 240 // Running extrema per descriptor kind -- the reason no candidate list is stored. 241 var wge_v: i64 = 0 242 var wge_o: i64 = 0 - 1 243 var wge_l: i64 = 0 244 var wmx_v: i64 = 0 245 var wmx_o: i64 = 0 - 1 246 var wmx_l: i64 = 0 247 var xge_v: i64 = 0 248 var xge_o: i64 = 0 - 1 249 var xge_l: i64 = 0 250 var xmx_v: i64 = 0 251 var xmx_o: i64 = 0 - 1 252 var xmx_l: i64 = 0 253 var pos: i64 = off 254 var done: i64 = 0 255 while done == 0 { 256 // 1. skip the run of whitespace and commas that separates candidates 257 var skipping: i64 = 1 258 while skipping == 1 { 259 skipping = 0 260 if pos < end { 261 let c: i64 = buf[pos] as i64 262 if bi_is_ws(c) == 1 { pos = pos + 1; skipping = 1 } 263 if c == BI_COMMA { pos = pos + 1; skipping = 1 } 264 } 265 } 266 if pos >= end { done = 1 } 267 if done == 0 { 268 // 2. the URL is the run up to the next whitespace. A URL may legally CONTAIN a comma, so 269 // it is NOT comma-terminated -- splitting on commas here is the classic srcset bug and 270 // it truncates every URL carrying one. 271 let ustart: i64 = pos 272 var scan: i64 = pos 273 var running: i64 = 1 274 while running == 1 { 275 running = 0 276 if scan < end { if bi_is_ws(buf[scan] as i64) == 0 { scan = scan + 1; running = 1 } } 277 } 278 pos = scan 279 var uend: i64 = scan 280 // 3. trailing commas belong to the SEPARATOR, not the URL, and their presence means this 281 // candidate has NO descriptor. 282 var trailing: i64 = 0 283 var trimming: i64 = 1 284 while trimming == 1 { 285 trimming = 0 286 if uend > ustart { if (buf[uend - 1] as i64) == BI_COMMA { uend = uend - 1; trailing = 1; trimming = 1 } } 287 } 288 var dstart: i64 = uend 289 var dend: i64 = uend 290 var dropped: i64 = 0 291 if trailing == 0 { 292 // 4. skip whitespace between URL and descriptor 293 var ws2: i64 = 1 294 while ws2 == 1 { 295 ws2 = 0 296 if pos < end { if bi_is_ws(buf[pos] as i64) == 1 { pos = pos + 1; ws2 = 1 } } 297 } 298 // 5. the descriptor is the run up to whitespace or comma 299 dstart = pos 300 var dscan: i64 = pos 301 var drun: i64 = 1 302 while drun == 1 { 303 drun = 0 304 if dscan < end { 305 let dc: i64 = buf[dscan] as i64 306 if bi_is_ws(dc) == 0 { if dc != BI_COMMA { dscan = dscan + 1; drun = 1 } } 307 } 308 } 309 dend = dscan 310 pos = dscan 311 // 6. whatever follows must be whitespace then a comma or the end. A SECOND descriptor 312 // token is a parse error -- and the rest of the candidate must be CONSUMED, not just 313 // refused. FOUND BY THIS LIB'S OWN GATE, 2026-08-26: stopping at the first descriptor 314 // left "2x" of "/a.png 100w 2x" unread, the loop re-entered URL collection on it, and 315 // a bare DESCRIPTOR was selected as if it were an image URL. The HTML descriptor 316 // tokenizer runs to the comma for exactly this reason, so this one does too. 317 var ws3: i64 = 1 318 while ws3 == 1 { 319 ws3 = 0 320 if pos < end { if bi_is_ws(buf[pos] as i64) == 1 { pos = pos + 1; ws3 = 1 } } 321 } 322 if pos < end { if (buf[pos] as i64) != BI_COMMA { 323 dropped = 1 324 var eat: i64 = 1 325 while eat == 1 { 326 eat = 0 327 if pos < end { if (buf[pos] as i64) != BI_COMMA { pos = pos + 1; eat = 1 } } 328 } 329 } } 330 } 331 if uend > ustart { 332 if dropped == 0 { 333 let kind: i64 = bi_desc(buf, dstart, dend, sc) 334 let val: i64 = sc[BI_SC_NUM] 335 if kind == BI_DESC_W { 336 has_w = 1 337 if val > wmx_v { wmx_v = val; wmx_o = ustart; wmx_l = uend - ustart } 338 if val >= target_px { if wge_o < 0 { wge_v = val; wge_o = ustart; wge_l = uend - ustart } else { if val < wge_v { wge_v = val; wge_o = ustart; wge_l = uend - ustart } } } 339 } 340 if kind == BI_DESC_X { 341 has_x = 1 342 if val > xmx_v { xmx_v = val; xmx_o = ustart; xmx_l = uend - ustart } 343 if val >= dpr_permil { if xge_o < 0 { xge_v = val; xge_o = ustart; xge_l = uend - ustart } else { if val < xge_v { xge_v = val; xge_o = ustart; xge_l = uend - ustart } } } 344 } 345 } 346 } 347 // a candidate that consumed no bytes would loop forever; the separator skip at the top of 348 // the next turn always advances because `pos` is past at least one non-whitespace byte here. 349 if pos <= ustart { pos = ustart + 1 } 350 } 351 } 352 // A list carrying BOTH kinds is invalid HTML and the whole attribute is in error. Refusing it is 353 // the fail-safe direction: the caller falls back to `src`, which is what happens today. 354 if has_w == 1 { if has_x == 1 { return BI_MIXED_DESCRIPTORS } } 355 if has_w == 1 { 356 if wge_o >= 0 { out_off[0] = wge_o; out_len[0] = wge_l; return BI_PICK_W } 357 if wmx_o >= 0 { out_off[0] = wmx_o; out_len[0] = wmx_l; return BI_PICK_W } 358 } 359 if has_x == 1 { 360 if xge_o >= 0 { out_off[0] = xge_o; out_len[0] = xge_l; return BI_PICK_X } 361 if xmx_o >= 0 { out_off[0] = xmx_o; out_len[0] = xmx_l; return BI_PICK_X } 362 } 363 return BI_NO_CANDIDATES 364} 365 366// ---- bi_sizes_px: the CSS width this image is expected to occupy ------------------------------------ 367// HTML says an <img> with no `sizes` attribute has a source size of 100vw -- i.e. the viewport width. 368// That default is not a guess this lib made up, it is the spec's own value, and it is what makes the 369// width-descriptor branch above derivable from the caller's viewport alone. 370// Evaluated forms: a bare `<n>px` and a bare `<n>vw`. Anything else (a media condition, a list, calc()) 371// REFUSES BY NAME -- sc[BI_SC_SIZECODE] carries BI_SIZES_UNPARSED and the return value is the 100vw 372// default, so a caller can SAY it did not honour the author's sizes rather than pretend it did. 373func bi_sizes_px(buf: *u8, off: i64, len: i64, vw_px: i64, sc: *i64) -> i64 { 374 sc[BI_SC_SIZECODE] = BI_SIZES_DEFAULT 375 if len <= 0 { return vw_px } 376 if vw_px <= 0 { return vw_px } 377 var a: i64 = off 378 var b: i64 = off + len 379 var t1: i64 = 1 380 while t1 == 1 { t1 = 0; if a < b { if bi_is_ws(buf[a] as i64) == 1 { a = a + 1; t1 = 1 } } } 381 var t2: i64 = 1 382 while t2 == 1 { t2 = 0; if b > a { if bi_is_ws(buf[b - 1] as i64) == 1 { b = b - 1; t2 = 1 } } } 383 if b <= a { sc[BI_SC_SIZECODE] = BI_SIZES_UNPARSED; return vw_px } 384 // A comma or a parenthesis means a source-size LIST or a media condition. This lib has no 385 // media-query engine, and guessing which arm applies would be a confident wrong answer. 386 var i: i64 = a 387 while i < b { 388 let c: i64 = buf[i] as i64 389 if c == BI_COMMA { sc[BI_SC_SIZECODE] = BI_SIZES_UNPARSED; return vw_px } 390 if c == 40 { sc[BI_SC_SIZECODE] = BI_SIZES_UNPARSED; return vw_px } 391 i = i + 1 392 } 393 if b - a < 3 { sc[BI_SC_SIZECODE] = BI_SIZES_UNPARSED; return vw_px } 394 let u0: i64 = bi_lc(buf[b - 2] as i64) 395 let u1: i64 = bi_lc(buf[b - 1] as i64) 396 var unit: i64 = BI_SIZES_UNPARSED 397 if u1 == BI_LC_X { if u0 == BI_LC_P { unit = BI_SIZES_PX } } 398 if u1 == BI_LC_W { if u0 == BI_LC_V { unit = BI_SIZES_VW } } 399 if unit == BI_SIZES_UNPARSED { sc[BI_SC_SIZECODE] = BI_SIZES_UNPARSED; return vw_px } 400 if bi_num_permil(buf, a, b - 2, sc) == 0 { sc[BI_SC_SIZECODE] = BI_SIZES_UNPARSED; return vw_px } 401 let n: i64 = sc[BI_SC_NUM] / BI_PERMIL_ONE 402 if n <= 0 { sc[BI_SC_SIZECODE] = BI_SIZES_UNPARSED; return vw_px } 403 sc[BI_SC_SIZECODE] = unit 404 if unit == BI_SIZES_PX { return n } 405 return (n * vw_px) / BI_VW_PER_VIEWPORT 406} 407 408// ---- bi_is_placeholder: is this src a PARKING SPOT rather than a picture? --------------------------- 409// A lazy-loader parks a `data:` URI (usually a 1x1 gif) in `src` precisely so the real URL can live in 410// data-src / srcset. An empty src is the same statement with fewer bytes. Recognising this is the 411// difference between "this element has a source" and "this element has a source WE CAN RENDER", and 412// mistaking one for the other is exactly the blank-figure defect BR14 names. 413func bi_is_placeholder(buf: *u8, off: i64, len: i64) -> i64 { 414 if len <= 0 { return 1 } 415 if off < 0 { return 1 } 416 if len < 5 { return 0 } 417 let d: *u8 = "data:" as *u8 418 var i: i64 = 0 419 while i < 5 { 420 if bi_lc(buf[off + i] as i64) != (d[i] as i64) { return 0 } 421 i = i + 1 422 } 423 return 1 424} 425 426// ---- bi_img_lazy: does this element declare loading="lazy"? ------------------------------------------ 427// DELIBERATELY A REPORT, NEVER A FILTER. A renderer that paints the WHOLE document at once (a 428// screenshot, a reftest, a scrolling page held in one framebuffer) must not let this suppress a fetch -- 429// suppressing it is how a page ends up with the exact blank figures this rung exists to remove. Its 430// use is ORDERING: with a BOUNDED fetch budget, spend it on eager images before lazy ones, because the 431// eager ones are the ones above the fold. 432func bi_img_lazy(buf: *u8, tag_off: i64, tag_len: i64, sc: *i64) -> i64 { 433 if nx_dom_find_attr(buf, tag_off, tag_len, "loading\x00" as *u8, ((sc as i64) + BI_SC_AOFF * 8) as *i64, ((sc as i64) + BI_SC_ALEN * 8) as *i64) == 0 { return 0 } 434 let o: i64 = sc[BI_SC_AOFF] 435 let l: i64 = sc[BI_SC_ALEN] 436 if l != 4 { return 0 } 437 let w: *u8 = "lazy" as *u8 438 var i: i64 = 0 439 while i < 4 { 440 if bi_lc(buf[o + i] as i64) != (w[i] as i64) { return 0 } 441 i = i + 1 442 } 443 return 1 444} 445 446// ---- bi_img_source: THE ELEMENT-LEVEL DECISION ------------------------------------------------------- 447// Given an <img> element's tag region buf[tag_off..tag_off+tag_len), return the byte range of the URL a 448// renderer should fetch. ORDER, and why each step is a REFUSAL of the one before it: 449// 1. srcset -- the author's own responsive set, selected by bi_srcset against `sizes` (or the 450// spec's 100vw default) and the device pixel ratio. This is what a browser does 451// FIRST, and it is why an <img> carrying both srcset and src takes the candidate. 452// 2. src -- unless it is a PLACEHOLDER, in which case the real URL is somewhere else and 453// returning the placeholder is what renders the figure blank. 454// 3. data-srcset -- the lazy-loader's parked responsive set: same ruler, different attribute. 455// 4. data-src / data-original / data-lazy-src -- the three lazy-loader conventions that between them 456// cover the shipping libraries. Each is tried by NAME; nothing is guessed from a 457// prefix, so a `data-srcbase` or a `data-original-title` can never be mistaken for 458// an image URL. 459// 5. the placeholder src, returned UNCHANGED -- so an element with nothing better is handed to the 460// consumer exactly as it was before this lib existed. 461// 462// THE EAGER PATH IS BYTE-IDENTICAL: an <img> carrying only a plain non-placeholder `src` takes step 2 463// and no other step runs, so it selects precisely the byte range br_extract selected before BR14. 464func bi_img_source(buf: *u8, tag_off: i64, tag_len: i64, vw_px: i64, dpr_permil: i64, sc: *i64, out_off: *i64, out_len: *i64) -> i64 { 465 out_off[0] = 0 - 1 466 out_len[0] = 0 467 let ao: *i64 = ((sc as i64) + BI_SC_AOFF * 8) as *i64 468 let al: *i64 = ((sc as i64) + BI_SC_ALEN * 8) as *i64 469 // the intended layout width: the author's `sizes` when this lib can evaluate it, else 100vw. 470 var size_px: i64 = vw_px 471 if nx_dom_find_attr(buf, tag_off, tag_len, "sizes\x00" as *u8, ao, al) == 1 { 472 size_px = bi_sizes_px(buf, ao[0], al[0], vw_px, sc) 473 } 474 if nx_dom_find_attr(buf, tag_off, tag_len, "srcset\x00" as *u8, ao, al) == 1 { 475 if bi_srcset(buf, ao[0], al[0], size_px, dpr_permil, sc, out_off, out_len) > 0 { return BI_SRC_SRCSET } 476 } 477 var have_src: i64 = 0 478 var src_o: i64 = 0 - 1 479 var src_l: i64 = 0 480 if nx_dom_find_attr(buf, tag_off, tag_len, "src\x00" as *u8, ao, al) == 1 { 481 have_src = 1 482 src_o = ao[0] 483 src_l = al[0] 484 if bi_is_placeholder(buf, src_o, src_l) == 0 { out_off[0] = src_o; out_len[0] = src_l; return BI_SRC_SRC } 485 } 486 if nx_dom_find_attr(buf, tag_off, tag_len, "data-srcset\x00" as *u8, ao, al) == 1 { 487 if bi_srcset(buf, ao[0], al[0], size_px, dpr_permil, sc, out_off, out_len) > 0 { return BI_SRC_DATA_SRCSET } 488 } 489 if nx_dom_find_attr(buf, tag_off, tag_len, "data-src\x00" as *u8, ao, al) == 1 { 490 if bi_is_placeholder(buf, ao[0], al[0]) == 0 { out_off[0] = ao[0]; out_len[0] = al[0]; return BI_SRC_DATA_ATTR } 491 } 492 if nx_dom_find_attr(buf, tag_off, tag_len, "data-original\x00" as *u8, ao, al) == 1 { 493 if bi_is_placeholder(buf, ao[0], al[0]) == 0 { out_off[0] = ao[0]; out_len[0] = al[0]; return BI_SRC_DATA_ATTR } 494 } 495 if nx_dom_find_attr(buf, tag_off, tag_len, "data-lazy-src\x00" as *u8, ao, al) == 1 { 496 if bi_is_placeholder(buf, ao[0], al[0]) == 0 { out_off[0] = ao[0]; out_len[0] = al[0]; return BI_SRC_DATA_ATTR } 497 } 498 if have_src == 1 { out_off[0] = src_o; out_len[0] = src_l; return BI_SRC_PLACEHOLDER } 499 return BI_SRC_NONE 500}