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
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 errorA 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 errorWhether 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 errorThe 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 errorHow 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.qweOr 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.ttfThen, in your program, all of it or only what you call:
use fonts.qweruse font_info, font_covers from fonts // Quillo 0.6: only theseIt uses this crate, which cargo fetches the first time the plugin is built:
ttf-parser = "0.25"Example
// 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
//! 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)}Related
models_3d.qwer · 1.0.0STL and OBJ models read: triangles, size, volume, surface area and whether the mesh is closed, a 3D-print quote by weight, and OBJ turned into binary STL.6 views · 7 downloadsDisk space disk_space.qwer · 1.0.0Free and total space of the disk a path is on, for health checks, refusing uploads before the disk fills, and admin pages.8 views · 8 downloadsFile type file_type.qwer · 1.0.0What a file really is from its first bytes, not its name: MIME type, extension and kind, for upload checks.7 views · 9 downloadsFont subsets font_subset.qwer · 1.0.0TrueType fonts cut down to the characters a page or a PDF uses, as .ttf, as .woff for the web, or as a data URL for CSS, with the characters the font lacks.6 views · 7 downloadsMIME types mime.qwer · 1.0.0MIME types from file names and extensions, the extensions of a type, and Content-Type headers with a charset for text.8 views · 7 downloadsText encodings text_encoding.qwer · 1.0.0Text files in old encodings (Windows-1252, Latin-1, Shift_JIS, GBK, Big5...) detected and read as UTF-8, and written back in them.8 views · 8 downloadsThread 0 posts
On Fonts: questions, tips, corrections, and code that uses it.
No posts yet. The first one opens the thread.