package font
import (
"bytes"
"compress/gzip"
"errors"
"fmt"
"io"
ot "github.com/go-text/typesetting/font/opentype"
"github.com/go-text/typesetting/font/opentype/tables"
)
var (
errEmptySbixTable = errors .New ("empty 'sbix' table" )
errEmptyBitmapTable = errors .New ("empty bitmap table" )
)
type (
Segment = ot .Segment
SegmentPoint = ot .SegmentPoint
)
type GlyphData interface {
isGlyphData()
}
func (GlyphOutline ) isGlyphData () {}
func (GlyphSVG ) isGlyphData () {}
func (GlyphBitmap ) isGlyphData () {}
func (GlyphColor ) isGlyphData () {}
type GlyphOutline struct {
Segments []Segment
}
func (o GlyphOutline ) Sideways (yOffset float32 ) {
for i := range o .Segments {
target := o .Segments [i ].Args [:]
target [0 ].X , target [0 ].Y = target [0 ].Y , -target [0 ].X +yOffset
target [1 ].X , target [1 ].Y = target [1 ].Y , -target [1 ].X +yOffset
target [2 ].X , target [2 ].Y = target [2 ].Y , -target [2 ].X +yOffset
}
}
type GlyphSVG struct {
Source []byte
Outline GlyphOutline
}
type GlyphBitmap struct {
Data []byte
Format BitmapFormat
Width, Height int
Outline *GlyphOutline
}
type BitmapFormat uint8
const (
_ BitmapFormat = iota
BlackAndWhite
PNG
JPG
TIFF
)
type BitmapSize struct {
Height, Width uint16
XPpem, YPpem uint16
}
type GlyphColor struct {
Paint tables .PaintTable
}
func (sb sbix ) glyphData (gid gID , xPpem , yPpem uint16 ) (GlyphBitmap , error ) {
st := sb .chooseStrike (xPpem , yPpem )
if st == nil {
return GlyphBitmap {}, errEmptySbixTable
}
glyph := strikeGlyph (st , gid , 0 )
if glyph .GraphicType == 0 {
return GlyphBitmap {}, fmt .Errorf ("no glyph %d in 'sbix' table for resolution (%d, %d)" , gid , xPpem , yPpem )
}
out := GlyphBitmap {Data : glyph .Data }
var err error
out .Width , out .Height , out .Format , err = decodeBitmapConfig (glyph )
return out , err
}
func (bt bitmap ) glyphData (gid gID , xPpem , yPpem uint16 ) (GlyphBitmap , error ) {
st := bt .chooseStrike (xPpem , yPpem )
if st == nil || st .ppemX == 0 || st .ppemY == 0 {
return GlyphBitmap {}, errEmptyBitmapTable
}
subtable := st .findTable (gid )
if subtable == nil {
return GlyphBitmap {}, fmt .Errorf ("no glyph %d in bitmap table for resolution (%d, %d)" , gid , xPpem , yPpem )
}
glyph := subtable .image (gid )
if glyph == nil {
return GlyphBitmap {}, fmt .Errorf ("no glyph %d in bitmap table for resolution (%d, %d)" , gid , xPpem , yPpem )
}
out := GlyphBitmap {
Data : glyph .image ,
Width : int (glyph .metrics .Width ),
Height : int (glyph .metrics .Height ),
}
switch subtable .imageFormat {
case 17 , 18 , 19 :
out .Format = PNG
case 2 , 5 :
out .Format = BlackAndWhite
L := out .Width * out .Height
if len (out .Data )*8 < L {
return GlyphBitmap {}, fmt .Errorf ("EOF in glyph bitmap: expected %d, got %d" , L , len (out .Data )*8 )
}
default :
return GlyphBitmap {}, fmt .Errorf ("unsupported format %d in bitmap table" , subtable .imageFormat )
}
return out , nil
}
func (s svg ) glyphData (gid gID ) (GlyphSVG , bool ) {
data , ok := s .rawGlyphData (gid )
if !ok {
return GlyphSVG {}, false
}
if r , err := gzip .NewReader (bytes .NewReader (data )); err == nil {
var buf bytes .Buffer
if _ , err := io .Copy (&buf , r ); err == nil {
data = buf .Bytes ()
}
}
return GlyphSVG {Source : data }, true
}
func midPoint(p , q SegmentPoint ) SegmentPoint {
return SegmentPoint {
X : (p .X + q .X ) / 2 ,
Y : (p .Y + q .Y ) / 2 ,
}
}
func buildSegments(points []contourPoint ) []Segment {
if len (points ) == 0 {
return nil
}
var (
firstOnCurveValid , firstOffCurveValid , lastOffCurveValid bool
firstOnCurve , firstOffCurve , lastOffCurve SegmentPoint
)
out := make ([]Segment , 0 , len (points )+2 )
for _ , point := range points {
p := point .SegmentPoint
if !firstOnCurveValid {
if point .isOnCurve {
firstOnCurve = p
firstOnCurveValid = true
out = append (out , Segment {
Op : ot .SegmentOpMoveTo ,
Args : [3 ]SegmentPoint {p },
})
} else if !firstOffCurveValid {
firstOffCurve = p
firstOffCurveValid = true
if !point .isEndPoint {
continue
}
} else {
firstOnCurve = midPoint (firstOffCurve , p )
firstOnCurveValid = true
lastOffCurve = p
lastOffCurveValid = true
out = append (out , Segment {
Op : ot .SegmentOpMoveTo ,
Args : [3 ]SegmentPoint {firstOnCurve },
})
}
} else if !lastOffCurveValid {
if !point .isOnCurve {
lastOffCurve = p
lastOffCurveValid = true
if !point .isEndPoint {
continue
}
} else {
out = append (out , Segment {
Op : ot .SegmentOpLineTo ,
Args : [3 ]SegmentPoint {p },
})
}
} else {
if !point .isOnCurve {
out = append (out , Segment {
Op : ot .SegmentOpQuadTo ,
Args : [3 ]SegmentPoint {
lastOffCurve ,
midPoint (lastOffCurve , p ),
},
})
lastOffCurve = p
lastOffCurveValid = true
} else {
out = append (out , Segment {
Op : ot .SegmentOpQuadTo ,
Args : [3 ]SegmentPoint {lastOffCurve , p },
})
lastOffCurveValid = false
}
}
if point .isEndPoint {
switch {
case !firstOffCurveValid && !lastOffCurveValid :
out = append (out , Segment {
Op : ot .SegmentOpLineTo ,
Args : [3 ]SegmentPoint {firstOnCurve },
})
case !firstOffCurveValid && lastOffCurveValid :
out = append (out , Segment {
Op : ot .SegmentOpQuadTo ,
Args : [3 ]SegmentPoint {lastOffCurve , firstOnCurve },
})
case firstOffCurveValid && !lastOffCurveValid :
out = append (out , Segment {
Op : ot .SegmentOpQuadTo ,
Args : [3 ]SegmentPoint {firstOffCurve , firstOnCurve },
})
case firstOffCurveValid && lastOffCurveValid :
out = append (out , Segment {
Op : ot .SegmentOpQuadTo ,
Args : [3 ]SegmentPoint {
lastOffCurve ,
midPoint (lastOffCurve , firstOffCurve ),
},
},
Segment {
Op : ot .SegmentOpQuadTo ,
Args : [3 ]SegmentPoint {firstOffCurve , firstOnCurve },
},
)
}
firstOnCurveValid = false
firstOffCurveValid = false
lastOffCurveValid = false
}
}
return out
}
type errGlyphOutOfRange int
func (e errGlyphOutOfRange ) Error () string {
return fmt .Sprintf ("out of range glyph %d" , e )
}
func (f *Face ) glyphDataFromGlyf (glyph gID ) (GlyphOutline , error ) {
if int (glyph ) >= len (f .glyf ) {
return GlyphOutline {}, errGlyphOutOfRange (glyph )
}
points := f .getPointsForGlyph (glyph )
segments := buildSegments (points [:len (points )-phantomCount ])
return GlyphOutline {Segments : segments }, nil
}
var (
errNoCFFTable error = errors .New ("no CFF table" )
errNoCFF2Table error = errors .New ("no CFF2 table" )
)
func (f *Font ) glyphDataFromCFF1 (glyph gID ) (GlyphOutline , error ) {
if f .cff == nil {
return GlyphOutline {}, errNoCFFTable
}
segments , _ , err := f .cff .LoadGlyph (glyph )
if err != nil {
return GlyphOutline {}, err
}
return GlyphOutline {Segments : segments }, nil
}
func (f *Face ) glyphDataFromCFF2 (glyph gID ) (GlyphOutline , error ) {
if f .cff2 == nil {
return GlyphOutline {}, errNoCFF2Table
}
segments , _ , err := f .cff2 .LoadGlyph (glyph , f .coords )
if err != nil {
return GlyphOutline {}, err
}
return GlyphOutline {Segments : segments }, nil
}
func (font *Font ) BitmapSizes () []BitmapSize {
upem := font .head .UnitsPerEm
avgWidth := font .os2 .xAvgCharWidth
if upem == 0 || font .os2 .version == 0xFFFF {
avgWidth = 1
upem = 1
}
if font .bitmap != nil {
return font .bitmap .availableSizes (avgWidth , upem )
}
if hori := font .hhea ; hori != nil {
return font .sbix .availableSizes (hori , avgWidth , upem )
}
return nil
}
func (f *Face ) GlyphData (gid GID ) GlyphData {
g := gID (gid )
if out , ok := f .GlyphDataColor (g ); ok {
return out
}
if out , ok := f .GlyphDataBitmap (g ); ok {
return out
}
if out , ok := f .GlyphDataSVG (g ); ok {
return out
}
if out , ok := f .GlyphDataOutline (g ); ok {
return out
}
return nil
}
func (f *Face ) GlyphDataOutline (gid gID ) (GlyphOutline , bool ) {
out , err := f .glyphDataFromCFF1 (gid )
if err == nil {
return out , true
}
out , err = f .glyphDataFromCFF2 (gid )
if err == nil {
return out , true
}
out , err = f .glyphDataFromGlyf (gid )
if err == nil {
return out , true
}
return GlyphOutline {}, false
}
func (f *Face ) GlyphDataSVG (gid gID ) (GlyphSVG , bool ) {
outS , ok := f .svg .glyphData (gid )
if !ok {
return GlyphSVG {}, false
}
outS .Outline , _ = f .GlyphDataOutline (gid )
return outS , true
}
func (f *Face ) GlyphDataColor (gid gID ) (GlyphColor , bool ) {
v , ok := f .COLR .Search (gid )
return GlyphColor {v }, ok
}
func (f *Face ) GlyphDataBitmap (gid gID ) (GlyphBitmap , bool ) {
outB , err := f .sbix .glyphData (gid , f .xPpem , f .yPpem )
if err == nil {
outline , ok := f .GlyphDataOutline (gid )
if ok {
outB .Outline = &outline
}
return outB , true
}
outB , err = f .bitmap .glyphData (gid , f .xPpem , f .yPpem )
if err == nil {
outline , ok := f .GlyphDataOutline (gid )
if ok {
outB .Outline = &outline
}
return outB , true
}
return GlyphBitmap {}, false
}
The pages are generated with Golds v0.8.7 . (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu .
PR and bug reports are welcome and can be submitted to the issue list .
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds .