From 627898392a4c6304ded005afc7bc944fe674391a Mon Sep 17 00:00:00 2001 From: Nigel Tao Date: Mon, 24 Aug 2015 14:01:27 +1000 Subject: [PATCH] shiny/font: add per-glyph metrics. Change-Id: Ie5c7e29b4eb7bd87b8e99de941f2f94b042e268f Reviewed-on: https://go-review.googlesource.com/13827 Reviewed-by: Rob Pike --- font/font.go | 43 ++++++++++++++++++++++++++++--- font/plan9font/plan9font.go | 50 +++++++++++++++++++++++++++++++++++-- 2 files changed, 88 insertions(+), 5 deletions(-) diff --git a/font/font.go b/font/font.go index c370986..8f8f4c1 100644 --- a/font/font.go +++ b/font/font.go @@ -37,8 +37,9 @@ type Face interface { // 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 @@ type Face interface { 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 @@ func (d *Drawer) DrawString(s string) { 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 @@ func (d *Drawer) DrawString(s string) { } } +// 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/font/plan9font/plan9font.go b/font/plan9font/plan9font.go index 35d6ee8..abcc655 100644 --- a/font/plan9font/plan9font.go +++ b/font/plan9font/plan9font.go @@ -94,6 +94,31 @@ func (f *subface) Glyph(dot fixed.Point26_6, r rune) ( 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) Kern(r0, r1 rune) fixed.Int26_6 { return 0 } 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 @@ func (f *face) Glyph(dot fixed.Point26_6, r rune) ( } 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,