nx_reveal_lib.nx source
↩ module page · 393 lines · 21668 B
1// nx_reveal_lib.nx -- THE ONE CLICK-TO-OPEN PRIMITIVE FOR PUBLIC BOARDS (ecosystem EC64, 2026-09-17).
2// Operator 2026-09-17: "i dont want nudity or other things shown without a clickthrough to open or expand on public boards"
3// and, of result pages, "i dont want them strewn about". Every emitter that puts result media on a public page composes
4// THIS lib, so the rule lives in the path and not in a seat's memory:
5// * a content CLASS is data on every media item; the class decides the MODE through the policy rows:
6// open = may render inline (charts, diagrams)
7// click = collapsed behind a native disclosure; nothing is drawn until the reader opens it, and with scripting
8// enabled a lazy image inside a closed disclosure is not fetched either
9// gated = never emitted on a public page: a named placeholder, and the href is NOT written (a URL is a leak)
10// * policy rows are DATA: class|<id>|<mode>|<rating>|<source>|<label>, the built-in rows below plus an optional file
11// (knowledge/reveal_policy.conf). THE FILE MAY ONLY TIGHTEN: the effective mode is the stricter of the two, a class the
12// file declares with rating adult can never be open, and a class NOBODY declares is gated. Wrong in the direction of
13// showing less, by construction.
14// * rating adult on any emitted item makes the page carry the rating meta the search engines' safe filters read.
15// * source is the IPTC digital source type term for the item (algorithmicMedia = "created purely by an algorithm not
16// based on any sampled training data", which is what the estate's SDF renders are), written as a data attribute so a
17// synthetic render says so in a machine-readable place as well as in words.
18// * rvl_audit is the VERIFIER: it counts media tags in an emitted page that sit outside a reveal block or an open
19// figure. Every consumer's gate holds that count at zero over its own output.
20// The disclosure is the platform's own (details/summary): keyboard operable, announced by assistive technology, and it
21// works with scripting off. A click-to-open is a CONTENT WARNING, NOT AGE ASSURANCE: that is why explicit media and nude
22// reference media of real people are gated off the public surface here rather than hidden behind a click.
23// license_tier: ORIGINAL No hw writes (Rule 26).
24import "nx_syscalls.nx"
25
26const RVL_MODE_OPEN: i64 = 0
27const RVL_MODE_CLICK: i64 = 1
28const RVL_MODE_GATED: i64 = 2
29const RVL_RATING_GENERAL: i64 = 0
30const RVL_RATING_ADULT: i64 = 1
31const RVL_OVERFLOW: i64 = 0 - 1
32const RVL_NL: i64 = 10
33const RVL_CR: i64 = 13
34const RVL_PIPE: i64 = 124
35const RVL_LT: i64 = 60
36const RVL_GT: i64 = 62
37const RVL_AMP: i64 = 38
38const RVL_QUOT: i64 = 34
39const RVL_APOS: i64 = 39
40const RVL_SLASH: i64 = 47
41const RVL_I64: i64 = 8
42const RVL_BYTE: i64 = 255
43// policy row fields after the leading "class" tag
44const RVL_F_ID: i64 = 1
45const RVL_F_MODE: i64 = 2
46const RVL_F_RATING: i64 = 3
47const RVL_F_SOURCE: i64 = 4
48const RVL_F_LABEL: i64 = 5
49const RVL_FIELDS: i64 = 6
50// a lookup record: found flag, mode, rating, then the source and label spans inside the policy buffer
51const RVL_L_FOUND: i64 = 0
52const RVL_L_MODE: i64 = 1
53const RVL_L_RATING: i64 = 2
54const RVL_L_SRC_S: i64 = 3
55const RVL_L_SRC_E: i64 = 4
56const RVL_L_LAB_S: i64 = 5
57const RVL_L_LAB_E: i64 = 6
58const RVL_L_N: i64 = 7
59// the emit tally a consumer prints on its receipt: the partition open + click + gated = items, unknown is a subset of gated
60const RVL_T_OPEN: i64 = 0
61const RVL_T_CLICK: i64 = 1
62const RVL_T_GATED: i64 = 2
63const RVL_T_UNKNOWN: i64 = 3
64const RVL_T_ADULT: i64 = 4
65const RVL_T_N: i64 = 5
66// the audit record
67const RVL_A_COVERED: i64 = 0
68const RVL_A_OPEN: i64 = 1
69const RVL_A_BARE: i64 = 2
70const RVL_A_BLOCKS: i64 = 3
71const RVL_A_N: i64 = 4
72const RVL_POLICY_FILE: *u8 = "knowledge/reveal_policy.conf"
73const RVL_POLICY_FILE_BUILDROOT: *u8 = "buildroot/knowledge/reveal_policy.conf"
74const RVL_POLICY_FILE_UP: *u8 = "../knowledge/reveal_policy.conf"
75const RVL_WHICH_NONE: i64 = 0
76const RVL_WHICH_HERE: i64 = 1
77const RVL_WHICH_BUILDROOT: i64 = 2
78const RVL_WHICH_UP: i64 = 3
79const RVL_BUILTIN: *u8 = "class|chart|open|general|algorithmicMedia|Chart or diagram drawn by the estate\nclass|face-synthetic|click|general|algorithmicMedia|Synthetic face render, generated by the estate\nclass|body-synthetic-clothed|click|general|algorithmicMedia|Synthetic body render, clothed, generated by the estate\nclass|reference-photo|click|general|digitalCapture|Reference photo of a real person, beta review only\nclass|measured-overlay|click|general|compositeCapture|Reference photo with the rulers' overlay, beta review only\nclass|model-3d|click|general|algorithmicMedia|3D model viewer, opens on request\nclass|unclassified-image|click|general||Image whose publisher declared no content class\nclass|anatomy-synthetic|click|adult|algorithmicMedia|Synthetic anatomical render, may show an unclothed body\nclass|nudity-synthetic|click|adult|algorithmicMedia|Synthetic render containing nudity\nclass|nudity-reference|gated|adult|digitalCapture|Reference media containing nudity, withheld from public pages\nclass|explicit|gated|adult|algorithmicMedia|Explicit media, withheld from public pages\n"
80const RVL_CLASS_TAG: *u8 = "class"
81const RVL_W_OPEN: *u8 = "open"
82const RVL_W_CLICK: *u8 = "click"
83const RVL_W_GATED: *u8 = "gated"
84const RVL_W_ADULT: *u8 = "adult"
85const RVL_UNKNOWN_LABEL: *u8 = "Undeclared content class, withheld from public pages until a policy row names it"
86const RVL_BLOCK_OPEN: *u8 = "<details class=\"nx-reveal\""
87const RVL_BLOCK_CLOSE: *u8 = "</details>"
88const RVL_FIG_OPEN: *u8 = "<figure class=\"nx-open\""
89const RVL_FIG_CLOSE: *u8 = "</figure>"
90
91func rvl_slen(s: *u8) -> i64 { var n: i64 = 0; while ((s[n] as i64) & RVL_BYTE) != 0 { n = n + 1 } return n }
92// bounded append: RVL_OVERFLOW once the buffer would overflow, and every later append keeps returning it (the caller
93// checks ONCE at the end; a page that did not fit is refused, never truncated)
94func rvl_cat(out: *u8, o: i64, cap: i64, s: *u8) -> i64 {
95 if o < 0 { return RVL_OVERFLOW }
96 let n: i64 = rvl_slen(s)
97 if o + n + 1 > cap { return RVL_OVERFLOW }
98 var i: i64 = 0
99 while i < n { out[o + i] = s[i]; i = i + 1 }
100 out[o + n] = 0 as u8
101 return o + n
102}
103func rvl_esc_byte(out: *u8, o: i64, cap: i64, c: i64) -> i64 {
104 if o < 0 { return RVL_OVERFLOW }
105 if c == RVL_LT { return rvl_cat(out, o, cap, "<" as *u8) }
106 if c == RVL_GT { return rvl_cat(out, o, cap, ">" as *u8) }
107 if c == RVL_AMP { return rvl_cat(out, o, cap, "&" as *u8) }
108 if c == RVL_QUOT { return rvl_cat(out, o, cap, """ as *u8) }
109 if c == RVL_APOS { return rvl_cat(out, o, cap, "'" as *u8) }
110 if o + 2 > cap { return RVL_OVERFLOW }
111 out[o] = c as u8
112 out[o + 1] = 0 as u8
113 return o + 1
114}
115// HTML-escaped append of a span [s, e) of buf: text and attribute values both go through this
116func rvl_cat_span_esc(out: *u8, o: i64, cap: i64, buf: *u8, s: i64, e: i64) -> i64 {
117 var p: i64 = o
118 var i: i64 = s
119 while i < e {
120 p = rvl_esc_byte(out, p, cap, (buf[i] as i64) & RVL_BYTE)
121 i = i + 1
122 }
123 return p
124}
125func rvl_cat_esc(out: *u8, o: i64, cap: i64, s: *u8) -> i64 { return rvl_cat_span_esc(out, o, cap, s, 0, rvl_slen(s)) }
126
127// does buf[s, e) equal the literal
128func rvl_span_is(buf: *u8, s: i64, e: i64, lit: *u8) -> i64 {
129 let n: i64 = rvl_slen(lit)
130 if e - s != n { return 0 }
131 var i: i64 = 0
132 while i < n { if buf[s + i] != lit[i] { return 0 } i = i + 1 }
133 return 1
134}
135func rvl_mode_of_span(buf: *u8, s: i64, e: i64) -> i64 {
136 if rvl_span_is(buf, s, e, RVL_W_OPEN) == 1 { return RVL_MODE_OPEN }
137 if rvl_span_is(buf, s, e, RVL_W_CLICK) == 1 { return RVL_MODE_CLICK }
138 return RVL_MODE_GATED // "gated" and every word nobody declared
139}
140// find the row class|<cls>|... in a policy buffer; rec gets the parsed row. Returns 1 when found
141func rvl_find(pol: *u8, pn: i64, cls: *u8, rec: *i64) -> i64 {
142 rec[RVL_L_FOUND] = 0
143 let fs: *i64 = sys_mmap((RVL_FIELDS + 1) * RVL_I64) as *i64
144 var i: i64 = 0
145 while i < pn {
146 var e: i64 = i
147 var going: i64 = 1
148 while going == 1 {
149 if e >= pn { going = 0 } else {
150 let c: i64 = (pol[e] as i64) & RVL_BYTE
151 if c == RVL_NL { going = 0 } else { e = e + 1 }
152 }
153 }
154 var le: i64 = e
155 if le > i { if ((pol[le - 1] as i64) & RVL_BYTE) == RVL_CR { le = le - 1 } }
156 // split the line on pipes
157 var nf: i64 = 0
158 fs[0] = i
159 var k: i64 = i
160 while k < le {
161 if ((pol[k] as i64) & RVL_BYTE) == RVL_PIPE { if nf + 1 < RVL_FIELDS { nf = nf + 1; fs[nf] = k + 1 } }
162 k = k + 1
163 }
164 if nf + 1 == RVL_FIELDS {
165 if rvl_span_is(pol, fs[0], fs[1] - 1, RVL_CLASS_TAG) == 1 {
166 if rvl_span_is(pol, fs[RVL_F_ID], fs[RVL_F_ID + 1] - 1, cls) == 1 {
167 rec[RVL_L_FOUND] = 1
168 rec[RVL_L_MODE] = rvl_mode_of_span(pol, fs[RVL_F_MODE], fs[RVL_F_MODE + 1] - 1)
169 rec[RVL_L_RATING] = rvl_span_is(pol, fs[RVL_F_RATING], fs[RVL_F_RATING + 1] - 1, RVL_W_ADULT)
170 rec[RVL_L_SRC_S] = fs[RVL_F_SOURCE]
171 rec[RVL_L_SRC_E] = fs[RVL_F_SOURCE + 1] - 1
172 rec[RVL_L_LAB_S] = fs[RVL_F_LABEL]
173 rec[RVL_L_LAB_E] = le
174 return 1
175 }
176 }
177 }
178 i = e + 1
179 }
180 return 0
181}
182// the optional policy file: probed at the three places a caller's CWD can put it; which[0] says where it came from
183// (RVL_WHICH_NONE = built-in rows only). Returns the buffer (0 when absent) and its length in len[0]
184func rvl_policy_load(len: *i64, which: *i64) -> *u8 {
185 which[0] = RVL_WHICH_NONE
186 len[0] = 0
187 var buf: *u8 = sys_read_file(RVL_POLICY_FILE, len)
188 if (buf as i64) != 0 { if len[0] > 0 { which[0] = RVL_WHICH_HERE; return buf } }
189 buf = sys_read_file(RVL_POLICY_FILE_BUILDROOT, len)
190 if (buf as i64) != 0 { if len[0] > 0 { which[0] = RVL_WHICH_BUILDROOT; return buf } }
191 buf = sys_read_file(RVL_POLICY_FILE_UP, len)
192 if (buf as i64) != 0 { if len[0] > 0 { which[0] = RVL_WHICH_UP; return buf } }
193 len[0] = 0
194 return 0 as *u8
195}
196// THE DECISION: the effective record for a class. The built-in row and the file row are both consulted and the STRICTER
197// mode wins; an adult-rated class is never open; a class neither declares is gated and flagged unknown (returns 0).
198// rec spans point into srcbuf[0] (whichever buffer supplied the label)
199func rvl_decide(file: *u8, fn: i64, cls: *u8, rec: *i64, srcbuf: *i64) -> i64 {
200 let rb: *i64 = sys_mmap(RVL_L_N * RVL_I64) as *i64
201 let rf: *i64 = sys_mmap(RVL_L_N * RVL_I64) as *i64
202 let hb: i64 = rvl_find(RVL_BUILTIN, rvl_slen(RVL_BUILTIN), cls, rb)
203 var hf: i64 = 0
204 if (file as i64) != 0 { if fn > 0 { hf = rvl_find(file, fn, cls, rf) } }
205 if hb == 0 { if hf == 0 {
206 rec[RVL_L_FOUND] = 0
207 rec[RVL_L_MODE] = RVL_MODE_GATED
208 rec[RVL_L_RATING] = RVL_RATING_ADULT
209 rec[RVL_L_SRC_S] = 0; rec[RVL_L_SRC_E] = 0
210 rec[RVL_L_LAB_S] = 0; rec[RVL_L_LAB_E] = rvl_slen(RVL_UNKNOWN_LABEL)
211 srcbuf[0] = RVL_UNKNOWN_LABEL as i64
212 return 0
213 } }
214 var mode: i64 = RVL_MODE_OPEN
215 var rating: i64 = RVL_RATING_GENERAL
216 if hb == 1 { mode = rb[RVL_L_MODE]; rating = rb[RVL_L_RATING] }
217 if hf == 1 {
218 if rf[RVL_L_MODE] > mode { mode = rf[RVL_L_MODE] }
219 if hb == 0 { mode = rf[RVL_L_MODE] }
220 if rf[RVL_L_RATING] == RVL_RATING_ADULT { rating = RVL_RATING_ADULT }
221 }
222 if rating == RVL_RATING_ADULT { if mode == RVL_MODE_OPEN { mode = RVL_MODE_CLICK } }
223 rec[RVL_L_FOUND] = 1
224 rec[RVL_L_MODE] = mode
225 rec[RVL_L_RATING] = rating
226 // the label and source come from the file row when it has one (the operator's words), else the built-in row
227 if hf == 1 {
228 rec[RVL_L_SRC_S] = rf[RVL_L_SRC_S]; rec[RVL_L_SRC_E] = rf[RVL_L_SRC_E]
229 rec[RVL_L_LAB_S] = rf[RVL_L_LAB_S]; rec[RVL_L_LAB_E] = rf[RVL_L_LAB_E]
230 srcbuf[0] = file as i64
231 } else {
232 rec[RVL_L_SRC_S] = rb[RVL_L_SRC_S]; rec[RVL_L_SRC_E] = rb[RVL_L_SRC_E]
233 rec[RVL_L_LAB_S] = rb[RVL_L_LAB_S]; rec[RVL_L_LAB_E] = rb[RVL_L_LAB_E]
234 srcbuf[0] = RVL_BUILTIN as i64
235 }
236 return 1
237}
238func rvl_mode(file: *u8, fn: i64, cls: *u8) -> i64 {
239 let rec: *i64 = sys_mmap(RVL_L_N * RVL_I64) as *i64
240 let sb: *i64 = sys_mmap(RVL_I64) as *i64
241 rvl_decide(file, fn, cls, rec, sb)
242 return rec[RVL_L_MODE]
243}
244func rvl_rating(file: *u8, fn: i64, cls: *u8) -> i64 {
245 let rec: *i64 = sys_mmap(RVL_L_N * RVL_I64) as *i64
246 let sb: *i64 = sys_mmap(RVL_I64) as *i64
247 rvl_decide(file, fn, cls, rec, sb)
248 if rec[RVL_L_MODE] == RVL_MODE_GATED { return RVL_RATING_GENERAL } // a gated item puts nothing on the page
249 return rec[RVL_L_RATING]
250}
251func rvl_mode_name(m: i64) -> *u8 {
252 if m == RVL_MODE_OPEN { return RVL_W_OPEN }
253 if m == RVL_MODE_CLICK { return RVL_W_CLICK }
254 return RVL_W_GATED
255}
256func rvl_tally_clear(t: *i64) -> i64 { var i: i64 = 0; while i < RVL_T_N { t[i] = 0; i = i + 1 } return 0 }
257
258// the page head: the rating meta when any emitted item is adult-rated (pass the tally of a dry pass, or the OR of
259// rvl_rating over the rows the page will carry)
260func rvl_head_meta(out: *u8, o: i64, cap: i64, any_adult: i64) -> i64 {
261 if any_adult == 0 { return o }
262 return rvl_cat(out, o, cap, "<meta name=\"rating\" content=\"adult\">\n" as *u8)
263}
264// the stylesheet of the disclosure: colours are the house tokens with a fallback, so a page that defines the tokens
265// themes the block and a bare page still reads
266func rvl_css(out: *u8, o: i64, cap: i64) -> i64 {
267 var p: i64 = rvl_cat(out, o, cap, ".nx-reveal{border:1px solid var(--nx-line,rgb(42,51,64));border-radius:10px;background:var(--nx-panel,rgb(18,24,33));margin:.6rem 0}\n" as *u8)
268 p = rvl_cat(out, p, cap, ".nx-reveal>summary{cursor:pointer;list-style:none;display:flex;flex-wrap:wrap;gap:.5rem .8rem;align-items:baseline;padding:.7rem .9rem}\n" as *u8)
269 p = rvl_cat(out, p, cap, ".nx-reveal>summary::-webkit-details-marker{display:none}\n" as *u8)
270 p = rvl_cat(out, p, cap, ".nx-reveal>summary:focus-visible{outline:2px solid var(--nx-accent,rgb(124,196,255));outline-offset:2px;border-radius:8px}\n" as *u8)
271 p = rvl_cat(out, p, cap, ".nx-reveal-act{font-weight:600;border:1px solid currentColor;border-radius:999px;padding:.1rem .7rem;white-space:nowrap}\n" as *u8)
272 p = rvl_cat(out, p, cap, ".nx-reveal-act::before{content:\"Open\"}.nx-reveal[open] .nx-reveal-act::before{content:\"Close\"}\n" as *u8)
273 p = rvl_cat(out, p, cap, ".nx-reveal-cls{opacity:.85}.nx-reveal[data-rating=adult] .nx-reveal-cls{color:var(--nx-warn,rgb(255,180,84));font-weight:600}\n" as *u8)
274 p = rvl_cat(out, p, cap, ".nx-reveal-cap{opacity:.7;flex-basis:100%}\n" as *u8)
275 p = rvl_cat(out, p, cap, ".nx-reveal figure,.nx-open{margin:0;padding:0 .9rem .9rem}.nx-reveal img,.nx-open img{max-width:100%;height:auto;display:block;border-radius:6px}\n" as *u8)
276 p = rvl_cat(out, p, cap, ".nx-gated{border:1px dashed var(--nx-line,rgb(42,51,64));border-radius:10px;padding:.7rem .9rem;opacity:.8}\n" as *u8)
277 return p
278}
279// a gated item: a named placeholder. The href is deliberately not a parameter -- a gated URL cannot leak from a
280// function that never receives it
281func rvl_emit_gated(out: *u8, o: i64, cap: i64, file: *u8, fn: i64, cls: *u8, caption: *u8, tally: *i64) -> i64 {
282 let rec: *i64 = sys_mmap(RVL_L_N * RVL_I64) as *i64
283 let sb: *i64 = sys_mmap(RVL_I64) as *i64
284 let known: i64 = rvl_decide(file, fn, cls, rec, sb)
285 tally[RVL_T_GATED] = tally[RVL_T_GATED] + 1
286 if known == 0 { tally[RVL_T_UNKNOWN] = tally[RVL_T_UNKNOWN] + 1 }
287 var p: i64 = rvl_cat(out, o, cap, "<p class=\"nx-gated\" data-class=\"" as *u8)
288 p = rvl_cat_esc(out, p, cap, cls)
289 p = rvl_cat(out, p, cap, "\"><b>Withheld from this public page.</b> " as *u8)
290 p = rvl_cat_span_esc(out, p, cap, sb[0] as *u8, rec[RVL_L_LAB_S], rec[RVL_L_LAB_E])
291 p = rvl_cat(out, p, cap, ": " as *u8)
292 p = rvl_cat_esc(out, p, cap, caption)
293 p = rvl_cat(out, p, cap, "</p>\n" as *u8)
294 return p
295}
296// open a block for a non-gated item: a closed disclosure (click) or an open figure. The consumer writes its own media
297// tag between rvl_block_open and rvl_block_close. Callers ask rvl_mode FIRST and send gated items to rvl_emit_gated;
298// a gated class that reaches this function anyway is written as a gated placeholder and returns with mode_out[0] =
299// RVL_MODE_GATED so the consumer knows to write NO media and NO close
300func rvl_block_open(out: *u8, o: i64, cap: i64, file: *u8, fn: i64, cls: *u8, caption: *u8, tally: *i64, mode_out: *i64) -> i64 {
301 let rec: *i64 = sys_mmap(RVL_L_N * RVL_I64) as *i64
302 let sb: *i64 = sys_mmap(RVL_I64) as *i64
303 rvl_decide(file, fn, cls, rec, sb)
304 mode_out[0] = rec[RVL_L_MODE]
305 if rec[RVL_L_MODE] == RVL_MODE_GATED { return rvl_emit_gated(out, o, cap, file, fn, cls, caption, tally) }
306 if rec[RVL_L_RATING] == RVL_RATING_ADULT { tally[RVL_T_ADULT] = tally[RVL_T_ADULT] + 1 }
307 var p: i64 = o
308 if rec[RVL_L_MODE] == RVL_MODE_OPEN {
309 tally[RVL_T_OPEN] = tally[RVL_T_OPEN] + 1
310 p = rvl_cat(out, p, cap, RVL_FIG_OPEN)
311 } else {
312 tally[RVL_T_CLICK] = tally[RVL_T_CLICK] + 1
313 p = rvl_cat(out, p, cap, RVL_BLOCK_OPEN)
314 }
315 p = rvl_cat(out, p, cap, " data-class=\"" as *u8)
316 p = rvl_cat_esc(out, p, cap, cls)
317 p = rvl_cat(out, p, cap, "\" data-rating=\"" as *u8)
318 if rec[RVL_L_RATING] == RVL_RATING_ADULT { p = rvl_cat(out, p, cap, RVL_W_ADULT) } else { p = rvl_cat(out, p, cap, "general" as *u8) }
319 p = rvl_cat(out, p, cap, "\" data-digital-source-type=\"" as *u8)
320 p = rvl_cat_span_esc(out, p, cap, sb[0] as *u8, rec[RVL_L_SRC_S], rec[RVL_L_SRC_E])
321 p = rvl_cat(out, p, cap, "\">" as *u8)
322 if rec[RVL_L_MODE] == RVL_MODE_CLICK {
323 p = rvl_cat(out, p, cap, "<summary><span class=\"nx-reveal-act\"></span><span class=\"nx-reveal-cls\">" as *u8)
324 p = rvl_cat_span_esc(out, p, cap, sb[0] as *u8, rec[RVL_L_LAB_S], rec[RVL_L_LAB_E])
325 p = rvl_cat(out, p, cap, "</span><span class=\"nx-reveal-cap\">" as *u8)
326 p = rvl_cat_esc(out, p, cap, caption)
327 p = rvl_cat(out, p, cap, "</span></summary><figure>" as *u8)
328 }
329 return p
330}
331func rvl_block_close(out: *u8, o: i64, cap: i64, mode: i64, caption: *u8) -> i64 {
332 if mode == RVL_MODE_GATED { return o }
333 var p: i64 = rvl_cat(out, o, cap, "<figcaption>" as *u8)
334 p = rvl_cat_esc(out, p, cap, caption)
335 p = rvl_cat(out, p, cap, "</figcaption>" as *u8)
336 if mode == RVL_MODE_CLICK { p = rvl_cat(out, p, cap, "</figure>" as *u8); p = rvl_cat(out, p, cap, RVL_BLOCK_CLOSE) } else { p = rvl_cat(out, p, cap, RVL_FIG_CLOSE) }
337 return rvl_cat(out, p, cap, "\n" as *u8)
338}
339// one image, whole: the block, the lazy image, the caption. alt is the image's text alternative
340func rvl_emit_img(out: *u8, o: i64, cap: i64, file: *u8, fn: i64, cls: *u8, href: *u8, alt: *u8, caption: *u8, tally: *i64) -> i64 {
341 let md: *i64 = sys_mmap(RVL_I64) as *i64
342 var p: i64 = rvl_block_open(out, o, cap, file, fn, cls, caption, tally, md)
343 if md[0] == RVL_MODE_GATED { return p }
344 p = rvl_cat(out, p, cap, "<img src=\"" as *u8)
345 p = rvl_cat_esc(out, p, cap, href)
346 p = rvl_cat(out, p, cap, "\" alt=\"" as *u8)
347 p = rvl_cat_esc(out, p, cap, alt)
348 p = rvl_cat(out, p, cap, "\" loading=\"lazy\" decoding=\"async\">" as *u8)
349 return rvl_block_close(out, p, cap, md[0], caption)
350}
351
352// does html[i..] start with the literal
353func rvl_at(html: *u8, n: i64, i: i64, lit: *u8) -> i64 {
354 let m: i64 = rvl_slen(lit)
355 if i + m > n { return 0 }
356 var k: i64 = 0
357 while k < m { if html[i + k] != lit[k] { return 0 } k = k + 1 }
358 return 1
359}
360func rvl_media_tag_at(html: *u8, n: i64, i: i64) -> i64 {
361 if rvl_at(html, n, i, "<img" as *u8) == 1 { return 1 }
362 if rvl_at(html, n, i, "<video" as *u8) == 1 { return 1 }
363 if rvl_at(html, n, i, "<canvas" as *u8) == 1 { return 1 }
364 if rvl_at(html, n, i, "<object" as *u8) == 1 { return 1 }
365 if rvl_at(html, n, i, "<embed" as *u8) == 1 { return 1 }
366 if rvl_at(html, n, i, "<iframe" as *u8) == 1 { return 1 }
367 if rvl_at(html, n, i, "<picture" as *u8) == 1 { return 1 }
368 return 0
369}
370// THE VERIFIER: every media tag of an emitted page is counted as covered (inside a reveal block), open (inside an open
371// figure, a class the policy allows inline) or BARE. Returns the bare count; a consumer's gate holds it at zero
372func rvl_audit(html: *u8, n: i64, rep: *i64) -> i64 {
373 var k: i64 = 0
374 while k < RVL_A_N { rep[k] = 0; k = k + 1 }
375 var in_block: i64 = 0
376 var in_fig: i64 = 0
377 var i: i64 = 0
378 while i < n {
379 if ((html[i] as i64) & RVL_BYTE) == RVL_LT {
380 if rvl_at(html, n, i, RVL_BLOCK_OPEN) == 1 { in_block = 1; rep[RVL_A_BLOCKS] = rep[RVL_A_BLOCKS] + 1 }
381 if rvl_at(html, n, i, RVL_BLOCK_CLOSE) == 1 { in_block = 0 }
382 if rvl_at(html, n, i, RVL_FIG_OPEN) == 1 { in_fig = 1 }
383 if rvl_at(html, n, i, RVL_FIG_CLOSE) == 1 { if in_block == 0 { in_fig = 0 } }
384 if rvl_media_tag_at(html, n, i) == 1 {
385 if in_block == 1 { rep[RVL_A_COVERED] = rep[RVL_A_COVERED] + 1 } else {
386 if in_fig == 1 { rep[RVL_A_OPEN] = rep[RVL_A_OPEN] + 1 } else { rep[RVL_A_BARE] = rep[RVL_A_BARE] + 1 }
387 }
388 }
389 }
390 i = i + 1
391 }
392 return rep[RVL_A_BARE]
393}