package font
import (
"bytes"
"encoding/binary"
"fmt"
"image"
"image/jpeg"
"image/png"
ot "github.com/go-text/typesetting/font/opentype"
"github.com/go-text/typesetting/font/opentype/tables"
"golang.org/x/image/tiff"
)
type contourPoint struct {
SegmentPoint
isOnCurve bool
isEndPoint bool
isExplicit bool
}
func (c *contourPoint ) translate (x , y float32 ) {
c .X += x
c .Y += y
}
func (c *contourPoint ) transform (matrix [4 ]float32 ) {
px := c .X *matrix [0 ] + c .Y *matrix [2 ]
c .Y = c .X *matrix [1 ] + c .Y *matrix [3 ]
c .X = px
}
const (
phantomLeft = iota
phantomRight
phantomTop
phantomBottom
phantomCount
)
type glyphSet map [tables .GlyphID ]struct {}
func (f *Face ) getPointsForGlyph (gid tables .GlyphID ) []contourPoint {
var out []contourPoint
seenGlyphs := make (glyphSet )
f .getPointsForGlyphRec (gid , 0 , seenGlyphs , &out )
return out
}
const maxCompositeNesting = 20
func (f *Face ) getPointsForGlyphRec (gid tables .GlyphID , currentDepth int , currentGlyphs glyphSet , allPoints *[]contourPoint ) {
if currentDepth > maxCompositeNesting || int (gid ) >= len (f .glyf ) {
return
}
g := f .glyf [gid ]
var points []contourPoint
if data , ok := g .Data .(tables .SimpleGlyph ); ok {
points = getContourPoints (data )
} else {
points = make ([]contourPoint , pointNumbersCount (g ))
}
points = append (points , make ([]contourPoint , phantomCount )...)
phantoms := points [len (points )-phantomCount :]
hDelta := float32 (g .XMin - f .hmtx .SideBearing (gid ))
vOrig := float32 (g .YMax + f .vmtx .SideBearing (gid ))
hAdv := float32 (f .getBaseAdvance (gid , f .hmtx , false ))
vAdv := float32 (f .getBaseAdvance (gid , f .vmtx , true ))
phantoms [phantomLeft ].X = hDelta
phantoms [phantomRight ].X = hAdv + hDelta
phantoms [phantomTop ].Y = vOrig
phantoms [phantomBottom ].Y = vOrig - vAdv
if f .isVar () {
f .gvar .applyDeltasToPoints (gid , f .coords , points )
}
switch data := g .Data .(type ) {
case tables .SimpleGlyph :
*allPoints = append (*allPoints , points ...)
case tables .CompositeGlyph :
for compIndex , item := range data .Glyphs {
if _ , has := currentGlyphs [item .GlyphIndex ]; has {
continue
}
currentGlyphs [item .GlyphIndex ] = struct {}{}
var compPoints []contourPoint
f .getPointsForGlyphRec (item .GlyphIndex , currentDepth +1 , currentGlyphs , &compPoints )
LC := len (compPoints )
if LC < phantomCount {
delete (currentGlyphs , item .GlyphIndex )
return
}
if item .HasUseMyMetrics () {
copy (phantoms , compPoints [LC -phantomCount :])
}
transformPoints (&item , compPoints )
tx , ty := points [compIndex ].X , points [compIndex ].Y
for i := range compPoints {
compPoints [i ].translate (tx , ty )
}
if item .IsAnchored () {
p1 , p2 := item .ArgsAsIndices ()
if p1 < len (*allPoints ) && p2 < LC {
tx , ty := (*allPoints )[p1 ].X -compPoints [p2 ].X , (*allPoints )[p1 ].Y -compPoints [p2 ].Y
for i := range compPoints {
compPoints [i ].translate (tx , ty )
}
}
}
*allPoints = append (*allPoints , compPoints [0 :LC -phantomCount ]...)
delete (currentGlyphs , item .GlyphIndex )
}
*allPoints = append (*allPoints , phantoms ...)
default :
*allPoints = append (*allPoints , phantoms ...)
}
if currentDepth == 0 {
tx := -phantoms [phantomLeft ].X
for i := range *allPoints {
(*allPoints )[i ].translate (tx , 0 )
}
}
}
func pointNumbersCount(g tables .Glyph ) int {
switch g := g .Data .(type ) {
case tables .SimpleGlyph :
return len (g .Points )
case tables .CompositeGlyph :
return len (g .Glyphs )
}
return 0
}
func getContourPoints(sg tables .SimpleGlyph ) []contourPoint {
const flagOnCurve = 1 << 0
points := make ([]contourPoint , len (sg .Points ))
for _ , end := range sg .EndPtsOfContours {
points [end ].isEndPoint = true
}
for i , p := range sg .Points {
points [i ].X , points [i ].Y = float32 (p .X ), float32 (p .Y )
points [i ].isOnCurve = p .Flag &flagOnCurve != 0
}
return points
}
func extentsFromPoints(allPoints []contourPoint ) (ext GlyphExtents ) {
truePoints := allPoints [:len (allPoints )-phantomCount ]
if len (truePoints ) == 0 {
return ext
}
minX , minY := truePoints [0 ].X , truePoints [0 ].Y
maxX , maxY := minX , minY
for _ , p := range truePoints {
minX = minF (minX , p .X )
minY = minF (minY , p .Y )
maxX = maxF (maxX , p .X )
maxY = maxF (maxY , p .Y )
}
ext .XBearing = minX
ext .YBearing = maxY
ext .Width = maxX - minX
ext .Height = minY - maxY
return ext
}
func (f *Face ) getGlyfPoints (gid tables .GlyphID , computeExtents bool ) (ext GlyphExtents , ph [phantomCount ]contourPoint ) {
if int (gid ) >= len (f .glyf ) {
return
}
allPoints := f .getPointsForGlyph (gid )
copy (ph [:], allPoints [len (allPoints )-phantomCount :])
if computeExtents {
ext = extentsFromPoints (allPoints )
}
return ext , ph
}
func min16(a , b int16 ) int16 {
if a < b {
return a
}
return b
}
func max16(a , b int16 ) int16 {
if a > b {
return a
}
return b
}
func minC(a , b VarCoord ) VarCoord {
if a < b {
return a
}
return b
}
func maxC(a , b VarCoord ) VarCoord {
if a > b {
return a
}
return b
}
func minF(a , b float32 ) float32 {
if a < b {
return a
}
return b
}
func maxF(a , b float32 ) float32 {
if a > b {
return a
}
return b
}
func transformPoints(c *tables .CompositeGlyphPart , points []contourPoint ) {
var transX , transY float32
if !c .IsAnchored () {
arg1 , arg2 := c .ArgsAsTranslation ()
transX , transY = float32 (arg1 ), float32 (arg2 )
}
scale := c .Scale
if transX == 0 && transY == 0 && scale == [4 ]float32 {1 , 0 , 0 , 1 } {
return
}
if c .IsScaledOffsets () {
for i := range points {
points [i ].translate (transX , transY )
points [i ].transform (scale )
}
} else {
for i := range points {
points [i ].transform (scale )
points [i ].translate (transX , transY )
}
}
}
func getGlyphExtents(g tables .Glyph , metrics tables .Hmtx , gid gID ) GlyphExtents {
var extents GlyphExtents
extents .XBearing = float32 (metrics .SideBearing (gid ))
extents .YBearing = float32 (max16 (g .YMin , g .YMax ))
extents .Width = float32 (max16 (g .XMin , g .XMax ) - min16 (g .XMin , g .XMax ))
extents .Height = float32 (min16 (g .YMin , g .YMax ) - max16 (g .YMin , g .YMax ))
return extents
}
var (
dupe = ot .MustNewTag ("dupe" )
tagPNG = ot .MustNewTag ("png " )
tagTIFF = ot .MustNewTag ("tiff" )
tagJPG = ot .MustNewTag ("jpg " )
)
func strikeGlyph(b *tables .Strike , glyph gID , recursionLevel int ) tables .BitmapGlyphData {
const maxRecursionLevel = 8
if int (glyph ) >= len (b .GlyphDatas ) {
return tables .BitmapGlyphData {}
}
out := b .GlyphDatas [glyph ]
if out .GraphicType == dupe {
if len (out .Data ) < 2 || recursionLevel > maxRecursionLevel {
return tables .BitmapGlyphData {}
}
glyph = gID (binary .BigEndian .Uint16 (out .Data ))
return strikeGlyph (b , glyph , recursionLevel +1 )
}
return out
}
func decodeBitmapConfig(b tables .BitmapGlyphData ) (width , height int , format BitmapFormat , err error ) {
var config image .Config
switch b .GraphicType {
case tagPNG :
format = PNG
config , err = png .DecodeConfig (bytes .NewReader (b .Data ))
case tagTIFF :
format = TIFF
config , err = tiff .DecodeConfig (bytes .NewReader (b .Data ))
case tagJPG :
format = JPG
config , err = jpeg .DecodeConfig (bytes .NewReader (b .Data ))
default :
err = fmt .Errorf ("unsupported graphic type in sbix table: %s" , b .GraphicType )
}
if err != nil {
return 0 , 0 , 0 , err
}
return config .Width , config .Height , format , nil
}
func bitmapGlyphExtents(b tables .BitmapGlyphData ) (out GlyphExtents , ok bool ) {
width , height , _ , err := decodeBitmapConfig (b )
if err != nil {
return out , false
}
out .XBearing = float32 (b .OriginOffsetX )
out .YBearing = float32 (height ) + float32 (b .OriginOffsetY )
out .Width = float32 (width )
out .Height = -float32 (height )
return out , true
}
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 .