...

Source file src/github.com/golang/freetype/example/genbasicfont/main.go

Documentation: github.com/golang/freetype/example/genbasicfont

     1  // Copyright 2016 The Freetype-Go Authors. All rights reserved.
     2  // Use of this source code is governed by your choice of either the
     3  // FreeType License or the GNU General Public License version 2 (or
     4  // any later version), both of which can be found in the LICENSE file.
     5  
     6  // +build example
     7  //
     8  // This build tag means that "go install github.com/golang/freetype/..."
     9  // doesn't install this example program. Use "go run main.go" to run it or "go
    10  // install -tags=example" to install it.
    11  
    12  // Program genbasicfont generates Go source code that imports
    13  // golang.org/x/image/font/basicfont to provide a fixed width font face.
    14  package main
    15  
    16  import (
    17  	"bytes"
    18  	"flag"
    19  	"fmt"
    20  	"go/format"
    21  	"image"
    22  	"image/draw"
    23  	"io/ioutil"
    24  	"log"
    25  	"net/http"
    26  	"strings"
    27  	"unicode"
    28  
    29  	"github.com/golang/freetype/truetype"
    30  	"golang.org/x/image/font"
    31  	"golang.org/x/image/math/fixed"
    32  )
    33  
    34  var (
    35  	fontfile = flag.String("fontfile", "../../testdata/luxisr.ttf", "filename or URL of the TTF font")
    36  	hinting  = flag.String("hinting", "none", "none, vertical or full")
    37  	pkg      = flag.String("pkg", "example", "the package name for the generated code")
    38  	size     = flag.Float64("size", 12, "the number of pixels in 1 em")
    39  	vr       = flag.String("var", "example", "the variable name for the generated code")
    40  )
    41  
    42  func loadFontFile() ([]byte, error) {
    43  	if strings.HasPrefix(*fontfile, "http://") || strings.HasPrefix(*fontfile, "https://") {
    44  		resp, err := http.Get(*fontfile)
    45  		if err != nil {
    46  			return nil, err
    47  		}
    48  		defer resp.Body.Close()
    49  		return ioutil.ReadAll(resp.Body)
    50  	}
    51  	return ioutil.ReadFile(*fontfile)
    52  }
    53  
    54  func parseHinting(h string) font.Hinting {
    55  	switch h {
    56  	case "full":
    57  		return font.HintingFull
    58  	case "vertical":
    59  		log.Fatal("TODO: have package truetype implement vertical hinting")
    60  		return font.HintingVertical
    61  	}
    62  	return font.HintingNone
    63  }
    64  
    65  func privateUseArea(r rune) bool {
    66  	return 0xe000 <= r && r <= 0xf8ff ||
    67  		0xf0000 <= r && r <= 0xffffd ||
    68  		0x100000 <= r && r <= 0x10fffd
    69  }
    70  
    71  func loadRanges(f *truetype.Font) (ret [][2]rune) {
    72  	rr := [2]rune{-1, -1}
    73  	for r := rune(0); r <= unicode.MaxRune; r++ {
    74  		if privateUseArea(r) {
    75  			continue
    76  		}
    77  		if f.Index(r) == 0 {
    78  			continue
    79  		}
    80  		if rr[1] == r {
    81  			rr[1] = r + 1
    82  			continue
    83  		}
    84  		if rr[0] != -1 {
    85  			ret = append(ret, rr)
    86  		}
    87  		rr = [2]rune{r, r + 1}
    88  	}
    89  	if rr[0] != -1 {
    90  		ret = append(ret, rr)
    91  	}
    92  	return ret
    93  }
    94  
    95  func emptyCol(m *image.Gray, r image.Rectangle, x int) bool {
    96  	for y := r.Min.Y; y < r.Max.Y; y++ {
    97  		if m.GrayAt(x, y).Y > 0 {
    98  			return false
    99  		}
   100  	}
   101  	return true
   102  }
   103  
   104  func emptyRow(m *image.Gray, r image.Rectangle, y int) bool {
   105  	for x := r.Min.X; x < r.Max.X; x++ {
   106  		if m.GrayAt(x, y).Y > 0 {
   107  			return false
   108  		}
   109  	}
   110  	return true
   111  }
   112  
   113  func tightBounds(m *image.Gray) (r image.Rectangle) {
   114  	r = m.Bounds()
   115  	for ; r.Min.Y < r.Max.Y && emptyRow(m, r, r.Min.Y+0); r.Min.Y++ {
   116  	}
   117  	for ; r.Min.Y < r.Max.Y && emptyRow(m, r, r.Max.Y-1); r.Max.Y-- {
   118  	}
   119  	for ; r.Min.X < r.Max.X && emptyCol(m, r, r.Min.X+0); r.Min.X++ {
   120  	}
   121  	for ; r.Min.X < r.Max.X && emptyCol(m, r, r.Max.X-1); r.Max.X-- {
   122  	}
   123  	return r
   124  }
   125  
   126  func printPix(ranges [][2]rune, glyphs map[rune]*image.Gray, b image.Rectangle) []byte {
   127  	buf := new(bytes.Buffer)
   128  	for _, rr := range ranges {
   129  		for r := rr[0]; r < rr[1]; r++ {
   130  			m := glyphs[r]
   131  			fmt.Fprintf(buf, "// U+%08x '%c'\n", r, r)
   132  			for y := b.Min.Y; y < b.Max.Y; y++ {
   133  				for x := b.Min.X; x < b.Max.X; x++ {
   134  					fmt.Fprintf(buf, "%#02x, ", m.GrayAt(x, y).Y)
   135  				}
   136  				fmt.Fprintln(buf)
   137  			}
   138  			fmt.Fprintln(buf)
   139  		}
   140  	}
   141  	return buf.Bytes()
   142  }
   143  
   144  func printRanges(ranges [][2]rune) []byte {
   145  	buf := new(bytes.Buffer)
   146  	offset := 0
   147  	for _, rr := range ranges {
   148  		fmt.Fprintf(buf, "{'\\U%08x', '\\U%08x', %d},\n", rr[0], rr[1], offset)
   149  		offset += int(rr[1] - rr[0])
   150  	}
   151  	return buf.Bytes()
   152  }
   153  
   154  func main() {
   155  	flag.Parse()
   156  	b, err := loadFontFile()
   157  	if err != nil {
   158  		log.Fatal(err)
   159  	}
   160  	f, err := truetype.Parse(b)
   161  	if err != nil {
   162  		log.Fatal(err)
   163  	}
   164  	face := truetype.NewFace(f, &truetype.Options{
   165  		Size:    *size,
   166  		Hinting: parseHinting(*hinting),
   167  	})
   168  	defer face.Close()
   169  
   170  	fBounds := f.Bounds(fixed.Int26_6(*size * 64))
   171  	iBounds := image.Rect(
   172  		+fBounds.Min.X.Floor(),
   173  		-fBounds.Max.Y.Ceil(),
   174  		+fBounds.Max.X.Ceil(),
   175  		-fBounds.Min.Y.Floor(),
   176  	)
   177  
   178  	tBounds := image.Rectangle{}
   179  	glyphs := map[rune]*image.Gray{}
   180  	advance := fixed.Int26_6(-1)
   181  
   182  	ranges := loadRanges(f)
   183  	for _, rr := range ranges {
   184  		for r := rr[0]; r < rr[1]; r++ {
   185  			dr, mask, maskp, adv, ok := face.Glyph(fixed.Point26_6{}, r)
   186  			if !ok {
   187  				log.Fatalf("could not load glyph for %U", r)
   188  			}
   189  			if advance < 0 {
   190  				advance = adv
   191  			} else if advance != adv {
   192  				log.Fatalf("advance was not constant: got %v and %v", advance, adv)
   193  			}
   194  			dst := image.NewGray(iBounds)
   195  			draw.DrawMask(dst, dr, image.White, image.Point{}, mask, maskp, draw.Src)
   196  			glyphs[r] = dst
   197  			tBounds = tBounds.Union(tightBounds(dst))
   198  		}
   199  	}
   200  
   201  	// height is the glyph image height, not the inter-line spacing.
   202  	width, height := tBounds.Dx(), tBounds.Dy()
   203  
   204  	buf := new(bytes.Buffer)
   205  	fmt.Fprintf(buf, "// generated by go generate; DO NOT EDIT.\n\npackage %s\n\n", *pkg)
   206  	fmt.Fprintf(buf, "import (\n\"image\"\n\n\"golang.org/x/image/font/basicfont\"\n)\n\n")
   207  	fmt.Fprintf(buf, "// %s contains %d %d×%d glyphs in %d Pix bytes.\n",
   208  		*vr, len(glyphs), width, height, len(glyphs)*width*height)
   209  	fmt.Fprintf(buf, `var %s = basicfont.Face{
   210  		Advance: %d,
   211  		Width:   %d,
   212  		Height:  %d,
   213  		Ascent:  %d,
   214  		Descent: %d,
   215  		Left: %d,
   216  		Mask: &image.Alpha{
   217  			Stride: %d,
   218  			Rect: image.Rectangle{Max: image.Point{%d, %d*%d}},
   219  			Pix: []byte{
   220  				%s
   221  			},
   222  		},
   223  		Ranges: []basicfont.Range{
   224  			%s
   225  		},
   226  	}`, *vr, advance.Ceil(), width, face.Metrics().Height.Ceil(), -tBounds.Min.Y, +tBounds.Max.Y, tBounds.Min.X,
   227  		width, width, len(glyphs), height,
   228  		printPix(ranges, glyphs, tBounds), printRanges(ranges))
   229  
   230  	fmted, err := format.Source(buf.Bytes())
   231  	if err != nil {
   232  		log.Fatalf("format.Source: %v", err)
   233  	}
   234  	if err := ioutil.WriteFile(*vr+".go", fmted, 0644); err != nil {
   235  		log.Fatalf("ioutil.WriteFile: %v", err)
   236  	}
   237  }
   238  

View as plain text