font/sfnt: add a ppem arg to Font.LoadGlyph.
This lets us load a glyph at e.g. 12 pixels per em. Change-Id: I048b3db89af8670782953a8361afe0e6373df9b0 Reviewed-on: https://go-review.googlesource.com/37175 Reviewed-by: David Crawshaw <crawshaw@golang.org>
This commit is contained in:
parent
791b615328
commit
ed91dc314e
125
font/sfnt/example_test.go
Normal file
125
font/sfnt/example_test.go
Normal file
|
@ -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++..
|
||||
// ..........++++++++......
|
||||
// ........................
|
||||
// ........................
|
||||
// ........................
|
||||
}
|
|
@ -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),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
|
|
@ -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++
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
Loading…
Reference in New Issue
Block a user