~mendelmaleh/dummy

f7699b29e4f35fb115237a684c7a50479144189e — Mendel E 30 days ago b862a10
Use fonts from gfonts, with fuzzy matching
3 files changed, 122 insertions(+), 31 deletions(-)

M .gitignore
M cmd/dummyserver/main.go
M cmd/dummyserver/options.go
M .gitignore => .gitignore +1 -0
@@ 1,3 1,4 @@
*.gob
*.out
*.png
*.prof

M cmd/dummyserver/main.go => cmd/dummyserver/main.go +103 -28
@@ 1,6 1,8 @@
package main

import (
	"encoding/gob"
	"errors"
	"fmt"
	"html/template"
	"image/png"


@@ 9,26 11,32 @@ import (
	"net"
	"net/http"
	"net/url"
	"os"
	"path/filepath"
	"strings"

	"git.sr.ht/~mendelmaleh/download"
	"git.sr.ht/~mendelmaleh/dummy"
	"git.sr.ht/~mendelmaleh/freetype"
	"git.sr.ht/~mendelmaleh/freetype/truetype"
	"git.sr.ht/~mendelmaleh/gfonts"
	"git.sr.ht/~mendelmaleh/match"
	"github.com/pelletier/go-toml"
	"golang.org/x/net/netutil"
)

type Config struct {
	Dummy struct {
		Conns                      int
		Addr, Pattern, Fonts, Font string
		Conns         int
		Addr, Pattern string
	}
	Fonts struct {
		Path, Default, Matcher, Key string
	}
}

var fonts = make(map[string]*truetype.Font)

func main() {
	// config
	doc, err := ioutil.ReadFile("config.toml")
	if err != nil {
		log.Fatal(err)


@@ 40,42 48,109 @@ func main() {
		log.Fatal(err)
	}

	fontfiles, err := ioutil.ReadDir(config.Dummy.Fonts)
	if err != nil {
		log.Fatal(err)
	}
	// get fonts
	fonts := make(map[string]string) // font name to font file
	matcher := new(match.Model)

	for _, file := range fontfiles {
		if filepath.Ext(file.Name()) != ".ttf" {
			continue
	// if fonts weren't downloaded already
	if _, err := os.Stat(config.Fonts.Path); errors.Is(err, os.ErrNotExist) {
		// get available fonts
		resp, err := gfonts.Get(config.Fonts.Key, gfonts.SortPopularity)
		if err != nil {
			log.Fatal(err)
		}

		filebytes, err := ioutil.ReadFile(config.Dummy.Fonts + file.Name())
		// downloader options
		download.DefaultOptions.Concurrent = true

		// download fonts
		files := make(chan download.FileBuffer)
		go func() {
			if err := download.New().Chan(resp.Styles("regular"), files, nil); err != nil {
				log.Fatal(err)
			}
		}()

		// register and save fonts
		var fontNames []string
		for f := range files {
			font, err := freetype.ParseFont(f.Buffer.Bytes())
			if err != nil {
				// TODO: except ttf version errors
				log.Printf("Error loading %s: %v", f.Path, err)
				continue
			}

			path := filepath.Join(config.Fonts.Path, f.Path)
			if err := os.MkdirAll(path, 0744); err != nil {
				log.Fatal(err)
			}

			path = filepath.Join(path, f.Name)
			if err := ioutil.WriteFile(path, f.Buffer.Bytes(), 0644); err != nil {
				log.Fatal(err)
			}

			name := font.Name(truetype.NameIDFontFamily)
			fonts[name] = path
			fontNames = append(fontNames, name)
		}

		// generate model
		matcher = match.New(fontNames, 2, 3, 4, 5)

		// save map and matcher
		file, err := os.Create(config.Fonts.Matcher)
		if err != nil {
			log.Println(err) // don't exit if one font doesn't work
			continue
			log.Fatal(err)
		}

		gb := gob.NewEncoder(file)

		if err := gb.Encode(fonts); err != nil {
			log.Fatal(err)
		}
		if err := gb.Encode(matcher); err != nil {
			log.Fatal(err)
		}

		font, err := freetype.ParseFont(filebytes)
		file.Close()
		log.Println("Done downloading fonts")
	} else {
		file, err := os.Open(config.Fonts.Matcher)
		if err != nil {
			log.Println(err)
			continue
			log.Fatal(err)
		}

		// fmt.Println(font.Name(truetype.NameIDFontFamily))
		fonts[font.Name(truetype.NameIDFontFamily)] = font
	}
		gb := gob.NewDecoder(file)

	if len(fonts) < 1 {
		log.Fatal("No fonts loaded")
		if err := gb.Decode(&fonts); err != nil {
			log.Fatal(err)
		}
		if err := gb.Decode(matcher); err != nil {
			log.Fatal(err)
		}

		file.Close()
		log.Println("Done loading from gob file")
	}

	// set default font after it's loaded
	font, ok := fonts[config.Dummy.Font]
	// set default font and ensure it can be loaded
	file, ok := fonts[config.Fonts.Default]
	if !ok {
		log.Fatal("Default font not found")
	}

	bits, err := ioutil.ReadFile(file)
	if err != nil {
		log.Fatal(err)
	}

	font, err := freetype.ParseFont(bits)
	if err != nil {
		log.Fatal(err)
	}

	dopt := dummy.DefaultOptions
	dopt.Font = font



@@ 93,25 168,25 @@ func main() {

	http.HandleFunc(config.Dummy.Pattern,
		func(w http.ResponseWriter, r *http.Request) {
			Dummy(w, r, Options{dopt.Copy()})
			Dummy(w, r, Options{dopt.Copy(), fonts, matcher})
		},
	)

	http.HandleFunc(config.Dummy.Pattern+"tar/",
		func(w http.ResponseWriter, r *http.Request) {
			Tar(w, r, Options{dopt.Copy()})
			Tar(w, r, Options{dopt.Copy(), fonts, matcher})
		},
	)

	http.HandleFunc(config.Dummy.Pattern+"zip/",
		func(w http.ResponseWriter, r *http.Request) {
			Zip(w, r, Options{dopt.Copy()})
			Zip(w, r, Options{dopt.Copy(), fonts, matcher})
		},
	)

	http.HandleFunc(config.Dummy.Pattern+"web/",
		func(w http.ResponseWriter, r *http.Request) {
			Web(w, r, Options{dopt.Copy()}, config, tmpl)
			Web(w, r, Options{dopt.Copy(), fonts, matcher}, config, tmpl)
		},
	)


M cmd/dummyserver/options.go => cmd/dummyserver/options.go +18 -3
@@ 1,14 1,19 @@
package main

import (
	"io/ioutil"
	"net/url"
	"strconv"

	"git.sr.ht/~mendelmaleh/dummy"
	"git.sr.ht/~mendelmaleh/freetype"
	"git.sr.ht/~mendelmaleh/match"
)

type Options struct {
	dummy.Options
	Fonts   map[string]string
	Matcher *match.Model
}

// FromForm gets values from the form (which includes the url encoded query)


@@ 55,9 60,19 @@ func (opt *Options) FromForm(form url.Values) error {
	}

	if v, ok := form["font"]; ok {
		// TODO: less strict font matching
		if f, ok := fonts[v[0]]; ok {
			opt.Font = f
		name := opt.Matcher.Closest(v[0])
		if name != "" {
			bits, err := ioutil.ReadFile(opt.Fonts[name])
			if err != nil {
				return err
			}

			font, err := freetype.ParseFont(bits)
			if err != nil {
				return err
			}

			opt.Font = font
		}
	}