Files

Fonts

A font file's names, weight and style, glyph count, which characters it covers, and how wide a text is set in it.

Version 1.0.0 · 4 functions · 1 crate

#fonts#files#uploads#text

Functions

What use fonts.qwer gives a program, with the types Quillo sees. To take only some, or under other names: use font_info, font_covers from fonts (Quillo 0.6).

font_info(path: str) → anyor an error

A font's details: { family, subfamily, full_name, postscript_name, version, weight, italic, bold, monospaced, variable, glyphs, characters, units_per_em, ascender, descender }. TrueType and OpenType (.ttf, .otf), and the first font of a collection (.ttc); weight from 100 (thin) to 900 (black), 400 regular; characters counts those it has a glyph for.

font_covers(path: str, text: str) → anyor an error

Whether the font has a glyph for every character of text (spaces and line breaks aside): { covers, missing }, missing the characters it lacks, each once. To pick a font for a name in another script, or check a font before using it in a PDF or an image.

font_ranges(path: str) → listor an error

The characters the font covers, as ranges { from, to, count } with from and to written "U+0041": "A" to "Z" is one range.

font_text_width(path: str, text: str, size: float) → floator an error

How wide text is when set in the font at size (pixels or points alike), from the glyphs' advances, without kerning: to fit a title in a box, or truncate it.

Get it

$ quillo install fonts               # fonts.qwer into this folder$ quillo install fonts --example     # and its example, fonts.qwe

Or with curl:

$ curl -O https://quillolang.org/plugins/files/fonts.qwer$ curl -O https://quillolang.org/plugins/files/fonts.qwe$ curl -O https://quillolang.org/plugins/files/fonts_sample.ttf

Then, in your program, all of it or only what you call:

main.qwe
use fonts.qweruse font_info, font_covers from fonts      // Quillo 0.6: only these

It uses this crate, which cargo fetches the first time the plugin is built:

[dependencies]
ttf-parser = "0.25"

Example

fonts.qwe
// A font picker's checks: what an uploaded font is, whether it can write a customer's name, and how wide a title is.////   quillo run fonts.qwe      then open http://localhost:8080/fontuse "fonts.qwer"let FONT = "fonts_sample.ttf"route GET /font = font_info(FONT)route GET /covers ? { text: str = "Quillo Café" } = font_covers(FONT, text)route GET /ranges = font_ranges(FONT)// Shrink a title until it fits 600 px.route GET /fit ? { title: str = "Spring Menu Tasting Evening" } {    let size = 96    for step in range(20) {        if font_text_width(FONT, title, size)? > 600 { size = size - 8 }    }    { title, size, width: font_text_width(FONT, title, size)? }}
$ quillo run fonts.qwe$ curl 'localhost:8080/font'$ curl 'localhost:8080/covers'$ curl 'localhost:8080/ranges'

Source

fonts.qwer
//! What a font file holds: its names, weight and style, glyph count, which characters it covers, and how wide a text is set in it.//! version: 1.0.0//!//! [dependencies]//! ttf-parser = "0.25"use serde_json::{json, Value};const MAX_BYTES: u64 = 64 * 1024 * 1024;fn load(path: &str) -> Result<Vec<u8>, String> {    let size = std::fs::metadata(path).map_err(|e| format!("{}: {}", path, e))?.len();    if size > MAX_BYTES {        return Err(format!("{}: larger than 64 MB", path));    }    std::fs::read(path).map_err(|e| format!("{}: {}", path, e))}fn face(data: &[u8]) -> Result<ttf_parser::Face<'_>, String> {    ttf_parser::Face::parse(data, 0).map_err(|e| format!("not a font it can read: {}", e))}fn name(f: &ttf_parser::Face, id: u16) -> Option<String> {    let names = f.names();    let mut fallback = None;    for n in names {        if n.name_id == id {            if let Some(s) = n.to_string() {                if n.language_id == 0x409 || n.language_id == 0 {                    return Some(s);                }                fallback.get_or_insert(s);            }        }    }    fallback}fn codepoints(f: &ttf_parser::Face) -> Vec<u32> {    let mut cps = vec![];    if let Some(cmap) = f.tables().cmap {        for sub in cmap.subtables {            if sub.is_unicode() {                sub.codepoints(|c| {                    if cps.len() < 2_000_000 {                        cps.push(c)                    }                });            }        }    }    cps.sort_unstable();    cps.dedup();    cps.retain(|&c| f.glyph_index(char::from_u32(c).unwrap_or('\0')).map_or(false, |g| g.0 != 0));    cps}/// A font's details: `{ family, subfamily, full_name, postscript_name, version, weight, italic, bold, monospaced, variable, glyphs, characters, units_per_em, ascender, descender }`./// TrueType and OpenType (.ttf, .otf), and the first font of a collection (.ttc); `weight` from 100 (thin) to 900 (black), 400 regular; `characters` counts those it has a glyph for.pub fn font_info(path: &str) -> Result<Value, String> {    let data = load(path)?;    let f = face(&data)?;    use ttf_parser::name_id::*;    Ok(json!({        "family": name(&f, TYPOGRAPHIC_FAMILY).or_else(|| name(&f, FAMILY)),        "subfamily": name(&f, TYPOGRAPHIC_SUBFAMILY).or_else(|| name(&f, SUBFAMILY)),        "full_name": name(&f, FULL_NAME),        "postscript_name": name(&f, POST_SCRIPT_NAME),        "version": name(&f, VERSION),        "weight": f.weight().to_number(),        "italic": f.is_italic(),        "bold": f.is_bold(),        "monospaced": f.is_monospaced(),        "variable": f.is_variable(),        "glyphs": f.number_of_glyphs(),        "characters": codepoints(&f).len(),        "units_per_em": f.units_per_em(),        "ascender": f.ascender(),        "descender": f.descender(),    }))}/// Whether the font has a glyph for every character of `text` (spaces and line breaks aside): `{ covers, missing }`, `missing` the characters it lacks, each once./// To pick a font for a name in another script, or check a font before using it in a PDF or an image.pub fn font_covers(path: &str, text: &str) -> Result<Value, String> {    let data = load(path)?;    let f = face(&data)?;    let mut missing: Vec<String> = vec![];    for c in text.chars().filter(|c| !c.is_whitespace() && !c.is_control()) {        if f.glyph_index(c).map_or(true, |g| g.0 == 0) && !missing.contains(&c.to_string()) {            missing.push(c.to_string());        }    }    Ok(json!({ "covers": missing.is_empty(), "missing": missing }))}/// The characters the font covers, as ranges `{ from, to, count }` with `from` and `to` written "U+0041": "A" to "Z" is one range.pub fn font_ranges(path: &str) -> Result<Vec<Value>, String> {    let data = load(path)?;    let f = face(&data)?;    let mut out: Vec<Value> = vec![];    let mut start: Option<(u32, u32)> = None;    let mut flush = |s: (u32, u32), out: &mut Vec<Value>| out.push(json!({ "from": format!("U+{:04X}", s.0), "to": format!("U+{:04X}", s.1), "count": s.1 - s.0 + 1 }));    for c in codepoints(&f) {        start = match start {            Some((a, b)) if c == b + 1 => Some((a, c)),            Some(s) => {                flush(s, &mut out);                Some((c, c))            }            None => Some((c, c)),        };    }    if let Some(s) = start {        flush(s, &mut out);    }    Ok(out)}/// How wide `text` is when set in the font at `size` (pixels or points alike), from the glyphs' advances, without kerning: to fit a title in a box, or truncate it.pub fn font_text_width(path: &str, text: &str, size: f64) -> Result<f64, String> {    let data = load(path)?;    let f = face(&data)?;    let upem = f.units_per_em() as f64;    let units: f64 = text.chars().map(|c| f.glyph_index(c).or_else(|| f.glyph_index('?')).and_then(|g| f.glyph_hor_advance(g)).unwrap_or(0) as f64).sum();    Ok((units / upem * size * 100.0).round() / 100.0)}
Thread
57Useful? Like it, or add to its thread below.

Thread 0 posts

On Fonts: questions, tips, corrections, and code that uses it.

No posts yet. The first one opens the thread.