clone

an offline sliding-block puzzle game in gleam and lustre, with an endless ladder of puzzles ranked by search

main
klot.ski / src / klotski / typeface.gleam
typeface.gleam 2.7K
1/// How wide a line of type is, and how tall.2///3/// A pure function of the face, the size and the tracking — a table lookup that4/// happens to live in a font file. The painters need it because several things5/// are positioned off the size type actually came out at rather than the size6/// it asked for: the title on the boot window sets the rule under it, and a7/// caption that had to shrink to fit its cell still has to be spaced from what8/// is above it.9///10/// Tiny5 is a bitmap face, so sizes are snapped to whole even pixels or its11/// stems come out uneven — which is `scene.snap`, and why nothing here ever12/// returns an odd number.13import gleam/float14import gleam/int15import klotski/scene.{type Face, Display}1617@external(javascript, "./typeface_ffi.mjs", "measure")18fn measure_ffi(text: String, size: Float, face: String, track: Float) -> Float1920fn face_name(face: Face) -> String {21 case face {22 Display -> "Tiny5"23 _ -> "DMMono"24 }25}2627/// The tracked width of a line, in layout units.28pub fn measure(text: String, size: Float, face: Face, track: Float) -> Float {29 measure_ffi(text, size, face_name(face), track)30}3132/// The height of a capital, as a fraction of the size asked for.33///34/// Everything in this app is set in capitals, so this — and not the em, and not35/// the ascent — is the height a line of type actually occupies. Stacking by the36/// em leaves a caption floating in space it does not fill; stacking by the cap37/// is how the space between two lines stays the space you asked for when the38/// faces change under it.39pub fn cap(face: Face) -> Float {40 case face {41 // Both are cut with a five-pixel capital, Tiny5 on an eight-pixel em and DM42 // Mono on a ten. Measured from the glyphs, not from the OS/2 table, which43 // several pixel faces fill in optimistically.44 Display -> 0.62545 _ -> 0.746 }47}4849/// The height of a capital at a given size, in layout units.50pub fn cap_height(size: Float, face: Face) -> Float {51 size *. cap(face)52}5354/// The largest snapped size at or below `size` whose tracked width fits.55pub fn fit_size(56 text: String,57 size: Float,58 face: Face,59 track: Float,60 max_w: Float,61) -> Float {62 shrink(scene.snap(size), text, face, track, max_w)63}6465fn shrink(66 size: Float,67 text: String,68 face: Face,69 track: Float,70 max_w: Float,71) -> Float {72 case size >. 4.0 && measure(text, size, face, track) >. max_w {73 True -> shrink(size -. 2.0, text, face, track, max_w)74 False -> size75 }76}7778/// A whole number of units, for the handful of places a measurement is used as79/// a coordinate rather than as a size.80pub fn units(value: Float) -> Int {81 float.round(value)82}8384pub fn scaled(value: Int, fraction: Float) -> Float {85 int.to_float(value) *. fraction86}