fontscan

package
v0.1.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Mar 29, 2024 License: BSD-3-Clause, Unlicense Imports: 26 Imported by: 6

README

Description and purpose of the package

This package provides a way to locate and load a font.Font, which is the fundamental object needed by go-text for shaping and text rendering.

Use case

This package may be used by UI toolkits and markup language renderers. Both use-cases may need to display large quantities of text of varying languages and writing systems, and want to make use of all available fonts, both packaged within the application and installed on the system. In both cases, content/UI authors provide hints about the fonts that they want chosen (family names, weights, styles, etc...) and want the closest available match to the requested properties.

Overview of the API

The entry point of the library is the FontMap type. It should be created for each text shaping task and be filled either with system fonts (by calling UseSystemFonts) or with user-provided font files (using AddFont, AddFace), or both. To leverage all the system fonts, the first usage of UseSystemFonts triggers a scan which builds a font index. Its content is saved on disk so that subsequent usage by the same app are not slowed down by this step.

Once initialized, the font map is used to select fonts matching a Query with SetQuery. A query is defined by one or several families and an Aspect, containining style, weight, stretchiness. Finally, the font map satisfies the shaping.Fontmap interface, so that is may be used with shaping.SplitByFace.

Zoom on the implementation

Font directories

Fonts are searched by walking the file system, in the folders returned by DefaultFontDirectories, which are platform dependent. The current list is copied from fontconfig and go-findfont.

Font family substitutions

A key concept of the implementation (inspired by fontconfig) is the idea to enlarge the requested family with similar known families. This ensure that suitable font fallbacks may be provided even if the required font is not available. It is implemented by a list of susbtitutions, each of them having a test and a list of additions.

Simplified example : if the list of susbtitutions is

  • Test: the input family is Arial, Addition: Arimo
  • Test: the input family is Arimo, Addition: sans-serif
  • Test: the input family is sans-serif, Addition: DejaVu Sans et Verdana

then,

  • for the Arimo input family, [Arimo, sans-serif, DejaVu Sans, Verdana] would be matched
  • for the Arial input family, [Arial, Arimo, sans-serif, DejaVu Sans, Verdana] would be matched

To respect the user request, the order of the list is significant (first entries have higher priority).

FontMap.SetQuery apply a list of hard-coded subsitutions, extracted from Fontconfig configurations files.

Style matching

FontMap.SetQuery takes an optional argument describing the style of the required font (style, weight, stretchiness).

When no exact match is found, the CSS font selection rules are applied to return the closest match. As an example, if the user asks for (Italic, ExtraBold) but only (Normal, Bold) and (Oblique, Bold) are available, the (Oblique, Bold) would be returned.

System font index

The FontMap type requires more information than the font paths to be able to quickly and accurately match a font against family, aspect, and rune coverage query. This information is provided by a list of font summaries, which are lightweight enough to be loaded and queried efficiently.

The initial scan required to build this index has a significant latency (say between 0.2 and 0.5 sec on a laptop). Once the first scan has been done, however, the subsequent launches are fast : at the first call of UseSystemFonts, the index is loaded from an on-disk cache, and its integrity is checked against the current file system state to detect font installation or suppression.

Documentation

Index

Examples

Constants

View Source
const (
	Fantasy   = "fantasy"
	Math      = "math"
	Emoji     = "emoji"
	Serif     = "serif"
	SansSerif = "sans-serif"
	Cursive   = "cursive"
	Monospace = "monospace"
)

Generic families as defined by https://www.w3.org/TR/css-fonts-4/#generic-font-families

Variables

This section is empty.

Functions

func DefaultFontDirectories

func DefaultFontDirectories(logger Logger) ([]string, error)

DefaultFontDirectories return the OS-dependent usual directories for fonts, or an error if no one exists. These are the directories used by `FontMap.UseSystemFonts` to locate fonts.

Types

type FontMap

type FontMap struct {
	// contains filtered or unexported fields
}

FontMap provides a mechanism to select a [font.Face] from a font description. It supports system and user-provided fonts, and implements the CSS font substitutions rules.

Note that FontMap is NOT safe for concurrent use, but several font maps may coexist in an application.

FontMap is mainly designed to work with an index built by scanning the system fonts : see [UseSystemFonts] for more details.

func NewFontMap

func NewFontMap(logger Logger) *FontMap

NewFontMap return a new font map, which should be filled with the `UseSystemFonts` or `AddFont` methods. The provided logger will be used to record non-fatal errors encountered during font loading. If logger is nil, log.Default() is used.

func (*FontMap) AddFace

func (fm *FontMap) AddFace(face font.Face, location Location, md meta.Description)

[AddFace] inserts an already-loaded font.Face into the FontMap. The caller is responsible for ensuring that [md] is accurate for the face.

The order of calls to [AddFont] and [AddFace] determines relative priority of manually loaded fonts. See [ResolveFace] for details about when this matters.

Example
// Open an on-disk font file.
fontFile, _ := os.Open("myFont.ttf") // error handling omitted
defer fontFile.Close()

// Load it and its metadata.
ld, _ := loader.NewLoader(fontFile) // error handling omitted
md := meta.Metadata(ld)
f, _ := fontapi.NewFont(ld) // error handling omitted
fontMap := NewFontMap(log.Default())
fontMap.AddFace(&fontapi.Face{Font: f}, Location{File: fmt.Sprint(md)}, md)

// set the font description
fontMap.SetQuery(Query{Families: []string{"Arial", "serif"}}) // regular Aspect

// `fontMap` is now ready for text shaping, using the `ResolveFace` method
Output:

func (*FontMap) AddFont

func (fm *FontMap) AddFont(fontFile font.Resource, fileID, familyName string) error

[AddFont] loads the faces contained in [fontFile] and add them to the font map. [fileID] is used as the [Location.File] entry returned by [FontLocation].

If `familyName` is not empty, it is used as the family name for `fontFile` instead of the one found in the font file.

An error is returned if the font resource is not supported.

The order of calls to [AddFont] and [AddFace] determines relative priority of manually loaded fonts. See [ResolveFace] for details about when this matters.

Example
// Open an on-disk font file. Do not close it, as the fontMap will need to parse
// it on-demand. If you need to close it, read all of the bytes into a bytes.Reader
// first.
fontFile, _ := os.Open("myFont.ttf") // error handling omitted

fontMap := NewFontMap(log.Default())
fontMap.AddFont(fontFile, "myFont.ttf", "My Font") // error handling omitted

// set the font description
fontMap.SetQuery(Query{Families: []string{"Arial", "serif"}}) // regular Aspect

// `fontMap` is now ready for text shaping, using the `ResolveFace` method
Output:

func (*FontMap) FindSystemFont

func (fm *FontMap) FindSystemFont(family string) (Location, bool)

FindSystemFont looks for a system font with the given [family], returning the first match, or false is no one is found.

User added fonts are ignored, and the FontMap must have been initialized with [UseSystemFonts] or this method will always return false.

Family names are compared through meta.Normalize.

func (*FontMap) FindSystemFonts

func (fm *FontMap) FindSystemFonts(family string) []Location

FindSystemFonts is the same as FindSystemFont, but returns all matched fonts.

func (*FontMap) FontLocation

func (fm *FontMap) FontLocation(ft font.Font) Location

FontLocation returns the origin of the provided font. If the font was not previously returned from this FontMap by a call to ResolveFace, the zero value will be returned instead.

func (*FontMap) FontMetadata

func (fm *FontMap) FontMetadata(ft font.Font) (family string, aspect meta.Aspect)

FontMetadata returns a description of the provided font. If the font was not previously returned from this FontMap by a call to ResolveFace, the zero value will be returned instead.

func (*FontMap) ResolveFace

func (fm *FontMap) ResolveFace(r rune) (face font.Face)

ResolveFace select a font based on the current query (see `SetQuery`), and supporting the given rune, applying CSS font selection rules. The function will return nil if the underlying font database is empty, or if the file system is broken; otherwise the returned [font.Face] is always valid.

If no fonts match the current query for the current rune according to the builtin matching process, the fonts added manually by [AddFont] and [AddFace] will be searched in the order in which they were added for a font with coverage for the provided rune. The first font covering the requested rune will be returned.

If no fonts match after the manual font search, an arbitrary face will be returned.

func (*FontMap) ResolveFaceForLang

func (fm *FontMap) ResolveFaceForLang(lang LangID) font.Face

ResolveForLang returns the first face supporting the given language (for the actual query), or nil if no one is found.

The matching logic is similar to the one used by [ResolveFace].

func (*FontMap) SetQuery

func (fm *FontMap) SetQuery(query Query)

SetQuery set the families and aspect required, influencing subsequent `ResolveFace` calls.

func (*FontMap) SetRuneCacheSize

func (fm *FontMap) SetRuneCacheSize(size int)

SetRuneCacheSize configures the size of the cache powering FontMap.ResolveFace. Applications displaying large quantities of text should tune this value to be greater than the number of unique glyphs they expect to display at one time in order to achieve optimal performance when segmenting text by face rune coverage.

func (*FontMap) UseSystemFonts

func (fm *FontMap) UseSystemFonts(cacheDir string) error

UseSystemFonts loads the system fonts and adds them to the font map. This method is safe for concurrent use, but should only be called once per font map. The first call of this method trigger a rather long scan. A per-application on-disk cache is used to speed up subsequent initialisations. Callers can provide an appropriate directory path within which this cache may be stored. If the empty string is provided, the FontMap will attempt to infer a correct, platform-dependent cache path.

NOTE: On Android, callers *must* provide a writable path manually, as it cannot be inferred without access to the Java runtime environment of the application.

Example
fontMap := NewFontMap(log.Default())
fontMap.UseSystemFonts("cachdir") // error handling omitted

// set the font description
fontMap.SetQuery(Query{Families: []string{"Arial", "serif"}}) // regular Aspect
// `fontMap` is now ready for text shaping, using the `ResolveFace` method
Output:

type LangID

type LangID uint16

LangID is a compact representation of a language this package has orthographic knowledge of.

func NewLangID

func NewLangID(l language.Language) (LangID, bool)

NewLangID returns the compact index of the given language, or false if it is not supported by this package.

Derived languages not exactly supported are mapped to their primary part : for instance, 'fr-be' is mapped to 'fr'

type Location

type Location = api.FontID

Location identifies where a font.Face is stored.

type Logger

type Logger interface {
	Printf(format string, args ...interface{})
}

Logger is a type that can log warnings.

type Query

type Query struct {
	// Families is a list of required families,
	// the first having the highest priority.
	// Each of them is tried until a suitable match is found.
	Families []string

	// Aspect selects which particular face to use among
	// the font matching the family criteria.
	Aspect meta.Aspect
}

Query exposes the intention of an author about the font to use to shape and render text.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL