// SPDX-License-Identifier: Unlicense OR BSD-3-Clausepackage fontimport ()// axis recordstype fvar []tables.VariationAxisRecordfunc newFvar( tables.Fvar) fvar { return .FvarRecords.Axis }type mvar struct { store tables.ItemVarStore values []tables.VarValueRecord}func newMvar( tables.MVAR, int) (mvar, error) {if := .ItemVariationStore.AxisCount(); != {returnmvar{}, fmt.Errorf("mvar: invalid number of axis (%d != %d)", , ) }returnmvar{.ItemVariationStore, .ValueRecords}, nil}// return 0 if `tag` is not foundfunc ( mvar) ( Tag, []VarCoord) float32 {// binary searchfor , := 0, len(.values); < ; { := + (-)/2 := .values[]if < .ValueTag { = } elseif .ValueTag < { = + 1 } else {return .store.GetDelta(.Index, ) } }return0}// ---------------------------------- gvar ----------------------------------type gvar struct { sharedTuples [][]VarCoord// with size tupleCount x axisCount variations [][]tupleVariation// with length glyphCount sharedTupleActiveIdx []int// with length tupleCount}func newGvar( tables.Gvar, tables.Glyf) (gvar, error) {iflen(.GlyphVariationDatas) != len() {returngvar{}, fmt.Errorf("invalid 'gvar' table: mismatch in glyphs count") } := gvar{sharedTuples: make([][]VarCoord, len(.SharedTuples.SharedTuples)),variations: make([][]tupleVariation, len(.GlyphVariationDatas)),sharedTupleActiveIdx: make([]int, len(.SharedTuples.SharedTuples)), }for , := range .SharedTuples.SharedTuples { .sharedTuples[] = .Values }for , := range .GlyphVariationDatas { := make([]tupleVariation, len(.TupleVariationHeaders))for , := range .TupleVariationHeaders { [].TupleVariationHeader = } := pointNumbersCount([]) + phantomCount := parseGlyphVariationSerializedData(.SerializedData, .HasSharedPointNumbers(), , false, )if != nil {return , } .variations[] = }// For shared tuples that only have one axis active, share the index of // that axis as a cache. This will speed up caclulateScalar() a lot // for fonts with lots of axes and many "monovar" tuples.for , := range .sharedTuples { := -1for , := range {if != 0 {if != -1 { // two peaks or more, do not cache = -1break } = } } .sharedTupleActiveIdx[] = }return , nil}type tupleVariation struct {tables.TupleVariationHeader pointNumbers []uint16// nil means allPointsNumbers// length 2*len(pointNumbers) for gvar table or 2*allPointsNumbers if zero deltas []int16}// sharedTuples has length tupleCount x axisCount// sharedTupleActiveIdx has length tupleCountfunc ( tupleVariation) ( []VarCoord, [][]VarCoord, []int) float32 { , := 0, len() := .PeakTuple.Valuesif == nil { // no peak specified -> use shared tuple := .Index()ifint() >= len() { // should not happendreturn0. } = []// use the cache to restrict the rangeif := []; != -1 { = = + 1 } } , := .IntermediateTuples[0].Values, .IntermediateTuples[1].Values := != nilvarfloat32 = 1.for := ; < ; ++ { , := [], []if == 0 || == {continue }if { := [] := []if > || > || ( < 0 && > 0 && != 0) {continue }if < || > {return0. }if < {if != { *= float32(-) / float32(-) } } else {if != { *= float32(-) / float32(-) } } } elseif == 0 || < minC(0, ) || > maxC(0, ) {return0. } else { *= float32() / float32() } }return}// complete `out`, which contains the parsed tuple headers.// pointNumbersCountAll is used when the tuple variation data provides deltas for all glyph pointsfunc parseGlyphVariationSerializedData( []byte, bool, int, bool, []tupleVariation) error {var ( []uint16error )if { , , = parsePointNumbers()if != nil {return } }for , := range {// adjust for the next iterationiflen() < int(.VariationDataSize) {returnerrors.New("invalid glyph variation serialized data (EOF)") } := [.VariationDataSize:]// default to shared points := if .HasPrivatePointNumbers() { , , = parsePointNumbers()if != nil {return } }// the number of point is precised or defaut to all the points := if != nil { = len() } [].pointNumbers = if ! { *= 2// for X and Y } [].deltas, = unpackDeltas(, )if != nil {return } = }returnnil}// the returned slice is nil if all glyph points are usedfunc parsePointNumbers( []byte) ([]uint16, []byte, error) { , , := getPackedPointCount()if != nil {returnnil, nil, }if == 0 {returnnil, , nil }varuint16 := make([]uint16, 0, ) // max value of count is 32767forlen() < int() { // loop through the runsiflen() == 0 {returnnil, nil, errors.New("invalid glyph variation points numbers (EOF)") } := [0] := &0x80 != 0 := int(&0x7F + 1)if { , := tables.ParseUint16s([1:], )if != nil {returnnil, nil, fmt.Errorf("invalid glyph variation points numbers: %s", ) }for , := range { := + = append(, ) = } = [1+2*:] } else {iflen() < 1+ {returnnil, nil, errors.New("invalid glyph variation points numbers (EOF)") }for , := range [1 : 1+] { := uint16() + = append(, ) = } = [1+:] } }return , , nil}// return the remaining data and special case of 00func getPackedPointCount( []byte) (uint16, []byte, error) {constbyte = 1 << 7iflen() < 1 {return0, nil, errors.New("invalid glyph variation points numbers (EOF)") }if [0] == 0 {return0, [1:], nil } elseif [0]& == 0 { := uint16([0])return , [1:], nil } else {iflen() < 2 {return0, nil, errors.New("invalid glyph variation points numbers (EOF)") } := uint16([0]&^)<<8 | uint16([1])return , [2:], nil }}func unpackDeltas( []byte, int) ([]int16, error) {const ( = 0x80 = 0x40 = 0x3F ) := make([]int16, ) := 0// number of point read : out[:nbRead] is valid// The data is read until the expected logic count of deltas is obtained.for < {iflen() == 0 {returnnil, errors.New("invalid packed deltas (EOF)") } := [0] := & + 1if := & != 0; {// no additional value to read, just fill with zeros += int() = [1:] } else {// we want to fill out[nbRead:nbRead+count-1], that is we must have // nbRead+count-1 < pointNumbersCount, ie // nbRead+count <= pointNumbersCountif := + int(); > {returnnil, fmt.Errorf("invalid packed deltas (expected %d point numbers, got %d)", , ) } := & != 0if {iflen() < 1+2*int() {returnnil, errors.New("invalid packed deltas (EOF)") }for := byte(0); < ; ++ { // count < 64 -> no overflow [] = int16(binary.BigEndian.Uint16([1+2*:])) ++ } = [1+2*:] } else {iflen() < 1+int() {returnnil, errors.New("invalid packed deltas (EOF)") }for := byte(0); < ; ++ { // count < 64 -> no overflow [] = int16(int8([1+])) ++ } = [1+:] } } }return , nil}// update `points` in placefunc ( gvar) ( gID, []VarCoord, []contourPoint) {// adapted from harfbuzz/src/hb-ot-var-gvar-table.hhconst = falseifint() >= len(.variations) { // should not happendreturn }// save original points for inferred delta calculation := append([]contourPoint(nil), ...)// flag is used to indicate referenced point := make([]contourPoint, len()) := .variations[]for , := range { := .calculateScalar(, .sharedTuples, .sharedTupleActiveIdx)if == 0 {continue } := len(.deltas) := .pointNumbers == nil , := .deltas[:/2], .deltas[/2:]// reset the current deltasfor := range { [] = contourPoint{} }for := range { := uint16()if ! { = .pointNumbers[] } [].isExplicit = true [].X += float32([]) * [].Y += float32([]) * }/* infer deltas for unreferenced points */if ! && ! { , := 0, 0for {for < len() && ![].isEndPoint { ++ }if == len() {break }// check the number of unreferenced points in a contour. // If no unref points or no ref points, nothing to do. := 0for , := range [ : +1] {if !.isExplicit { ++ } } := if == 0 || > - {goto }for {/* Locate the next gap of unreferenced points between two referenced points prev and next. * Note that a gap may wrap around at left (startPoint) and/or at right (endPoint). */var , , intfor { = = nextIndex(, , )if [].isExplicit && ![].isExplicit {break } } , = , for { = = nextIndex(, , )if ![].isExplicit && [].isExplicit {break } } = /* Infer deltas for all unref points in the gap between prev and next */ = for { = nextIndex(, , )if == {break } [].X = inferDelta([].X, [].X, [].X, [].X, [].X) [].Y = inferDelta([].Y, [].Y, [].Y, [].Y, [].Y) --if == 0 {goto } } } : = + 1 = } }// apply specified / inferred deltas to pointsfor , := range { [].translate(.X, .Y) } }}func nextIndex(, , int) int {if >= {return }return + 1}func inferDelta(, , , , float32) float32 {if == {if == {return }return0 } elseif <= minF(, ) {if < {return }return } elseif >= maxF(, ) {if > {return }return }// linear interpolation := ( - ) / ( - )return + *(-)}func sanitizeGDEF( tables.GDEF, int) error {// check axis countif := .ItemVarStore.AxisCount(); != -1 && != {returnfmt.Errorf("GDEF: invalid number of axis (%d != %d)", , ) }// check LigCarets lengthif .LigCaretList.Coverage != nil { := .LigCaretList.Coverage.Len() := len(.LigCaretList.LigGlyphs)if != {returnfmt.Errorf("GDEF: invalid number of lig gyphs (%d != %d)", , ) } }returnnil}// ------------------------------------- external API -------------------------------------// Variation defines a value for a wanted variation axis.typeVariationstruct { Tag Tag// Variation-axis identifier tag Value float32// In design units}// SetVariations applies a list of font-variation settings to a font,// defaulting to the values given in the `fvar` table.// Note that passing an empty slice will instead remove the coordinates.func ( *Face) ( []Variation) {iflen() == 0 { .SetCoords(nil)return } := .Font.fvariflen() == 0 { // the font is not variable... .SetCoords(nil)return } := .getDesignCoordsDefault() .SetCoords(.NormalizeVariations())}// getDesignCoordsDefault returns the design coordinates corresponding to the given pairs of axis/value.// The default value of the axis is used when not specified in the variations.func ( fvar) ( []Variation) []float32 { := make([]float32, len())// start with default valuesfor , := range { [] = .Default } .getDesignCoords(, )return}// getDesignCoords updates the design coordinates, with the given pairs of axis/value.// It will panic if `designCoords` has not the length expected by the table, that is the number of axis.func ( fvar) ( []Variation, []float32) {for , := range {// allow for multiple axis with the same tagfor , := range {if .Tag == .Tag { [] = .Value } } }}// normalize based on the [min,def,max] values for the axis to be [-1,0,1].func ( fvar) ( []float32) []VarCoord { := make([]VarCoord, len())for , := range { := []// out of range: clampingif > .Maximum { = .Maximum } elseif < .Minimum { = .Minimum }if < .Default { = -( - .Default) / (.Minimum - .Default) } elseif > .Default { = ( - .Default) / (.Maximum - .Default) } else { = 0 } [] = VarCoord(math.Round(float64( * 16384))) // 1 << 14 }return}// NormalizeVariations normalize the given design-space coordinates. The minimum and maximum// values for the axis are mapped to the interval [-1,1], with the default// axis value mapped to 0.//// Any additional scaling defined in the face's `avar` table is also// applied, as described at https://docs.microsoft.com/en-us/typography/opentype/spec/avar.//// This method panics if `coords` has not the correct length, that is the number of axis inf 'fvar'.func ( *Font) ( []float32) []VarCoord {// Axis normalization is a two-stage process. First we normalize // based on the [min,def,max] values for the axis to be [-1,0,1]. // Then, if there's an `avar' table, we renormalize this range. := .fvar.normalizeCoordinates()// now applying 'avar'for , := range .avar.AxisSegmentMaps { [] = .Map([]) }return}
The pages are generated with Goldsv0.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.