// Ported from https://github.com/faiface/pixel/tree/master/text// Trimmed down to essentials of measuring textpackage textmeasureimport ()constTAB_SIZE = 4constSIZELESS_FONT_SIZE = 0constCODE_LINE_HEIGHT = 1.3// Runes encompasses ASCII, Latin-1, and geometric shapes like black squarevarRunes []runefunc init() {// ASCII range (U+0000 to U+007F)for := rune(0x0000); <= rune(0x007F); ++ {Runes = append(Runes, ) }// Latin-1 Supplement (U+0080 to U+00FF)for := rune(0x0080); <= rune(0x00FF); ++ {Runes = append(Runes, ) }// Geometric Shapes (U+25A0 to U+25FF)for := rune(0x25A0); <= rune(0x25FF); ++ {Runes = append(Runes, ) }}// Ruler allows for effiecient and convenient text drawing.//// To create a Ruler object, use the New constructor://// txt := text.New(pixel.ZV, text.NewAtlas(face, text.ASCII))//// As suggested by the constructor, a Ruler object is always associated with one font face and a// fixed set of runes. For example, the Ruler we created above can draw text using the font face// contained in the face variable and is capable of drawing ASCII characters.//// Here we create a Ruler object which can draw ASCII and Katakana characters://// txt := text.New(0, text.NewAtlas(face, text.ASCII, text.RangeTable(unicode.Katakana)))//// Similarly to IMDraw, Ruler functions as a buffer. It implements io.Writer interface, so writing// text to it is really simple://// fmt.Print(txt, "Hello, world!")//// Newlines, tabs and carriage returns are supported.//// Finally, if we want the written text to show up on some other Target, we can draw it://// txt.Draw(target)//// Ruler exports two important fields: Orig and Dot. Dot is the position where the next character// will be written. Dot is automatically moved when writing to a Ruler object, but you can also// manipulate it manually. Orig specifies the text origin, usually the top-left dot position. Dot is// always aligned to Orig when writing newlines. The Clear method resets the Dot to Orig.typeRulerstruct {// Orig specifies the text origin, usually the top-left dot position. Dot is always aligned // to Orig when writing newlines. Orig *geo.Point// Dot is the position where the next character will be written. Dot is automatically moved // when writing to a Ruler object, but you can also manipulate it manually Dot *geo.Point// lineHeight is the vertical distance between two lines of text. // // Example: // txt.lineHeight = 1.5 * txt.atlas.lineHeight LineHeightFactor float64 lineHeights map[d2fonts.Font]float64// tabWidth is the horizontal tab width. Tab characters will align to the multiples of this // width. // // Example: // txt.tabWidth = 8 * txt.atlas.glyph(' ').Advance tabWidths map[d2fonts.Font]float64 atlases map[d2fonts.Font]*atlas ttfs map[d2fonts.Font]*truetype.Font buf []byte prevR rune bounds *rect// when drawing text also union Ruler.bounds with Dot boundsWithDot bool}// New creates a new Ruler capable of drawing runes contained in the provided atlas. Orig and Dot// will be initially set to orig.//// Here we create a Ruler capable of drawing ASCII characters using the Go Regular font.//// ttf, err := truetype.Parse(goregular.TTF)// if err != nil {// panic(err)// }// face := truetype.NewFace(ttf, &truetype.Options{// Size: 14,// })// txt := text.New(orig, text.NewAtlas(face, text.ASCII))func () (*Ruler, error) { := geo.NewPoint(0, 0) := &Ruler{Orig: ,Dot: .Copy(),LineHeightFactor: 1.,lineHeights: make(map[d2fonts.Font]float64),tabWidths: make(map[d2fonts.Font]float64),atlases: make(map[d2fonts.Font]*atlas),ttfs: make(map[d2fonts.Font]*truetype.Font), }for , := ranged2fonts.FontFamilies {for , := ranged2fonts.FontStyles { := d2fonts.Font{Family: ,Style: , }// Note: FontFaces lookup is size-agnostic , := d2fonts.FontFaces.Lookup()if ! {continue }if , := .ttfs[]; ! { , := truetype.Parse()if != nil {returnnil, } .ttfs[] = } } } .clear()return , nil}func ( *Ruler) ( *d2fonts.FontFamily) bool {for , := ranged2fonts.FontStyles { := d2fonts.Font{Family: *,Style: ,Size: SIZELESS_FONT_SIZE, } , := .ttfs[]if ! {returnfalse } }returntrue}func ( *Ruler) ( d2fonts.Font) { := .Size = SIZELESS_FONT_SIZE := truetype.NewFace(.ttfs[], &truetype.Options{Size: float64(.Size), }) := NewAtlas(, Runes) .atlases[] = .lineHeights[] = .lineHeight .tabWidths[] = .glyph(' ').advance * TAB_SIZE}func ( *Ruler) ( float64, d2fonts.Font, string) float64 {// Weird unicode stuff is going on when this is true // See https://github.com/rivo/uniseg#grapheme-clusters // This method is a good-enough approximation. It overshoots, but not by much. // I suspect we need to import a font with the right glyphs to get the precise measurements // but Hans fonts are heavy.ifuniseg.GraphemeClusterCount() != len() {for , := rangestrings.Split(, "\n") { , := .MeasurePrecise(, ) := uniseg.NewGraphemes() := d2fonts.SourceCodePro.Font(.Size, .Style)for .Next() {if .Width() == 1 {continue }// For each grapheme which doesn't have width=1, the ruler measured wrongly. // So, replace the measured width with a scaled measurement of a monospace versionvarrune := .Orig.Copy() := newRect()for , := range .Runes() {varbool , = .controlRune(, , )if {continue }var *rect _, _, , = .atlases[].DrawRune(, , ) = .union() = } -= .w() += .spaceWidth() * float64(.Width()) } = math.Max(, ) } }return}func ( *Ruler) ( d2fonts.Font, string) (, int) { := .boundsWithDot .boundsWithDot = true , = .Measure(, ) .boundsWithDot = return , }func ( *Ruler) ( d2fonts.Font, string) (, int) { , := .MeasurePrecise(, ) = .scaleUnicode(, , )returnint(math.Ceil()), int(math.Ceil())}func ( *Ruler) ( d2fonts.Font, string) (, float64) {if , := .atlases[]; ! { .addFontSize() } .clear() .buf = append(.buf, ...) .drawBuf() := .boundsreturn .w(), .h()}// clear removes all written text from the Ruler. The Dot field is reset to Orig.func ( *Ruler) () { .prevR = -1 .bounds = newRect() .Dot = .Orig.Copy()}// controlRune checks if r is a control rune (newline, tab, ...). If it is, a new dot position and// true is returned. If r is not a control rune, the original dot and false is returned.func ( *Ruler) ( rune, *geo.Point, d2fonts.Font) ( *geo.Point, bool) {switch {case'\n': .X = .Orig.X .Y -= .LineHeightFactor * .lineHeights[]case'\r': .X = .Orig.Xcase'\t': := math.Mod(.X-.Orig.X, .tabWidths[]) = math.Mod(, +.tabWidths[])if == 0 { = .tabWidths[] } .X += default:return , false }return , true}func ( *Ruler) ( d2fonts.Font) {if !utf8.FullRune(.buf) {return }forutf8.FullRune(.buf) { , := utf8.DecodeRune(.buf) .buf = .buf[:]varbool .Dot, = .controlRune(, .Dot, )if {continue }var *rect _, _, , .Dot = .atlases[].DrawRune(.prevR, , .Dot) .prevR = if .boundsWithDot { .bounds = .bounds.union(&rect{.Dot, .Dot}) .bounds = .bounds.union() } else {if .bounds.w()*.bounds.h() == 0 { .bounds = } else { .bounds = .bounds.union() } } }}func ( *Ruler) ( d2fonts.Font) float64 {if , := .atlases[]; ! { .addFontSize() } , := utf8.DecodeRuneInString(" ")return .atlases[].glyph().advance}
The pages are generated with Goldsv0.8.2. (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu.
PR and bug reports are welcome and can be submitted to the issue list.
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds.