;;;; Drawing a number, because the language cannot make one into a string. ;;;; ;;;; Five of the ten ported examples call raylib's `TextFormat` to put a number ;;;; on the screen. Flan has no string formatting and no way to reach it: ;;;; ;;;; - `i64->bytes` is a builtin and answers a `[u8]`; ;;;; - `draw-text` takes a `string`; ;;;; - nothing converts a `[u8]` into a `string`. A string is a compile-time ;;;; literal or a parameter, and there is no allocator to build one in. ;;;; ;;;; `TextFormat` itself is not bindable either, and not because of the FFI ;;;; rules: it is variadic, so its signature is not a signature — declaring it ;;;; with fixed arguments would be a claim about the ABI that is false on every ;;;; target at once, and it returns a `char *` into a rotating static buffer, ;;;; which `declare-c` refuses by name anyway ("a string only crosses as a ;;;; parameter — a C function that *returns* one returns something Flan has no ;;;; owner for"). ;;;; ;;;; So a number reaches the screen one digit at a time, each digit drawn as a ;;;; one-character `string` out of the table below. sand.flan already does this ;;;; for a single digit with `draw-text-codepoint`; this is the same trick ;;;; generalised, in one place, so the gap shows up in the report as one gap ;;;; rather than as five separate improvisations. ;;;; ;;;; 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 digit on top of the first. (import rl "vendor:raylib") ;; A `[10 string]` — a fixed array whose element type is `string`. That works, ;; which is worth recording: a string is ptr+len and the array is ten of those ;; laid out flat, with the bytes themselves in the module's constant data. (defconst digit-glyphs [10 string] ["0" "1" "2" "3" "4" "5" "6" "7" "8" "9"]) (defconst minus-glyph "-") (defconst dot-glyph ".") ;; One glyph, 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, which is also what keeps the spacing identical to what ;; draw-text would have produced for the whole string at once. (defn draw-glyph [g string x i32 y i32 size i32 color rl/Color] i32 (rl/draw-text g x y size color) (rl/measure-text g size)) ;; How many decimal digits `n` has, for n >= 0. 0 has one. (defn digit-count [n i32] i32 (let [d 1 r (/ n 10)] (while (> r 0) (set d (+ d 1)) (set r (/ r 10))) d)) (defn pow10 [e i32] i32 (let [p 1] (dotimes [i e] (set p (* p 10))) p)) ;; The whole point of the file. 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. ;; ;; Negative numbers get the sign and then the magnitude. i32's most negative ;; value is NOT handled: negating it wraps to itself, so it would print its own ;; bit pattern with a minus in front. Nothing here ever reaches it — these are ;; screen coordinates, frame counts and axis readings — and guarding it would ;; be a branch that no call site can take. (defn draw-int [n i32 x i32 y i32 size i32 color rl/Color] i32 (let [cx x v n] (when (< v 0) (set cx (+ cx (draw-glyph minus-glyph cx y size color))) (set v (- 0 v))) (let [count (digit-count v)] (dotimes [i count] (let [d (% (/ v (pow10 (- (- count 1) i))) 10)] (set cx (+ cx (draw-glyph (at digit-glyphs d) cx y size color)))))) (- cx x))) ;; The same with a fixed number of leading zeroes — the C's "%03i". A number ;; wider than `width` is drawn in full rather than truncated, which is what ;; printf does too. (defn draw-int-padded [n i32 width i32 x i32 y i32 size i32 color rl/Color] i32 (let [cx x v n] (when (< v 0) (set cx (+ cx (draw-glyph minus-glyph cx y size color))) (set v (- 0 v))) (let [count (max width (digit-count v))] (dotimes [i count] (let [d (% (/ v (pow10 (- (- count 1) i))) 10)] (set cx (+ cx (draw-glyph (at digit-glyphs d) 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. (defn draw-f32 [v f32 places i32 x i32 y i32 size i32 color rl/Color] i32 (let [cx x av v] (when (< av 0.0) (set cx (+ cx (draw-glyph minus-glyph cx y size color))) (set 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)] (set cx (+ cx (draw-int whole cx y size color))) (when (> places 0) (set cx (+ cx (draw-glyph dot-glyph cx y size color))) (set cx (+ cx (draw-int-padded frac places cx y size color))))) (- cx x)))