├── .github └── workflows │ └── go.yml ├── LICENSE ├── README.md ├── cmd ├── find-fonts │ └── find-fonts.go └── list-fonts │ └── list-fonts.go ├── findfont.go ├── findfont_test.go ├── fontdirs_darwin.go ├── fontdirs_js.go ├── fontdirs_unix.go ├── fontdirs_windows.go ├── go.mod └── renovate.json /.github/workflows/go.yml: -------------------------------------------------------------------------------- 1 | # This workflow will build a golang project 2 | # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-go 3 | 4 | name: Go 5 | 6 | on: 7 | push: 8 | branches: [ "master" ] 9 | pull_request: 10 | branches: [ "master" ] 11 | 12 | jobs: 13 | 14 | build: 15 | runs-on: ubuntu-latest 16 | steps: 17 | - uses: actions/checkout@v4 18 | 19 | - name: Set up Go 20 | uses: actions/setup-go@v5 21 | with: 22 | go-version: '1.24' 23 | 24 | - name: Vet 25 | run: go vet ./... 26 | 27 | - name: Test 28 | run: go test -v ./... 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Florian Pigorsch 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![PkgGoDev](https://pkg.go.dev/badge/github.com/flopp/go-findfont)](https://pkg.go.dev/github.com/flopp/go-findfont) 2 | [![Go Report Card](https://goreportcard.com/badge/github.com/flopp/go-findfont)](https://goreportcard.com/report/github.com/flopp/go-findfont) 3 | [![License MIT](https://img.shields.io/badge/license-MIT-lightgrey.svg?style=flat)](https://github.com/flopp/go-findfont/) 4 | 5 | # go-findfont 6 | A platform-agnostic go (golang) library to easily locate truetype font files in your system's user and system font directories. 7 | 8 | ## What? 9 | `go-findfont` is a golang library that allows you to locate font file on your system. The library is currently aware of the default font directories on Linux/Unix, Windows, and MacOS. 10 | 11 | ## How? 12 | 13 | ### Installation 14 | 15 | Installing `go-findfont` is as easy as 16 | 17 | ```bash 18 | go get -u github.com/flopp/go-findfont 19 | ``` 20 | 21 | ### Library Usage 22 | 23 | ```go 24 | 25 | import ( 26 | "fmt" 27 | "io/ioutil" 28 | 29 | "github.com/flopp/go-findfont" 30 | "github.com/golang/freetype/truetype" 31 | ) 32 | 33 | func main() { 34 | fontPath, err := findfont.Find("arial.ttf") 35 | if err != nil { 36 | panic(err) 37 | } 38 | fmt.Printf("Found 'arial.ttf' in '%s'\n", fontPath) 39 | 40 | // load the font with the freetype library 41 | fontData, err := ioutil.ReadFile(fontPath) 42 | if err != nil { 43 | panic(err) 44 | } 45 | font, err := truetype.Parse(fontData) 46 | if err != nil { 47 | panic(err) 48 | } 49 | 50 | // use the font... 51 | } 52 | ``` 53 | 54 | ## License 55 | Copyright 2016 Florian Pigorsch. All rights reserved. 56 | 57 | Use of this source code is governed by a MIT-style license that can be found in the LICENSE file. 58 | -------------------------------------------------------------------------------- /cmd/find-fonts/find-fonts.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Florian Pigorsch. All rights reserved. 2 | // 3 | // Use of this source code is governed by a MIT-style 4 | // license that can be found in the LICENSE file. 5 | 6 | package main 7 | 8 | import ( 9 | "flag" 10 | "fmt" 11 | 12 | "github.com/flopp/go-findfont" 13 | ) 14 | 15 | func main() { 16 | flag.Parse() 17 | for _, font := range flag.Args() { 18 | if filePath, err := findfont.Find(font); err != nil { 19 | fmt.Println(err) 20 | } else { 21 | fmt.Printf("Found %s at %s\n", font, filePath) 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /cmd/list-fonts/list-fonts.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Florian Pigorsch. All rights reserved. 2 | // 3 | // Use of this source code is governed by a MIT-style 4 | // license that can be found in the LICENSE file. 5 | 6 | package main 7 | 8 | import ( 9 | "fmt" 10 | 11 | "github.com/flopp/go-findfont" 12 | ) 13 | 14 | func main() { 15 | for _, path := range findfont.List() { 16 | fmt.Println(path) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /findfont.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Florian Pigorsch. All rights reserved. 2 | // 3 | // Use of this source code is governed by a MIT-style 4 | // license that can be found in the LICENSE file. 5 | 6 | package findfont 7 | 8 | import ( 9 | "fmt" 10 | "os" 11 | "os/user" 12 | "path/filepath" 13 | "strings" 14 | ) 15 | 16 | func defaultSuffixes() []string { 17 | return []string{".ttf", ".ttc", ".otf"} 18 | } 19 | 20 | // Find tries to locate the specified font file in the current directory as 21 | // well as in platform specific user and system font directories; if there is 22 | // no exact match, Find tries substring matching - files with the standard font suffixes (.ttf, .ttc, .otf) are considered. 23 | func Find(fileName string) (filePath string, err error) { 24 | return FindWithSuffixes(fileName, defaultSuffixes()) 25 | } 26 | 27 | // FindWithSuffixes tries to locate the specified font file in the current directory as 28 | // well as in platform specific user and system font directories; if there is 29 | // no exact match, Find tries substring matching - only font files with the give suffixes are considered. 30 | func FindWithSuffixes(fileName string, suffixes []string) (filePath string, err error) { 31 | // check if fileName already points to a readable file 32 | if _, err := os.Stat(fileName); err == nil { 33 | return fileName, nil 34 | } 35 | 36 | // search in user and system directories 37 | return find(filepath.Base(fileName), suffixes) 38 | } 39 | 40 | // List returns a list of all font files (determined by standard suffixes: .ttf, .ttc, .otf) found on the system. 41 | func List() (filePaths []string) { 42 | return ListWithSuffixes(defaultSuffixes()) 43 | } 44 | 45 | // ListWithSuffixes returns a list of all font files (determined by given file suffixes) found on the system. 46 | func ListWithSuffixes(suffixes []string) (filePaths []string) { 47 | pathList := []string{} 48 | 49 | walkF := func(path string, info os.FileInfo, err error) error { 50 | if err == nil { 51 | if !info.IsDir() && isFontFile(path, suffixes) { 52 | pathList = append(pathList, path) 53 | } 54 | } 55 | return nil 56 | } 57 | for _, dir := range getFontDirectories() { 58 | filepath.Walk(dir, walkF) 59 | } 60 | 61 | return pathList 62 | } 63 | 64 | func isFontFile(fileName string, suffixes []string) bool { 65 | lower := strings.ToLower(fileName) 66 | for _, suffix := range suffixes { 67 | if strings.HasSuffix(lower, suffix) { 68 | return true 69 | } 70 | } 71 | return false 72 | } 73 | 74 | func stripExtension(fileName string) string { 75 | return strings.TrimSuffix(fileName, filepath.Ext(fileName)) 76 | } 77 | 78 | func expandUser(path string) (expandedPath string) { 79 | if strings.HasPrefix(path, "~") { 80 | if u, err := user.Current(); err == nil { 81 | return strings.Replace(path, "~", u.HomeDir, -1) 82 | } 83 | } 84 | return path 85 | } 86 | 87 | func find(needle string, suffixes []string) (filePath string, err error) { 88 | lowerNeedle := strings.ToLower(needle) 89 | lowerNeedleBase := stripExtension(lowerNeedle) 90 | 91 | match := "" 92 | partial := "" 93 | partialScore := -1 94 | 95 | walkF := func(path string, info os.FileInfo, err error) error { 96 | // we have already found a match -> nothing to do 97 | if match != "" { 98 | return nil 99 | } 100 | if err != nil { 101 | return nil 102 | } 103 | 104 | lowerPath := strings.ToLower(info.Name()) 105 | 106 | if !info.IsDir() && isFontFile(lowerPath, suffixes) { 107 | lowerBase := stripExtension(lowerPath) 108 | if lowerPath == lowerNeedle { 109 | // exact match 110 | match = path 111 | } else if strings.Contains(lowerBase, lowerNeedleBase) { 112 | // partial match 113 | score := len(lowerBase) - len(lowerNeedle) 114 | if partialScore < 0 || score < partialScore { 115 | partialScore = score 116 | partial = path 117 | } 118 | } 119 | } 120 | return nil 121 | } 122 | 123 | for _, dir := range getFontDirectories() { 124 | filepath.Walk(dir, walkF) 125 | if match != "" { 126 | return match, nil 127 | } 128 | } 129 | 130 | if partial != "" { 131 | return partial, nil 132 | } 133 | 134 | return "", fmt.Errorf("cannot find font '%s' in user or system directories", needle) 135 | } 136 | -------------------------------------------------------------------------------- /findfont_test.go: -------------------------------------------------------------------------------- 1 | package findfont 2 | 3 | import ( 4 | "path/filepath" 5 | "strings" 6 | "testing" 7 | ) 8 | 9 | func TestList(t *testing.T) { 10 | // List must find something 11 | fonts := List() 12 | if len(fonts) == 0 { 13 | t.Errorf("No font files found in system folders") 14 | } 15 | 16 | // ListWithSuffix using bad suffix 17 | bad_suffix_fonts := ListWithSuffixes([]string{".bad-suffix"}) 18 | if len(bad_suffix_fonts) != 0 { 19 | t.Errorf("Unexpectedly found font files with bad suffix, e.g. %s", bad_suffix_fonts[0]) 20 | } 21 | 22 | // ListWithSuffixes using good suffix 23 | good_suffix_fonts := ListWithSuffixes([]string{filepath.Ext(fonts[0])}) 24 | if len(good_suffix_fonts) == 0 { 25 | t.Errorf("No font files with suffix %s", filepath.Ext(fonts[0])) 26 | } 27 | } 28 | 29 | func TestFind(t *testing.T) { 30 | // Try to find a non-existing font 31 | font, err := Find("this-font-does-not-exist.ttf") 32 | if err == nil { 33 | t.Errorf("Expected match when searching for non-existant font: %s", font) 34 | } 35 | 36 | fonts := List() 37 | if len(fonts) > 0 { 38 | // Direct search for existing font file 39 | font, err = Find(fonts[0]) 40 | if err != nil { 41 | t.Errorf("Direct search failed: %v", err) 42 | } 43 | if font != fonts[0] { 44 | t.Errorf("Unexpected match for direct search: %s, expected: %s", font, fonts[0]) 45 | } 46 | 47 | // Search only for basename 48 | needle := filepath.Base(fonts[0]) 49 | needle = strings.TrimSuffix(needle, filepath.Ext(needle)) 50 | _, err = Find(needle) 51 | if err != nil { 52 | t.Errorf("Basename search failed: %v", err) 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /fontdirs_darwin.go: -------------------------------------------------------------------------------- 1 | //go:build darwin 2 | 3 | // Copyright 2016 Florian Pigorsch. All rights reserved. 4 | // 5 | // Use of this source code is governed by a MIT-style 6 | // license that can be found in the LICENSE file. 7 | 8 | package findfont 9 | 10 | func getFontDirectories() (paths []string) { 11 | return []string{ 12 | expandUser("~/Library/Fonts/"), 13 | "/Library/Fonts/", 14 | "/System/Library/Fonts/", 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /fontdirs_js.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Florian Pigorsch. All rights reserved. 2 | // 3 | // Use of this source code is governed by a MIT-style 4 | // license that can be found in the LICENSE file. 5 | 6 | package findfont 7 | 8 | func getFontDirectories() (paths []string) { 9 | return nil 10 | } 11 | -------------------------------------------------------------------------------- /fontdirs_unix.go: -------------------------------------------------------------------------------- 1 | //go:build unix && !darwin 2 | 3 | // Copyright 2016 Florian Pigorsch. All rights reserved. 4 | // 5 | // Use of this source code is governed by a MIT-style 6 | // license that can be found in the LICENSE file. 7 | 8 | package findfont 9 | 10 | import ( 11 | "os" 12 | "path/filepath" 13 | "runtime" 14 | ) 15 | 16 | func getFontDirectories() (paths []string) { 17 | switch runtime.GOOS { 18 | case "android": 19 | return []string{"/system/fonts"} 20 | default: 21 | directories := getUserFontDirs() 22 | directories = append(directories, getSystemFontDirs()...) 23 | return directories 24 | } 25 | } 26 | 27 | func getUserFontDirs() (paths []string) { 28 | if dataPath := os.Getenv("XDG_DATA_HOME"); dataPath != "" { 29 | return []string{expandUser("~/.fonts/"), filepath.Join(expandUser(dataPath), "fonts")} 30 | } 31 | return []string{expandUser("~/.fonts/"), expandUser("~/.local/share/fonts/")} 32 | } 33 | 34 | func getSystemFontDirs() (paths []string) { 35 | if dataPaths := os.Getenv("XDG_DATA_DIRS"); dataPaths != "" { 36 | for _, dataPath := range filepath.SplitList(dataPaths) { 37 | paths = append(paths, filepath.Join(expandUser(dataPath), "fonts")) 38 | } 39 | return paths 40 | } 41 | return []string{"/usr/local/share/fonts/", "/usr/share/fonts/"} 42 | } 43 | -------------------------------------------------------------------------------- /fontdirs_windows.go: -------------------------------------------------------------------------------- 1 | //go:build windows 2 | 3 | // Copyright 2016 Florian Pigorsch. All rights reserved. 4 | // 5 | // Use of this source code is governed by a MIT-style 6 | // license that can be found in the LICENSE file. 7 | 8 | package findfont 9 | 10 | import ( 11 | "os" 12 | "path/filepath" 13 | ) 14 | 15 | func getFontDirectories() (paths []string) { 16 | return []string{ 17 | filepath.Join(os.Getenv("windir"), "Fonts"), 18 | filepath.Join(os.Getenv("localappdata"), "Microsoft", "Windows", "Fonts"), 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/flopp/go-findfont 2 | 3 | go 1.22 4 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": [ 4 | "config:recommended" 5 | ] 6 | } 7 | --------------------------------------------------------------------------------