func (f *Face) Metrics() font.Metrics { return font.Metrics{ Height: fixed.I(f.Height), Ascent: fixed.I(f.Ascent), Descent: fixed.I(f.Descent), } }
func (f *subface) Metrics() font.Metrics { return font.Metrics{ Height: fixed.I(f.height), Ascent: fixed.I(f.ascent), Descent: fixed.I(f.height - f.ascent), } }
func generateImage(params Params) image.Image { fgColor := white var detectedFont *truetype.Font if []rune(params.text)[0] > '\u2E7F' { detectedFont = cjkFont } else { detectedFont = defaultFont } fontSize := float64(params.size) * 0.5 img := image.NewRGBA(image.Rect(0, 0, params.size, params.size)) if params.seed != 0 { rand.Seed(params.seed) } var bgColor color.RGBA if params.color == (color.RGBA{}) { bgColor = colors[rand.Intn(len(colors))] } else { bgColor = params.color } if params.border { bgColor, fgColor = fgColor, bgColor } draw.Draw(img, img.Bounds(), &image.Uniform{bgColor}, image.ZP, draw.Src) if params.border { strokeWidth := fontSize * 0.08 circleSize := fontSize * 0.92 arcAngle := math.Pi * 2 gc := draw2dimg.NewGraphicContext(img) gc.SetStrokeColor(fgColor) gc.SetLineWidth(strokeWidth) gc.ArcTo(fontSize, fontSize, circleSize, circleSize, arcAngle, arcAngle) gc.Stroke() } d := &font.Drawer{ Dst: img, Src: image.NewUniform(fgColor), Face: truetype.NewFace(detectedFont, &truetype.Options{ Size: fontSize, DPI: 72, }), } d.Dot = fixed.Point26_6{ X: (fixed.I(params.size) - d.MeasureString(params.text)) / 2, Y: fixed.I(int(math.Ceil(fontSize * 1.35))), } d.DrawString(params.text) return img }
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 (c *Context) Render(txt string, size float64, col color.Color) (*image.RGBA, error) { bnd := c.fnt.Bounds(fixed.I(int(size + 0.5))) lh := int26_6ToFloat64(bnd.Max.Y) - int26_6ToFloat64(bnd.Min.Y) - 0.5 c.ft.SetSrc(image.NewUniform(col)) c.ft.SetFontSize(size) /* Render image to temporary buffer to determine final size */ tmp := nullImage{} c.ft.SetDst(tmp) c.ft.SetClip(tmp.Bounds()) p, err := c.ft.DrawString(txt, fixed.P(0, int(lh))) if err != nil { return nil, err } dst := image.NewRGBA(image.Rect(0, 0, int(int26_6ToFloat64(p.X)+0.5), int(lh))) draw.Draw(dst, dst.Bounds(), image.NewUniform(color.RGBA{}), image.ZP, draw.Src) c.ft.SetDst(dst) c.ft.SetClip(dst.Bounds()) p, err = c.ft.DrawString(txt, fixed.P(0, int(size))) if err != nil { return nil, err } return dst, nil }
func NewFontFace(path string, pixels float32, fg, bg color.Color) (fontface *FontFace, err error) { var ( font *truetype.Font fontbytes []byte bounds fixed.Rectangle26_6 context *freetype.Context points float32 dpi float32 = 96 ) if fontbytes, err = ioutil.ReadFile(path); err != nil { return } if font, err = freetype.ParseFont(fontbytes); err != nil { return } points = pixels * 72 / dpi bounds = font.Bounds(fixed.I(int(pixels))) context = freetype.NewContext() context.SetFont(font) context.SetFontSize(float64(points)) context.SetDPI(float64(dpi)) fontface = &FontFace{ font: font, charw: float32(bounds.Max.X-bounds.Min.X) / 64, charh: float32(bounds.Max.Y-bounds.Min.Y) / 64, offy: float32(bounds.Min.Y) / 64, fg: fg, bg: bg, context: context, } return }
func (f *Face) Glyph(dot fixed.Point26_6, r rune) ( dr image.Rectangle, mask image.Image, maskp image.Point, advance fixed.Int26_6, ok bool) { loop: for _, rr := range [2]rune{r, '\ufffd'} { for _, rng := range f.Ranges { if rr < rng.Low || rng.High <= rr { continue } maskp.Y = (int(rr-rng.Low) + rng.Offset) * f.Height ok = true break loop } } if !ok { return image.Rectangle{}, nil, image.Point{}, 0, false } minX := int(dot.X+32) >> 6 minY := int(dot.Y+32)>>6 - f.Ascent dr = image.Rectangle{ Min: image.Point{ X: minX, Y: minY, }, Max: image.Point{ X: minX + f.Width, Y: minY + f.Height, }, } return dr, f.Mask, maskp, fixed.I(f.Advance), true }
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 main() { const ( w = 400 h = 400 ) r := raster.NewRasterizer(w, h) r.UseNonZeroWinding = true cjs := []struct { c raster.Capper j raster.Joiner }{ {raster.RoundCapper, raster.RoundJoiner}, {raster.ButtCapper, raster.BevelJoiner}, {raster.SquareCapper, raster.BevelJoiner}, } for i, cj := range cjs { var path raster.Path path.Start(fixed.P(30+100*i, 30+120*i)) path.Add1(fixed.P(180+100*i, 80+120*i)) path.Add1(fixed.P(50+100*i, 130+120*i)) raster.Stroke(r, path, fixed.I(20), cj.c, cj.j) } rgba := image.NewRGBA(image.Rect(0, 0, w, h)) draw.Draw(rgba, rgba.Bounds(), image.Black, image.Point{}, draw.Src) p := raster.NewRGBAPainter(rgba) p.SetColor(color.RGBA{0x7f, 0x7f, 0x7f, 0xff}) r.Rasterize(p) white := color.RGBA{0xff, 0xff, 0xff, 0xff} for i := range cjs { rgba.SetRGBA(30+100*i, 30+120*i, white) rgba.SetRGBA(180+100*i, 80+120*i, white) rgba.SetRGBA(50+100*i, 130+120*i, white) } // Save that RGBA image to disk. outFile, err := os.Create("out.png") if err != nil { log.Println(err) os.Exit(1) } defer outFile.Close() b := bufio.NewWriter(outFile) err = png.Encode(b, rgba) if err != nil { log.Println(err) os.Exit(1) } err = b.Flush() if err != nil { log.Println(err) os.Exit(1) } fmt.Println("Wrote out.png OK.") }
// 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) }
// our avatar image is square func (g *drawer) Draw(s string, size int, bg *color.RGBA) image.Image { // draw the background dst := image.NewRGBA(image.Rect(0, 0, size, size)) draw.Draw(dst, dst.Bounds(), &image.Uniform{bg}, image.ZP, draw.Src) // draw the text drawer := &font.Drawer{ Dst: dst, Src: image.White, Face: g.face, } // font index fi := g.font.Index([]rune(s)[0]) // glyph example: http://www.freetype.org/freetype2/docs/tutorial/metrics.png var gbuf truetype.GlyphBuf var err error var _fsize fixed.Int26_6 = fixed.Int26_6(g.fontSize * g.dpi * (64.0 / 72.0)) err = gbuf.Load(g.font, _fsize, fi, font.HintingFull) if err != nil { // fixme drawer.DrawString("") return dst } // center dY := int((size - int(gbuf.Bounds.Max.Y-gbuf.Bounds.Min.Y)>>6) / 2) dX := int((size - int(gbuf.Bounds.Max.X-gbuf.Bounds.Min.X)>>6) / 2) y := int(gbuf.Bounds.Max.Y>>6) + dY x := 0 - int(gbuf.Bounds.Min.X>>6) + dX //y := 10 + int(math.Ceil(g.fontSize*g.dpi/72)) //FIXME: what does it mean? drawer.Dot = fixed.Point26_6{ X: fixed.I(x), //(fixed.I(size) - drawer.MeasureString(s)) / 2, Y: fixed.I(y), } drawer.DrawString(s) return dst }
// our avatar image is square func (g *drawer) Draw(s string, size int, bg *color.RGBA) image.Image { // draw the background dst := image.NewRGBA(image.Rect(0, 0, size, size)) draw.Draw(dst, dst.Bounds(), &image.Uniform{bg}, image.ZP, draw.Src) // draw the text drawer := &font.Drawer{ Dst: dst, Src: image.White, Face: g.face, } y := 10 + int(math.Ceil(g.fontSize*g.dpi/72)) //FIXME: what does it mean? drawer.Dot = fixed.Point26_6{ X: (fixed.I(size) - drawer.MeasureString(s)) / 2, Y: fixed.I(y), } drawer.DrawString(s) return dst }
// renderLoadingScreen will render the Loading screen. func (m *Module) renderInformation(rgba *image.RGBA, dimensions image.Point, background image.Image, text string, textSizing string) { // Prepare a dark grey background to draw on. draw.Draw(rgba, rgba.Bounds(), background, image.ZP, draw.Src) d := &font.Drawer{ Dst: rgba, Src: foreground, Face: truetype.NewFace(m.font, &truetype.Options{ Size: informationPopupFontSize, DPI: dpi, Hinting: font.HintingNone, }), } dy := int(math.Ceil(informationPopupFontSize * dpi / 72)) textWidth := d.MeasureString(textSizing) d.Dot = fixed.Point26_6{ X: fixed.I(dimensions.X/2) - (textWidth / 2), Y: fixed.I(dimensions.Y/2 + dy/2), } d.DrawString(text) }
func TestVerticalLayout(t *testing.T) { layout := NewVerticalLayout(fixed.P(10, 10), fixed.I(100)) if r, ok := layout.NextBounds(); !ok { t.Error("no bounds returned by the vertical layout") } else if r != fixed.R(10, 10, 110, 33554431) { t.Error("invalid bounds returned by the vertical layout:", r) } if _, ok := layout.NextBounds(); ok { t.Error("incomplete vertical layout") } }
// buildSprite renders an image from a text and font face as well as computing its // bounding box. // // TODO: This is a really naive implementation that really should be refactored // into something more elegant. func buildSprite(text string, face font.Face) Word { var ( canvasWidth = 1024 canvasHeight = 1024 ) // Create temporary scratch image to determine real font size. scratch := image.NewRGBA(image.Rect(0, 0, canvasWidth, canvasHeight)) draw.Draw(scratch, scratch.Bounds(), image.Transparent, image.ZP, draw.Src) d := &font.Drawer{ Dst: scratch, Src: image.White, Face: face, } d.Dot = fixed.Point26_6{ X: fixed.I(canvasWidth / 2), Y: fixed.I(canvasHeight / 2), } d.DrawString(text) // Generate trimmed image and quadtree from temporary canvas. tt := buildTree(scratch, color.RGBA{0, 0, 0, 0}).Trimmed() r := tt.Extents tr := image.Rect(0, 0, r.Dx(), r.Dy()) im := image.NewRGBA(tr) draw.Draw(im, tr, scratch, r.Min, draw.Src) t := buildTree(im, color.RGBA{0, 0, 0, 0}) return Word{ Text: text, Image: im, Bounds: t, } }
func Render(letter string, bgColor color.Color, width int, out io.Writer) error { fg := pickForegroundColor(bgColor) rgba := image.NewRGBA(image.Rect(0, 0, width, width)) draw.Draw(rgba, rgba.Bounds(), &image.Uniform{bgColor}, image.ZP, draw.Src) fontSize := fontSizeFactor * float64(width) d := &font.Drawer{ Dst: rgba, Src: &image.Uniform{fg}, Face: truetype.NewFace(fnt, &truetype.Options{ Size: fontSize, DPI: dpi, Hinting: font.HintingNone, }), } y := int(yOffsetFactor*float64(width)) + int(math.Ceil(fontSize*dpi/72)) d.Dot = fixed.Point26_6{ X: (fixed.I(width) - d.MeasureString(letter)) / 2, Y: fixed.I(y), } d.DrawString(letter) b := bufio.NewWriter(out) encoder := png.Encoder{CompressionLevel: png.DefaultCompression} err := encoder.Encode(b, rgba) if err != nil { return err } err = b.Flush() if err != nil { return err } return nil }
func (t *Text) GetStringSize(s string) (fixed.Int26_6, fixed.Int26_6) { // Assumes 72 DPI fupe := fixed.Int26_6(t.fontSize * 64.0) width := fixed.I(0) prev, hasPrev := t.font.Index(0), false for _, r := range s { idx := t.font.Index(r) if hasPrev { width += t.font.Kerning(fupe, prev, idx) } width += t.font.HMetric(fupe, idx).AdvanceWidth prev, hasPrev = idx, true } fontBounds := t.font.Bounds(fupe) return width, (fontBounds.YMax - fontBounds.YMin) }
func NewFontAtlas(filename string, dpi, fontSize float64) (*FontAtlas, error) { atlas := &FontAtlas{} atlas.Rendered = make(map[rune]Glyph, 256) content, err := ioutil.ReadFile(filename) if err != nil { return nil, err } atlas.drawPadding = float32(fontSize * 0.5) atlas.lineHeight = float32(fontSize * 1.2) atlas.TTF, err = truetype.Parse(content) if err != nil { return nil, err } atlas.Image = image.NewRGBA(image.Rect(0, 0, 1024, 1024)) atlas.Context = freetype.NewContext() atlas.Context.SetDPI(dpi) atlas.Context.SetFont(atlas.TTF) atlas.Context.SetFontSize(fontSize) atlas.Context.SetClip(atlas.Image.Bounds()) atlas.Context.SetSrc(image.White) atlas.Context.SetDst(atlas.Image) atlas.maxBounds = atlas.TTF.Bounds(fixed.I(int(fontSize))) opts := &truetype.Options{} opts.Size = fontSize opts.Hinting = font.HintingFull atlas.Face = truetype.NewFace(atlas.TTF, opts) return atlas, nil }
func main() { flag.Parse() chimg := make(chan image.Image) go func() { resp, err := http.Get(fmt.Sprintf("%s/%dx%d", unsplashUrl, *width, *height)) if err != nil { log.Fatal(err) } img, err := jpeg.Decode(resp.Body) if err != nil { log.Fatal(err) } chimg <- img }() //load font chmask := make(chan image.Image) go func() { fontBytes, err := ubuntu.Asset("Ubuntu-B.ttf") if err != nil { log.Fatal(err) } f, err := truetype.Parse(fontBytes) if err != nil { log.Fatal(err) } //generate text mast mask := image.NewRGBA(image.Rect(0, 0, *width, *height)) //draw.Draw(mask, mask.Bounds(), image.White, image.ZP, draw.Src) d := &font.Drawer{ Dst: mask, Src: image.White, Face: truetype.NewFace(f, &truetype.Options{ Size: float64(*fontSize), DPI: float64(*dpi), Hinting: font.HintingNone, }), } d.Dot = fixed.Point26_6{ X: (fixed.I(*width) - d.MeasureString(*text)) / 2, Y: fixed.I(*height) / 2, } d.DrawString(*text) chmask <- mask }() mask := <-chmask img := <-chimg finalDst := image.NewRGBA(img.Bounds()) changedDst := InvertColors(Flip(img)) //Convert dst dstB := img.Bounds() draw.Draw(finalDst, finalDst.Bounds(), img, dstB.Min, draw.Src) draw.DrawMask(finalDst, finalDst.Bounds(), changedDst, image.ZP, mask, image.ZP, draw.Over) file, err := os.Create(*output) if err != nil { log.Fatal(err) } png.Encode(file, finalDst) }
func (f *Face) GlyphAdvance(r rune) (advance fixed.Int26_6, ok bool) { return fixed.I(f.Advance), true }
func (f *Face) GlyphBounds(r rune) (bounds fixed.Rectangle26_6, advance fixed.Int26_6, ok bool) { return fixed.R(0, -f.Ascent, f.Width, -f.Ascent+f.Height), fixed.I(f.Advance), true }
func (m *Module) render(rgba *image.RGBA, display Display, dimensions image.Point) { // Prepare a blue background to draw on. draw.Draw(rgba, rgba.Bounds(), background, image.ZP, draw.Src) // First, render the very next train's minutes on the left half of the screen. d := &font.Drawer{ Dst: rgba, Src: foreground, Face: truetype.NewFace(m.font, &truetype.Options{ Size: nextTrainFontSize, DPI: dpi, Hinting: font.HintingNone, }), } textWidth := d.MeasureString(strconv.Itoa(display.NextTrainMinutes)) d.Dot = fixed.Point26_6{ X: fixed.I(dimensions.X/4) - (textWidth / 2), Y: fixed.I(int(4 * dimensions.Y / 5)), } d.DrawString(strconv.Itoa(display.NextTrainMinutes)) // Render the little "min" label next to the next train time. m.renderMin(rgba, fixed.Point26_6{ X: d.Dot.X, Y: d.Dot.Y, }) // Now, render the next next train's minutes on the right half of the screen. if display.NextNextOK { d = &font.Drawer{ Dst: rgba, Src: foreground, Face: truetype.NewFace(m.font, &truetype.Options{ Size: nextNextTrainFontSize, DPI: dpi, Hinting: font.HintingNone, }), } textWidth = d.MeasureString(strconv.Itoa(display.NextNextTrainMinutes)) d.Dot = fixed.Point26_6{ X: fixed.I(3*dimensions.X/4) - (textWidth / 2), Y: fixed.I(int(2 * dimensions.Y / 3)), } d.DrawString(strconv.Itoa(display.NextNextTrainMinutes)) // Render the little "min" label next to the next, next train time. m.renderMin(rgba, fixed.Point26_6{ X: d.Dot.X, Y: d.Dot.Y, }) } // Render the text indicating the freshness of the presented data. d = &font.Drawer{ Dst: rgba, Src: secondaryForeground, Face: truetype.NewFace(m.font, &truetype.Options{ Size: lastUpdatedAtFontSize, DPI: dpi, Hinting: font.HintingNone, }), } updatedAt := fmt.Sprintf("Predictions accurate as of %v seconds ago, from %s.", display.UpdatedSecondsAgo, display.PredictionSource) textWidth = d.MeasureString(updatedAt) d.Dot = fixed.Point26_6{ X: fixed.I(dimensions.X-10) - textWidth, Y: fixed.I(dimensions.Y - lastUpdatedAtFontSize), } d.DrawString(updatedAt) }
func (b BoxGoalGenerator) CreateSignature(req util.ParsedSignatureRequest) (util.Signature, error) { username := req.GetProperty("username").(string) skill := req.GetProperty("skill").(util.Skill) goal := req.GetProperty("goal").(int) goaltype := req.GetProperty("goaltype").(util.GoalType) stats, err := util.GetStats(username) if err != nil { var s util.Signature return s, errors.New(fmt.Sprintf("Failed to fetch stats for %s", username)) } stat := util.GetStatBySkill(stats, skill) currentLevel := util.LevelFromXP(stat.Skill, stat.Xp) currentXP := stat.Xp var goalXP int var remainder int if goaltype == util.GoalXP { goalXP = goal remainder = goalXP - currentXP } else { goalXP = util.XPForLevel(stat.Skill, goal) remainder = util.XPToLevel(stat.Skill, currentXP, goal) } goalLevel := util.LevelFromXP(stat.Skill, goalXP) if remainder < 0 { remainder = 0 } percent := int(float64(currentXP) / float64(goalXP) * 100.0) if percent > 100 { percent = 100 } c := freetype.NewContext() createDrawer := func(img draw.Image, color *image.Uniform, f *truetype.Font, size float64, dpi float64, hinting font.Hinting) *font.Drawer { return &font.Drawer{ Dst: img, Src: color, Face: truetype.NewFace(f, &truetype.Options{ Size: size, DPI: dpi, Hinting: hinting, })} } baseImage := cloneImage(baseImage) drawer := createDrawer(baseImage, fontColor, baseFont, size, dpi, font.HintingFull) drawString := func(str string, x fixed.Int26_6, y int) { drawer.Dot = fixed.Point26_6{ X: x, //Y: fixed.I(y + int((c.PointToFixed(size) >> 6))), Y: fixed.I(y + int(c.PointToFixed(size)>>6)), } drawer.DrawString(str) } drawRightAlignedString := func(str string, x, y int) { width := drawer.MeasureString(str) drawString(str, fixed.I(x)-width, y) } // Skill name and current level drawString( fmt.Sprintf("%s: %d/%d", skill.Name, currentLevel, goalLevel), fixed.I(7), 1) for _, label := range staticLabels { if label.str == "Target lvl:" && goaltype == util.GoalXP { label.str = "Target XP:" } drawString(label.str, fixed.I(label.x), label.y) } x, y := 150, 15 // current xp drawRightAlignedString(util.Format(currentXP), x, y) y += 15 // goal drawRightAlignedString(util.Format(goal), x, y) y += 15 // remainder drawRightAlignedString(util.Format(remainder), x, y) y += 15 // bar drawBar(baseImage, percent) // bar percentage x = 71 y = 61 color := image.White if percent >= 50 { color = image.Black } drawer = createDrawer(baseImage, color, baseFont, 11, dpi, font.HintingFull) drawString(fmt.Sprintf("%d%%", percent), fixed.I(x), y) return util.Signature{username, baseImage}, nil }
func (s *Service) Draw(siteName string, w http.ResponseWriter) { // Read the font data. fontBytes := MustAsset("assets/luxisr.ttf") f, err := truetype.Parse(fontBytes) if err != nil { log.Println(err) return } // Draw the background and the guidelines. fg, bg := image.Black, image.White const imgW, imgH = 1200, 700 rgba := image.NewRGBA(image.Rect(0, 0, imgW, imgH)) draw.Draw(rgba, rgba.Bounds(), bg, image.ZP, draw.Src) d := &font.Drawer{ Dst: rgba, Src: fg, Face: truetype.NewFace(f, &truetype.Options{ Size: size, DPI: dpi, Hinting: hinting, }), } y := (imgH / 2) + (int(math.Ceil(size*dpi/72)) / 2) //dy := int(math.Ceil(size * spacing * dpi / 72)) d.Dot = fixed.Point26_6{ X: (fixed.I(imgW) - d.MeasureString(siteName)) / 2, Y: fixed.I(y), } d.DrawString(siteName) d = &font.Drawer{ Dst: rgba, Src: image.NewUniform(color.Gray16{0xaaaa}), Face: truetype.NewFace(f, &truetype.Options{ Size: smallSize, DPI: dpi, Hinting: hinting, }), } dy := int(math.Ceil(smallSize * spacing * dpi / 72)) d.Dot = fixed.Point26_6{ X: (fixed.I(imgW) - d.MeasureString(sas)) / 2, Y: fixed.I(y + dy), } d.DrawString(sas) b := bufio.NewWriter(w) err = png.Encode(b, rgba) if err != nil { log.Println(err) return } err = b.Flush() if err != nil { log.Println(err) return } }
func tryLoadFont(fontFilePath string) (Font, error) { defer tlog.FuncLog(tlog.Func("LoadFont")) var data []byte { fd, err := os.Open(fontFilePath) if err != nil { return Font{}, err } defer fd.Close() data, err = ioutil.ReadAll(fd) if err != nil { return Font{}, err } } texsdfpath := filepath.Join(os.TempDir(), filepath.Base(fontFilePath)+".sdf") texsdf, err := LoadPng(texsdfpath) sdfLoaded := err == nil if err != nil { texsdf = image.NewGray(image.Rect(0, 0, texSize, texSize)) } name := filepath.Base(fontFilePath) name = name[:len(name)-len(filepath.Ext(name))] var f = Font{ SdfTex: texsdf, Glyphs: make([]Glyph, int(highRune-lowRune+1)), FirstRune: lowRune, Name: name, Height: 0, Leading: 0, } { // Read the truetype font. ttf, err := truetype.Parse(data) if err != nil { return Font{}, err } var baseline = 3 * sdfSize / 4 var targetGlyphExtentPxs = float64(sdfSize) * 3.0 / 4.0 var fontHeight float64 // maximum height, in renormalized EMs var renormalizeEM float64 // (real max glyph size / em) { // the real max glyph extent in EMs var maxGlyphExtentEMs float64 { var max fixed.Int26_6 // = 0; max extent(height or width) of a glyph in FontUnits var height fixed.Int26_6 // = 0; max height of a glyph in FontUnits { scale := fixed.I(int(ttf.FUnitsPerEm())) b := ttf.Bounds(scale) max = b.Max.X - b.Min.X height = b.Max.Y - b.Min.Y if max < height { max = height } for ch := lowRune; ch <= highRune; ch++ { hmetric := ttf.HMetric(scale, ttf.Index(ch)) vmetric := ttf.VMetric(scale, ttf.Index(ch)) if max < hmetric.AdvanceWidth { max = hmetric.AdvanceWidth } if height < vmetric.AdvanceHeight { height = vmetric.AdvanceHeight } if max < height { max = height } } } maxGlyphExtentEMs = float64(max) / float64(ttf.FUnitsPerEm()) fontHeight = float64(height) / float64(ttf.FUnitsPerEm()) } renormalizeEM = 1 / maxGlyphExtentEMs fontHeight *= renormalizeEM } f.Height = em(fontHeight) f.Leading = em(leadingFactor * fontHeight) scale := fixed.I(int(ttf.FUnitsPerEm())) graphicIdx := 0 for ch := lowRune; ch <= highRune; ch++ { // fmt.Printf("%c", ch) idx := int(ch - lowRune) var aw float64 { hmetric := ttf.HMetric(scale, ttf.Index(ch)) aw = float64(hmetric.AdvanceWidth) / float64(ttf.FUnitsPerEm()) * renormalizeEM f.Glyphs[idx].AdvanceWidth = em(aw) } if !unicode.IsGraphic(ch) || unicode.IsSpace(ch) { f.Glyphs[idx].Cell = image.ZR continue } x0 := cellSize * (graphicIdx % texNCells) y0 := cellSize * (graphicIdx / texNCells) graphicIdx++ f.Glyphs[idx].Cell = image.Rect(x0, y0, x0+cellSize, y0+cellSize) sdfTargetWidth := targetGlyphExtentPxs * aw sdfTargetHeight := targetGlyphExtentPxs * fontHeight if !sdfLoaded { img := image.NewGray(image.Rect(0, 0, sdfSize, sdfSize)) c := freetype.NewContext() c.SetFont(ttf) c.SetDPI(72) // so that one Pt == one Pixel c.SetFontSize(targetGlyphExtentPxs * renormalizeEM) // how many Pt the max glyph has c.SetClip(img.Bounds()) c.SetDst(img) c.SetSrc(image.White) sdfX := (sdfSize - int(sdfTargetWidth)) / 2 c.DrawString(string(ch), freetype.Pt(sdfX, baseline)) sdfize(img) scaleDownTo(f.SdfTex, f.Glyphs[idx].Cell, img) } dx := (sdfSize - int(sdfTargetWidth)) / 2 * cellSize / sdfSize cellHeight := sdfTargetHeight * float64(cellSize) / float64(sdfSize) dy := (cellSize - int(cellHeight)) / 4 f.Glyphs[idx].Cell = image.Rect(x0+dx, y0+3*dy, x0+cellSize-dx, y0+cellSize-dy) // drawRect(f.SdfTex, f.Glyphs[idx].Cell, color.Gray{255}) } } if !sdfLoaded { SavePng(texsdfpath, f.SdfTex) tlog.Println("sdf ", texsdfpath, " saved") } // ShowImage(f.SdfTex) tlog.Println(fontFilePath, " loaded") return f, nil }
func TestLineReaderSingle(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}, Style{Offset: 5, Face: f, Foreground: image.White}, Style{Offset: 13, Face: f, Foreground: image.Black}, ) 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)}, }) var line Line var err error if line, err = r.ReadLine(); err != nil { t.Error(line, err) } if !reflect.DeepEqual(line, Line{ Runs: []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(15, 5)}, }, }, { 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(15, 5)}, }, }, }, 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(65, 55), Y: int26_6(15, 5)}, }, }) { t.Error("invalid first line:", line) } if line, err = r.ReadLine(); err != nil { t.Error(err) } if !reflect.DeepEqual(line, Line{ LF: Char{ Offset: 12, Rune: '\n', Face: f, Foreground: image.White, Background: image.White, }, Bounds: fixed.Rectangle26_6{ Min: fixed.Point26_6{X: int26_6(1, 0), Y: int26_6(13, 0)}, Max: fixed.Point26_6{X: int26_6(1, 0), Y: int26_6(25, 0)}, }, }) { t.Error("invalid second line:", line) } if _, err := r.ReadLine(); err != io.EOF { t.Error(err) } }
func TestLineReaderSimple(t *testing.T) { f := truetype.NewFace(clearSans, nil) defer f.Close() c := NewReader(strings.NewReader("Hello World!"), Style{Offset: 0, Face: f, Foreground: image.Black, Background: image.White}, Style{Offset: 5, Face: f, Foreground: 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)}, }) var line Line var err error if line, err = r.ReadLine(); err != nil { t.Error(err) } if !reflect.DeepEqual(line, Line{ Runs: []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(15, 5)}, }, }, { 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(15, 5)}, }, }, }, 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(65, 55), Y: int26_6(15, 5)}, }, }) { t.Error("invalid line:", line) } // The first EOF will come from the underlying run reader. if _, err := r.ReadLine(); err != io.EOF { t.Error(err) } // The second EOF will come from the line reader which has cached that it // has nothing to return anymore. if _, err := r.ReadLine(); err != io.EOF { t.Error(err) } }
func i26_6(f C.CGFloat) fixed.Int26_6 { a, b := math.Modf(float64(f)) return fixed.I(int(a)) + fixed.Int26_6(b*64) }
func main() { flag.Parse() // Read the font data. fontBytes, err := ioutil.ReadFile(*fontfile) if err != nil { log.Println(err) return } f, err := truetype.Parse(fontBytes) if err != nil { log.Println(err) return } // Draw the background and the guidelines. fg, bg := image.Black, image.White ruler := color.RGBA{0xdd, 0xdd, 0xdd, 0xff} if *wonb { fg, bg = image.White, image.Black ruler = color.RGBA{0x22, 0x22, 0x22, 0xff} } const imgW, imgH = 640, 480 rgba := image.NewRGBA(image.Rect(0, 0, imgW, imgH)) draw.Draw(rgba, rgba.Bounds(), bg, image.ZP, draw.Src) for i := 0; i < 200; i++ { rgba.Set(10, 10+i, ruler) rgba.Set(10+i, 10, ruler) } // Draw the text. h := font.HintingNone switch *hinting { case "full": h = font.HintingFull } d := &font.Drawer{ Dst: rgba, Src: fg, Face: truetype.NewFace(f, &truetype.Options{ Size: *size, DPI: *dpi, Hinting: h, }), } y := 10 + int(math.Ceil(*size**dpi/72)) dy := int(math.Ceil(*size * *spacing * *dpi / 72)) d.Dot = fixed.Point26_6{ X: (fixed.I(imgW) - d.MeasureString(title)) / 2, Y: fixed.I(y), } d.DrawString(title) y += dy for _, s := range text { d.Dot = fixed.P(10, y) d.DrawString(s) y += dy } // Save that RGBA image to disk. outFile, err := os.Create("out.png") if err != nil { log.Println(err) os.Exit(1) } defer outFile.Close() b := bufio.NewWriter(outFile) err = png.Encode(b, rgba) if err != nil { log.Println(err) os.Exit(1) } err = b.Flush() if err != nil { log.Println(err) os.Exit(1) } fmt.Println("Wrote out.png OK.") }
func testScaling(t *testing.T, h font.Hinting) { for _, tc := range scalingTestCases { f, testdataIsOptional, err := parseTestdataFont(tc.name) if err != nil { if testdataIsOptional { t.Log(err) } else { t.Error(err) } continue } hintingStr := "sans" if h != font.HintingNone { hintingStr = "with" } testFile, err := os.Open(fmt.Sprintf( "../testdata/%s-%dpt-%s-hinting.txt", tc.name, tc.size, hintingStr)) if err != nil { t.Errorf("%s: Open: %v", tc.name, err) continue } defer testFile.Close() wants := []scalingTestData{} scanner := bufio.NewScanner(testFile) if scanner.Scan() { major, minor, patch := 0, 0, 0 _, err := fmt.Sscanf(scanner.Text(), "freetype version %d.%d.%d", &major, &minor, &patch) if err != nil { t.Errorf("%s: version information: %v", tc.name, err) } if (major < 2) || (major == 2 && minor < 5) || (major == 2 && minor == 5 && patch < 1) { t.Errorf("%s: need freetype version >= 2.5.1.\n"+ "Try setting LD_LIBRARY_PATH=/path/to/freetype_built_from_src/objs/.libs/\n"+ "and re-running testdata/make-other-hinting-txts.sh", tc.name) continue } } else { t.Errorf("%s: no version information", tc.name) continue } for scanner.Scan() { wants = append(wants, scalingTestParse(scanner.Text())) } if err := scanner.Err(); err != nil && err != io.EOF { t.Errorf("%s: Scanner: %v", tc.name, err) continue } glyphBuf := &GlyphBuf{} for i, want := range wants { if err = glyphBuf.Load(f, fixed.I(tc.size), Index(i), h); err != nil { t.Errorf("%s: glyph #%d: Load: %v", tc.name, i, err) continue } got := scalingTestData{ advanceWidth: glyphBuf.AdvanceWidth, bounds: glyphBuf.Bounds, points: glyphBuf.Points, } if got.advanceWidth != want.advanceWidth { t.Errorf("%s: glyph #%d advance width:\ngot %v\nwant %v", tc.name, i, got.advanceWidth, want.advanceWidth) continue } if got.bounds != want.bounds { t.Errorf("%s: glyph #%d bounds:\ngot %v\nwant %v", tc.name, i, got.bounds, want.bounds) continue } for i := range got.points { got.points[i].Flags &= 0x01 } if len(got.points) != len(want.points) { t.Errorf("%s: glyph #%d:\ngot %v\nwant %v\ndifferent slice lengths: %d versus %d", tc.name, i, got.points, want.points, len(got.points), len(want.points)) continue } if j, equals := scalingTestEquals(got.points, want.points); !equals { t.Errorf("%s: glyph #%d:\ngot %v\nwant %v\nat index %d: %v versus %v", tc.name, i, got.points, want.points, j, got.points[j], want.points[j]) continue } } } }