text: add family DSL parser

This commit adds a parser for a simple domain-specific language that
can express a comma-delimited list of font families within a string.

I chose to encode families in this way because the string can be used
as an efficient hash key in a way that a slice of families cannot. Similarly,
using a slice of families would require allocations on the caller side.

The particular format was chosen to allow lists to be written with as little
fanfare as possible. This is why quotation marks are completely optional. It's
easy to read:

  Times New Roman, Georgia, serif

Why force the user to type this (this will parse the same):

  "Times New Roman", "Georgia", "serif"

I've tried to handle edge cases exhaustively. Commas are legal within quotes.
Within a quoted string, you can escape instances of the surrounding quote with
a backslash, and can escape literal backslashes by adding another backslash.

I wrote the lexer/parser by hand, and I hope that they're both easy to understand
and (if need be) extend.

A side effect of the DSL I've chosen (and part of my reasoning for allowing both
single and double quoted strings) is that CSS font-family rules will generally be
valid font family lists in Gio. This means the syntax is already familiar to users
coming from other technologies, and that you can copy from a web-based application
to get a similar font stack in Gio.

Fixes: https://todo.sr.ht/~eliasnaur/gio/317
Signed-off-by: Chris Waldon <christopher.waldon.dev@gmail.com>
This commit is contained in:
Chris Waldon
2023-07-03 16:37:58 -04:00
committed by Elias Naur
parent 43c47f0883
commit 6384ab6087
4 changed files with 471 additions and 4 deletions
+35 -3
View File
@@ -22,7 +22,8 @@ type Weight int
// Font specify a particular typeface variant, style and weight.
type Font struct {
// Typeface specifies the name of the family of faces.
// Typeface specifies the name(s) of the the font faces to try. See [Typeface]
// for details.
Typeface Typeface
// Style specifies the kind of text style.
Style Style
@@ -36,8 +37,39 @@ type Face interface {
Face() font.Face
}
// Typeface identifies a particular typeface design. The empty
// string denotes the default typeface.
// Typeface identifies a list of font families to attempt to use for displaying
// a string. The syntax is a comma-delimited list of family names. In order to
// allow for the remote possibility of needing to express a font family name
// containing a comma, name entries may be quoted using either single or double
// quotes. Within quotes, a literal quotation mark can be expressed by escaping
// it with `\`. A literal backslash may be expressed by escaping it with another
// `\`.
//
// Here's an example Typeface:
//
// Times New Roman, Georgia, serif
//
// This is equivalent to the above:
//
// "Times New Roman", 'Georgia', serif
//
// Here are some valid uses of escape sequences:
//
// "Contains a literal \" doublequote", 'Literal \' Singlequote', "\\ Literal backslash", '\\ another'
//
// This syntax has the happy side effect that most CSS "font-family" rules are
// valid Typefaces (without the trailing semicolon).
//
// Generic CSS font families are supported, and are automatically expanded to lists
// of known font families with a matching style. The supported generic families are:
//
// - fantasy
// - math
// - emoji
// - serif
// - sans-serif
// - cursive
// - monospace
type Typeface string
const (