;;;; Drawing a number, now that the language can make one into a string. ;;;; ;;;; This file used to be the workaround for a gap: five of the ten ported ;;;; examples call raylib's `TextFormat` to put a number on the screen, and ;;;; nothing in Flan could reach `draw-text` with one — `i64->bytes` answered a ;;;; `[u8]`, `draw-text` wanted a `string`, and there was no bridge and no ;;;; allocator to build one in. So a number was drawn one glyph at a time out ;;;; of a `[10 string]` table. ;;;; ;;;; `(string b)` closed that. It is the mirror of `(bytes-view s)` and costs no ;;;; instructions — a `string` and a `[u8]` are the same 16-byte %slice — so ;;;; `(string (i64->bytes n))` draws in one call and the table, the per-glyph ;;;; pen and the digit arithmetic behind them are gone. ;;;; ;;;; What is left is the part `(string ...)` does not answer, which is ;;;; *formatting*: `i64->bytes` has no field width, so "%03i" still has to be ;;;; assembled, and `f64->bytes` is `%g` and not "%.02f", so a fixed number of ;;;; decimal places still has to be split and drawn in two pieces. Those two ;;;; are why the file survives at all, and the three signatures are unchanged ;;;; so the five callers did not have to move. ;;;; ;;;; `TextFormat` itself is still not bindable, and not because of the FFI ;;;; rules: it is variadic, so its signature is not a signature, and it returns ;;;; a `char *` into a rotating static buffer, which `declare-c` refuses by ;;;; name anyway. ;;;; ;;;; `i64->bytes` and `f64->bytes` put their text in the temp allocator, where ;;;; it lasts until the next `(free-temp)`. A program drawing these every frame ;;;; calls `(free-temp)` once per frame, after drawing, and clones any text it ;;;; keeps longer. ;;;; ;;;; Everything here needs a window: `measure-text` answers 0 for every string ;;;; until init-window has loaded the default font, and a zero advance would ;;;; stack every piece on top of the first. import rl "vendor:raylib" ;; Padding is drawn from a literal, one zero at a time. This is the last of the ;; old glyph table and it is here only because there is no field width. const zero-glyph = "0" ;; One piece of text, and how far the pen moved. raylib's default font is not ;; monospaced — "1" is narrower than "8" — so the advance is measured rather ;; than assumed. ;; ;; Nothing may be formatted between the draw and the measure: `s` may be a view ;; of the shared buffer, and both calls have to see the same bytes. fn draw-piece(s: str, x: i32, y: i32, size: i32, color: rl/Color) -> i32 rl/draw-text(s, x, y, size, color) rl/measure-text(s, size) ;; How many decimal digits `n` has, for n >= 0. 0 has one. Only the padded ;; forms need it now — it is how many zeroes go in front. fn digit-count(n: i32) -> i32 let d = 1 r = n / 10 while r > 0 d += 1 r /= 10 d fn pow10(e: i32) -> i32 let p = 1 for i in range(e) p *= 10 p ;; The whole number, in one draw-text. Answers the width drawn, so a caller can ;; put something after it — which is how the `TextFormat("%s: %i", ...)` shapes ;; in the C are reassembled here: draw the literal part with draw-text, then ;; this at x plus its width. ;; ;; The sign comes free now: i64->bytes renders "-7" itself, which also retires ;; the old note about i32's most negative value — it is widened to i64 before ;; formatting, so there is no negation to wrap. fn draw-int(n: i32, x: i32, y: i32, size: i32, color: rl/Color) -> i32 draw-piece(str(i64->bytes(i64(n))), x, y, size, color) ;; The same with a fixed minimum number of digits — the C's "%03i". A number ;; wider than `width` is drawn in full rather than truncated, which is what ;; printf does too. ;; ;; The zeroes are drawn first and the number after, so the one formatted value ;; is still live when it is drawn. A sign goes in front of the padding, as ;; printf's "%03i" does for -7 → "-07"; hence the magnitude is what gets ;; counted and the minus is drawn separately. fn draw-int-padded(n: i32, width: i32, x: i32, y: i32, size: i32, color: rl/Color) -> i32 let cx = x v = n if v < 0 cx += draw-piece("-", cx, y, size, color) v = 0 - v let pad = width - digit-count(v) for i in range(pad) cx += draw-piece(zero-glyph, cx, y, size, color) cx += draw-int(v, cx, y, size, color) cx - x ;; "%.02f" and friends. `places` digits after the point, rounded by adding a ;; half at that scale before the split — so 0.999 at two places is "1.00" and ;; not "0.99", which is what the C's printf would have done and what a reader ;; comparing the two screens would expect. ;; ;; f32 and not f64 deliberately: every number this draws comes out of raylib, ;; and raylib's are floats. Widening them here would suggest a precision the ;; value does not have. ;; ;; `f64->bytes` is not used at all — it is "%g", which would print 0.5 for a ;; value asked for at three places and 1e+06 for a large one. The split into ;; two integers is what buys the fixed width, and it also keeps every formatted ;; value drawn before the next one is made. fn draw-f32(v: f32, places: i32, x: i32, y: i32, size: i32, color: rl/Color) -> i32 let cx = x av = v if av < 0.0 cx += draw-piece("-", cx, y, size, color) av = 0.0 - av let scale = pow10(places) ;; The rounding and the split happen in one integer so the two halves ;; cannot disagree: rounding them separately is how "0.999" becomes ;; "0.100" — the fraction carries and the whole part does not hear ;; about it. total = i32(av * f32(scale) + 0.5) whole = total / scale frac = total % scale cx += draw-int(whole, cx, y, size, color) if places > 0 cx += draw-piece(".", cx, y, size, color) cx += draw-int-padded(frac, places, cx, y, size, color) cx - x