// Render draws rune r front the specified font at the specified dpi and scale. It returns a // grayscale image that is just large enough to contain the rune. func Render(font *truetype.Font, r rune, dpi, scale float64) (*image.Gray, error) { glyph := truetype.NewGlyphBuf() index := font.Index(r) glyph.Load(font, font.FUnitsPerEm(), index, truetype.FullHinting) ctx := freetype.NewContext() boxer := makeBoundingBoxer() ctx.SetSrc(image.NewUniform(color.White)) ctx.SetDst(boxer) ctx.SetClip(boxer.largeBounds) ctx.SetFontSize(250) ctx.SetDPI(dpi) ctx.SetFont(font) if err := glyph.Load(font, font.FUnitsPerEm(), font.Index(r), truetype.FullHinting); err != nil { return nil, fmt.Errorf("Unable to load glyph: %v\n", err) } var rp raster.Point rp.X = ctx.PointToFix32(0) rp.Y = ctx.PointToFix32(100) ctx.DrawString(string(r), rp) boxer.complete() g := gift.New( gift.Resize(int(float64(boxer.Bounds().Dx())*scale+0.5), int(float64(boxer.Bounds().Dy())*scale+0.5), gift.CubicResampling), ) dst := image.NewGray(g.Bounds(boxer.Bounds())) g.Draw(dst, boxer) return dst, nil }
func printRuneInfo(canvas *Canvas, font *truetype.Font, r rune) { index := font.Index(r) scale := int32(50) hmetric := font.HMetric(scale, index) vmetric := font.VMetric(scale, index) println("Index:", index) println("FUnitsPerEm:", font.FUnitsPerEm()) println("Scale:", scale) println("HMetric:", hmetric) println("VMetric:", vmetric) }
func generateAtlas(font *truetype.Font, scale int32, dpi float64, width, height float32) ([]Vector4, *image.RGBA, []float32) { var low rune = 32 var high rune = 127 glyphCount := int32(high - low + 1) offsets := make([]float32, glyphCount) bounds := font.Bounds(scale) gw := float32(bounds.XMax - bounds.XMin) gh := float32(bounds.YMax - bounds.YMin) imageWidth := glh.Pow2(uint32(gw * float32(glyphCount))) imageHeight := glh.Pow2(uint32(gh)) imageBounds := image.Rect(0, 0, int(imageWidth), int(imageHeight)) sx := float32(2) / width sy := float32(2) / height w := gw * sx h := gh * sy img := image.NewRGBA(imageBounds) c := freetype.NewContext() c.SetDst(img) c.SetClip(img.Bounds()) c.SetSrc(image.White) c.SetDPI(dpi) c.SetFontSize(float64(scale)) c.SetFont(font) var gi int32 var gx, gy float32 verts := make([]Vector4, 0) texWidth := float32(img.Bounds().Dx()) texHeight := float32(img.Bounds().Dy()) for ch := low; ch <= high; ch++ { index := font.Index(ch) metric := font.HMetric(scale, index) //the offset is used when drawing a string of glyphs - we will advance a glyph's quad by the width of all previous glyphs in the string offsets[gi] = float32(metric.AdvanceWidth) * sx //draw the glyph into the atlas at the correct location pt := freetype.Pt(int(gx), int(gy)+int(c.PointToFix32(float64(scale))>>8)) c.DrawString(string(ch), pt) tx1 := gx / texWidth ty1 := gy / texHeight tx2 := (gx + gw) / texWidth ty2 := (gy + gh) / texHeight //the x,y coordinates are the same for each quad; only the texture coordinates (stored in z,w) change. //an optimization would be to only store texture coords, but I haven't figured that out yet verts = append(verts, Vector4{-1, 1, tx1, ty1}, Vector4{-1 + (w), 1, tx2, ty1}, Vector4{-1, 1 - (h), tx1, ty2}, Vector4{-1 + (w), 1 - (h), tx2, ty2}) gx += gw gi++ } return verts, img, offsets }
// Text takes an image and, using the freetype package, writes text in the // position specified on to the image. A color.Color, a font size and a font // must also be specified. // Finally, the (x, y) coordinate advanced by the text extents is returned. // // Note that the ParseFont helper function can be used to get a *truetype.Font // value without having to import freetype-go directly. // // If you need more control over the 'context' used to draw text (like the DPI), // then you'll need to ignore this convenience method and use your own. func (im *Image) Text(x, y int, clr color.Color, fontSize float64, font *truetype.Font, text string) (int, int, error) { // Create a solid color image textClr := image.NewUniform(clr) // Set up the freetype context... mostly boiler plate c := ftContext(font, fontSize) c.SetClip(im.Bounds()) c.SetDst(im) c.SetSrc(textClr) // Now let's actually draw the text... pt := freetype.Pt(x, y+int(font.FUnitsPerEm())) newpt, err := c.DrawString(text, pt) if err != nil { return 0, 0, err } // i think this is right... return int(newpt.X / 256), int(newpt.Y / 256), nil }
// Extents returns the FontExtents for a font. // TODO needs to read this https://developer.apple.com/fonts/TrueType-Reference-Manual/RM02/Chap2.html#intro func Extents(font *truetype.Font, size float64) FontExtents { bounds := font.Bounds(font.FUnitsPerEm()) scale := size / float64(font.FUnitsPerEm()) return FontExtents{ Ascent: float64(bounds.YMax) * scale, Descent: float64(bounds.YMin) * scale, Height: float64(bounds.YMax-bounds.YMin) * scale, } }
func printGlyph(font *truetype.Font, c rune, resolution int32) { var idx = font.Index(c) var hm = font.HMetric(resolution, idx) var g = truetype.NewGlyphBuf() err := g.Load(font, resolution, idx, truetype.NoHinting) if err != nil { log.Println(err) return } fmt.Printf("'%c' glyph\n", c) fmt.Printf("AdvanceWidth:%d LeftSideBearing:%d\n", hm.AdvanceWidth, hm.LeftSideBearing) printGlyphCurve(g) c1 := 'A' i1 := font.Index(c1) fmt.Printf("\n'%c', '%c' Kerning:%d\n", c, c1, font.Kerning(resolution, idx, i1)) }
func genGlyphs(font *truetype.Font, size int, text string) (glyphs []*glyph) { scale := int32(float64(size) * dpi * (64.0 / 72.0)) clip := image.Rect(0, 0, width, height) // Calculate the rasterizer's bounds to handle the largest glyph. b := font.Bounds(scale) xmin := int(b.XMin) >> 6 ymin := -int(b.YMax) >> 6 xmax := int(b.XMax+63) >> 6 ymax := -int(b.YMin-63) >> 6 r := raster.NewRasterizer(xmax-xmin, ymax-ymin) buf := truetype.NewGlyphBuf() for _, variant := range []string{strings.ToUpper(text), strings.ToLower(text)} { pt := Pt(30, 10+int(pointToFix32(float64(size))>>8)) for _, char := range variant { idx := font.Index(char) buf.Load(font, scale, idx, truetype.FullHinting) // Calculate the integer-pixel bounds for the glyph. xmin := int(raster.Fix32(buf.B.XMin<<2)) >> 8 ymin := int(-raster.Fix32(buf.B.YMax<<2)) >> 8 xmax := int(raster.Fix32(buf.B.XMax<<2)+0xff) >> 8 ymax := int(-raster.Fix32(buf.B.YMin<<2)+0xff) >> 8 fx := raster.Fix32(-xmin << 8) fy := raster.Fix32(-ymin << 8) ix := int(pt.X >> 8) iy := int(pt.Y >> 8) // Rasterize the glyph's vectors. r.Clear() e0 := 0 for _, e1 := range buf.End { drawContour(r, buf.Point[e0:e1], fx, fy) e0 = e1 } mask := image.NewAlpha(image.Rect(0, 0, xmax-xmin, ymax-ymin)) r.Rasterize(raster.NewAlphaSrcPainter(mask)) pt.X += raster.Fix32(buf.AdvanceWidth << 2) offset := image.Point{xmin + ix, ymin + iy} glyphRect := mask.Bounds().Add(offset) dr := clip.Intersect(glyphRect) mp := image.Point{0, dr.Min.Y - glyphRect.Min.Y} glyphs = append(glyphs, &glyph{ mask: mask, mp: mp, dr: dr, }) } } return }
func ExpectedSize(font *truetype.Font, s string) (int32, int32, error) { c := freetype.NewContext() c.SetDPI(dpi) c.SetFont(font) c.SetFontSize(size) scale := size / float64(font.FUnitsPerEm()) prev := font.Index(rune(s[0])) width := int32(font.HMetric(font.FUnitsPerEm(), prev).AdvanceWidth) for _, char := range s[1:] { index := font.Index(char) width += int32(font.Kerning(font.FUnitsPerEm(), prev, index) + font.HMetric(font.FUnitsPerEm(), index).AdvanceWidth) prev = index } width = int32(float64(width) * scale) bounds := font.Bounds(font.FUnitsPerEm()) height := int32(float64(bounds.YMax-bounds.YMin) * scale) return width, height, nil }
// Returns the max width and height extents of a string given a font. // This is calculated by determining the number of pixels in an "em" unit // for the given font, and multiplying by the number of characters in 'text'. // Since a particular character may be smaller than one "em" unit, this has // a tendency to overestimate the extents. // It is provided because I do not know how to calculate the precise extents // using freetype-go. // TODO: This does not currently account for multiple lines. It may never do so. func TextMaxExtents(font *truetype.Font, fontSize float64, text string) (width int, height int) { emSquarePix := int(font.FUnitsPerEm()) return len(text) * emSquarePix, emSquarePix }