func TestDrawView(t *testing.T) { const str = "Hello World!\nHow are you doing?" f1 := truetype.NewFace(clearSans, &truetype.Options{DPI: 144}) f2 := truetype.NewFace(clearSansBoldItalic, &truetype.Options{DPI: 144}) red := image.NewUniform(color.RGBA{255, 0, 0, 255}) yellow := image.NewUniform(color.RGBA{255, 255, 0, 255}) view, _ := Render( NewReader( strings.NewReader("Hello World!\nHow are you doing?"), Style{Offset: 0, Face: f1, Foreground: image.Black, Background: yellow}, Style{Offset: 10, Face: f2, Foreground: red, Background: yellow}, Style{Offset: 20, Face: f1, Foreground: image.Black, Background: image.White}, ), NewNaturalLayout(fixed.P(0, 0)), ) size := view.Bounds.Max.Sub(view.Bounds.Min) for _, a := range []Alignment{Left, Right, Center, Justify} { dst := image.NewRGBA(image.Rect(0, 0, int(size.X>>6)+1, int(size.Y>>6)+1)) view.Align(a) view.Draw(dst, LeftToRight) saveTest(t, dst, "text.View.Draw_"+a.(fmt.Stringer).String()+".png") } }
func newDrawer(fontFile string) (*drawer, error) { if fontFile == "" { return nil, errFontRequired } g := new(drawer) g.fontSize = 75.0 g.dpi = 72.0 g.fontHinting = font.HintingNone ttf, err := getTTF(fontFile) if err != nil { return nil, errInvalidTTF } g.face = truetype.NewFace(ttf, &truetype.Options{ Size: g.fontSize, DPI: g.dpi, Hinting: g.fontHinting, }) fontBytes, err := ioutil.ReadFile(fontFile) if err != nil { return nil, errInvalidTTF } font, err := freetype.ParseFont(fontBytes) if err != nil { return nil, errInvalidTTF } g.font = font return g, nil }
func (l *Label) newTextTexture(eng sprite.Engine) sprite.SubTex { fg, bg := image.Black, image.White draw.Draw(l.rgba, l.rgba.Bounds(), bg, image.ZP, draw.Src) d := &sfont.Drawer{ Dst: l.rgba, Src: fg, Face: truetype.NewFace(l.font, truetype.Options{ Size: l.fontSize, DPI: 72, Hinting: sfont.HintingFull, }), } spacing := 1.5 dy := int(math.Ceil(l.fontSize * spacing)) for i, s := range strings.Split(l.Text, "\n") { d.Dot = fixed.P(0, int(l.fontSize*0.8)+dy*i) d.DrawString(s) } t, err := eng.LoadTexture(l.rgba) if err != nil { log.Fatal(err) } return sprite.SubTex{t, l.rgba.Bounds()} }
// buildWords generates word sprites. func buildWords(words map[string]int, f *truetype.Font) []Word { ch := make(chan Word) wg := &sync.WaitGroup{} wg.Add(len(words)) for k, v := range words { go func(w string, occ int) { face := truetype.NewFace(f, &truetype.Options{ Size: float64(occ * 12), DPI: 72, Hinting: font.HintingFull, }) wd := buildSprite(w, face) wd.Weight = occ ch <- wd wg.Done() }(k, v) } go func() { wg.Wait() close(ch) }() var data []Word for w := range ch { data = append(data, w) } return data }
// ReadFaceFile parses the contents of path as a truetype font. func ReadFaceFile(path string, opt *truetype.Options) (font.Face, error) { ttf, err := ReadFontFile(path) if err != nil { return nil, err } face := truetype.NewFace(ttf, opt) return face, nil }
// ReadFace parses the data read from r as a truetype font. func ReadFace(r io.Reader, opt *truetype.Options) (font.Face, error) { ttf, err := ReadFont(r) if err != nil { return nil, err } face := truetype.NewFace(ttf, opt) return face, nil }
// initLayout constructs two masks for drawing the battery and the remaining // energy as well as sets the pixel bounds for drawing energy capacity. the // masks allow for simplified space-fills and reduced chance of pixel gaps. func (app *App) initLayout() { var zeropt image.Point rectOutTop := image.Rectangle{Min: app.Layout.battRect.Min, Max: app.Layout.battRect.Min.Add(image.Point{2, 2})} rectOutBottom := rectOutTop.Add(image.Point{Y: app.Layout.battRect.Size().Y - rectOutTop.Size().Y}) capRect := image.Rectangle{ Min: image.Point{X: rectOutTop.Min.X, Y: rectOutTop.Max.Y}, Max: image.Point{X: rectOutBottom.Max.X, Y: rectOutBottom.Min.Y}, } bodyRect := app.Layout.battRect bodyRect.Min.X = capRect.Max.X // energy will be drawn under the battery shell. The only place where it // is not safe to draw energy is outside the battery on the positive end. energyMask := image.NewAlpha(app.Layout.battRect) draw.Draw(energyMask, app.Layout.battRect, opaque, zeropt, draw.Over) draw.Draw(energyMask, rectOutTop, transparent, zeropt, draw.Src) draw.Draw(energyMask, rectOutBottom, transparent, zeropt, draw.Src) app.maskEnergy = energyMask // the body uses the same mask as the energy with additional transparency // inside the battery's shell. the mask construction is complex because // area inside the cap may be exposed. bodyMask := image.NewAlpha(app.Layout.battRect) draw.Draw(bodyMask, app.Layout.battRect, energyMask, app.Layout.battRect.Min, draw.Over) bodyMaskRect := shrinkRect(bodyRect, app.Layout.thickness) draw.Draw(bodyMask, bodyMaskRect, transparent, zeropt, draw.Src) capMaskRect := shrinkRect(capRect, app.Layout.thickness) capMaskRect.Max.X += 2 * app.Layout.thickness draw.Draw(bodyMask, capMaskRect, transparent, zeropt, draw.Src) app.maskBattery = bodyMask // create a freetype.Context to render text. each time the context is used // it must have its SetDst method called. app.tt = freetype.NewContext() app.tt.SetSrc(black) app.tt.SetClip(app.Layout.textRect) app.tt.SetDPI(app.Layout.DPI) app.tt.SetFont(app.Layout.font) app.tt.SetFontSize(app.Layout.fontSize) ttopt := &truetype.Options{ Size: app.Layout.fontSize, DPI: app.Layout.DPI, } ttface := truetype.NewFace(app.Layout.font, ttopt) app.font = &font.Drawer{ Src: black, Face: ttface, } // the rectangle in which energy is drawn needs to account for thickness to // make the visible percentage more accurate. after adjustment reduce the // energy rect to account for the account of energy drained. the energy // mask makes computing Y bounds largely irrelevant. app.minEnergy = capMaskRect.Min.X app.maxEnergy = bodyMaskRect.Max.X }
func (f *Font) setup() { f.drawer = &font.Drawer{ Face: truetype.NewFace(f.fnt, &truetype.Options{ Size: f.Size, DPI: f.DPI, Hinting: font.HintingNone, }), } }
func TestRunReaderEndOfLine(t *testing.T) { f := truetype.NewFace(clearSans, nil) defer f.Close() c := NewReader(strings.NewReader("Hello World!\nHow are you doing?"), Style{Offset: 0, Face: f, Foreground: image.Black, Background: image.White}, Style{Offset: 5, Face: f, Foreground: image.White}, ) r := NewRunReader(c, nil, fixed.Rectangle26_6{ Min: fixed.Point26_6{X: fixed.I(1), Y: fixed.I(1)}, Max: fixed.Point26_6{X: fixed.I(600), Y: fixed.I(600)}, }) var run Run var err error if run, err = r.ReadRun(); err != nil { t.Error(run, err) } if run != (Run{ Offset: 0, Text: "Hello", Face: f, Foreground: image.Black, Background: image.White, Bounds: fixed.Rectangle26_6{ Min: fixed.Point26_6{X: int26_6(1, 0), Y: int26_6(1, 0)}, Max: fixed.Point26_6{X: int26_6(28, 37), Y: int26_6(13, 0)}, }, }) { t.Error("invalid first run:", run) } if run, err = r.ReadRun(); err != nil { t.Error(run, err) } if run != (Run{ Offset: 5, Text: " World!", Face: f, Foreground: image.White, Background: image.White, Bounds: fixed.Rectangle26_6{ Min: fixed.Point26_6{X: int26_6(28, 37), Y: int26_6(1, 0)}, Max: fixed.Point26_6{X: int26_6(65, 55), Y: int26_6(13, 0)}, }, }) { t.Error("invalid second run:", run) } if _, err = r.ReadRun(); err != io.EOF { t.Error(err) } }
func TTF(ttf *truetype.Font, data FaceData) Face { return ttfFace{ Face: truetype.NewFace(ttf, &truetype.Options{ Size: data.Size, DPI: data.DPI, Hinting: data.Hinting, }), font: ttf, data: data, } }
func TestMeasureEmpty(t *testing.T) { f := truetype.NewFace(clearSans, nil) defer f.Close() s := MeasureBytes(nil, f) if s != (fixed.Point26_6{ X: int26_6(0, 0), Y: int26_6(14, 60), }) { t.Error("invalid result of measuring an empty string:", s) } }
func TestMeasureMultiLine(t *testing.T) { f := truetype.NewFace(clearSans, nil) defer f.Close() s := MeasureString("Hello World!\nHello World!", f) if s != (fixed.Point26_6{ X: int26_6(64, 55), Y: int26_6(26, 60), }) { t.Error("invalid result of measuring a multi-line string:", s) } }
func (m *Module) renderMin(rgba *image.RGBA, position fixed.Point26_6) { d := &font.Drawer{ Dst: rgba, Src: secondaryForeground, Face: truetype.NewFace(m.font, &truetype.Options{ Size: minsFontSize, DPI: dpi, Hinting: font.HintingNone, }), } d.Dot = position d.DrawString("min") }
func TestReaderPanicNotSorted(t *testing.T) { f := truetype.NewFace(clearSans, nil) defer f.Close() defer func() { recover() }() NewReader(nil, Style{Offset: 0, Face: f, Foreground: image.Black, Background: image.White}, Style{Offset: 2, Face: f, Foreground: image.White}, Style{Offset: 1, Face: f, Foreground: image.Black}, ) t.Error("expected panic: styles must be in the right order") }
func TestReaderPanicBadBackground(t *testing.T) { f := truetype.NewFace(clearSans, nil) defer f.Close() defer func() { recover() }() NewReader(nil, Style{ Offset: 0, Face: f, Foreground: image.Black, Background: nil, }) t.Error("expected panic: no color was given") }
func TestReaderPanicBadOffset(t *testing.T) { f := truetype.NewFace(clearSans, nil) defer f.Close() defer func() { recover() }() NewReader(nil, Style{ Offset: 1, Face: f, Foreground: image.Black, Background: image.White, }) t.Error("expected panic: invalid first style offset") }
// from fogleman/gg func loadFontFace(path string, points float64) (font.Face, error) { fontBytes, err := ioutil.ReadFile(path) if err != nil { return nil, err } f, err := truetype.Parse(fontBytes) if err != nil { return nil, err } face := truetype.NewFace(f, &truetype.Options{ Size: points, Hinting: font.HintingFull, }) return face, nil }
func BenchmarkBuildSprite(b *testing.B) { f, err := readFont(defaultFont) if err != nil { b.FailNow() } face := truetype.NewFace(f, &truetype.Options{ Size: 12.0, DPI: 72, Hinting: font.HintingFull, }) for i := 0; i < b.N; i++ { buildSprite("test", face) } }
// MeasureFont returns the pixel width of the string s at font size sz. // It tries to use system Arial font if possible, but falls back to a // conservative ballpark estimate otherwise. func MeasureFont(s string, sz int) int { // use actual TTF font metrics if available if theFont != nil { myFace := truetype.NewFace(theFont, &truetype.Options{ Size: float64(sz), DPI: float64(DefaultSettings.dpi), }) d := &font.Drawer{Face: myFace} w := d.MeasureString(s) // convert from 26.6 fixed point to pixels return int(w >> 6) } return len(s) * (sz - 2) }
func (f *font) glyphTable(resolution resolution) *glyphTable { t, found := f.resolutions[resolution] if !found { opt := truetype.Options{ Size: float64(f.size), DPI: float64(resolution.intDipsToPixels(72)), Hinting: fnt.HintingFull, GlyphCacheEntries: 1, SubPixelsX: 1, SubPixelsY: 1, } t = newGlyphTable(truetype.NewFace(f.ttf, &opt)) f.resolutions[resolution] = t } return t }
func TestComputeWordsBounds(t *testing.T) { f := truetype.NewFace(clearSans, nil) defer f.Close() words := []Run{ {Offset: 0, Face: f, Text: "Hello"}, {Offset: 5, Face: f, Text: " "}, {Offset: 6, Face: f, Text: "World!"}, } computeWordsBounds(words, fixed.Rectangle26_6{ Min: fixed.Point26_6{X: int26_6(1, 0), Y: int26_6(1, 0)}, Max: fixed.Point26_6{X: int26_6(65, 55), Y: int26_6(15, 5)}, }) if !reflect.DeepEqual(words, []Run{ { Offset: 0, Text: "Hello", Face: f, Bounds: fixed.Rectangle26_6{ Min: fixed.Point26_6{X: int26_6(1, 0), Y: int26_6(1, 0)}, Max: fixed.Point26_6{X: int26_6(28, 37), Y: int26_6(15, 5)}, }, }, { Offset: 5, Text: " ", Face: f, Bounds: fixed.Rectangle26_6{ Min: fixed.Point26_6{X: int26_6(28, 37), Y: int26_6(1, 0)}, Max: fixed.Point26_6{X: int26_6(31, 35), Y: int26_6(15, 5)}, }, }, { Offset: 6, Text: "World!", Face: f, Bounds: fixed.Rectangle26_6{ Min: fixed.Point26_6{X: int26_6(31, 35), Y: int26_6(1, 0)}, Max: fixed.Point26_6{X: int26_6(65, 55), Y: int26_6(15, 5)}, }, }, }) { t.Error(words) } }
// LoadAsset loads the asset at path and interprets it as a font for rendering // with golang.org/x/image/font using opt to create the font.Face object. func LoadAsset(path string, opt *truetype.Options) (*truetype.Font, font.Face, error) { f, err := asset.Open(path) if err != nil { return nil, nil, err } defer f.Close() raw, err := ioutil.ReadAll(f) if err != nil { return nil, nil, err } ttf, err := freetype.ParseFont(raw) if err != nil { return nil, nil, err } face := truetype.NewFace(ttf, opt) return ttf, face, nil }
func TestLineReaderEmpty(t *testing.T) { f := truetype.NewFace(clearSans, nil) defer f.Close() c := NewReader(strings.NewReader(""), Style{Offset: 0, Face: f, Foreground: image.Black, Background: image.White}, ) r := NewLineReader(c, nil, fixed.Rectangle26_6{ Min: fixed.Point26_6{X: fixed.I(1), Y: fixed.I(1)}, Max: fixed.Point26_6{X: fixed.I(600), Y: fixed.I(600)}, }) if _, err := r.ReadLine(); err != io.EOF { t.Error(err) } }
func TestRenderEmptyText(t *testing.T) { f := truetype.NewFace(clearSans, nil) defer f.Close() c := NewReader(strings.NewReader(""), Style{Offset: 0, Face: f, Foreground: image.Black, Background: image.White}, ) view, err := Render(c, NewFixedLayout(fixed.R(1, 1, 600, 600))) if err != nil { t.Error(err) } if !reflect.DeepEqual(view, View{}) { t.Error("invalid view rendered from empty string:", view) } }
// renderTransitRouteName will render the name of the route in the top left corner. func (m *Module) renderTransitRouteName(rgba *image.RGBA, dimensions image.Point, text string) { d := &font.Drawer{ Dst: rgba, Src: foreground, Face: truetype.NewFace(m.font, &truetype.Options{ Size: transitRouteNameFontSize, DPI: dpi, Hinting: font.HintingNone, }), } dy := int(math.Ceil(transitRouteNameFontSize * dpi / 72)) // I can't figure out how to get rid of the annoying notification bar. For now, the Y offset here // needs to include the height of the notification bar which is 48dp ~= 0.3in. d.Dot = fixed.Point26_6{ X: fixed.I(5), Y: fixed.I(int(math.Ceil(0.3*float64(dpi))) + dy), } d.DrawString(text) }
func NewFontAtlas(filename string, dpi, fontSize float64) (*FontAtlas, error) { atlas := &FontAtlas{} content, err := ioutil.ReadFile(filename) if err != nil { return nil, err } atlas.TTF, err = truetype.Parse(content) if err != nil { return nil, err } opts := &truetype.Options{} opts.Size = fontSize atlas.Face = truetype.NewFace(atlas.TTF, opts) return atlas, nil }
func testDraw(t *testing.T, dir Direction) { const str = "Hello World!\nHow are you doing?" var face = truetype.NewFace(clearSans, &truetype.Options{DPI: 144}) var size fixed.Point26_6 var err error if size, err = Measure(strings.NewReader(str), face); err != nil { t.Error(err) return } dst := image.NewRGBA(image.Rect(0, 0, int(size.X>>6)+1, int(size.Y>>6)+1)) src := image.NewUniform(color.Black) for i, n := 0, len(dst.Pix); i != n; i++ { dst.Pix[i] = 0xFF } DrawString( str, face, dst, src, fixed.Rectangle26_6{Max: size}, dir, ) ok := false for i, n := 0, len(dst.Pix); i != n; i++ { if dst.Pix[i] != 0xFF { ok = true break } } if !ok { t.Error("it looks like nothing was written to the image where we should have rendered the text") } saveTest(t, dst, "text.Draw_"+dir.(fmt.Stringer).String()+".png") }
func FontLoad(fontName string, fontSize int) (font.Face, error) { // TODO: select the correct font path fontBytes, err := fonts.LoadFont(fontName) if err != nil { return nil, err } fontFace, err := truetype.Parse(fontBytes) if err != nil { return nil, err } face := truetype.NewFace(fontFace, &truetype.Options{ Size: float64(fontSize) * 72.0 / 96.0, DPI: 72, // Hinting: font.HintingNone, Hinting: font.HintingFull, }) return face, nil }
func TestCharAtSuccess(t *testing.T) { f := truetype.NewFace(clearSans, nil) defer f.Close() c := NewReader(strings.NewReader("Hello World!\n"), Style{Offset: 0, Face: f, Foreground: image.Black, Background: image.White}, ) view, err := Render(c, NewNaturalLayout(fixed.P(1, 1))) if err != nil { t.Error(err) return } char, bounds, ok := view.CharAt(fixed.Point26_6{ X: int26_6(31, 0), Y: int26_6(5, 0), }, LeftToRight) if !ok { t.Error("no char found") return } if char != (Char{ Offset: 5, Rune: ' ', Face: f, Foreground: image.Black, Background: image.White, }) { t.Error("invalid char found:", char) } if bounds != (fixed.Rectangle26_6{ Min: fixed.Point26_6{X: int26_6(28, 37), Y: int26_6(1, 0)}, Max: fixed.Point26_6{X: int26_6(31, 35), Y: int26_6(15, 5)}, }) { t.Error("invalid char bounds:", bounds) } }
func main() { flag.Parse() fmt.Printf("Loading fontfile %q\n", *fontfile) b, err := ioutil.ReadFile(*fontfile) if err != nil { log.Println(err) return } f, err := truetype.Parse(b) if err != nil { log.Println(err) return } fupe := fixed.Int26_6(f.FUnitsPerEm()) printBounds(f.Bounds(fupe)) fmt.Printf("FUnitsPerEm:%d\n\n", fupe) c0, c1 := 'A', 'V' i0 := f.Index(c0) hm := f.HMetric(fupe, i0) g := &truetype.GlyphBuf{} err = g.Load(f, fupe, i0, font.HintingNone) if err != nil { log.Println(err) return } fmt.Printf("'%c' glyph\n", c0) fmt.Printf("AdvanceWidth:%d LeftSideBearing:%d\n", hm.AdvanceWidth, hm.LeftSideBearing) printGlyph(g) i1 := f.Index(c1) fmt.Printf("\n'%c', '%c' Kern:%d\n", c0, c1, f.Kern(fupe, i0, i1)) fmt.Printf("\nThe numbers above are in FUnits.\n" + "The numbers below are in 26.6 fixed point pixels, at 12pt and 72dpi.\n\n") a := truetype.NewFace(f, &truetype.Options{ Size: 12, DPI: 72, }) fmt.Printf("%#v\n", a.Metrics()) }