package sfnt
import (
"golang.org/x/image/math/fixed"
)
const (
flagOnCurve = 1 << 0
flagXShortVector = 1 << 1
flagYShortVector = 1 << 2
flagRepeat = 1 << 3
flagPositiveXShortVector = 1 << 4
flagThisXIsSame = 1 << 4
flagPositiveYShortVector = 1 << 5
flagThisYIsSame = 1 << 5
)
const (
flagArg1And2AreWords = 1 << 0
flagArgsAreXYValues = 1 << 1
flagRoundXYToGrid = 1 << 2
flagWeHaveAScale = 1 << 3
flagReserved4 = 1 << 4
flagMoreComponents = 1 << 5
flagWeHaveAnXAndYScale = 1 << 6
flagWeHaveATwoByTwo = 1 << 7
flagWeHaveInstructions = 1 << 8
flagUseMyMetrics = 1 << 9
flagOverlapCompound = 1 << 10
flagScaledComponentOffset = 1 << 11
flagUnscaledComponentOffset = 1 << 12
)
func midPoint(p , q fixed .Point26_6 ) fixed .Point26_6 {
return fixed .Point26_6 {
X : (p .X + q .X ) / 2 ,
Y : (p .Y + q .Y ) / 2 ,
}
}
func parseLoca(src *source , loca table , glyfOffset uint32 , indexToLocFormat bool , numGlyphs int32 ) (locations []uint32 , err error ) {
if indexToLocFormat {
if loca .length != 4 *uint32 (numGlyphs +1 ) {
return nil , errInvalidLocaTable
}
} else {
if loca .length != 2 *uint32 (numGlyphs +1 ) {
return nil , errInvalidLocaTable
}
}
locations = make ([]uint32 , numGlyphs +1 )
buf , err := src .view (nil , int (loca .offset ), int (loca .length ))
if err != nil {
return nil , err
}
if indexToLocFormat {
for i := range locations {
locations [i ] = 1 *uint32 (u32 (buf [4 *i :])) + glyfOffset
}
} else {
for i := range locations {
locations [i ] = 2 *uint32 (u16 (buf [2 *i :])) + glyfOffset
}
}
return locations , err
}
const glyfHeaderLen = 10
func loadGlyf(f *Font , b *Buffer , x GlyphIndex , stackBottom , recursionDepth uint32 ) error {
data , _ , _ , err := f .viewGlyphData (b , x )
if err != nil {
return err
}
if len (data ) == 0 {
return nil
}
if len (data ) < glyfHeaderLen {
return errInvalidGlyphData
}
index := glyfHeaderLen
numContours , numPoints := int16 (u16 (data )), 0
switch {
case numContours == -1 :
case numContours == 0 :
return nil
case numContours > 0 :
index += 2 * int (numContours )
if index > len (data ) {
return errInvalidGlyphData
}
numPoints = 1 + int (u16 (data [index -2 :]))
default :
return errInvalidGlyphData
}
if numContours < 0 {
return loadCompoundGlyf (f , b , data [glyfHeaderLen :], stackBottom , recursionDepth )
}
index += 2
if index > len (data ) {
return errInvalidGlyphData
}
hintsLength := int (u16 (data [index -2 :]))
index += hintsLength
if index > len (data ) {
return errInvalidGlyphData
}
flagIndex := int32 (index )
xIndex , yIndex , ok := findXYIndexes (data , index , numPoints )
if !ok {
return errInvalidGlyphData
}
g := glyfIter {
data : data ,
flagIndex : flagIndex ,
xIndex : xIndex ,
yIndex : yIndex ,
endIndex : glyfHeaderLen ,
prevEnd : -1 ,
finalEnd : int32 (numPoints - 1 ),
numContours : int32 (numContours ),
}
for g .nextContour () {
for g .nextSegment () {
b .segments = append (b .segments , g .seg )
}
}
return g .err
}
func findXYIndexes(data []byte , index , numPoints int ) (xIndex , yIndex int32 , ok bool ) {
xDataLen := 0
yDataLen := 0
for i := 0 ; ; {
if i > numPoints {
return 0 , 0 , false
}
if i == numPoints {
break
}
repeatCount := 1
if index >= len (data ) {
return 0 , 0 , false
}
flag := data [index ]
index ++
if flag &flagRepeat != 0 {
if index >= len (data ) {
return 0 , 0 , false
}
repeatCount += int (data [index ])
index ++
}
xSize := 0
if flag &flagXShortVector != 0 {
xSize = 1
} else if flag &flagThisXIsSame == 0 {
xSize = 2
}
xDataLen += xSize * repeatCount
ySize := 0
if flag &flagYShortVector != 0 {
ySize = 1
} else if flag &flagThisYIsSame == 0 {
ySize = 2
}
yDataLen += ySize * repeatCount
i += repeatCount
}
if index +xDataLen +yDataLen > len (data ) {
return 0 , 0 , false
}
return int32 (index ), int32 (index + xDataLen ), true
}
func loadCompoundGlyf(f *Font , b *Buffer , data []byte , stackBottom , recursionDepth uint32 ) error {
if recursionDepth ++; recursionDepth == maxCompoundRecursionDepth {
return errUnsupportedCompoundGlyph
}
stackTop := stackBottom
for {
if stackTop >= maxCompoundStackSize {
return errUnsupportedCompoundGlyph
}
elem := &b .compoundStack [stackTop ]
stackTop ++
if len (data ) < 4 {
return errInvalidGlyphData
}
flags := u16 (data )
elem .glyphIndex = GlyphIndex (u16 (data [2 :]))
if flags &flagArg1And2AreWords == 0 {
if len (data ) < 6 {
return errInvalidGlyphData
}
elem .dx = int16 (int8 (data [4 ]))
elem .dy = int16 (int8 (data [5 ]))
data = data [6 :]
} else {
if len (data ) < 8 {
return errInvalidGlyphData
}
elem .dx = int16 (u16 (data [4 :]))
elem .dy = int16 (u16 (data [6 :]))
data = data [8 :]
}
if flags &flagArgsAreXYValues == 0 {
return errUnsupportedCompoundGlyph
}
elem .hasTransform = flags &(flagWeHaveAScale |flagWeHaveAnXAndYScale |flagWeHaveATwoByTwo ) != 0
if elem .hasTransform {
switch {
case flags &flagWeHaveAScale != 0 :
if len (data ) < 2 {
return errInvalidGlyphData
}
elem .transformXX = int16 (u16 (data ))
elem .transformXY = 0
elem .transformYX = 0
elem .transformYY = elem .transformXX
data = data [2 :]
case flags &flagWeHaveAnXAndYScale != 0 :
if len (data ) < 4 {
return errInvalidGlyphData
}
elem .transformXX = int16 (u16 (data [0 :]))
elem .transformXY = 0
elem .transformYX = 0
elem .transformYY = int16 (u16 (data [2 :]))
data = data [4 :]
case flags &flagWeHaveATwoByTwo != 0 :
if len (data ) < 8 {
return errInvalidGlyphData
}
elem .transformXX = int16 (u16 (data [0 :]))
elem .transformXY = int16 (u16 (data [2 :]))
elem .transformYX = int16 (u16 (data [4 :]))
elem .transformYY = int16 (u16 (data [6 :]))
data = data [8 :]
}
}
if flags &flagMoreComponents == 0 {
break
}
}
for i := stackBottom ; i < stackTop ; i ++ {
elem := &b .compoundStack [i ]
base := len (b .segments )
if err := loadGlyf (f , b , elem .glyphIndex , stackTop , recursionDepth ); err != nil {
return err
}
dx , dy := fixed .Int26_6 (elem .dx ), fixed .Int26_6 (elem .dy )
segments := b .segments [base :]
if elem .hasTransform {
txx := elem .transformXX
txy := elem .transformXY
tyx := elem .transformYX
tyy := elem .transformYY
for j := range segments {
transformArgs (&segments [j ].Args , txx , txy , tyx , tyy , dx , dy )
}
} else {
for j := range segments {
translateArgs (&segments [j ].Args , dx , dy )
}
}
}
return nil
}
type glyfIter struct {
data []byte
err error
flagIndex int32
xIndex int32
yIndex int32
endIndex int32
prevEnd int32
finalEnd int32
c, numContours int32
p, nPoints int32
x, y int16
on bool
flag uint8
repeats uint8
closing bool
closed bool
firstOnCurveValid bool
firstOffCurveValid bool
lastOffCurveValid bool
firstOnCurve fixed .Point26_6
firstOffCurve fixed .Point26_6
lastOffCurve fixed .Point26_6
seg Segment
}
func (g *glyfIter ) nextContour () (ok bool ) {
if g .c == g .numContours {
if g .prevEnd != g .finalEnd {
g .err = errInvalidGlyphData
}
return false
}
g .c ++
end := int32 (u16 (g .data [g .endIndex :]))
g .endIndex += 2
if (end <= g .prevEnd ) || (g .finalEnd < end ) {
g .err = errInvalidGlyphData
return false
}
g .nPoints = end - g .prevEnd
g .p = 0
g .prevEnd = end
g .closing = false
g .closed = false
g .firstOnCurveValid = false
g .firstOffCurveValid = false
g .lastOffCurveValid = false
return true
}
func (g *glyfIter ) close () {
switch {
case !g .firstOffCurveValid && !g .lastOffCurveValid :
g .closed = true
g .seg = Segment {
Op : SegmentOpLineTo ,
Args : [3 ]fixed .Point26_6 {g .firstOnCurve },
}
case !g .firstOffCurveValid && g .lastOffCurveValid :
g .closed = true
g .seg = Segment {
Op : SegmentOpQuadTo ,
Args : [3 ]fixed .Point26_6 {g .lastOffCurve , g .firstOnCurve },
}
case g .firstOffCurveValid && !g .lastOffCurveValid :
g .closed = true
g .seg = Segment {
Op : SegmentOpQuadTo ,
Args : [3 ]fixed .Point26_6 {g .firstOffCurve , g .firstOnCurve },
}
case g .firstOffCurveValid && g .lastOffCurveValid :
g .lastOffCurveValid = false
g .seg = Segment {
Op : SegmentOpQuadTo ,
Args : [3 ]fixed .Point26_6 {
g .lastOffCurve ,
midPoint (g .lastOffCurve , g .firstOffCurve ),
},
}
}
}
func (g *glyfIter ) nextSegment () (ok bool ) {
for !g .closed {
if g .closing || !g .nextPoint () {
g .closing = true
g .close ()
return true
}
p := fixed .Point26_6 {
X : fixed .Int26_6 (g .x ),
Y : fixed .Int26_6 (g .y ),
}
if !g .firstOnCurveValid {
if g .on {
g .firstOnCurve = p
g .firstOnCurveValid = true
g .seg = Segment {
Op : SegmentOpMoveTo ,
Args : [3 ]fixed .Point26_6 {p },
}
return true
} else if !g .firstOffCurveValid {
g .firstOffCurve = p
g .firstOffCurveValid = true
continue
} else {
g .firstOnCurve = midPoint (g .firstOffCurve , p )
g .firstOnCurveValid = true
g .lastOffCurve = p
g .lastOffCurveValid = true
g .seg = Segment {
Op : SegmentOpMoveTo ,
Args : [3 ]fixed .Point26_6 {g .firstOnCurve },
}
return true
}
} else if !g .lastOffCurveValid {
if !g .on {
g .lastOffCurve = p
g .lastOffCurveValid = true
continue
} else {
g .seg = Segment {
Op : SegmentOpLineTo ,
Args : [3 ]fixed .Point26_6 {p },
}
return true
}
} else {
if !g .on {
g .seg = Segment {
Op : SegmentOpQuadTo ,
Args : [3 ]fixed .Point26_6 {
g .lastOffCurve ,
midPoint (g .lastOffCurve , p ),
},
}
g .lastOffCurve = p
g .lastOffCurveValid = true
return true
} else {
g .seg = Segment {
Op : SegmentOpQuadTo ,
Args : [3 ]fixed .Point26_6 {g .lastOffCurve , p },
}
g .lastOffCurveValid = false
return true
}
}
}
return false
}
func (g *glyfIter ) nextPoint () (ok bool ) {
if g .p == g .nPoints {
return false
}
g .p ++
if g .repeats > 0 {
g .repeats --
} else {
g .flag = g .data [g .flagIndex ]
g .flagIndex ++
if g .flag &flagRepeat != 0 {
g .repeats = g .data [g .flagIndex ]
g .flagIndex ++
}
}
if g .flag &flagXShortVector != 0 {
if g .flag &flagPositiveXShortVector != 0 {
g .x += int16 (g .data [g .xIndex ])
} else {
g .x -= int16 (g .data [g .xIndex ])
}
g .xIndex += 1
} else if g .flag &flagThisXIsSame == 0 {
g .x += int16 (u16 (g .data [g .xIndex :]))
g .xIndex += 2
}
if g .flag &flagYShortVector != 0 {
if g .flag &flagPositiveYShortVector != 0 {
g .y += int16 (g .data [g .yIndex ])
} else {
g .y -= int16 (g .data [g .yIndex ])
}
g .yIndex += 1
} else if g .flag &flagThisYIsSame == 0 {
g .y += int16 (u16 (g .data [g .yIndex :]))
g .yIndex += 2
}
g .on = g .flag &flagOnCurve != 0
return true
}
The pages are generated with Golds v0.8.2 . (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 .