// rasterize returns the advance width, glyph mask and integer-pixel offset // to render the given glyph at the given sub-pixel offsets. // The 26.6 fixed point arguments fx and fy must be in the range [0, 1). func (c *Context) rasterize(glyph truetype.Index, fx, fy fixed.Int26_6) ( fixed.Int26_6, *image.Alpha, image.Point, error) { if err := c.glyphBuf.Load(c.f, c.scale, glyph, c.hinting); err != nil { return 0, nil, image.Point{}, err } // Calculate the integer-pixel bounds for the glyph. xmin := int(fx+c.glyphBuf.Bounds.Min.X) >> 6 ymin := int(fy-c.glyphBuf.Bounds.Max.Y) >> 6 xmax := int(fx+c.glyphBuf.Bounds.Max.X+0x3f) >> 6 ymax := int(fy-c.glyphBuf.Bounds.Min.Y+0x3f) >> 6 if xmin > xmax || ymin > ymax { return 0, nil, image.Point{}, errors.New("freetype: negative sized glyph") } // A TrueType's glyph's nodes can have negative co-ordinates, but the // rasterizer clips anything left of x=0 or above y=0. xmin and ymin are // the pixel offsets, based on the font's FUnit metrics, that let a // negative co-ordinate in TrueType space be non-negative in rasterizer // space. xmin and ymin are typically <= 0. fx -= fixed.Int26_6(xmin << 6) fy -= fixed.Int26_6(ymin << 6) // Rasterize the glyph's vectors. c.r.Clear() e0 := 0 for _, e1 := range c.glyphBuf.Ends { c.drawContour(c.glyphBuf.Points[e0:e1], fx, fy) e0 = e1 } a := image.NewAlpha(image.Rect(0, 0, xmax-xmin, ymax-ymin)) c.r.Rasterize(raster.NewAlphaSrcPainter(a)) return c.glyphBuf.AdvanceWidth, a, image.Point{xmin, ymin}, nil }
func TestNewFontFaceItalic(t *testing.T) { base, _ := NewFontWithName("Verdana") defer base.Close() face := base.Face(text.FaceData{ Size: 20.0, Style: font.StyleItalic, }) defer face.Close() if data := face.Data(); data != (text.FaceData{ Size: 20.0, DPI: 72.0, Hinting: font.HintingNone, Stretch: font.StretchNormal, Style: font.StyleItalic, Weight: font.WeightNormal, }) { t.Error("invalid font face data:", data) } if metrics := face.Metrics(); metrics != (font.Metrics{ Height: fixed.Int26_6((24 << 6) | 18), Ascent: fixed.Int26_6((20 << 6) | 6), Descent: fixed.Int26_6((4 << 6) | 12), }) { t.Error("invalid font face metrics:", metrics) } if s := fmt.Sprint(face); s != "Verdana Italic { size: 20, DPI: 72 }" { t.Error("invalid face representation:", s) } }
func p(n node) fixed.Point26_6 { x, y := 20+n.x/4, 380-n.y/4 return fixed.Point26_6{ X: fixed.Int26_6(x << 6), Y: fixed.Int26_6(y << 6), } }
// pRot45CCW returns the vector p rotated counter-clockwise by 45 degrees. // // Note that the Y-axis grows downwards, so {1, 0}.Rot45CCW is {1/√2, -1/√2}. func pRot45CCW(p fixed.Point26_6) fixed.Point26_6 { // 181/256 is approximately 1/√2, or sin(π/4). px, py := int64(p.X), int64(p.Y) qx := (+px + py) * 181 / 256 qy := (-px + py) * 181 / 256 return fixed.Point26_6{fixed.Int26_6(qx), fixed.Int26_6(qy)} }
// unscaledVMetric returns the unscaled vertical metrics for the glyph with // the given index. yMax is the top of the glyph's bounding box. func (f *Font) unscaledVMetric(i Index, yMax fixed.Int26_6) (v VMetric) { j := int(i) if j < 0 || f.nGlyph <= j { return VMetric{} } if 4*j+4 <= len(f.vmtx) { return VMetric{ AdvanceHeight: fixed.Int26_6(u16(f.vmtx, 4*j)), TopSideBearing: fixed.Int26_6(int16(u16(f.vmtx, 4*j+2))), } } // The OS/2 table has grown over time. // https://developer.apple.com/fonts/TTRefMan/RM06/Chap6OS2.html // says that it was originally 68 bytes. Optional fields, including // the ascender and descender, are described at // http://www.microsoft.com/typography/otspec/os2.htm if len(f.os2) >= 72 { sTypoAscender := fixed.Int26_6(int16(u16(f.os2, 68))) sTypoDescender := fixed.Int26_6(int16(u16(f.os2, 70))) return VMetric{ AdvanceHeight: sTypoAscender - sTypoDescender, TopSideBearing: sTypoAscender - yMax, } } return VMetric{ AdvanceHeight: fixed.Int26_6(f.fUnitsPerEm), TopSideBearing: 0, } }
// GetStringBounds returns the approximate pixel bounds of the string s at x, y. // The the left edge of the em square of the first character of s // and the baseline intersect at 0, 0 in the returned coordinates. // Therefore the top and left coordinates may well be negative. func (gc *GraphicContext) GetStringBounds(s string) (left, top, right, bottom float64) { font, err := gc.loadCurrentFont() if err != nil { log.Println(err) return 0, 0, 0, 0 } top, left, bottom, right = 10e6, 10e6, -10e6, -10e6 cursor := 0.0 prev, hasPrev := truetype.Index(0), false for _, rune := range s { index := font.Index(rune) if hasPrev { cursor += fUnitsToFloat64(int32(font.Kern(fixed.Int26_6(gc.Current.Scale), prev, index))) } if err := gc.glyphBuf.Load(gc.Current.Font, fixed.Int26_6(gc.Current.Scale), index, expfont.HintingNone); err != nil { log.Println(err) return 0, 0, 0, 0 } e0 := 0 for _, e1 := range gc.glyphBuf.End { ps := gc.glyphBuf.Point[e0:e1] for _, p := range ps { x, y := pointToF64Point(p) top = math.Min(top, y) bottom = math.Max(bottom, y) left = math.Min(left, x+cursor) right = math.Max(right, x+cursor) } } cursor += fUnitsToFloat64(int32(font.HMetric(fixed.Int26_6(gc.Current.Scale), index).AdvanceWidth)) prev, hasPrev = index, true } return left, top, right, bottom }
func TestGlyphBounds(t *testing.T) { base, _ := NewFontWithName("Verdana") defer base.Close() face := base.Face(text.FaceData{ Size: 20.0, }) defer face.Close() bounds, advance, ok := face.GlyphBounds('A') if !ok { t.Error("failed to get glyph bounds for 'A'") return } if advance != fixed.Int26_6((13<<6)|43) { t.Error("invalid glyph advance for 'A':", advance) return } if bounds != (fixed.Rectangle26_6{ Min: fixed.Point26_6{ X: fixed.Int26_6((0 << 6) | 16), Y: fixed.Int26_6(0), }, Max: fixed.Point26_6{ X: fixed.Int26_6((13 << 6) | 26), Y: fixed.Int26_6((14 << 6) | 34), }, }) { t.Error("invalid glyph bounds for 'A':", bounds) } }
// TestParse tests that the luxisr.ttf metrics and glyphs are parsed correctly. // The numerical values can be manually verified by examining luxisr.ttx. func TestParse(t *testing.T) { f, _, err := parseTestdataFont("luxisr") if err != nil { t.Fatal(err) } if got, want := f.FUnitsPerEm(), int32(2048); got != want { t.Errorf("FUnitsPerEm: got %v, want %v", got, want) } fupe := fixed.Int26_6(f.FUnitsPerEm()) if got, want := f.Bounds(fupe), mkBounds(-441, -432, 2024, 2033); got != want { t.Errorf("Bounds: got %v, want %v", got, want) } i0 := f.Index('A') i1 := f.Index('V') if i0 != 36 || i1 != 57 { t.Fatalf("Index: i0, i1 = %d, %d, want 36, 57", i0, i1) } if got, want := f.HMetric(fupe, i0), (HMetric{1366, 19}); got != want { t.Errorf("HMetric: got %v, want %v", got, want) } if got, want := f.VMetric(fupe, i0), (VMetric{2465, 553}); got != want { t.Errorf("VMetric: got %v, want %v", got, want) } if got, want := f.Kern(fupe, i0, i1), fixed.Int26_6(-144); got != want { t.Errorf("Kern: got %v, want %v", got, want) } g := &GlyphBuf{} err = g.Load(f, fupe, i0, font.HintingNone) if err != nil { t.Fatalf("Load: %v", err) } g0 := &GlyphBuf{ Bounds: g.Bounds, Points: g.Points, Ends: g.Ends, } g1 := &GlyphBuf{ Bounds: mkBounds(19, 0, 1342, 1480), Points: []Point{ {19, 0, 51}, {581, 1480, 1}, {789, 1480, 51}, {1342, 0, 1}, {1116, 0, 35}, {962, 410, 3}, {368, 410, 33}, {214, 0, 3}, {428, 566, 19}, {904, 566, 33}, {667, 1200, 3}, }, Ends: []int{8, 11}, } if got, want := fmt.Sprint(g0), fmt.Sprint(g1); got != want { t.Errorf("GlyphBuf:\ngot %v\nwant %v", got, want) } }
// scale returns x divided by f.fUnitsPerEm, rounded to the nearest integer. func (f *Font) scale(x fixed.Int26_6) fixed.Int26_6 { if x >= 0 { x += fixed.Int26_6(f.fUnitsPerEm) / 2 } else { x -= fixed.Int26_6(f.fUnitsPerEm) / 2 } return x / fixed.Int26_6(f.fUnitsPerEm) }
// Metrics satisfies the font.Face interface. func (a *face) Metrics() font.Metrics { scale := float64(a.scale) fupe := float64(a.f.FUnitsPerEm()) return font.Metrics{ Height: a.scale, Ascent: fixed.Int26_6(math.Ceil(scale * float64(+a.f.ascent) / fupe)), Descent: fixed.Int26_6(math.Ceil(scale * float64(-a.f.descent) / fupe)), } }
// pNorm returns the vector p normalized to the given length, or zero if p is // degenerate. func pNorm(p fixed.Point26_6, length fixed.Int26_6) fixed.Point26_6 { d := pLen(p) if d == 0 { return fixed.Point26_6{} } s, t := int64(length), int64(d) x := int64(p.X) * s / t y := int64(p.Y) * s / t return fixed.Point26_6{fixed.Int26_6(x), fixed.Int26_6(y)} }
func (s *Sparkline) scale(idx int, n, min, max float32, dx, dy int) fixed.Point26_6 { // 26.6 format, so shift by 6 x := float32(idx) / float32(s.items) * float32(dx) y := (1.0 - ((n - min) / max)) * float32(dy) p := fixed.Point26_6{ X: fixed.Int26_6(x)<<6 | (fixed.Int26_6(x*64) & (1<<6 - 1)), Y: fixed.Int26_6(y)<<6 | (fixed.Int26_6(y*64) & (1<<6 - 1)), } return p }
// NewFace returns a new font.Face for the given Font. func NewFace(f *Font, opts *Options) font.Face { a := &face{ f: f, hinting: opts.hinting(), scale: fixed.Int26_6(0.5 + (opts.size() * opts.dpi() * 64 / 72)), glyphCache: make([]glyphCacheEntry, opts.glyphCacheEntries()), } a.subPixelX, a.subPixelBiasX, a.subPixelMaskX = opts.subPixelsX() a.subPixelY, a.subPixelBiasY, a.subPixelMaskY = opts.subPixelsY() // Fill the cache with invalid entries. Valid glyph cache entries have fx // and fy in the range [0, 64). Valid index cache entries have rune >= 0. for i := range a.glyphCache { a.glyphCache[i].key.fy = 0xff } for i := range a.indexCache { a.indexCache[i].rune = -1 } // Set the rasterizer's bounds to be big enough to handle the largest glyph. b := f.Bounds(a.scale) xmin := +int(b.Min.X) >> 6 ymin := -int(b.Max.Y) >> 6 xmax := +int(b.Max.X+63) >> 6 ymax := -int(b.Min.Y-63) >> 6 a.maxw = xmax - xmin a.maxh = ymax - ymin a.masks = image.NewAlpha(image.Rect(0, 0, a.maxw, a.maxh*len(a.glyphCache))) a.r.SetBounds(a.maxw, a.maxh) a.p = facePainter{a} return a }
// Width returns width of a string when drawn using the font. func (f *Font) Width(s string) Length { // scale converts truetype.FUnit to float64 scale := f.Size / Points(float64(f.font.FUnitsPerEm())) width := 0 prev, hasPrev := truetype.Index(0), false for _, rune := range s { index := f.font.Index(rune) if hasPrev { width += int(f.font.Kern(fixed.Int26_6(f.font.FUnitsPerEm()), prev, index)) } width += int(f.font.HMetric(fixed.Int26_6(f.font.FUnitsPerEm()), index).AdvanceWidth) prev, hasPrev = index, true } return Points(float64(width)) * scale }
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) { r -= f.firstRune if r < 0 || f.n <= int(r) { return fixed.Point26_6{}, image.Rectangle{}, nil, image.Point{}, false } i := &f.fontchars[r+0] j := &f.fontchars[r+1] newDot = fixed.Point26_6{ X: dot.X + fixed.Int26_6(i.width)<<6, Y: dot.Y, } minX := int(dot.X+32)>>6 + int(i.left) minY := int(dot.Y+32)>>6 + int(i.top) - f.ascent dr = image.Rectangle{ Min: image.Point{ X: minX, Y: minY, }, Max: image.Point{ X: minX + int(j.x-i.x), Y: minY + int(i.bottom) - int(i.top), }, } return newDot, dr, f.img, image.Point{int(i.x), int(i.top)}, true }
func Measure(r io.RuneReader, f font.Face) (size fixed.Point26_6, err error) { var char rune var prev rune w := fixed.Int26_6(0) m := f.Metrics() size.Y = m.Height + m.Descent for { if char, _, err = r.ReadRune(); err != nil { if err == io.EOF { err = nil } size.X = maxInt26_6(size.X, w) return } if char == '\n' { size.X = maxInt26_6(size.X, w) size.Y += m.Height w, prev = 0, 0 continue } if prev != 0 { w += f.Kern(prev, char) } w += advance(f, char) prev = char } }
// dotProduct returns the dot product of [x, y] and q. It is almost the same as // px := int64(x) // py := int64(y) // qx := int64(q[0]) // qy := int64(q[1]) // return fixed.Int26_6((px*qx + py*qy + 1<<13) >> 14) // except that the computation is done with 32-bit integers to produce exactly // the same rounding behavior as C Freetype. func dotProduct(x, y fixed.Int26_6, q [2]f2dot14) fixed.Int26_6 { // Compute x*q[0] as 64-bit value. l := uint32((int32(x) & 0xFFFF) * int32(q[0])) m := (int32(x) >> 16) * int32(q[0]) lo1 := l + (uint32(m) << 16) hi1 := (m >> 16) + (int32(l) >> 31) + bool2int32(lo1 < l) // Compute y*q[1] as 64-bit value. l = uint32((int32(y) & 0xFFFF) * int32(q[1])) m = (int32(y) >> 16) * int32(q[1]) lo2 := l + (uint32(m) << 16) hi2 := (m >> 16) + (int32(l) >> 31) + bool2int32(lo2 < l) // Add them. lo := lo1 + lo2 hi := hi1 + hi2 + bool2int32(lo < lo1) // Divide the result by 2^14 with rounding. s := hi >> 31 l = lo + uint32(s) hi += s + bool2int32(l < lo) lo = l l = lo + 0x2000 hi += bool2int32(l < lo) return fixed.Int26_6((uint32(hi) << 18) | (l >> 14)) }
func main() { flag.Parse() fmt.Printf("Loading fontfile %q\n", *fontfile) b, err := ioutil.ReadFile(*fontfile) if err != nil { log.Println(err) return } f, err := truetype.Parse(b) if err != nil { log.Println(err) return } fupe := fixed.Int26_6(f.FUnitsPerEm()) printBounds(f.Bounds(fupe)) fmt.Printf("FUnitsPerEm:%d\n\n", fupe) c0, c1 := 'A', 'V' i0 := f.Index(c0) hm := f.HMetric(fupe, i0) g := &truetype.GlyphBuf{} err = g.Load(f, fupe, i0, font.HintingNone) if err != nil { log.Println(err) return } fmt.Printf("'%c' glyph\n", c0) fmt.Printf("AdvanceWidth:%d LeftSideBearing:%d\n", hm.AdvanceWidth, hm.LeftSideBearing) printGlyph(g) i1 := f.Index(c1) fmt.Printf("\n'%c', '%c' Kern:%d\n", c0, c1, f.Kern(fupe, i0, i1)) }
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 }
// https://code.google.com/p/plotinum/source/browse/vg/font.go#160 func widthOfString(font *truetype.Font, size float64, s string) float64 { // scale converts truetype.FUnit to float64 scale := size / float64(font.FUnitsPerEm()) width := 0 prev, hasPrev := truetype.Index(0), false for _, rune := range s { index := font.Index(rune) if hasPrev { width += int(font.Kern(fixed.Int26_6(font.FUnitsPerEm()), prev, index)) } width += int(font.HMetric(fixed.Int26_6(font.FUnitsPerEm()), index).AdvanceWidth) prev, hasPrev = index, true } return float64(width) * scale }
// unscaledHMetric returns the unscaled horizontal metrics for the glyph with // the given index. func (f *Font) unscaledHMetric(i Index) (h HMetric) { j := int(i) if j < 0 || f.nGlyph <= j { return HMetric{} } if j >= f.nHMetric { p := 4 * (f.nHMetric - 1) return HMetric{ AdvanceWidth: fixed.Int26_6(u16(f.hmtx, p)), LeftSideBearing: fixed.Int26_6(int16(u16(f.hmtx, p+2*(j-f.nHMetric)+4))), } } return HMetric{ AdvanceWidth: fixed.Int26_6(u16(f.hmtx, 4*j)), LeftSideBearing: fixed.Int26_6(int16(u16(f.hmtx, 4*j+2))), } }
// Add2 adds a quadratic segment to the current curve. func (r *Rasterizer) Add2(b, c fixed.Point26_6) { // Calculate nSplit (the number of recursive decompositions) based on how // 'curvy' it is. Specifically, how much the middle point b deviates from // (a+c)/2. dev := maxAbs(r.a.X-2*b.X+c.X, r.a.Y-2*b.Y+c.Y) / fixed.Int26_6(r.splitScale2) nsplit := 0 for dev > 0 { dev /= 4 nsplit++ } // dev is 32-bit, and nsplit++ every time we shift off 2 bits, so maxNsplit // is 16. const maxNsplit = 16 if nsplit > maxNsplit { panic("freetype/raster: Add2 nsplit too large: " + strconv.Itoa(nsplit)) } // Recursively decompose the curve nSplit levels deep. var ( pStack [2*maxNsplit + 3]fixed.Point26_6 sStack [maxNsplit + 1]int i int ) sStack[0] = nsplit pStack[0] = c pStack[1] = b pStack[2] = r.a for i >= 0 { s := sStack[i] p := pStack[2*i:] if s > 0 { // Split the quadratic curve p[:3] into an equivalent set of two // shorter curves: p[:3] and p[2:5]. The new p[4] is the old p[2], // and p[0] is unchanged. mx := p[1].X p[4].X = p[2].X p[3].X = (p[4].X + mx) / 2 p[1].X = (p[0].X + mx) / 2 p[2].X = (p[1].X + p[3].X) / 2 my := p[1].Y p[4].Y = p[2].Y p[3].Y = (p[4].Y + my) / 2 p[1].Y = (p[0].Y + my) / 2 p[2].Y = (p[1].Y + p[3].Y) / 2 // The two shorter curves have one less split to do. sStack[i] = s - 1 sStack[i+1] = s - 1 i++ } else { // Replace the level-0 quadratic with a two-linear-piece // approximation. midx := (p[0].X + 2*p[1].X + p[2].X) / 4 midy := (p[0].Y + 2*p[1].Y + p[2].Y) / 4 r.Add1(fixed.Point26_6{midx, midy}) r.Add1(p[0]) i-- } } }
// 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(fixed.Int26_6(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, } }
// Extents returns the FontExtents for a font. func (f *Font) Extents() FontExtents { bounds := f.font.Bounds(fixed.Int26_6(f.Font().FUnitsPerEm())) scale := f.Size / Points(float64(f.Font().FUnitsPerEm())) return FontExtents{ Ascent: Points(float64(bounds.Max.Y)) * scale, Descent: Points(float64(bounds.Min.Y)) * scale, Height: Points(float64(bounds.Max.Y-bounds.Min.Y)) * scale, } }
func (f *Font) parseHead() error { if len(f.head) != 54 { return FormatError(fmt.Sprintf("bad head length: %d", len(f.head))) } f.fUnitsPerEm = int32(u16(f.head, 18)) f.bounds.Min.X = fixed.Int26_6(int16(u16(f.head, 36))) f.bounds.Min.Y = fixed.Int26_6(int16(u16(f.head, 38))) f.bounds.Max.X = fixed.Int26_6(int16(u16(f.head, 40))) f.bounds.Max.Y = fixed.Int26_6(int16(u16(f.head, 42))) switch i := u16(f.head, 50); i { case 0: f.locaOffsetFormat = locaOffsetFormatShort case 1: f.locaOffsetFormat = locaOffsetFormatLong default: return FormatError(fmt.Sprintf("bad indexToLocFormat: %d", i)) } return nil }
func (gc *GraphicContext) drawGlyph(glyph truetype.Index, dx, dy float64) error { if err := gc.glyphBuf.Load(gc.Current.Font, fixed.Int26_6(gc.Current.Scale), glyph, expfont.HintingNone); err != nil { return err } e0 := 0 for _, e1 := range gc.glyphBuf.End { DrawContour(gc, gc.glyphBuf.Point[e0:e1], dx, dy) e0 = e1 } return nil }
func (f *Font) TextDimensions(text string) (int, int, int) { fnt := f.ttf size := f.Size var ( totalWidth = fixed.Int26_6(0) totalHeight = fixed.Int26_6(size) maxYBearing = fixed.Int26_6(0) ) fupe := fixed.Int26_6(fnt.FUnitsPerEm()) for _, char := range text { idx := fnt.Index(char) hm := fnt.HMetric(fupe, idx) vm := fnt.VMetric(fupe, idx) g := truetype.GlyphBuf{} err := g.Load(fnt, fupe, idx, font.HintingNone) if err != nil { log.Println(err) return 0, 0, 0 } totalWidth += hm.AdvanceWidth yB := (vm.TopSideBearing * fixed.Int26_6(size)) / fupe if yB > maxYBearing { maxYBearing = yB } } // Scale to actual pixel size totalWidth *= fixed.Int26_6(size) totalWidth /= fupe return int(totalWidth), int(totalHeight), int(maxYBearing) }
// CreateStringPath creates a path from the string s at x, y, and returns the string width. // The text is placed so that the left edge of the em square of the first character of s // and the baseline intersect at x, y. The majority of the affected pixels will be // above and to the right of the point, but some may be below or to the left. // For example, drawing a string that starts with a 'J' in an italic font may // affect pixels below and left of the point. func (gc *GraphicContext) CreateStringPath(s string, x, y float64) float64 { font, err := gc.loadCurrentFont() if err != nil { log.Println(err) return 0.0 } startx := x prev, hasPrev := truetype.Index(0), false for _, rune := range s { index := font.Index(rune) if hasPrev { x += fUnitsToFloat64(int32(font.Kern(fixed.Int26_6(gc.Current.Scale), prev, index))) } err := gc.drawGlyph(index, x, y) if err != nil { log.Println(err) return startx - x } x += fUnitsToFloat64(int32(font.HMetric(fixed.Int26_6(gc.Current.Scale), index).AdvanceWidth)) prev, hasPrev = index, true } return x - startx }
func TestKern(t *testing.T) { base, _ := NewFontWithName("Verdana") defer base.Close() face := base.Face(text.FaceData{ Size: 20.0, DPI: 144, }) defer face.Close() if kern := face.Kern('W', '\\'); kern != -fixed.Int26_6(12) { t.Error("invalid kerning for characters 'W' and '\\':", kern) } }
func (h *hinter) move(p *Point, distance fixed.Int26_6, touch bool) { fvx := int64(h.gs.fv[0]) pvx := int64(h.gs.pv[0]) if fvx == 0x4000 && pvx == 0x4000 { p.X += fixed.Int26_6(distance) if touch { p.Flags |= flagTouchedX } return } fvy := int64(h.gs.fv[1]) pvy := int64(h.gs.pv[1]) if fvy == 0x4000 && pvy == 0x4000 { p.Y += fixed.Int26_6(distance) if touch { p.Flags |= flagTouchedY } return } fvDotPv := (fvx*pvx + fvy*pvy) >> 14 if fvx != 0 { p.X += fixed.Int26_6(mulDiv(fvx, int64(distance), fvDotPv)) if touch { p.Flags |= flagTouchedX } } if fvy != 0 { p.Y += fixed.Int26_6(mulDiv(fvy, int64(distance), fvDotPv)) if touch { p.Flags |= flagTouchedY } } }