diff --git a/font/sfnt/example_test.go b/font/sfnt/example_test.go new file mode 100644 index 0000000..f9a6697 --- /dev/null +++ b/font/sfnt/example_test.go @@ -0,0 +1,125 @@ +// Copyright 2017 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package sfnt_test + +import ( + "image" + "image/draw" + "log" + "os" + + "golang.org/x/image/font/gofont/goregular" + "golang.org/x/image/font/sfnt" + "golang.org/x/image/math/fixed" + "golang.org/x/image/vector" +) + +func ExampleRasterizeGlyph() { + const ( + ppem = 32 + width = 24 + height = 32 + originX = 0 + originY = 28 + ) + + f, err := sfnt.Parse(goregular.TTF) + if err != nil { + log.Fatalf("Parse: %v", err) + } + var b sfnt.Buffer + x, err := f.GlyphIndex(&b, 'G') + if err != nil { + log.Fatalf("GlyphIndex: %v", err) + } + if x == 0 { + log.Fatalf("GlyphIndex: no glyph index found for the rune 'G'") + } + segments, err := f.LoadGlyph(&b, x, fixed.I(ppem), nil) + + r := vector.NewRasterizer(width, height) + r.DrawOp = draw.Src + for _, seg := range segments { + // The divisions by 64 below is because the seg.Args values have type + // fixed.Int26_6, a 26.6 fixed point number, and 1<<6 == 64. + switch seg.Op { + case sfnt.SegmentOpMoveTo: + r.MoveTo( + originX+float32(seg.Args[0])/64, + originY-float32(seg.Args[1])/64, + ) + case sfnt.SegmentOpLineTo: + r.LineTo( + originX+float32(seg.Args[0])/64, + originY-float32(seg.Args[1])/64, + ) + case sfnt.SegmentOpQuadTo: + r.QuadTo( + originX+float32(seg.Args[0])/64, + originY-float32(seg.Args[1])/64, + originX+float32(seg.Args[2])/64, + originY-float32(seg.Args[3])/64, + ) + case sfnt.SegmentOpCubeTo: + r.CubeTo( + originX+float32(seg.Args[0])/64, + originY-float32(seg.Args[1])/64, + originX+float32(seg.Args[2])/64, + originY-float32(seg.Args[3])/64, + originX+float32(seg.Args[4])/64, + originY-float32(seg.Args[5])/64, + ) + } + } + // TODO: call ClosePath? Once overall or once per contour (i.e. MoveTo)? + + dst := image.NewAlpha(image.Rect(0, 0, width, height)) + r.Draw(dst, dst.Bounds(), image.Opaque, image.Point{}) + + const asciiArt = ".++8" + buf := make([]byte, 0, height*(width+1)) + for y := 0; y < height; y++ { + for x := 0; x < width; x++ { + a := dst.AlphaAt(x, y).A + buf = append(buf, asciiArt[a>>6]) + } + buf = append(buf, '\n') + } + os.Stdout.Write(buf) + + // Output: + // ........................ + // ........................ + // ........................ + // ........................ + // ..........+++++++++..... + // .......+8888888888888+.. + // ......8888888888888888.. + // ....+8888+........++88.. + // ....8888................ + // ...8888................. + // ..+888+................. + // ..+888.................. + // ..888+.................. + // .+888+.................. + // .+888................... + // .+888................... + // .+888................... + // .+888..........+++++++.. + // .+888..........8888888.. + // .+888+.........+++8888.. + // ..888+............+888.. + // ..8888............+888.. + // ..+888+...........+888.. + // ...8888+..........+888.. + // ...+8888+.........+888.. + // ....+88888+.......+888.. + // .....+8888888888888888.. + // .......+888888888888++.. + // ..........++++++++...... + // ........................ + // ........................ + // ........................ +} diff --git a/font/sfnt/postscript.go b/font/sfnt/postscript.go index d5d6d9a..8142bdc 100644 --- a/font/sfnt/postscript.go +++ b/font/sfnt/postscript.go @@ -653,8 +653,8 @@ func t2CAppendMoveto(p *psInterpreter) { p.type2Charstrings.segments = append(p.type2Charstrings.segments, Segment{ Op: SegmentOpMoveTo, Args: [6]fixed.Int26_6{ - 0: fixed.Int26_6(p.type2Charstrings.x) << 6, - 1: fixed.Int26_6(p.type2Charstrings.y) << 6, + 0: fixed.Int26_6(p.type2Charstrings.x), + 1: fixed.Int26_6(p.type2Charstrings.y), }, }) } @@ -663,8 +663,8 @@ func t2CAppendLineto(p *psInterpreter) { p.type2Charstrings.segments = append(p.type2Charstrings.segments, Segment{ Op: SegmentOpLineTo, Args: [6]fixed.Int26_6{ - 0: fixed.Int26_6(p.type2Charstrings.x) << 6, - 1: fixed.Int26_6(p.type2Charstrings.y) << 6, + 0: fixed.Int26_6(p.type2Charstrings.x), + 1: fixed.Int26_6(p.type2Charstrings.y), }, }) } @@ -685,12 +685,12 @@ func t2CAppendCubeto(p *psInterpreter, dxa, dya, dxb, dyb, dxc, dyc int32) { p.type2Charstrings.segments = append(p.type2Charstrings.segments, Segment{ Op: SegmentOpCubeTo, Args: [6]fixed.Int26_6{ - 0: fixed.Int26_6(xa) << 6, - 1: fixed.Int26_6(ya) << 6, - 2: fixed.Int26_6(xb) << 6, - 3: fixed.Int26_6(yb) << 6, - 4: fixed.Int26_6(xc) << 6, - 5: fixed.Int26_6(yc) << 6, + 0: fixed.Int26_6(xa), + 1: fixed.Int26_6(ya), + 2: fixed.Int26_6(xb), + 3: fixed.Int26_6(yb), + 4: fixed.Int26_6(xc), + 5: fixed.Int26_6(yc), }, }) } diff --git a/font/sfnt/proprietary_test.go b/font/sfnt/proprietary_test.go index f4f803e..d25c62a 100644 --- a/font/sfnt/proprietary_test.go +++ b/font/sfnt/proprietary_test.go @@ -35,6 +35,8 @@ import ( "io/ioutil" "path/filepath" "testing" + + "golang.org/x/image/math/fixed" ) var ( @@ -132,6 +134,7 @@ func testProprietary(t *testing.T, proprietor, filename string, minNumGlyphs, fi if err != nil { t.Fatalf("Parse: %v", err) } + ppem := fixed.Int26_6(f.UnitsPerEm()) << 6 numGlyphs := f.NumGlyphs() if numGlyphs < minNumGlyphs { @@ -144,7 +147,7 @@ func testProprietary(t *testing.T, proprietor, filename string, minNumGlyphs, fi iMax = firstUnsupportedGlyph } for i, numErrors := 0, 0; i < iMax; i++ { - if _, err := f.LoadGlyph(&buf, GlyphIndex(i), nil); err != nil { + if _, err := f.LoadGlyph(&buf, GlyphIndex(i), ppem, nil); err != nil { t.Errorf("LoadGlyph(%d): %v", i, err) numErrors++ } diff --git a/font/sfnt/sfnt.go b/font/sfnt/sfnt.go index 995b287..b181688 100644 --- a/font/sfnt/sfnt.go +++ b/font/sfnt/sfnt.go @@ -131,6 +131,17 @@ const ( // display resolution (DPI) and font size (e.g. a 12 point font). type Units int32 +// scale returns x divided by unitsPerEm, rounded to the nearest fixed.Int26_6 +// value (1/64th of a pixel). +func scale(x fixed.Int26_6, unitsPerEm Units) fixed.Int26_6 { + if x >= 0 { + x += fixed.Int26_6(unitsPerEm) / 2 + } else { + x -= fixed.Int26_6(unitsPerEm) / 2 + } + return x / fixed.Int26_6(unitsPerEm) +} + func u16(b []byte) uint16 { _ = b[1] // Bounds check hint to compiler. return uint16(b[0])<<8 | uint16(b[1])<<0 @@ -274,6 +285,12 @@ func ParseReaderAt(src io.ReaderAt) (*Font, error) { // // The Font methods that don't take a *Buffer argument are always safe to call // concurrently. +// +// Some methods provide lengths or co-ordinates, e.g. bounds, font metrics and +// control points. All of these methods take a ppem parameter, which is the +// number of pixels in 1 em, expressed as a 26.6 fixed point value. For +// example, if 1 em is 10 pixels then ppem is fixed.I(10), which equals +// fixed.Int26_6(10 << 6). type Font struct { src source @@ -623,15 +640,16 @@ func (f *Font) viewGlyphData(b *Buffer, x GlyphIndex) ([]byte, error) { // LoadGlyphOptions are the options to the Font.LoadGlyph method. type LoadGlyphOptions struct { - // TODO: scale / transform / hinting. + // TODO: transform / hinting. } -// LoadGlyph returns the vector segments for the x'th glyph. +// LoadGlyph returns the vector segments for the x'th glyph. ppem is the number +// of pixels in 1 em. // // If b is non-nil, the segments become invalid to use once b is re-used. // // It returns ErrNotFound if the glyph index is out of range. -func (f *Font) LoadGlyph(b *Buffer, x GlyphIndex, opts *LoadGlyphOptions) ([]Segment, error) { +func (f *Font) LoadGlyph(b *Buffer, x GlyphIndex, ppem fixed.Int26_6, opts *LoadGlyphOptions) ([]Segment, error) { if b == nil { b = &Buffer{} } @@ -656,7 +674,19 @@ func (f *Font) LoadGlyph(b *Buffer, x GlyphIndex, opts *LoadGlyphOptions) ([]Seg b.segments = segments } - // TODO: look at opts to scale / transform / hint the Buffer.segments. + // Scale the segments. If we want to support hinting, we'll have to push + // the scaling computation into the PostScript / TrueType specific glyph + // loading code, such as the appendGlyfSegments body, since TrueType + // hinting bytecode works on the scaled glyph vectors. For now, though, + // it's simpler to scale as a post-processing step. + for i := range b.segments { + s := &b.segments[i] + for j := range s.Args { + s.Args[j] = scale(s.Args[j]*ppem, f.cached.unitsPerEm) + } + } + + // TODO: look at opts to transform / hint the Buffer.segments. return b.segments, nil } diff --git a/font/sfnt/sfnt_test.go b/font/sfnt/sfnt_test.go index ca30321..6f6fff9 100644 --- a/font/sfnt/sfnt_test.go +++ b/font/sfnt/sfnt_test.go @@ -6,6 +6,7 @@ package sfnt import ( "bytes" + "fmt" "io/ioutil" "path/filepath" "testing" @@ -24,6 +25,16 @@ func moveTo(xa, ya int) Segment { } } +func moveTo26_6(xa, ya fixed.Int26_6) Segment { + return Segment{ + Op: SegmentOpMoveTo, + Args: [6]fixed.Int26_6{ + 0: xa, + 1: ya, + }, + } +} + func lineTo(xa, ya int) Segment { return Segment{ Op: SegmentOpLineTo, @@ -34,6 +45,16 @@ func lineTo(xa, ya int) Segment { } } +func lineTo26_6(xa, ya fixed.Int26_6) Segment { + return Segment{ + Op: SegmentOpLineTo, + Args: [6]fixed.Int26_6{ + 0: xa, + 1: ya, + }, + } +} + func quadTo(xa, ya, xb, yb int) Segment { return Segment{ Op: SegmentOpQuadTo, @@ -60,6 +81,20 @@ func cubeTo(xa, ya, xb, yb, xc, yc int) Segment { } } +func checkSegmentsEqual(got, want []Segment) error { + if len(got) != len(want) { + return fmt.Errorf("got %d elements, want %d\noverall:\ngot %v\nwant %v", + len(got), len(want), got, want) + } + for i, g := range got { + if w := want[i]; g != w { + return fmt.Errorf("element %d:\ngot %v\nwant %v\noverall:\ngot %v\nwant %v", + i, g, w, got, want) + } + } + return nil +} + func TestTrueTypeParse(t *testing.T) { f, err := Parse(goregular.TTF) if err != nil { @@ -389,38 +424,30 @@ func TestTrueTypeSegments(t *testing.T) { func testSegments(t *testing.T, filename string, wants [][]Segment) { data, err := ioutil.ReadFile(filepath.FromSlash("../testdata/" + filename)) if err != nil { - t.Fatal(err) + t.Fatalf("ReadFile: %v", err) } f, err := Parse(data) if err != nil { - t.Fatal(err) + t.Fatalf("Parse: %v", err) } + ppem := fixed.Int26_6(f.UnitsPerEm()) << 6 if ng := f.NumGlyphs(); ng != len(wants) { t.Fatalf("NumGlyphs: got %d, want %d", ng, len(wants)) } var b Buffer -loop: for i, want := range wants { - got, err := f.LoadGlyph(&b, GlyphIndex(i), nil) + got, err := f.LoadGlyph(&b, GlyphIndex(i), ppem, nil) if err != nil { t.Errorf("i=%d: LoadGlyph: %v", i, err) continue } - if len(got) != len(want) { - t.Errorf("i=%d: got %d elements, want %d\noverall:\ngot %v\nwant %v", - i, len(got), len(want), got, want) + if err := checkSegmentsEqual(got, want); err != nil { + t.Errorf("i=%d: %v", i, err) continue } - for j, g := range got { - if w := want[j]; g != w { - t.Errorf("i=%d: element %d:\ngot %v\nwant %v\noverall:\ngot %v\nwant %v", - i, j, g, w, got, want) - continue loop - } - } } - if _, err := f.LoadGlyph(nil, 0xffff, nil); err != ErrNotFound { + if _, err := f.LoadGlyph(nil, 0xffff, ppem, nil); err != ErrNotFound { t.Errorf("LoadGlyph(..., 0xffff, ...):\ngot %v\nwant %v", err, ErrNotFound) } @@ -432,6 +459,60 @@ loop: } } +func TestPPEM(t *testing.T) { + data, err := ioutil.ReadFile(filepath.FromSlash("../testdata/glyfTest.ttf")) + if err != nil { + t.Fatalf("ReadFile: %v", err) + } + f, err := Parse(data) + if err != nil { + t.Fatalf("Parse: %v", err) + } + var b Buffer + x, err := f.GlyphIndex(&b, '1') + if err != nil { + t.Fatalf("GlyphIndex: %v", err) + } + if x == 0 { + t.Fatalf("GlyphIndex: no glyph index found for the rune '1'") + } + + testCases := []struct { + ppem fixed.Int26_6 + want []Segment + }{{ + ppem: fixed.I(12), + want: []Segment{ + moveTo26_6(77, 0), + lineTo26_6(77, 614), + lineTo26_6(230, 614), + lineTo26_6(230, 0), + lineTo26_6(77, 0), + }, + }, { + ppem: fixed.I(2048), + want: []Segment{ + moveTo(205, 0), + lineTo(205, 1638), + lineTo(614, 1638), + lineTo(614, 0), + lineTo(205, 0), + }, + }} + + for i, tc := range testCases { + got, err := f.LoadGlyph(&b, x, tc.ppem, nil) + if err != nil { + t.Errorf("i=%d: LoadGlyph: %v", i, err) + continue + } + if err := checkSegmentsEqual(got, tc.want); err != nil { + t.Errorf("i=%d: %v", i, err) + continue + } + } +} + func TestGlyphName(t *testing.T) { f, err := Parse(goregular.TTF) if err != nil { diff --git a/font/sfnt/truetype.go b/font/sfnt/truetype.go index be90154..4111feb 100644 --- a/font/sfnt/truetype.go +++ b/font/sfnt/truetype.go @@ -357,9 +357,18 @@ func (g *glyfIter) nextSegment() (ok bool) { return true } + // Convert the tuple (g.x, g.y) to a fixed.Point26_6, since the latter + // is what's held in a Segment. The input (g.x, g.y) is a pair of int16 + // values, measured in font units, since that is what the underlying + // format provides. The output is a pair of fixed.Int26_6 values. A + // fixed.Int26_6 usually represents a 26.6 fixed number of pixels, but + // this here is just a straight numerical conversion, with no scaling + // factor. A later step scales the Segment.Args values by such a factor + // to convert e.g. 1792 font units to 10.5 pixels at 2048 font units + // per em and 12 ppem (pixels per em). p := fixed.Point26_6{ - X: fixed.Int26_6(g.x) << 6, - Y: fixed.Int26_6(g.y) << 6, + X: fixed.Int26_6(g.x), + Y: fixed.Int26_6(g.y), } if !g.firstOnCurveValid {