nx_imgattr_lib.nx
buildroot/runtime/nx_imgattr_lib.nx
about
nx_imgattr_lib.nx -- THE ONE PLACE THAT DECIDES HOW AN <img> IS WRITTEN, AND THE ONE PLACE THAT
READS ONE BACK.
MEASURED 2026-08-26 ON OUR OWN HEADLINE SURFACE, served live at /compare/koikatsu:
<img src='/compare/koikatsu/native_kk396262.png' alt='The card's own portrait -- the oracle we
match against' loading='lazy'>
The apostrophe came straight out of the .gallery caption DATA and was written raw into a
single-quoted attribute. Every real HTML parser ends alt at the two words -The card-, reads the
remainder as bogus attribute names, and the stray quote then swallows loading='lazy' as well. The
accessible name of that image is -The card-, and the browser hint that keeps 14 large PNGs off the
critical path is gone.
AND THE PAGE STILL VALIDATED GREEN. nx_page_verify's a11y check counts the byte pattern - alt=-
over the whole page and read 15 with-alt of 15 img. A SUBSTRING COUNTER CANNOT SEE ATTRIBUTE SCOPE,
so the emitter's defect and the ruler's blindness are ONE defect met twice -- and the ruler's
blindness is the half that let it ship and stay shipped.
SO THE WRITER AND THE READER LIVE IN ONE LIB. The function that WRITES an img and the function that
CHECKS an img share one definition of -an img with a usable alt-, and disagreement between them is
impossible by construction rather than by discipline.
THE INCUMBENT WAS EXTENDED, NOT DUPLICATED. nx_adnet_slot's aslot_cat_esc is the estate's existing
attribute escaper and its lit-flag shape is kept here verbatim. It is changed in exactly one
direction: it replaces an apostrophe with a SPACE, which is lossy -- -The card's- becomes -The
card s- -- and rule 25 says rewrite it better rather than strip it, so this escaper emits the
numeric character reference and KEEPS the byte.
NO GUESSED CEILING LIVES HERE. Every buffer this lib fills is sized by the caller from the strings
the caller already holds (ia_alt_cap_for / ia_img_cap_for), so there is no bound to tune in either
direction and none can silently truncate. The guards remain and ANNOUNCE, for a caller that sizes
its own buffer wrong.
DECLARED IMPRECISION, so the next reader does not trust this as exact: ia_scan is a LEXICAL scan.
It skips comments, script and style bodies, so markup quoted inside those cannot be miscounted, but
it does not build a tree -- an img inside a CDATA section or produced by script at runtime is
outside what it can see, and it reports on the bytes as served.
license_tier: ORIGINAL
dependencies 1 imports · 12 importers
diagram shows first 10 each side; +0 more imports, +2 more importers in the complete lists below.
imports: nx_syscalls.nx
imported by: nx_compare_shared_fit_lib_20260910.nxnx_imgattr_lib_gate.nxnx_models_page_emit.nxnx_page_verify.nxnx_swcompare_evidence_lib_t138.nxnx_swcompare_growth_candidate_20260910.nxnx_swcompare_heading_lib_20260907.nxnx_swcompare_ladder_candidate.nxnx_swcompare_lib.nxnx_swcompare_preview_lib_t56.nxnx_swcompare_production_lib_20260907.nxnx_swcompare_release_candidate.nx
structs
| none |
consts
| 42 | const IA_TAB: i64 = 9 |
| 43 | const IA_LF: i64 = 10 |
| 44 | const IA_CR: i64 = 13 |
| 45 | const IA_SP: i64 = 32 |
| 46 | const IA_DQ: i64 = 34 |
| 47 | const IA_HASH: i64 = 35 |
| 48 | const IA_AMP: i64 = 38 |
| 49 | const IA_SQ: i64 = 39 |
| 50 | const IA_HYPHEN: i64 = 45 |
| 51 | const IA_DOT: i64 = 46 |
| 52 | const IA_SLASH: i64 = 47 |
| 53 | const IA_DIGIT_3: i64 = 51 |
| 54 | const IA_DIGIT_9: i64 = 57 |
| 55 | const IA_SEMI: i64 = 59 |
| 56 | const IA_LT: i64 = 60 |
| 57 | const IA_EQ: i64 = 61 |
| 58 | const IA_GT: i64 = 62 |
| 59 | const IA_UPPER_A: i64 = 65 |
| 60 | const IA_UPPER_Z: i64 = 90 |
| 61 | const IA_USCORE: i64 = 95 |
| 62 | const IA_CASE_DELTA: i64 = 32 |
| 66 | const IA_MAX_EXPANSION: i64 = 6 |
| 68 | const IA_SEP_AND_NUL: i64 = 2 |
| 73 | const IA_TAG_OPEN: *u8 = "<img src='" as *u8 |
| 74 | const IA_TAG_MID: *u8 = "' alt='" as *u8 |
| 75 | const IA_TAG_ENDQ: *u8 = "'" as *u8 |
| 76 | const IA_TAG_CLOSE: *u8 = ">" as *u8 |
| 77 | const IA_IMG_OPEN: *u8 = "<img" as *u8 |
| 80 | const IA_S_IMGS: i64 = 0 |
| 81 | const IA_S_ALT_OK: i64 = 1 |
| 82 | const IA_S_SRC_OK: i64 = 2 |
| 83 | const IA_S_MALFORMED: i64 = 3 |
| 92 | const IA_S_QUOTEBREAK: i64 = 4 |
| 93 | const IA_SCAN_SLOTS: i64 = 5 |
| 94 | const IA_SLOT_BYTES: i64 = 8 |
functions
| 96 | func ia_slen(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n } |
| 98 | func ia_is_space(c: i64) -> i64 |
| 106 | func ia_lower(c: i64) -> i64 |
| 111 | func ia_put(dst: *u8, off: i64, c: i64) -> i64 { dst[off] = c as u8; return off + 1 } |
| 113 | func ia_cat(dst: *u8, off: i64, s: *u8, cap: i64) -> i64 |
| 133 | func ia_esc_attr(dst: *u8, off: i64, s: *u8, cap: i64, trunc: *i64) -> i64 |
| 163 | func ia_esc_text(dst: *u8, off: i64, s: *u8, cap: i64, trunc: *i64) -> i64 |
| 183 | func ia_attr_hostile(s: *u8) -> i64 called by 1: main |
| 197 | func ia_solid(p: *u8, a: i64, b: i64) -> i64 |
| 212 | func ia_alt_derive(caption: *u8, file: *u8, out: *u8, cap: i64) -> i64 called by 11: gal_html_headinggal_html_headingmainmaingal_html_headinggal_html_heading+5 calls 2: ia_slenia_solid |
| 257 | func ia_alt_cap_for(caption: *u8, file: *u8) -> i64 called by 11: gal_html_headinggal_html_headingmainmaingal_html_headinggal_html_heading+5 calls 1: ia_slen |
| 264 | func ia_img_cap_for(src: *u8, alt: *u8, extra: *u8) -> i64 called by 11: gal_html_headinggal_html_headingmainmaingal_html_headinggal_html_heading+5 calls 1: ia_slen |
| 277 | func ia_img_emit(dst: *u8, off: i64, cap: i64, src: *u8, alt: *u8, extra: *u8) -> i64 |
| 301 | func ia_starts_ci(p: *u8, at: i64, n: i64, lit: *u8) -> i64 |
| 312 | func ia_skip_past(p: *u8, from: i64, n: i64, lit: *u8) -> i64 |
| 324 | func ia_is_img_open(p: *u8, at: i64, n: i64) -> i64 |
| 335 | func ia_name_is(p: *u8, ns: i64, nlen: i64, lit: *u8) -> i64 |
| 352 | func ia_img_tag(p: *u8, at: i64, n: i64, res: *i64) -> i64 |
| 447 | func ia_scan(page: *u8, n: i64, out: *i64) -> i64 called by 2: ig_img_unreadablemain calls 6: sys_mmapia_starts_ciia_skip_pastia_slenia_is_img_openia_img_tag |