shiny/font: add per-glyph metrics.

Change-Id: Ie5c7e29b4eb7bd87b8e99de941f2f94b042e268f
Reviewed-on: https://go-review.googlesource.com/13827
Reviewed-by: Rob Pike <r@golang.org>
diff --git a/shiny/font/font.go b/shiny/font/font.go
index c370986..8f8f4c1 100644
--- a/shiny/font/font.go
+++ b/shiny/font/font.go
@@ -37,8 +37,9 @@
 
 	// Glyph returns the draw.DrawMask parameters (dr, mask, maskp) to draw r's
 	// glyph at the sub-pixel destination location dot. It also returns the new
-	// dot after adding the glyph's advance width. It returns !ok if the face
-	// does not contain a glyph for r.
+	// dot after adding the glyph's advance width.
+	//
+	// It returns !ok if the face does not contain a glyph for r.
 	//
 	// The contents of the mask image returned by one Glyph call may change
 	// after the next Glyph call. Callers that want to cache the mask must make
@@ -46,11 +47,26 @@
 	Glyph(dot fixed.Point26_6, r rune) (
 		newDot fixed.Point26_6, dr image.Rectangle, mask image.Image, maskp image.Point, ok bool)
 
+	// GlyphBounds returns the bounding box of r's glyph, drawn at a dot equal
+	// to the origin, and that glyph's advance width.
+	//
+	// It returns !ok if the face does not contain a glyph for r.
+	//
+	// The glyph's ascent and descent equal -bounds.Min.Y and +bounds.Max.Y. A
+	// visual depiction of what these metrics are is at
+	// https://developer.apple.com/library/mac/documentation/TextFonts/Conceptual/CocoaTextArchitecture/Art/glyph_metrics_2x.png
+	GlyphBounds(r rune) (bounds fixed.Rectangle26_6, advance fixed.Int26_6, ok bool)
+
+	// GlyphAdvance returns the advance width of r's glyph.
+	//
+	// It returns !ok if the face does not contain a glyph for r.
+	GlyphAdvance(r rune) (advance fixed.Int26_6, ok bool)
+
 	// Kern returns the horizontal adjustment for the kerning pair (r0, r1). A
 	// positive kern means to move the glyphs further apart.
 	Kern(r0, r1 rune) fixed.Int26_6
 
-	// TODO: per-font and per-glyph Metrics.
+	// TODO: per-font Metrics.
 	// TODO: ColoredGlyph for various emoji?
 	// TODO: Ligatures? Shaping?
 }
@@ -102,6 +118,7 @@
 		if !ok {
 			// TODO: is falling back on the U+FFFD glyph the responsibility of
 			// the Drawer or the Face?
+			// TODO: set prevC = '\ufffd'?
 			continue
 		}
 		draw.DrawMask(d.Dst, dr, d.Src, image.Point{}, mask, maskp, draw.Over)
@@ -109,6 +126,26 @@
 	}
 }
 
+// MeasureString returns how far dot would advance by drawing s.
+func (d *Drawer) MeasureString(s string) (advance fixed.Int26_6) {
+	var prevC rune
+	for i, c := range s {
+		if i != 0 {
+			advance += d.Face.Kern(prevC, c)
+		}
+		a, ok := d.Face.GlyphAdvance(c)
+		if !ok {
+			// TODO: is falling back on the U+FFFD glyph the responsibility of
+			// the Drawer or the Face?
+			// TODO: set prevC = '\ufffd'?
+			continue
+		}
+		advance += a
+		prevC = c
+	}
+	return advance
+}
+
 // Hinting selects how to quantize a vector font's glyph nodes.
 //
 // Not all fonts support hinting.
diff --git a/shiny/font/plan9font/plan9font.go b/shiny/font/plan9font/plan9font.go
index 35d6ee8..abcc655 100644
--- a/shiny/font/plan9font/plan9font.go
+++ b/shiny/font/plan9font/plan9font.go
@@ -94,6 +94,31 @@
 	return newDot, dr, f.img, image.Point{int(i.x), int(i.top)}, true
 }
 
+func (f *subface) GlyphBounds(r rune) (bounds fixed.Rectangle26_6, advance fixed.Int26_6, ok bool) {
+	r -= f.firstRune
+	if r < 0 || f.n <= int(r) {
+		return fixed.Rectangle26_6{}, 0, false
+	}
+	i := &f.fontchars[r+0]
+	j := &f.fontchars[r+1]
+
+	bounds = fixed.R(
+		int(i.left),
+		int(i.top)-f.ascent,
+		int(i.left)+int(j.x-i.x),
+		int(i.bottom)-f.ascent,
+	)
+	return bounds, fixed.Int26_6(i.width) << 6, true
+}
+
+func (f *subface) GlyphAdvance(r rune) (advance fixed.Int26_6, ok bool) {
+	r -= f.firstRune
+	if r < 0 || f.n <= int(r) {
+		return 0, false
+	}
+	return fixed.Int26_6(f.fontchars[r].width) << 6, true
+}
+
 // runeRange maps a single rune range [lo, hi] to a lazily loaded subface. Both
 // ends of the range are inclusive.
 type runeRange struct {
@@ -121,6 +146,27 @@
 func (f *face) Glyph(dot fixed.Point26_6, r rune) (
 	newDot fixed.Point26_6, dr image.Rectangle, mask image.Image, maskp image.Point, ok bool) {
 
+	if s, rr := f.subface(r); s != nil {
+		return s.Glyph(dot, rr)
+	}
+	return fixed.Point26_6{}, image.Rectangle{}, nil, image.Point{}, false
+}
+
+func (f *face) GlyphBounds(r rune) (bounds fixed.Rectangle26_6, advance fixed.Int26_6, ok bool) {
+	if s, rr := f.subface(r); s != nil {
+		return s.GlyphBounds(rr)
+	}
+	return fixed.Rectangle26_6{}, 0, false
+}
+
+func (f *face) GlyphAdvance(r rune) (advance fixed.Int26_6, ok bool) {
+	if s, rr := f.subface(r); s != nil {
+		return s.GlyphAdvance(rr)
+	}
+	return 0, false
+}
+
+func (f *face) subface(r rune) (*subface, rune) {
 	// Fall back on U+FFFD if we can't find r.
 	for _, rr := range [2]rune{r, '\ufffd'} {
 		// We have to do linear, not binary search. plan9port's
@@ -148,10 +194,10 @@
 				}
 				x.subface = sub.(*subface)
 			}
-			return x.subface.Glyph(dot, rr)
+			return x.subface, rr
 		}
 	}
-	return fixed.Point26_6{}, image.Rectangle{}, nil, image.Point{}, false
+	return nil, 0
 }
 
 // ParseFont parses a Plan 9 font file. data is the contents of that font file,