chore(v2): vendor dependencies for offline/China builds
go mod vendor pins onnxruntime_go v1.12.1, Gio and the rest into v2/vendor so go run/build work without hitting proxy.golang.org (blocked/slow in China). Verified: CGO_ENABLED=1 go build -mod=vendor ./internal/spike and GOOS=windows go build -mod=vendor ./internal/ui both pass. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+55
@@ -0,0 +1,55 @@
|
||||
This project is provided under the terms of the UNLICENSE or
|
||||
the BSD license denoted by the following SPDX identifier:
|
||||
|
||||
SPDX-License-Identifier: Unlicense OR BSD-3-Clause
|
||||
|
||||
You may use the project under the terms of either license.
|
||||
|
||||
Both licenses are reproduced below.
|
||||
|
||||
----
|
||||
The BSD 3 Clause License
|
||||
|
||||
Copyright 2021 The go-text authors
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
---
|
||||
|
||||
|
||||
|
||||
---
|
||||
The UNLICENSE
|
||||
|
||||
This is free and unencumbered software released into the public domain.
|
||||
|
||||
Anyone is free to copy, modify, publish, use, compile, sell, or
|
||||
distribute this software, either in source code form or as a compiled
|
||||
binary, for any purpose, commercial or non-commercial, and by any
|
||||
means.
|
||||
|
||||
In jurisdictions that recognize copyright laws, the author or authors
|
||||
of this software dedicate any and all copyright interest in the
|
||||
software to the public domain. We make this dedication for the benefit
|
||||
of the public at large and to the detriment of our heirs and
|
||||
successors. We intend this dedication to be an overt act of
|
||||
relinquishment in perpetuity of all present and future rights to this
|
||||
software under copyright law.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
||||
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
||||
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
For more information, please refer to <https://unlicense.org/>
|
||||
---
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
# di
|
||||
|
||||
di is a library that converts bi-directional text into uni-directional text
|
||||
+130
@@ -0,0 +1,130 @@
|
||||
package di
|
||||
|
||||
import (
|
||||
"github.com/go-text/typesetting/harfbuzz"
|
||||
)
|
||||
|
||||
// Direction indicates the layout direction of a piece of text.
|
||||
type Direction uint8
|
||||
|
||||
const (
|
||||
// DirectionLTR is for Left-to-Right text.
|
||||
DirectionLTR Direction = iota
|
||||
// DirectionRTL is for Right-to-Left text.
|
||||
DirectionRTL
|
||||
// DirectionTTB is for Top-to-Bottom text.
|
||||
DirectionTTB
|
||||
// DirectionBTT is for Bottom-to-Top text.
|
||||
DirectionBTT
|
||||
)
|
||||
|
||||
const (
|
||||
progression Direction = 1 << iota
|
||||
// axisVertical is the bit for the axis, 0 for horizontal, 1 for vertical
|
||||
axisVertical
|
||||
|
||||
// If this flag is set, the orientation is chosen
|
||||
// using the [verticalSideways] flag.
|
||||
// Otherwise, the segmenter will resolve the orientation based
|
||||
// on unicode properties
|
||||
verticalOrientationSet
|
||||
// verticalSideways is set for 'sideways', unset for 'upright'
|
||||
// It implies BVerticalOrientationSet is set
|
||||
verticalSideways
|
||||
)
|
||||
|
||||
// IsVertical returns whether d is laid out on a vertical
|
||||
// axis. If the return value is false, d is on the horizontal
|
||||
// axis.
|
||||
func (d Direction) IsVertical() bool { return d&axisVertical != 0 }
|
||||
|
||||
// Axis returns the layout axis for d.
|
||||
func (d Direction) Axis() Axis {
|
||||
if d.IsVertical() {
|
||||
return Vertical
|
||||
}
|
||||
return Horizontal
|
||||
}
|
||||
|
||||
// SwitchAxis switches from horizontal to vertical (and vice versa), preserving
|
||||
// the progression.
|
||||
func (d Direction) SwitchAxis() Direction { return d ^ axisVertical }
|
||||
|
||||
// Progression returns the text layout progression for d.
|
||||
func (d Direction) Progression() Progression {
|
||||
if d&progression == 0 {
|
||||
return FromTopLeft
|
||||
}
|
||||
return TowardTopLeft
|
||||
}
|
||||
|
||||
// SetProgression sets the progression, preserving the others bits.
|
||||
func (d *Direction) SetProgression(p Progression) {
|
||||
if p == FromTopLeft {
|
||||
*d &= ^progression
|
||||
} else {
|
||||
*d |= progression
|
||||
}
|
||||
}
|
||||
|
||||
// Axis indicates the axis of layout for a piece of text.
|
||||
type Axis bool
|
||||
|
||||
const (
|
||||
Horizontal Axis = false
|
||||
Vertical Axis = true
|
||||
)
|
||||
|
||||
// Progression indicates how text is read within its Axis relative
|
||||
// to the top left corner.
|
||||
type Progression bool
|
||||
|
||||
const (
|
||||
// FromTopLeft indicates text in which a reader starts reading
|
||||
// at the top left corner of the text and moves away from it.
|
||||
// DirectionLTR and DirectionTTB are examples of FromTopLeft
|
||||
// Progression.
|
||||
FromTopLeft Progression = false
|
||||
// TowardTopLeft indicates text in which a reader starts reading
|
||||
// at the opposite end of the text's Axis from the top left corner
|
||||
// and moves towards it. DirectionRTL and DirectionBTT are examples
|
||||
// of TowardTopLeft progression.
|
||||
TowardTopLeft Progression = true
|
||||
)
|
||||
|
||||
// HasVerticalOrientation returns true if the direction has set up
|
||||
// an orientation for vertical text (typically using [SetSideways] or [SetUpright])
|
||||
func (d Direction) HasVerticalOrientation() bool { return d&verticalOrientationSet != 0 }
|
||||
|
||||
// IsSideways returns true if the direction is vertical with a 'sideways'
|
||||
// orientation.
|
||||
//
|
||||
// When shaping vertical text, 'sideways' means that the glyphs are rotated
|
||||
// by 90°, clock-wise. This flag should be used by renderers to properly
|
||||
// rotate the glyphs when drawing.
|
||||
func (d Direction) IsSideways() bool { return d.IsVertical() && d&verticalSideways != 0 }
|
||||
|
||||
// SetSideways makes d vertical with 'sideways' or 'upright' orientation, preserving only the
|
||||
// progression.
|
||||
func (d *Direction) SetSideways(sideways bool) {
|
||||
*d |= axisVertical | verticalOrientationSet
|
||||
if sideways {
|
||||
*d |= verticalSideways
|
||||
} else {
|
||||
*d &= ^verticalSideways
|
||||
}
|
||||
}
|
||||
|
||||
// Harfbuzz returns the equivalent direction used by harfbuzz.
|
||||
func (d Direction) Harfbuzz() harfbuzz.Direction {
|
||||
switch d & (progression | axisVertical) {
|
||||
case DirectionRTL:
|
||||
return harfbuzz.RightToLeft
|
||||
case DirectionBTT:
|
||||
return harfbuzz.BottomToTop
|
||||
case DirectionTTB:
|
||||
return harfbuzz.TopToBottom
|
||||
default:
|
||||
return harfbuzz.LeftToRight
|
||||
}
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
# font
|
||||
|
||||
font is a library that handles loading and utilizing Opentype fonts.
|
||||
|
||||
`font/opentype` implements the low level parsing of a font file and its tables,
|
||||
and `font` provides an higher level API usable by shapers and renderers.
|
||||
+322
@@ -0,0 +1,322 @@
|
||||
// SPDX-License-Identifier: Unlicense OR BSD-3-Clause
|
||||
|
||||
package font
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
|
||||
"github.com/go-text/typesetting/font/opentype/tables"
|
||||
)
|
||||
|
||||
// Kernx represents a 'kern' or 'kerx' kerning table.
|
||||
// It supports both Microsoft and Apple formats.
|
||||
type Kernx []KernSubtable
|
||||
|
||||
func newKernxFromKerx(kerx tables.Kerx) Kernx {
|
||||
if len(kerx.Tables) == 0 {
|
||||
return nil
|
||||
}
|
||||
out := make(Kernx, len(kerx.Tables))
|
||||
for i, ta := range kerx.Tables {
|
||||
out[i] = newKerxSubtable(ta)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func newKernxFromKern(kern tables.Kern) Kernx {
|
||||
if len(kern.Tables) == 0 {
|
||||
return nil
|
||||
}
|
||||
out := make(Kernx, len(kern.Tables))
|
||||
for i, ta := range kern.Tables {
|
||||
out[i] = newKernSubtable(ta)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// KernSubtable represents a 'kern' or 'kerx' subtable.
|
||||
type KernSubtable struct {
|
||||
Data interface{ isKernSubtable() }
|
||||
|
||||
// high bit of the Coverage field, following 'kerx' conventions
|
||||
coverage byte
|
||||
|
||||
// IsExtended is [true] for AAT `kerx` subtables, false for 'kern' subtables
|
||||
IsExtended bool
|
||||
|
||||
// 0 for scalar values
|
||||
TupleCount int
|
||||
}
|
||||
|
||||
func newKernSubtable(table tables.KernSubtable) (out KernSubtable) {
|
||||
out.IsExtended = false
|
||||
switch table := table.(type) {
|
||||
case tables.OTKernSubtableHeader:
|
||||
// synthesize a coverage flag following kerx conventions
|
||||
const (
|
||||
Horizontal = 0x01
|
||||
CrossStream = 0x04
|
||||
)
|
||||
if table.Coverage&Horizontal == 0 { // vertical
|
||||
out.coverage |= kerxVertical
|
||||
}
|
||||
if table.Coverage&CrossStream != 0 {
|
||||
out.coverage |= kerxCrossStream
|
||||
}
|
||||
case tables.AATKernSubtableHeader:
|
||||
out.coverage = table.Coverage
|
||||
out.TupleCount = int(table.TupleCount)
|
||||
}
|
||||
switch data := table.Data().(type) {
|
||||
case tables.KernData0:
|
||||
out.Data = newKern0(data)
|
||||
case tables.KernData1:
|
||||
out.Data = newKern1(data)
|
||||
case tables.KernData2:
|
||||
out.Data = newKern2(data)
|
||||
case tables.KernData3:
|
||||
out.Data = Kern3(data)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func newKerxSubtable(table tables.KerxSubtable) (out KernSubtable) {
|
||||
out.IsExtended = true
|
||||
out.TupleCount = int(table.TupleCount)
|
||||
out.coverage = byte(table.Coverage >> 8) // high bit only
|
||||
|
||||
switch data := table.Data.(type) {
|
||||
case tables.KerxData0:
|
||||
out.Data = newKern0x(data)
|
||||
case tables.KerxData1:
|
||||
out.Data = newKern1x(data)
|
||||
case tables.KerxData2:
|
||||
out.Data = Kern2(data)
|
||||
case tables.KerxData4:
|
||||
out.Data = newKern4(data)
|
||||
case tables.KerxData6:
|
||||
out.Data = Kern6(data)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func (Kern0) isKernSubtable() {}
|
||||
func (Kern1) isKernSubtable() {}
|
||||
func (Kern2) isKernSubtable() {}
|
||||
func (Kern3) isKernSubtable() {}
|
||||
func (Kern4) isKernSubtable() {}
|
||||
func (Kern6) isKernSubtable() {}
|
||||
|
||||
var (
|
||||
_ SimpleKerns = Kern0(nil)
|
||||
_ SimpleKerns = (*Kern2)(nil)
|
||||
_ SimpleKerns = (*Kern3)(nil)
|
||||
_ SimpleKerns = (*Kern6)(nil)
|
||||
)
|
||||
|
||||
// SimpleKerns store a compact form of the kerning values,
|
||||
// which is restricted to (one direction) kerning pairs.
|
||||
// It is only implemented by [Kern0], [Kern2], [Kern3] and [Kern6],
|
||||
// where [Kern1] and [Kern4] requires a state machine to be interpreted.
|
||||
type SimpleKerns interface {
|
||||
// KernPair return the kern value for the given pair, or zero.
|
||||
// The value is expressed in glyph units and
|
||||
// is negative when glyphs should be closer.
|
||||
KernPair(left, right GID) int16
|
||||
}
|
||||
|
||||
// kernx coverage flags
|
||||
const (
|
||||
kerxBackwards = 1 << (12 - 8)
|
||||
kerxVariation = 1 << (13 - 8)
|
||||
kerxCrossStream = 1 << (14 - 8)
|
||||
kerxVertical = 1 << (15 - 8)
|
||||
)
|
||||
|
||||
// IsHorizontal returns true if the subtable has horizontal kerning values.
|
||||
func (k KernSubtable) IsHorizontal() bool { return k.coverage&kerxVertical == 0 }
|
||||
|
||||
// IsBackwards returns true if state-table based should process the glyphs backwards.
|
||||
func (k KernSubtable) IsBackwards() bool { return k.coverage&kerxBackwards != 0 }
|
||||
|
||||
// IsCrossStream returns true if the subtable has cross-stream kerning values.
|
||||
func (k KernSubtable) IsCrossStream() bool { return k.coverage&kerxCrossStream != 0 }
|
||||
|
||||
// IsVariation returns true if the subtable has variation kerning values.
|
||||
func (k KernSubtable) IsVariation() bool { return k.coverage&kerxVariation != 0 }
|
||||
|
||||
type Kern0 []tables.Kernx0Record
|
||||
|
||||
func newKern0(k tables.KernData0) Kern0 { return k.Pairs }
|
||||
func newKern0x(k tables.KerxData0) Kern0 { return k.Pairs }
|
||||
|
||||
func kernPair(records []tables.Kernx0Record, left, right GID) int16 {
|
||||
key := uint32(left)<<16 | uint32(right)
|
||||
low, high := 0, len(records)
|
||||
for low < high {
|
||||
mid := low + (high-low)/2 // avoid overflow when computing mid
|
||||
p := recordKey(records[mid])
|
||||
if key < p {
|
||||
high = mid
|
||||
} else if key > p {
|
||||
low = mid + 1
|
||||
} else {
|
||||
return records[mid].Value
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func recordKey(kp tables.Kernx0Record) uint32 { return uint32(kp.Left)<<16 | uint32(kp.Right) }
|
||||
|
||||
func (kd Kern0) KernPair(left, right GID) int16 { return kernPair(kd, left, right) }
|
||||
|
||||
type Kern1 struct {
|
||||
Values []int16 // After successful parsing, may be safely indexed by AATStateEntry.AsKernxIndex() from `Machine`
|
||||
Machine AATStateTable
|
||||
}
|
||||
|
||||
// convert from non extended to extended
|
||||
func newKern1(k tables.KernData1) Kern1 {
|
||||
class := tables.AATLoopkup8{
|
||||
AATLoopkup8Data: tables.AATLoopkup8Data{
|
||||
FirstGlyph: k.ClassTable.StartGlyph,
|
||||
Values: make([]uint16, len(k.ClassTable.Values)),
|
||||
},
|
||||
}
|
||||
for i, b := range k.ClassTable.Values {
|
||||
class.Values[i] = uint16(b)
|
||||
}
|
||||
states := make([][]uint16, len(k.States))
|
||||
for i, row := range k.States {
|
||||
v := make([]uint16, len(row))
|
||||
for j, b := range row {
|
||||
v[j] = uint16(b)
|
||||
}
|
||||
states[i] = v
|
||||
}
|
||||
return Kern1{
|
||||
Values: k.Values,
|
||||
Machine: AATStateTable{
|
||||
nClass: uint32(k.StateSize),
|
||||
Class: class,
|
||||
states: states,
|
||||
entries: k.Entries,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func newKern1x(k tables.KerxData1) Kern1 {
|
||||
return Kern1{Values: k.Values, Machine: newAATStableTable(k.AATStateTableExt)}
|
||||
}
|
||||
|
||||
type Kern2 tables.KerxData2
|
||||
|
||||
// convert from non extended to extended
|
||||
func newKern2(k tables.KernData2) Kern2 {
|
||||
return Kern2{
|
||||
Left: tables.AATLoopkup8{AATLoopkup8Data: k.Left},
|
||||
Right: tables.AATLoopkup8{AATLoopkup8Data: k.Right},
|
||||
KerningStart: tables.Offset32(k.KerningStart),
|
||||
KerningData: k.KerningData,
|
||||
}
|
||||
}
|
||||
|
||||
func (kd Kern2) KernPair(left, right GID) int16 {
|
||||
l, _ := kd.Left.Class(tables.GlyphID(left))
|
||||
r, _ := kd.Right.Class(tables.GlyphID(right))
|
||||
index := int(l) + int(r)
|
||||
if len(kd.KerningData) < index+2 || index < int(kd.KerningStart) {
|
||||
return 0
|
||||
}
|
||||
kernVal := binary.BigEndian.Uint16(kd.KerningData[index:])
|
||||
return int16(kernVal)
|
||||
}
|
||||
|
||||
type Kern3 tables.KernData3
|
||||
|
||||
func (kd Kern3) KernPair(left, right GID) int16 {
|
||||
if int(left) >= len(kd.LeftClass) || int(right) >= len(kd.RightClass) { // should not happend
|
||||
return 0
|
||||
}
|
||||
|
||||
lc, rc := int(kd.LeftClass[left]), int(kd.RightClass[right])
|
||||
index := kd.KernIndex[lc*int(kd.RightClassCount)+rc] // sanitized during parsing
|
||||
return kd.Kernings[index] // sanitized during parsing
|
||||
}
|
||||
|
||||
type Kern4 struct {
|
||||
Anchors tables.KerxAnchors
|
||||
Machine AATStateTable
|
||||
flags uint32
|
||||
}
|
||||
|
||||
func newKern4(k tables.KerxData4) Kern4 {
|
||||
return Kern4{
|
||||
Machine: newAATStableTable(k.AATStateTableExt),
|
||||
Anchors: k.Anchors,
|
||||
flags: k.Flags,
|
||||
}
|
||||
}
|
||||
|
||||
// ActionType returns 0, 1 or 2 .
|
||||
func (k Kern4) ActionType() uint8 {
|
||||
const ActionType = 0xC0000000 // A two-bit field containing the action type.
|
||||
return uint8(k.flags & ActionType >> 30)
|
||||
}
|
||||
|
||||
type Kern6 tables.KerxData6
|
||||
|
||||
func (kd Kern6) KernPair(left, right GID) int16 {
|
||||
l := kd.Row.ClassUint32(tables.GlyphID(left))
|
||||
r := kd.Column.ClassUint32(tables.GlyphID(right))
|
||||
index := int(l) + int(r)
|
||||
if len(kd.Kernings) <= index {
|
||||
return 0
|
||||
}
|
||||
return kd.Kernings[index]
|
||||
}
|
||||
|
||||
// --------------------------------------- state machine ---------------------------------------
|
||||
|
||||
// AATStateTable supports both regular and extended AAT state machines
|
||||
type AATStateTable struct {
|
||||
nClass uint32
|
||||
Class tables.AATLookup
|
||||
states [][]uint16 // each sub array has length stateSize
|
||||
entries []tables.AATStateEntry // length is the maximum state + 1
|
||||
}
|
||||
|
||||
func newAATStableTable(k tables.AATStateTableExt) AATStateTable {
|
||||
return AATStateTable{
|
||||
nClass: k.StateSize,
|
||||
Class: k.Class,
|
||||
states: k.States,
|
||||
entries: k.Entries,
|
||||
}
|
||||
}
|
||||
|
||||
// GetClass return the class for the given glyph, with the correct default value.
|
||||
func (st *AATStateTable) GetClass(glyph GID) uint16 {
|
||||
if glyph == 0xFFFF { // deleted glyph
|
||||
return 2 // class deleted
|
||||
}
|
||||
c, ok := st.Class.Class(tables.GlyphID(glyph))
|
||||
if !ok {
|
||||
return 1 // class out of bounds
|
||||
}
|
||||
return c // class for a state table can't be uint32
|
||||
}
|
||||
|
||||
// GetEntry return the entry for the given state and class,
|
||||
// and handle invalid values (by returning an empty entry).
|
||||
func (st *AATStateTable) GetEntry(state, class uint16) tables.AATStateEntry {
|
||||
if uint32(class) >= st.nClass {
|
||||
class = 1 // class out of bounds
|
||||
}
|
||||
if int(state) >= len(st.states) {
|
||||
return tables.AATStateEntry{}
|
||||
}
|
||||
entry := st.states[state][class] // access check when parsing
|
||||
return st.entries[entry] // access check when parsing
|
||||
}
|
||||
+108
@@ -0,0 +1,108 @@
|
||||
// SPDX-License-Identifier: Unlicense OR BSD-3-Clause
|
||||
|
||||
package font
|
||||
|
||||
import "github.com/go-text/typesetting/font/opentype/tables"
|
||||
|
||||
type Morx []MorxChain
|
||||
|
||||
func newMorx(table tables.Morx) Morx {
|
||||
if len(table.Chains) == 0 {
|
||||
return nil
|
||||
}
|
||||
out := make(Morx, len(table.Chains))
|
||||
for i, c := range table.Chains {
|
||||
out[i] = newMorxChain(c)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
type MorxChain struct {
|
||||
Features []tables.AATFeature
|
||||
Subtables []MorxSubtable
|
||||
DefaultFlags uint32
|
||||
}
|
||||
|
||||
func newMorxChain(table tables.MorxChain) (out MorxChain) {
|
||||
out.DefaultFlags = table.Flags
|
||||
out.Features = table.Features
|
||||
out.Subtables = make([]MorxSubtable, len(table.Subtables))
|
||||
for i, s := range table.Subtables {
|
||||
out.Subtables[i] = newMorxSubtable(s)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
type MorxSubtable struct {
|
||||
Data interface{ isMorxSubtable() }
|
||||
Coverage uint8 // high byte of the coverage flag
|
||||
Flags uint32 // Mask identifying which subtable this is.
|
||||
}
|
||||
|
||||
func (MorxRearrangementSubtable) isMorxSubtable() {}
|
||||
func (MorxContextualSubtable) isMorxSubtable() {}
|
||||
func (MorxLigatureSubtable) isMorxSubtable() {}
|
||||
func (MorxNonContextualSubtable) isMorxSubtable() {}
|
||||
func (MorxInsertionSubtable) isMorxSubtable() {}
|
||||
|
||||
func newMorxSubtable(table tables.MorxChainSubtable) (out MorxSubtable) {
|
||||
out.Coverage = table.Coverage
|
||||
out.Flags = table.SubFeatureFlags
|
||||
switch data := table.Data.(type) {
|
||||
case tables.MorxSubtableRearrangement:
|
||||
out.Data = MorxRearrangementSubtable(newAATStableTable(data.AATStateTableExt))
|
||||
case tables.MorxSubtableContextual:
|
||||
out.Data = MorxContextualSubtable{
|
||||
Machine: newAATStableTable(data.AATStateTableExt),
|
||||
Substitutions: data.Substitutions.Substitutions,
|
||||
}
|
||||
case tables.MorxSubtableLigature:
|
||||
s := MorxLigatureSubtable{
|
||||
Machine: newAATStableTable(data.AATStateTableExt),
|
||||
LigatureAction: data.LigActions,
|
||||
Components: data.Components,
|
||||
Ligatures: make([]GID, len(data.Ligatures)),
|
||||
}
|
||||
for i, g := range data.Ligatures {
|
||||
s.Ligatures[i] = GID(g)
|
||||
}
|
||||
out.Data = s
|
||||
case tables.MorxSubtableNonContextual:
|
||||
out.Data = MorxNonContextualSubtable{Class: data.Class}
|
||||
case tables.MorxSubtableInsertion:
|
||||
s := MorxInsertionSubtable{
|
||||
Machine: newAATStableTable(data.AATStateTableExt),
|
||||
Insertions: make([]GID, len(data.Insertions)),
|
||||
}
|
||||
for i, g := range data.Insertions {
|
||||
s.Insertions[i] = GID(g)
|
||||
}
|
||||
out.Data = s
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
type MorxRearrangementSubtable AATStateTable
|
||||
|
||||
type MorxContextualSubtable struct {
|
||||
Substitutions []tables.AATLookup
|
||||
Machine AATStateTable
|
||||
}
|
||||
|
||||
type MorxLigatureSubtable struct {
|
||||
LigatureAction []uint32
|
||||
Components []uint16
|
||||
Ligatures []GID
|
||||
Machine AATStateTable
|
||||
}
|
||||
|
||||
type MorxNonContextualSubtable struct {
|
||||
Class tables.AATLookup // the lookup value is interpreted as a GlyphIndex
|
||||
}
|
||||
|
||||
type MorxInsertionSubtable struct {
|
||||
// After successul parsing, this array may be safely
|
||||
// indexed by the indexes and counts from Machine entries.
|
||||
Insertions []GID
|
||||
Machine AATStateTable
|
||||
}
|
||||
+476
@@ -0,0 +1,476 @@
|
||||
// SPDX-License-Identifier: Unlicense OR BSD-3-Clause
|
||||
|
||||
package font
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
|
||||
ot "github.com/go-text/typesetting/font/opentype"
|
||||
"github.com/go-text/typesetting/font/opentype/tables"
|
||||
)
|
||||
|
||||
// sbix
|
||||
|
||||
type sbix []tables.Strike
|
||||
|
||||
func newSbix(table tables.Sbix) sbix { return table.Strikes }
|
||||
|
||||
// chooseStrike selects the best match for the given resolution.
|
||||
// It returns nil only if the table is empty
|
||||
func (sb sbix) chooseStrike(xPpem, yPpem uint16) *tables.Strike {
|
||||
if len(sb) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
request := maxu16(xPpem, yPpem)
|
||||
if request == 0 {
|
||||
request = math.MaxUint16 // choose largest strike
|
||||
}
|
||||
|
||||
var (
|
||||
bestIndex = 0
|
||||
bestPpem = sb[0].Ppem
|
||||
)
|
||||
for i, s := range sb {
|
||||
ppem := s.Ppem
|
||||
if request <= ppem && ppem < bestPpem || request > bestPpem && ppem > bestPpem {
|
||||
bestIndex = i
|
||||
bestPpem = ppem
|
||||
}
|
||||
}
|
||||
return &sb[bestIndex]
|
||||
}
|
||||
|
||||
func (sb sbix) availableSizes(horizontal *tables.Hhea, avgWidth, upem uint16) []BitmapSize {
|
||||
out := make([]BitmapSize, 0, len(sb))
|
||||
for _, size := range sb {
|
||||
v := strikeSizeMetrics(size, horizontal, avgWidth, upem)
|
||||
// only use strikes with valid PPEM values
|
||||
if v.XPpem == 0 || v.YPpem == 0 {
|
||||
continue
|
||||
}
|
||||
out = append(out, v)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func strikeSizeMetrics(b tables.Strike, hori *tables.Hhea, avgWidth, upem uint16) (out BitmapSize) {
|
||||
out.XPpem, out.YPpem = b.Ppem, b.Ppem
|
||||
out.Height = mulDiv(uint16(hori.Ascender-hori.Descender+hori.LineGap), b.Ppem, upem)
|
||||
|
||||
inferBitmapWidth(&out, avgWidth, upem)
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
// ---------------------------- bitmap ----------------------------
|
||||
|
||||
func loadBitmap(ld *ot.Loader, tagLoc, tagData ot.Tag) (bitmap, error) {
|
||||
raw, err := ld.RawTable(tagLoc)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
loc, _, err := tables.ParseCBLC(raw)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
imageTable, err := ld.RawTable(tagData)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return newBitmap(loc, imageTable)
|
||||
}
|
||||
|
||||
// CBLC/CBDT or EBLC/EBDT or BLOC/BDAT
|
||||
type bitmap []bitmapStrike
|
||||
|
||||
func newBitmap(table tables.EBLC, imageTable []byte) (bitmap, error) {
|
||||
out := make(bitmap, len(table.BitmapSizes))
|
||||
for i, strike := range table.BitmapSizes {
|
||||
subtables := table.IndexSubTables[i]
|
||||
out[i] = bitmapStrike{
|
||||
subTables: make([]bitmapSubtable, len(subtables)),
|
||||
hori: strike.Hori,
|
||||
vert: strike.Vert,
|
||||
ppemX: uint16(strike.PpemX),
|
||||
ppemY: uint16(strike.PpemY),
|
||||
}
|
||||
for j, subtable := range subtables {
|
||||
var err error
|
||||
out[i].subTables[j], err = newBitmapSubtable(subtable, imageTable)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (t bitmap) availableSizes(avgWidth, upem uint16) []BitmapSize {
|
||||
out := make([]BitmapSize, 0, len(t))
|
||||
for _, size := range t {
|
||||
v := size.sizeMetrics(avgWidth, upem)
|
||||
// only use strikes with valid PPEM values
|
||||
if v.XPpem == 0 || v.YPpem == 0 {
|
||||
continue
|
||||
}
|
||||
out = append(out, v)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
type bitmapStrike struct {
|
||||
subTables []bitmapSubtable
|
||||
hori, vert tables.SbitLineMetrics
|
||||
ppemX, ppemY uint16
|
||||
}
|
||||
|
||||
// chooseStrike selects the best match for the given resolution.
|
||||
// It returns nil only if the table is empty
|
||||
func (bt bitmap) chooseStrike(xPpem, yPpem uint16) *bitmapStrike {
|
||||
if len(bt) == 0 {
|
||||
return nil
|
||||
}
|
||||
request := maxu16(xPpem, yPpem)
|
||||
if request == 0 {
|
||||
request = math.MaxUint16 // choose largest strike
|
||||
}
|
||||
var (
|
||||
bestIndex = 0
|
||||
bestPpem = maxu16(bt[0].ppemX, bt[0].ppemY)
|
||||
)
|
||||
for i, s := range bt {
|
||||
ppem := maxu16(s.ppemX, s.ppemY)
|
||||
if request <= ppem && ppem < bestPpem || request > bestPpem && ppem > bestPpem {
|
||||
bestIndex = i
|
||||
bestPpem = ppem
|
||||
}
|
||||
}
|
||||
return &bt[bestIndex]
|
||||
}
|
||||
|
||||
func (b *bitmapStrike) sizeMetrics(avgWidth, upem uint16) (out BitmapSize) {
|
||||
out.XPpem, out.YPpem = b.ppemX, b.ppemY
|
||||
ascender := int16(b.hori.Ascender)
|
||||
descender := int16(b.hori.Descender)
|
||||
|
||||
maxBeforeBl := b.hori.MaxBeforeBL
|
||||
minAfterBl := b.hori.MinAfterBL
|
||||
|
||||
/* Due to fuzzy wording in the EBLC documentation, we find both */
|
||||
/* positive and negative values for `descender'. Additionally, */
|
||||
/* many fonts have both `ascender' and `descender' set to zero */
|
||||
/* (which is definitely wrong). MS Windows simply ignores all */
|
||||
/* those values... For these reasons we apply some heuristics */
|
||||
/* to get a reasonable, non-zero value for the height. */
|
||||
|
||||
if descender > 0 {
|
||||
if minAfterBl < 0 {
|
||||
descender = -descender
|
||||
}
|
||||
} else if descender == 0 {
|
||||
if ascender == 0 {
|
||||
/* sanitize buggy ascender and descender values */
|
||||
if maxBeforeBl != 0 || minAfterBl != 0 {
|
||||
ascender = int16(maxBeforeBl)
|
||||
descender = int16(minAfterBl)
|
||||
} else {
|
||||
ascender = int16(out.YPpem)
|
||||
descender = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if h := ascender - descender; h > 0 {
|
||||
out.Height = uint16(h)
|
||||
} else {
|
||||
out.Height = out.YPpem
|
||||
}
|
||||
|
||||
inferBitmapWidth(&out, avgWidth, upem)
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
func inferBitmapWidth(size *BitmapSize, avgWidth, upem uint16) {
|
||||
size.Width = uint16((uint32(avgWidth)*uint32(size.XPpem) + uint32(upem/2)) / uint32(upem))
|
||||
}
|
||||
|
||||
// return nil when not found
|
||||
func (b *bitmapStrike) findTable(glyph gID) *bitmapSubtable {
|
||||
for i, subtable := range b.subTables {
|
||||
if subtable.first <= glyph && glyph <= subtable.last {
|
||||
return &b.subTables[i]
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type bitmapSubtable struct {
|
||||
first gID // First glyph ID of this range.
|
||||
last gID // Last glyph ID of this range (inclusive).
|
||||
imageFormat uint16
|
||||
index bitmapIndex
|
||||
}
|
||||
|
||||
func newBitmapSubtable(header tables.BitmapSubtable, dataTable []byte) (bitmapSubtable, error) {
|
||||
out := bitmapSubtable{
|
||||
first: header.FirstGlyph,
|
||||
last: header.LastGlyph,
|
||||
imageFormat: header.ImageFormat,
|
||||
}
|
||||
if L, E := len(dataTable), int(header.ImageDataOffset); L < E {
|
||||
return bitmapSubtable{}, errors.New("invalid bitmap table (EOF)")
|
||||
}
|
||||
imageData := dataTable[header.ImageDataOffset:]
|
||||
|
||||
var err error
|
||||
switch index := header.IndexData.(type) {
|
||||
case tables.IndexData1:
|
||||
out.index, err = parseIndexSubTable1(header, index, imageData)
|
||||
case tables.IndexData2:
|
||||
out.index, err = parseIndexSubTable2(header, index, imageData)
|
||||
case tables.IndexData3:
|
||||
out.index, err = parseIndexSubTable3(header, index, imageData)
|
||||
case tables.IndexData4:
|
||||
out.index, err = parseIndexSubTable4(header, index, imageData)
|
||||
case tables.IndexData5:
|
||||
out.index, err = parseIndexSubTable5(header, index, imageData)
|
||||
}
|
||||
return out, err
|
||||
}
|
||||
|
||||
func (subT *bitmapSubtable) image(glyph gID) *bitmapImage {
|
||||
return subT.index.imageFor(glyph, subT.first, subT.last)
|
||||
}
|
||||
|
||||
type bitmapIndex interface {
|
||||
// first, last is the range of the subtable
|
||||
imageFor(glyph gID, first, last gID) *bitmapImage
|
||||
}
|
||||
|
||||
type bitmapImage struct {
|
||||
image []byte
|
||||
metrics tables.SmallGlyphMetrics
|
||||
}
|
||||
|
||||
type indexSubTable1And3 struct {
|
||||
// length lastGlyph - firstGlyph + 1, elements may be nil
|
||||
glyphs []bitmapImage
|
||||
format uint16
|
||||
}
|
||||
|
||||
func (idx indexSubTable1And3) imageFor(gid gID, first, last gID) *bitmapImage {
|
||||
if gid < first || gid > last {
|
||||
return nil
|
||||
}
|
||||
return &idx.glyphs[gid-first]
|
||||
}
|
||||
|
||||
// imageData starts at the image (table[imageDataOffset:])
|
||||
func parseIndexSubTable1(header tables.BitmapSubtable, index tables.IndexData1, imageData []byte) (indexSubTable1And3, error) {
|
||||
out := indexSubTable1And3{
|
||||
format: header.ImageFormat,
|
||||
glyphs: make([]bitmapImage, len(index.SbitOffsets)-1),
|
||||
}
|
||||
for i := range out.glyphs {
|
||||
if index.SbitOffsets[i] == index.SbitOffsets[i+1] {
|
||||
continue
|
||||
}
|
||||
var err error
|
||||
out.glyphs[i], err = parseBitmapDataMetrics(imageData, index.SbitOffsets[i], index.SbitOffsets[i+1], header.ImageFormat)
|
||||
if err != nil {
|
||||
return out, fmt.Errorf("invalid bitmap index format 1: %s", err)
|
||||
}
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func parseIndexSubTable3(header tables.BitmapSubtable, index tables.IndexData3, imageData []byte) (indexSubTable1And3, error) {
|
||||
out := indexSubTable1And3{
|
||||
format: header.ImageFormat,
|
||||
glyphs: make([]bitmapImage, len(index.SbitOffsets)-1),
|
||||
}
|
||||
for i := range out.glyphs {
|
||||
if index.SbitOffsets[i] == index.SbitOffsets[i+1] {
|
||||
continue
|
||||
}
|
||||
var err error
|
||||
out.glyphs[i], err = parseBitmapDataMetrics(imageData, tables.Offset32(index.SbitOffsets[i]), tables.Offset32(index.SbitOffsets[i+1]), header.ImageFormat)
|
||||
if err != nil {
|
||||
return out, fmt.Errorf("invalid bitmap index format 1: %s", err)
|
||||
}
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
type bitmapDataStandalone []byte
|
||||
|
||||
type indexSubTable2 struct {
|
||||
glyphs []bitmapDataStandalone
|
||||
format uint16
|
||||
metrics tables.BigGlyphMetrics
|
||||
}
|
||||
|
||||
func (idx indexSubTable2) imageFor(gid gID, first, last gID) *bitmapImage {
|
||||
if gid < first || gid > last {
|
||||
return nil
|
||||
}
|
||||
return &bitmapImage{image: idx.glyphs[gid-first], metrics: idx.metrics.SmallGlyphMetrics}
|
||||
}
|
||||
|
||||
// imageData starts at the image (table[imageDataOffset:])
|
||||
func parseIndexSubTable2(header tables.BitmapSubtable, index tables.IndexData2, imageData []byte) (indexSubTable2, error) {
|
||||
out := indexSubTable2{
|
||||
format: header.ImageFormat,
|
||||
metrics: index.BigMetrics,
|
||||
glyphs: make([]bitmapDataStandalone, int(header.LastGlyph)-int(header.FirstGlyph)+1),
|
||||
}
|
||||
for i := range out.glyphs {
|
||||
var err error
|
||||
out.glyphs[i], err = parseBitmapDataStandalone(imageData, index.ImageSize*uint32(i), index.ImageSize*uint32(i+1), header.ImageFormat)
|
||||
if err != nil {
|
||||
return out, fmt.Errorf("invalid bitmap index format 2: %s", err)
|
||||
}
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
type indexedBitmapGlyph struct {
|
||||
data bitmapImage
|
||||
glyph gID
|
||||
}
|
||||
|
||||
type indexSubTable4 struct {
|
||||
glyphs []indexedBitmapGlyph
|
||||
format uint16
|
||||
}
|
||||
|
||||
func (idx indexSubTable4) imageFor(gid gID, first, last gID) *bitmapImage {
|
||||
if gid < first || gid > last {
|
||||
return nil
|
||||
}
|
||||
for i, g := range idx.glyphs {
|
||||
if g.glyph == gid {
|
||||
return &idx.glyphs[i].data
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// imageData starts at the image (table[imageDataOffset:])
|
||||
func parseIndexSubTable4(header tables.BitmapSubtable, index tables.IndexData4, imageData []byte) (indexSubTable4, error) {
|
||||
out := indexSubTable4{
|
||||
format: header.ImageFormat,
|
||||
glyphs: make([]indexedBitmapGlyph, len(index.GlyphArray)-1),
|
||||
}
|
||||
for i := range out.glyphs {
|
||||
current, next := index.GlyphArray[i], index.GlyphArray[i+1]
|
||||
out.glyphs[i].glyph = current.GlyphID
|
||||
var err error
|
||||
out.glyphs[i].data, err = parseBitmapDataMetrics(imageData, tables.Offset32(current.SbitOffset), tables.Offset32(next.SbitOffset), header.ImageFormat)
|
||||
if err != nil {
|
||||
return out, fmt.Errorf("invalid bitmap index format 4: %s", err)
|
||||
}
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
type indexSubTable5 struct {
|
||||
glyphIndexes []gID // sorted by glyph index
|
||||
glyphs []bitmapDataStandalone // corresponding to glyphIndexes
|
||||
format uint16
|
||||
metrics tables.BigGlyphMetrics
|
||||
}
|
||||
|
||||
func (idx indexSubTable5) imageFor(gid gID, first, last gID) *bitmapImage {
|
||||
if gid < first || gid > last {
|
||||
return nil
|
||||
}
|
||||
// binary search
|
||||
for i, j := 0, len(idx.glyphIndexes); i < j; {
|
||||
h := i + (j-i)/2
|
||||
entry := idx.glyphIndexes[h]
|
||||
if gid < entry {
|
||||
j = h
|
||||
} else if entry < gid {
|
||||
i = h + 1
|
||||
} else {
|
||||
return &bitmapImage{image: idx.glyphs[h], metrics: idx.metrics.SmallGlyphMetrics}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// imageData starts at the image (table[imageDataOffset:])
|
||||
func parseIndexSubTable5(header tables.BitmapSubtable, index tables.IndexData5, imageData []byte) (indexSubTable5, error) {
|
||||
out := indexSubTable5{
|
||||
format: header.ImageFormat,
|
||||
metrics: index.BigMetrics,
|
||||
glyphIndexes: index.GlyphIdArray,
|
||||
glyphs: make([]bitmapDataStandalone, len(index.GlyphIdArray)),
|
||||
}
|
||||
|
||||
for i := range out.glyphs {
|
||||
var err error
|
||||
out.glyphs[i], err = parseBitmapDataStandalone(imageData, index.ImageSize*uint32(i), (index.ImageSize+1)*uint32(i), header.ImageFormat)
|
||||
if err != nil {
|
||||
return out, fmt.Errorf("invalid bitmap index format 5: %s", err)
|
||||
}
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func parseBitmapDataMetrics(imageData []byte, start, end tables.Offset32, imageFormat uint16) (bitmapImage, error) {
|
||||
if len(imageData) < int(end) || start > end {
|
||||
return bitmapImage{}, errors.New("invalid bitmap data table (EOF)")
|
||||
}
|
||||
imageData = imageData[start:end]
|
||||
switch imageFormat {
|
||||
case 1, 6, 7, 8, 9:
|
||||
return bitmapImage{}, fmt.Errorf("valid but currently not implemented bitmap image format: %d", imageFormat)
|
||||
case 2:
|
||||
data, _, err := tables.ParseBitmapData2(imageData)
|
||||
return bitmapImage{metrics: data.SmallGlyphMetrics, image: data.Image}, err
|
||||
case 17:
|
||||
data, _, err := tables.ParseBitmapData17(imageData)
|
||||
return bitmapImage{metrics: data.SmallGlyphMetrics, image: data.Image}, err
|
||||
case 18:
|
||||
data, _, err := tables.ParseBitmapData18(imageData)
|
||||
return bitmapImage{metrics: data.SmallGlyphMetrics, image: data.Image}, err
|
||||
default:
|
||||
return bitmapImage{}, fmt.Errorf("unsupported bitmap image format: %d", imageFormat)
|
||||
}
|
||||
}
|
||||
|
||||
func parseBitmapDataStandalone(imageData []byte, start, end uint32, format uint16) (bitmapDataStandalone, error) {
|
||||
if len(imageData) < int(end) || start > end {
|
||||
return nil, fmt.Errorf("invalid bitmap data table (EOF for [%d,%d])", start, end)
|
||||
}
|
||||
imageData = imageData[start:end]
|
||||
switch format {
|
||||
case 4:
|
||||
return nil, fmt.Errorf("valid but currently not implemented bitmap image format: %d", format)
|
||||
case 5:
|
||||
data, _, err := tables.ParseBitmapData5(imageData)
|
||||
return data.Image, err
|
||||
case 19:
|
||||
data, _, err := tables.ParseBitmapData19(imageData)
|
||||
return data.Image, err
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported bitmap image format: %d", format)
|
||||
}
|
||||
}
|
||||
|
||||
func maxu16(a, b uint16) uint16 {
|
||||
if a > b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func mulDiv(a, b, c uint16) uint16 {
|
||||
return uint16(uint32(a) * uint32(b) / uint32(c))
|
||||
}
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
package font
|
||||
|
||||
type glyphExtents struct {
|
||||
valid bool
|
||||
extents GlyphExtents
|
||||
}
|
||||
|
||||
type extentsCache []glyphExtents
|
||||
|
||||
func (ec extentsCache) get(gid GID) (GlyphExtents, bool) {
|
||||
if int(gid) >= len(ec) {
|
||||
return GlyphExtents{}, false
|
||||
}
|
||||
ge := ec[gid]
|
||||
return ge.extents, ge.valid
|
||||
}
|
||||
|
||||
func (ec extentsCache) set(gid GID, extents GlyphExtents) {
|
||||
if int(gid) >= len(ec) {
|
||||
return
|
||||
}
|
||||
ec[gid].valid = true
|
||||
ec[gid].extents = extents
|
||||
}
|
||||
|
||||
func (ec extentsCache) reset() {
|
||||
for i := range ec {
|
||||
ec[i] = glyphExtents{}
|
||||
}
|
||||
}
|
||||
|
||||
func (f *Face) GlyphExtents(glyph GID) (GlyphExtents, bool) {
|
||||
if e, ok := f.extentsCache.get(glyph); ok {
|
||||
return e, ok
|
||||
}
|
||||
e, ok := f.glyphExtentsRaw(glyph)
|
||||
if ok {
|
||||
f.extentsCache.set(glyph, e)
|
||||
}
|
||||
return e, ok
|
||||
}
|
||||
+260
@@ -0,0 +1,260 @@
|
||||
package cff
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
ps "github.com/go-text/typesetting/font/cff/interpreter"
|
||||
"github.com/go-text/typesetting/font/opentype/tables"
|
||||
)
|
||||
|
||||
//go:generate ../../../../typesetting-utils/generators/binarygen/cmd/generator . _src.go
|
||||
|
||||
// CFF2 represents a parsed 'CFF2' Opentype table.
|
||||
type CFF2 struct {
|
||||
fdSelect fdSelect // maybe nil if there is only one font dict
|
||||
|
||||
// Charstrings contains the actual glyph definition.
|
||||
// It has a length of numGlyphs and is indexed by glyph ID.
|
||||
// See `LoadGlyph` for a way to intepret the glyph data.
|
||||
Charstrings [][]byte
|
||||
|
||||
globalSubrs [][]byte
|
||||
|
||||
// array of length 1 if fdSelect is nil
|
||||
// otherwise, it can be safely indexed by `fdSelect` output
|
||||
fonts []privateFonts
|
||||
|
||||
VarStore tables.ItemVarStore // optional
|
||||
}
|
||||
|
||||
type privateFonts struct {
|
||||
localSubrs [][]byte
|
||||
defaultVSIndex int32
|
||||
}
|
||||
|
||||
// ParseCFF2 parses 'src', which must be the content of a 'CFF2' Opentype table.
|
||||
//
|
||||
// See also https://learn.microsoft.com/en-us/typography/opentype/spec/cff2
|
||||
func ParseCFF2(src []byte) (*CFF2, error) {
|
||||
if L := len(src); L < 5 {
|
||||
return nil, fmt.Errorf("reading header: EOF: expected length: 5, got %d", L)
|
||||
}
|
||||
var header header2
|
||||
header.mustParse(src)
|
||||
topDictEnd := int(header.headerSize) + int(header.topDictLength)
|
||||
if L := len(src); L < topDictEnd {
|
||||
return nil, fmt.Errorf("reading topDict: EOF: expected length: %d, got %d", topDictEnd, L)
|
||||
}
|
||||
topDictSrc := src[header.headerSize:topDictEnd]
|
||||
|
||||
var (
|
||||
tp topDict2
|
||||
psi ps.Machine
|
||||
)
|
||||
if err := psi.Run(topDictSrc, nil, nil, &tp); err != nil {
|
||||
return nil, fmt.Errorf("reading top dict: %s", err)
|
||||
}
|
||||
|
||||
var (
|
||||
out CFF2
|
||||
err error
|
||||
)
|
||||
|
||||
out.globalSubrs, err = parseIndex2(src, topDictEnd)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// parse charstrings
|
||||
out.Charstrings, err = parseIndex2(src, int(tp.charStrings))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
fdIndex, err := parseIndex2(src, int(tp.fdArray))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
out.fonts = make([]privateFonts, len(fdIndex))
|
||||
// private dict reference
|
||||
for i, font := range fdIndex {
|
||||
var fd fontDict2
|
||||
err = psi.Run(font, nil, nil, &fd)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("reading font dict: %s", err)
|
||||
}
|
||||
end := int(fd.privateDictOffset + fd.privateDictSize)
|
||||
if L := len(src); L < end {
|
||||
return nil, fmt.Errorf("reading private dict: EOF: expected length: %d, got %d", end, L)
|
||||
}
|
||||
// parse private dict
|
||||
var pd privateDict2
|
||||
err = psi.Run(src[fd.privateDictOffset:end], nil, nil, &pd)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("reading private dict: %s", err)
|
||||
}
|
||||
|
||||
out.fonts[i].defaultVSIndex = pd.vsindex
|
||||
// if required, parse the local subroutines
|
||||
if pd.subrsOffset != 0 {
|
||||
out.fonts[i].localSubrs, err = parseIndex2(src, int(pd.subrsOffset))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(fdIndex) > 1 {
|
||||
// parse the fdSelect
|
||||
if L := len(src); L < int(tp.fdSelect) {
|
||||
return nil, fmt.Errorf("reading fdSelect: EOF: expected length: %d, got %d", tp.fdSelect, L)
|
||||
}
|
||||
out.fdSelect, _, err = parseFdSelect(src[tp.fdSelect:], len(out.Charstrings))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// sanitize fdSelect outputs
|
||||
indexExtent := out.fdSelect.extent()
|
||||
if len(fdIndex) < indexExtent {
|
||||
return nil, fmt.Errorf("invalid number of font dicts: %d (for %d)", len(fdIndex), indexExtent)
|
||||
}
|
||||
}
|
||||
|
||||
// parse variation store
|
||||
if tp.vstore != 0 {
|
||||
// See https://learn.microsoft.com/en-us/typography/opentype/spec/cff2#variationstore-data-contents
|
||||
if E, L := int(tp.vstore)+2, len(src); L < E {
|
||||
return nil, fmt.Errorf("reading variation store: EOF: expected length: %d, got %d", E, L)
|
||||
}
|
||||
size := int(binary.BigEndian.Uint16(src[tp.vstore:]))
|
||||
end := int(tp.vstore) + 2 + size
|
||||
if L := len(src); L < end {
|
||||
return nil, fmt.Errorf("reading variation store: EOF: expected length: %d, got %d", end, L)
|
||||
}
|
||||
vstore := src[tp.vstore+2 : end]
|
||||
out.VarStore, _, err = tables.ParseItemVarStore(vstore)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return &out, nil
|
||||
}
|
||||
|
||||
func parseIndex2(src []byte, offset int) ([][]byte, error) {
|
||||
if L := len(src); L < offset+5 {
|
||||
return nil, fmt.Errorf("reading INDEX: EOF: expected length: %d, got %d", offset+5, L)
|
||||
}
|
||||
var is indexStart
|
||||
is.mustParse(src[offset:])
|
||||
out, _, err := parseIndexContent(src[offset+5:], is)
|
||||
return out, err
|
||||
}
|
||||
|
||||
type topDict2 struct {
|
||||
charStrings int32 // offset
|
||||
fdArray int32 // offset
|
||||
fdSelect int32 // offset
|
||||
vstore int32 // offset
|
||||
}
|
||||
|
||||
func (tp *topDict2) Context() ps.Context { return ps.TopDict }
|
||||
|
||||
func (tp *topDict2) Apply(state *ps.Machine, op ps.Operator) error {
|
||||
switch op {
|
||||
case ps.Operator{Operator: 7, IsEscaped: true}: // FontMatrix
|
||||
// skip
|
||||
state.ArgStack.Clear()
|
||||
return nil
|
||||
case ps.Operator{Operator: 17, IsEscaped: false}: // CharStrings
|
||||
if state.ArgStack.Top < 1 {
|
||||
return fmt.Errorf("invalid number of arguments for operator %s in Top Dict", op)
|
||||
}
|
||||
tp.charStrings = int32(state.ArgStack.Pop())
|
||||
case ps.Operator{Operator: 36, IsEscaped: true}: // FDArray
|
||||
if state.ArgStack.Top < 1 {
|
||||
return fmt.Errorf("invalid number of arguments for operator %s in Top Dict", op)
|
||||
}
|
||||
tp.fdArray = int32(state.ArgStack.Pop())
|
||||
case ps.Operator{Operator: 37, IsEscaped: true}: // FDSelect
|
||||
if state.ArgStack.Top < 1 {
|
||||
return fmt.Errorf("invalid number of arguments for operator %s in Top Dict", op)
|
||||
}
|
||||
tp.fdSelect = int32(state.ArgStack.Pop())
|
||||
case ps.Operator{Operator: 24, IsEscaped: false}: // vstore
|
||||
if state.ArgStack.Top < 1 {
|
||||
return fmt.Errorf("invalid number of arguments for operator %s in Top Dict", op)
|
||||
}
|
||||
tp.vstore = int32(state.ArgStack.Pop())
|
||||
default:
|
||||
return fmt.Errorf("invalid operator %s in Top Dict", op)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type fontDict2 struct {
|
||||
privateDictSize int32
|
||||
privateDictOffset int32
|
||||
}
|
||||
|
||||
func (fd *fontDict2) Context() ps.Context { return ps.TopDict }
|
||||
|
||||
func (fd *fontDict2) Apply(state *ps.Machine, op ps.Operator) error {
|
||||
switch op {
|
||||
case ps.Operator{Operator: 18, IsEscaped: false}: // Private
|
||||
if state.ArgStack.Top < 2 {
|
||||
return fmt.Errorf("invalid number of arguments for operator %s in Font Dict", op)
|
||||
}
|
||||
fd.privateDictOffset = int32(state.ArgStack.Pop())
|
||||
fd.privateDictSize = int32(state.ArgStack.Pop())
|
||||
return nil
|
||||
default:
|
||||
return fmt.Errorf("invalid operator %s in Font Dict", op)
|
||||
}
|
||||
}
|
||||
|
||||
// privateDict2 contains fields specific to the Private DICT context.
|
||||
type privateDict2 struct {
|
||||
subrsOffset int32
|
||||
vsindex int32 // itemVariationData index in the VariationStore structure table.
|
||||
}
|
||||
|
||||
func (privateDict2) Context() ps.Context { return ps.PrivateDict }
|
||||
|
||||
// The Private DICT operators are defined by 5176.CFF.pdf Table 23 "Private
|
||||
// DICT Operators".
|
||||
func (priv *privateDict2) Apply(state *ps.Machine, op ps.Operator) error {
|
||||
if !op.IsEscaped { // 1-byte operators.
|
||||
switch op.Operator {
|
||||
case 6, 7, 8, 9: // "BlueValues" "OtherBlues" "FamilyBlues" "FamilyOtherBlues"
|
||||
return state.ArgStack.PopN(-2)
|
||||
case 10, 11: // "StdHW" "StdVW"
|
||||
return state.ArgStack.PopN(1)
|
||||
case 19: // "Subrs" pop 1
|
||||
if state.ArgStack.Top < 1 {
|
||||
return errors.New("invalid stack size for 'subrs' in private Dict charstring")
|
||||
}
|
||||
priv.subrsOffset = int32(state.ArgStack.Pop())
|
||||
return nil
|
||||
case 22: // "vsindex"
|
||||
if state.ArgStack.Top < 1 {
|
||||
return fmt.Errorf("invalid stack size for %s in private Dict", op)
|
||||
}
|
||||
priv.vsindex = int32(state.ArgStack.Pop())
|
||||
return nil
|
||||
case 23: // "blend"
|
||||
return nil
|
||||
}
|
||||
} else { // 2-byte operators. The first byte is the escape byte.
|
||||
switch op.Operator {
|
||||
case 9, 10, 11, 17, 18: // "BlueScale" "BlueShift" "BlueFuzz" "LanguageGroup" "ExpansionFactor"
|
||||
return state.ArgStack.PopN(1)
|
||||
case 12, 13: // "StemSnapH" "StemSnapV"
|
||||
return state.ArgStack.PopN(-2)
|
||||
}
|
||||
}
|
||||
return errors.New("invalid operand in private Dict charstring")
|
||||
}
|
||||
+151
@@ -0,0 +1,151 @@
|
||||
// SPDX-License-Identifier: Unlicense OR BSD-3-Clause
|
||||
|
||||
package cff
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// Code generated by binarygen from cff2_src.go. DO NOT EDIT
|
||||
|
||||
func (item *header2) mustParse(src []byte) {
|
||||
_ = src[4] // early bound checking
|
||||
item.majorVersion = src[0]
|
||||
item.minorVersion = src[1]
|
||||
item.headerSize = src[2]
|
||||
item.topDictLength = binary.BigEndian.Uint16(src[3:])
|
||||
}
|
||||
|
||||
func (item *indexStart) mustParse(src []byte) {
|
||||
_ = src[4] // early bound checking
|
||||
item.count = binary.BigEndian.Uint32(src[0:])
|
||||
item.offSize = src[4]
|
||||
}
|
||||
|
||||
func parseFdSelect(src []byte, fdsCount int) (fdSelect, int, error) {
|
||||
var item fdSelect
|
||||
|
||||
if L := len(src); L < 1 {
|
||||
return item, 0, fmt.Errorf("reading fdSelect: "+"EOF: expected length: 1, got %d", L)
|
||||
}
|
||||
format := uint8(src[0])
|
||||
var (
|
||||
read int
|
||||
err error
|
||||
)
|
||||
switch format {
|
||||
case 0:
|
||||
item, read, err = parseFdSelect0(src[0:], fdsCount)
|
||||
case 3:
|
||||
item, read, err = parseFdSelect3(src[0:])
|
||||
case 4:
|
||||
item, read, err = parseFdSelect4(src[0:])
|
||||
default:
|
||||
err = fmt.Errorf("unsupported fdSelect format %d", format)
|
||||
}
|
||||
if err != nil {
|
||||
return item, 0, fmt.Errorf("reading fdSelect: %s", err)
|
||||
}
|
||||
|
||||
return item, read, nil
|
||||
}
|
||||
|
||||
func parseFdSelect0(src []byte, fdsCount int) (fdSelect0, int, error) {
|
||||
var item fdSelect0
|
||||
n := 0
|
||||
if L := len(src); L < 1 {
|
||||
return item, 0, fmt.Errorf("reading fdSelect0: "+"EOF: expected length: 1, got %d", L)
|
||||
}
|
||||
item.format = src[0]
|
||||
n += 1
|
||||
|
||||
{
|
||||
|
||||
L := int(1 + fdsCount)
|
||||
if len(src) < L {
|
||||
return item, 0, fmt.Errorf("reading fdSelect0: "+"EOF: expected length: %d, got %d", L, len(src))
|
||||
}
|
||||
item.fds = src[1:L]
|
||||
n = L
|
||||
}
|
||||
return item, n, nil
|
||||
}
|
||||
|
||||
func parseFdSelect3(src []byte) (fdSelect3, int, error) {
|
||||
var item fdSelect3
|
||||
n := 0
|
||||
if L := len(src); L < 3 {
|
||||
return item, 0, fmt.Errorf("reading fdSelect3: "+"EOF: expected length: 3, got %d", L)
|
||||
}
|
||||
_ = src[2] // early bound checking
|
||||
item.format = src[0]
|
||||
item.nRanges = binary.BigEndian.Uint16(src[1:])
|
||||
n += 3
|
||||
|
||||
{
|
||||
arrayLength := int(item.nRanges)
|
||||
|
||||
if L := len(src); L < 3+arrayLength*3 {
|
||||
return item, 0, fmt.Errorf("reading fdSelect3: "+"EOF: expected length: %d, got %d", 3+arrayLength*3, L)
|
||||
}
|
||||
|
||||
item.ranges = make([]range3, arrayLength) // allocation guarded by the previous check
|
||||
for i := range item.ranges {
|
||||
item.ranges[i].mustParse(src[3+i*3:])
|
||||
}
|
||||
n += arrayLength * 3
|
||||
}
|
||||
if L := len(src); L < n+2 {
|
||||
return item, 0, fmt.Errorf("reading fdSelect3: "+"EOF: expected length: n + 2, got %d", L)
|
||||
}
|
||||
item.sentinel = binary.BigEndian.Uint16(src[n:])
|
||||
n += 2
|
||||
|
||||
return item, n, nil
|
||||
}
|
||||
|
||||
func parseFdSelect4(src []byte) (fdSelect4, int, error) {
|
||||
var item fdSelect4
|
||||
n := 0
|
||||
if L := len(src); L < 5 {
|
||||
return item, 0, fmt.Errorf("reading fdSelect4: "+"EOF: expected length: 5, got %d", L)
|
||||
}
|
||||
_ = src[4] // early bound checking
|
||||
item.format = src[0]
|
||||
item.nRanges = binary.BigEndian.Uint32(src[1:])
|
||||
n += 5
|
||||
|
||||
{
|
||||
arrayLength := int(item.nRanges)
|
||||
|
||||
if L := len(src); L < 5+arrayLength*6 {
|
||||
return item, 0, fmt.Errorf("reading fdSelect4: "+"EOF: expected length: %d, got %d", 5+arrayLength*6, L)
|
||||
}
|
||||
|
||||
item.ranges = make([]range4, arrayLength) // allocation guarded by the previous check
|
||||
for i := range item.ranges {
|
||||
item.ranges[i].mustParse(src[5+i*6:])
|
||||
}
|
||||
n += arrayLength * 6
|
||||
}
|
||||
if L := len(src); L < n+4 {
|
||||
return item, 0, fmt.Errorf("reading fdSelect4: "+"EOF: expected length: n + 4, got %d", L)
|
||||
}
|
||||
item.sentinel = binary.BigEndian.Uint32(src[n:])
|
||||
n += 4
|
||||
|
||||
return item, n, nil
|
||||
}
|
||||
|
||||
func (item *range3) mustParse(src []byte) {
|
||||
_ = src[2] // early bound checking
|
||||
item.first = binary.BigEndian.Uint16(src[0:])
|
||||
item.fd = src[2]
|
||||
}
|
||||
|
||||
func (item *range4) mustParse(src []byte) {
|
||||
_ = src[5] // early bound checking
|
||||
item.first = binary.BigEndian.Uint32(src[0:])
|
||||
item.fd = binary.BigEndian.Uint16(src[4:])
|
||||
}
|
||||
+162
@@ -0,0 +1,162 @@
|
||||
package cff
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/go-text/typesetting/font/opentype/tables"
|
||||
)
|
||||
|
||||
//go:generate ../../../../../typesetting-utils/generators/binarygen/cmd/generator . _src.go
|
||||
|
||||
type header2 struct {
|
||||
majorVersion uint8 // Format major version. Set to 2.
|
||||
minorVersion uint8 // Format minor version. Set to zero.
|
||||
headerSize uint8 // Header size (bytes).
|
||||
topDictLength uint16 // Length of Top DICT structure in bytes.
|
||||
}
|
||||
|
||||
type indexStart struct {
|
||||
count uint32 // Number of objects stored in INDEX
|
||||
offSize uint8 // Offset array element size
|
||||
// then
|
||||
// offset []Offset
|
||||
// data []byte
|
||||
}
|
||||
|
||||
//lint:ignore U1000 this type is required so that the code generator add a ParseFdSelect function
|
||||
type dummy struct {
|
||||
fd fdSelect
|
||||
}
|
||||
|
||||
// fdSelect holds a CFF font's Font Dict Select data.
|
||||
type fdSelect interface {
|
||||
isFdSelect()
|
||||
|
||||
fontDictIndex(glyph tables.GlyphID) (byte, error)
|
||||
// return the maximum index + 1 (it's the length of an array
|
||||
// which can be safely indexed by the indexes)
|
||||
extent() int
|
||||
}
|
||||
|
||||
func (fdSelect0) isFdSelect() {}
|
||||
func (fdSelect3) isFdSelect() {}
|
||||
func (fdSelect4) isFdSelect() {}
|
||||
|
||||
type fdSelect0 struct {
|
||||
format uint8 `unionTag:"0"` // Set to 0
|
||||
fds []uint8 // [nGlyphs] FD selector array
|
||||
}
|
||||
|
||||
var errGlyph = errors.New("invalid glyph index")
|
||||
|
||||
func (fds fdSelect0) fontDictIndex(glyph tables.GlyphID) (byte, error) {
|
||||
if int(glyph) >= len(fds.fds) {
|
||||
return 0, errGlyph
|
||||
}
|
||||
return fds.fds[glyph], nil
|
||||
}
|
||||
|
||||
func (fds fdSelect0) extent() int {
|
||||
max := -1
|
||||
for _, b := range fds.fds {
|
||||
if int(b) > max {
|
||||
max = int(b)
|
||||
}
|
||||
}
|
||||
return max + 1
|
||||
}
|
||||
|
||||
type fdSelect3 struct {
|
||||
format uint8 `unionTag:"3"` // Set to 3
|
||||
nRanges uint16 // Number of ranges
|
||||
ranges []range3 `arrayCount:"ComputedField-nRanges"` // [nRanges] Array of Range3 records (see below)
|
||||
sentinel uint16 // Sentinel GID
|
||||
}
|
||||
|
||||
type range3 struct {
|
||||
first tables.GlyphID // First glyph index in range
|
||||
fd uint8 // FD index for all glyphs in range
|
||||
}
|
||||
|
||||
func (fds fdSelect3) fontDictIndex(x tables.GlyphID) (byte, error) {
|
||||
lo, hi := 0, len(fds.ranges)
|
||||
for lo < hi {
|
||||
i := (lo + hi) / 2
|
||||
r := fds.ranges[i]
|
||||
xlo := r.first
|
||||
if x < xlo {
|
||||
hi = i
|
||||
continue
|
||||
}
|
||||
xhi := fds.sentinel
|
||||
if i < len(fds.ranges)-1 {
|
||||
xhi = fds.ranges[i+1].first
|
||||
}
|
||||
if xhi <= x {
|
||||
lo = i + 1
|
||||
continue
|
||||
}
|
||||
return r.fd, nil
|
||||
}
|
||||
return 0, errGlyph
|
||||
}
|
||||
|
||||
func (fds fdSelect3) extent() int {
|
||||
max := -1
|
||||
for _, b := range fds.ranges {
|
||||
if int(b.fd) > max {
|
||||
max = int(b.fd)
|
||||
}
|
||||
}
|
||||
return max + 1
|
||||
}
|
||||
|
||||
type fdSelect4 struct {
|
||||
format uint8 `unionTag:"4"` // Set to 4
|
||||
nRanges uint32 // Number of ranges
|
||||
ranges []range4 `arrayCount:"ComputedField-nRanges"` // [nRanges] Array of Range4 records (see below)
|
||||
sentinel uint32 // Sentinel GID
|
||||
}
|
||||
|
||||
type range4 struct {
|
||||
first uint32 // First glyph index in range
|
||||
fd uint16 // FD index for all glyphs in range
|
||||
}
|
||||
|
||||
func (fds fdSelect4) fontDictIndex(x tables.GlyphID) (byte, error) {
|
||||
fd, err := fds.fontDictIndex32(uint32(x))
|
||||
return byte(fd), err
|
||||
}
|
||||
|
||||
func (fds fdSelect4) fontDictIndex32(x uint32) (uint16, error) {
|
||||
lo, hi := 0, len(fds.ranges)
|
||||
for lo < hi {
|
||||
i := (lo + hi) / 2
|
||||
r := fds.ranges[i]
|
||||
xlo := r.first
|
||||
if x < xlo {
|
||||
hi = i
|
||||
continue
|
||||
}
|
||||
xhi := fds.sentinel
|
||||
if i < len(fds.ranges)-1 {
|
||||
xhi = fds.ranges[i+1].first
|
||||
}
|
||||
if xhi <= x {
|
||||
lo = i + 1
|
||||
continue
|
||||
}
|
||||
return r.fd, nil
|
||||
}
|
||||
return 0, errGlyph
|
||||
}
|
||||
|
||||
func (fds fdSelect4) extent() int {
|
||||
max := -1
|
||||
for _, b := range fds.ranges {
|
||||
if int(b.fd) > max {
|
||||
max = int(b.fd)
|
||||
}
|
||||
}
|
||||
return max + 1
|
||||
}
|
||||
+423
@@ -0,0 +1,423 @@
|
||||
// SPDX-License-Identifier: Unlicense OR BSD-3-Clause
|
||||
|
||||
package cff
|
||||
|
||||
var (
|
||||
charsetISOAdobe = [229]uint16{
|
||||
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32,
|
||||
33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65,
|
||||
66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98,
|
||||
99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131,
|
||||
132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164,
|
||||
165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197,
|
||||
198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228,
|
||||
}
|
||||
|
||||
charsetExpert = [166]uint16{
|
||||
0, 1, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 13, 14, 15, 99, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 27, 28, 249, 250, 251, 252, 253, 254,
|
||||
255, 256, 257, 258, 259, 260, 261, 262, 263, 264, 265, 266, 109, 110, 267, 268, 269, 270, 271, 272, 273, 274, 275, 276, 277, 278, 279, 280, 281, 282, 283, 284, 285, 286,
|
||||
287, 288, 289, 290, 291, 292, 293, 294, 295, 296, 297, 298, 299, 300, 301, 302, 303, 304, 305, 306, 307, 308, 309, 310, 311, 312, 313, 314, 315, 316, 317, 318, 158, 155,
|
||||
163, 319, 320, 321, 322, 323, 324, 325, 326, 150, 164, 169, 327, 328, 329, 330, 331, 332, 333, 334, 335, 336, 337, 338, 339, 340, 341, 342, 343, 344, 345, 346, 347, 348,
|
||||
349, 350, 351, 352, 353, 354, 355, 356, 357, 358, 359, 360, 361, 362, 363, 364, 365, 366, 367, 368, 369, 370, 371, 372, 373, 374, 375, 376, 377, 378,
|
||||
}
|
||||
|
||||
charsetExpertSubset = [87]uint16{
|
||||
0, 1, 231, 232, 235, 236, 237, 238, 13, 14, 15, 99, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 27, 28, 249, 250, 251, 253, 254,
|
||||
255, 256, 257, 258, 259, 260, 261, 262, 263, 264, 265, 266, 109, 110, 267, 268, 269, 270, 272, 300, 301, 302, 305, 314, 315, 158, 155, 163, 320,
|
||||
321, 322, 323, 324, 325, 326, 150, 164, 169, 327, 328, 329, 330, 331, 332, 333, 334, 335, 336, 337, 338, 339, 340, 341, 342, 343, 344, 345, 346,
|
||||
}
|
||||
)
|
||||
|
||||
var stdStrings = [391]string{
|
||||
".notdef",
|
||||
"space",
|
||||
"exclam",
|
||||
"quotedbl",
|
||||
"numbersign",
|
||||
"dollar",
|
||||
"percent",
|
||||
"ampersand",
|
||||
"quoteright",
|
||||
"parenleft",
|
||||
"parenright",
|
||||
"asterisk",
|
||||
"plus",
|
||||
"comma",
|
||||
"hyphen",
|
||||
"period",
|
||||
"slash",
|
||||
"zero",
|
||||
"one",
|
||||
"two",
|
||||
"three",
|
||||
"four",
|
||||
"five",
|
||||
"six",
|
||||
"seven",
|
||||
"eight",
|
||||
"nine",
|
||||
"colon",
|
||||
"semicolon",
|
||||
"less",
|
||||
"equal",
|
||||
"greater",
|
||||
"question",
|
||||
"at",
|
||||
"A",
|
||||
"B",
|
||||
"C",
|
||||
"D",
|
||||
"E",
|
||||
"F",
|
||||
"G",
|
||||
"H",
|
||||
"I",
|
||||
"J",
|
||||
"K",
|
||||
"L",
|
||||
"M",
|
||||
"N",
|
||||
"O",
|
||||
"P",
|
||||
"Q",
|
||||
"R",
|
||||
"S",
|
||||
"T",
|
||||
"U",
|
||||
"V",
|
||||
"W",
|
||||
"X",
|
||||
"Y",
|
||||
"Z",
|
||||
"bracketleft",
|
||||
"backslash",
|
||||
"bracketright",
|
||||
"asciicircum",
|
||||
"underscore",
|
||||
"quoteleft",
|
||||
"a",
|
||||
"b",
|
||||
"c",
|
||||
"d",
|
||||
"e",
|
||||
"f",
|
||||
"g",
|
||||
"h",
|
||||
"i",
|
||||
"j",
|
||||
"k",
|
||||
"l",
|
||||
"m",
|
||||
"n",
|
||||
"o",
|
||||
"p",
|
||||
"q",
|
||||
"r",
|
||||
"s",
|
||||
"t",
|
||||
"u",
|
||||
"v",
|
||||
"w",
|
||||
"x",
|
||||
"y",
|
||||
"z",
|
||||
"braceleft",
|
||||
"bar",
|
||||
"braceright",
|
||||
"asciitilde",
|
||||
"exclamdown",
|
||||
"cent",
|
||||
"sterling",
|
||||
"fraction",
|
||||
"yen",
|
||||
"florin",
|
||||
"section",
|
||||
"currency",
|
||||
"quotesingle",
|
||||
"quotedblleft",
|
||||
"guillemotleft",
|
||||
"guilsinglleft",
|
||||
"guilsinglright",
|
||||
"fi",
|
||||
"fl",
|
||||
"endash",
|
||||
"dagger",
|
||||
"daggerdbl",
|
||||
"periodcentered",
|
||||
"paragraph",
|
||||
"bullet",
|
||||
"quotesinglbase",
|
||||
"quotedblbase",
|
||||
"quotedblright",
|
||||
"guillemotright",
|
||||
"ellipsis",
|
||||
"perthousand",
|
||||
"questiondown",
|
||||
"grave",
|
||||
"acute",
|
||||
"circumflex",
|
||||
"tilde",
|
||||
"macron",
|
||||
"breve",
|
||||
"dotaccent",
|
||||
"dieresis",
|
||||
"ring",
|
||||
"cedilla",
|
||||
"hungarumlaut",
|
||||
"ogonek",
|
||||
"caron",
|
||||
"emdash",
|
||||
"AE",
|
||||
"ordfeminine",
|
||||
"Lslash",
|
||||
"Oslash",
|
||||
"OE",
|
||||
"ordmasculine",
|
||||
"ae",
|
||||
"dotlessi",
|
||||
"lslash",
|
||||
"oslash",
|
||||
"oe",
|
||||
"germandbls",
|
||||
"onesuperior",
|
||||
"logicalnot",
|
||||
"mu",
|
||||
"trademark",
|
||||
"Eth",
|
||||
"onehalf",
|
||||
"plusminus",
|
||||
"Thorn",
|
||||
"onequarter",
|
||||
"divide",
|
||||
"brokenbar",
|
||||
"degree",
|
||||
"thorn",
|
||||
"threequarters",
|
||||
"twosuperior",
|
||||
"registered",
|
||||
"minus",
|
||||
"eth",
|
||||
"multiply",
|
||||
"threesuperior",
|
||||
"copyright",
|
||||
"Aacute",
|
||||
"Acircumflex",
|
||||
"Adieresis",
|
||||
"Agrave",
|
||||
"Aring",
|
||||
"Atilde",
|
||||
"Ccedilla",
|
||||
"Eacute",
|
||||
"Ecircumflex",
|
||||
"Edieresis",
|
||||
"Egrave",
|
||||
"Iacute",
|
||||
"Icircumflex",
|
||||
"Idieresis",
|
||||
"Igrave",
|
||||
"Ntilde",
|
||||
"Oacute",
|
||||
"Ocircumflex",
|
||||
"Odieresis",
|
||||
"Ograve",
|
||||
"Otilde",
|
||||
"Scaron",
|
||||
"Uacute",
|
||||
"Ucircumflex",
|
||||
"Udieresis",
|
||||
"Ugrave",
|
||||
"Yacute",
|
||||
"Ydieresis",
|
||||
"Zcaron",
|
||||
"aacute",
|
||||
"acircumflex",
|
||||
"adieresis",
|
||||
"agrave",
|
||||
"aring",
|
||||
"atilde",
|
||||
"ccedilla",
|
||||
"eacute",
|
||||
"ecircumflex",
|
||||
"edieresis",
|
||||
"egrave",
|
||||
"iacute",
|
||||
"icircumflex",
|
||||
"idieresis",
|
||||
"igrave",
|
||||
"ntilde",
|
||||
"oacute",
|
||||
"ocircumflex",
|
||||
"odieresis",
|
||||
"ograve",
|
||||
"otilde",
|
||||
"scaron",
|
||||
"uacute",
|
||||
"ucircumflex",
|
||||
"udieresis",
|
||||
"ugrave",
|
||||
"yacute",
|
||||
"ydieresis",
|
||||
"zcaron",
|
||||
"exclamsmall",
|
||||
"Hungarumlautsmall",
|
||||
"dollaroldstyle",
|
||||
"dollarsuperior",
|
||||
"ampersandsmall",
|
||||
"Acutesmall",
|
||||
"parenleftsuperior",
|
||||
"parenrightsuperior",
|
||||
"twodotenleader",
|
||||
"onedotenleader",
|
||||
"zerooldstyle",
|
||||
"oneoldstyle",
|
||||
"twooldstyle",
|
||||
"threeoldstyle",
|
||||
"fouroldstyle",
|
||||
"fiveoldstyle",
|
||||
"sixoldstyle",
|
||||
"sevenoldstyle",
|
||||
"eightoldstyle",
|
||||
"nineoldstyle",
|
||||
"commasuperior",
|
||||
"threequartersemdash",
|
||||
"periodsuperior",
|
||||
"questionsmall",
|
||||
"asuperior",
|
||||
"bsuperior",
|
||||
"centsuperior",
|
||||
"dsuperior",
|
||||
"esuperior",
|
||||
"isuperior",
|
||||
"lsuperior",
|
||||
"msuperior",
|
||||
"nsuperior",
|
||||
"osuperior",
|
||||
"rsuperior",
|
||||
"ssuperior",
|
||||
"tsuperior",
|
||||
"ff",
|
||||
"ffi",
|
||||
"ffl",
|
||||
"parenleftinferior",
|
||||
"parenrightinferior",
|
||||
"Circumflexsmall",
|
||||
"hyphensuperior",
|
||||
"Gravesmall",
|
||||
"Asmall",
|
||||
"Bsmall",
|
||||
"Csmall",
|
||||
"Dsmall",
|
||||
"Esmall",
|
||||
"Fsmall",
|
||||
"Gsmall",
|
||||
"Hsmall",
|
||||
"Ismall",
|
||||
"Jsmall",
|
||||
"Ksmall",
|
||||
"Lsmall",
|
||||
"Msmall",
|
||||
"Nsmall",
|
||||
"Osmall",
|
||||
"Psmall",
|
||||
"Qsmall",
|
||||
"Rsmall",
|
||||
"Ssmall",
|
||||
"Tsmall",
|
||||
"Usmall",
|
||||
"Vsmall",
|
||||
"Wsmall",
|
||||
"Xsmall",
|
||||
"Ysmall",
|
||||
"Zsmall",
|
||||
"colonmonetary",
|
||||
"onefitted",
|
||||
"rupiah",
|
||||
"Tildesmall",
|
||||
"exclamdownsmall",
|
||||
"centoldstyle",
|
||||
"Lslashsmall",
|
||||
"Scaronsmall",
|
||||
"Zcaronsmall",
|
||||
"Dieresissmall",
|
||||
"Brevesmall",
|
||||
"Caronsmall",
|
||||
"Dotaccentsmall",
|
||||
"Macronsmall",
|
||||
"figuredash",
|
||||
"hypheninferior",
|
||||
"Ogoneksmall",
|
||||
"Ringsmall",
|
||||
"Cedillasmall",
|
||||
"questiondownsmall",
|
||||
"oneeighth",
|
||||
"threeeighths",
|
||||
"fiveeighths",
|
||||
"seveneighths",
|
||||
"onethird",
|
||||
"twothirds",
|
||||
"zerosuperior",
|
||||
"foursuperior",
|
||||
"fivesuperior",
|
||||
"sixsuperior",
|
||||
"sevensuperior",
|
||||
"eightsuperior",
|
||||
"ninesuperior",
|
||||
"zeroinferior",
|
||||
"oneinferior",
|
||||
"twoinferior",
|
||||
"threeinferior",
|
||||
"fourinferior",
|
||||
"fiveinferior",
|
||||
"sixinferior",
|
||||
"seveninferior",
|
||||
"eightinferior",
|
||||
"nineinferior",
|
||||
"centinferior",
|
||||
"dollarinferior",
|
||||
"periodinferior",
|
||||
"commainferior",
|
||||
"Agravesmall",
|
||||
"Aacutesmall",
|
||||
"Acircumflexsmall",
|
||||
"Atildesmall",
|
||||
"Adieresissmall",
|
||||
"Aringsmall",
|
||||
"AEsmall",
|
||||
"Ccedillasmall",
|
||||
"Egravesmall",
|
||||
"Eacutesmall",
|
||||
"Ecircumflexsmall",
|
||||
"Edieresissmall",
|
||||
"Igravesmall",
|
||||
"Iacutesmall",
|
||||
"Icircumflexsmall",
|
||||
"Idieresissmall",
|
||||
"Ethsmall",
|
||||
"Ntildesmall",
|
||||
"Ogravesmall",
|
||||
"Oacutesmall",
|
||||
"Ocircumflexsmall",
|
||||
"Otildesmall",
|
||||
"Odieresissmall",
|
||||
"OEsmall",
|
||||
"Oslashsmall",
|
||||
"Ugravesmall",
|
||||
"Uacutesmall",
|
||||
"Ucircumflexsmall",
|
||||
"Udieresissmall",
|
||||
"Yacutesmall",
|
||||
"Thornsmall",
|
||||
"Ydieresissmall",
|
||||
"001.000",
|
||||
"001.001",
|
||||
"001.002",
|
||||
"001.003",
|
||||
"Black",
|
||||
"Bold",
|
||||
"Book",
|
||||
"Light",
|
||||
"Medium",
|
||||
"Regular",
|
||||
"Roman",
|
||||
"Semibold",
|
||||
}
|
||||
+315
@@ -0,0 +1,315 @@
|
||||
// SPDX-License-Identifier: Unlicense OR BSD-3-Clause
|
||||
|
||||
package cff
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
ps "github.com/go-text/typesetting/font/cff/interpreter"
|
||||
ot "github.com/go-text/typesetting/font/opentype"
|
||||
"github.com/go-text/typesetting/font/opentype/tables"
|
||||
)
|
||||
|
||||
// LoadGlyph parses the glyph charstring to compute segments and path bounds.
|
||||
// It returns an error if the glyph is invalid or if decoding the charstring fails.
|
||||
func (f *CFF) LoadGlyph(glyph tables.GlyphID) ([]ot.Segment, ps.PathBounds, error) {
|
||||
if int(glyph) >= len(f.Charstrings) {
|
||||
return nil, ps.PathBounds{}, errGlyph
|
||||
}
|
||||
|
||||
var (
|
||||
psi ps.Machine
|
||||
loader type2CharstringHandler
|
||||
index byte = 0
|
||||
err error
|
||||
)
|
||||
if f.fdSelect != nil {
|
||||
index, err = f.fdSelect.fontDictIndex(glyph)
|
||||
if err != nil {
|
||||
return nil, ps.PathBounds{}, err
|
||||
}
|
||||
}
|
||||
|
||||
subrs := f.localSubrs[index]
|
||||
err = psi.Run(f.Charstrings[glyph], subrs, f.globalSubrs, &loader)
|
||||
return loader.cs.Segments, loader.cs.Bounds, err
|
||||
}
|
||||
|
||||
// type2CharstringHandler implements operators needed to fetch Type2 charstring metrics
|
||||
type type2CharstringHandler struct {
|
||||
cs ps.CharstringReader
|
||||
|
||||
// found in private DICT, needed since we can't differenciate
|
||||
// no width set from 0 width
|
||||
// `width` must be initialized to default width
|
||||
nominalWidthX float64
|
||||
width float64
|
||||
}
|
||||
|
||||
func (type2CharstringHandler) Context() ps.Context { return ps.Type2Charstring }
|
||||
|
||||
func (met *type2CharstringHandler) Apply(state *ps.Machine, op ps.Operator) error {
|
||||
var err error
|
||||
if !op.IsEscaped {
|
||||
switch op.Operator {
|
||||
case 11: // return
|
||||
return state.Return() // do not clear the arg stack
|
||||
case 14: // endchar
|
||||
if state.ArgStack.Top > 0 { // width is optional
|
||||
met.width = met.nominalWidthX + state.ArgStack.Vals[0]
|
||||
}
|
||||
met.cs.ClosePath()
|
||||
return ps.ErrInterrupt
|
||||
case 10: // callsubr
|
||||
return ps.LocalSubr(state) // do not clear the arg stack
|
||||
case 29: // callgsubr
|
||||
return ps.GlobalSubr(state) // do not clear the arg stack
|
||||
case 21: // rmoveto
|
||||
if state.ArgStack.Top > 2 { // width is optional
|
||||
met.width = met.nominalWidthX + state.ArgStack.Vals[0]
|
||||
}
|
||||
err = met.cs.Rmoveto(state)
|
||||
case 22: // hmoveto
|
||||
if state.ArgStack.Top > 1 { // width is optional
|
||||
met.width = met.nominalWidthX + state.ArgStack.Vals[0]
|
||||
}
|
||||
err = met.cs.Hmoveto(state)
|
||||
case 4: // vmoveto
|
||||
if state.ArgStack.Top > 1 { // width is optional
|
||||
met.width = met.nominalWidthX + state.ArgStack.Vals[0]
|
||||
}
|
||||
err = met.cs.Vmoveto(state)
|
||||
case 1, 18: // hstem, hstemhm
|
||||
met.cs.Hstem(state)
|
||||
case 3, 23: // vstem, vstemhm
|
||||
met.cs.Vstem(state)
|
||||
case 19, 20: // hintmask, cntrmask
|
||||
// variable number of arguments, but always even
|
||||
// for xxxmask, if there are arguments on the stack, then this is an impliied stem
|
||||
if state.ArgStack.Top&1 != 0 {
|
||||
met.width = met.nominalWidthX + state.ArgStack.Vals[0]
|
||||
}
|
||||
met.cs.Hintmask(state)
|
||||
// the stack is managed by the previous call
|
||||
return nil
|
||||
|
||||
case 5: // rlineto
|
||||
met.cs.Rlineto(state)
|
||||
case 6: // hlineto
|
||||
met.cs.Hlineto(state)
|
||||
case 7: // vlineto
|
||||
met.cs.Vlineto(state)
|
||||
case 8: // rrcurveto
|
||||
met.cs.Rrcurveto(state)
|
||||
case 24: // rcurveline
|
||||
err = met.cs.Rcurveline(state)
|
||||
case 25: // rlinecurve
|
||||
err = met.cs.Rlinecurve(state)
|
||||
case 26: // vvcurveto
|
||||
met.cs.Vvcurveto(state)
|
||||
case 27: // hhcurveto
|
||||
met.cs.Hhcurveto(state)
|
||||
case 30: // vhcurveto
|
||||
met.cs.Vhcurveto(state)
|
||||
case 31: // hvcurveto
|
||||
met.cs.Hvcurveto(state)
|
||||
default:
|
||||
// no other operands are allowed before the ones handled above
|
||||
err = fmt.Errorf("invalid operator %s in charstring", op)
|
||||
}
|
||||
} else {
|
||||
switch op.Operator {
|
||||
case 34: // hflex
|
||||
err = met.cs.Hflex(state)
|
||||
case 35: // flex
|
||||
err = met.cs.Flex(state)
|
||||
case 36: // hflex1
|
||||
err = met.cs.Hflex1(state)
|
||||
case 37: // flex1
|
||||
err = met.cs.Flex1(state)
|
||||
default:
|
||||
// no other operands are allowed before the ones handled above
|
||||
err = fmt.Errorf("invalid operator %s in charstring", op)
|
||||
}
|
||||
}
|
||||
state.ArgStack.Clear()
|
||||
return err
|
||||
}
|
||||
|
||||
// ---------------------------- CFF2 format ----------------------------
|
||||
|
||||
// LoadGlyph parses the glyph charstring to compute segments and path bounds.
|
||||
// It returns an error if the glyph is invalid or if decoding the charstring fails.
|
||||
//
|
||||
// [coords] must either have the same length as the variations axis, or be empty,
|
||||
// and be normalized
|
||||
func (f *CFF2) LoadGlyph(glyph tables.GlyphID, coords []tables.Coord) ([]ot.Segment, ps.PathBounds, error) {
|
||||
if int(glyph) >= len(f.Charstrings) {
|
||||
return nil, ps.PathBounds{}, errGlyph
|
||||
}
|
||||
|
||||
var (
|
||||
psi ps.Machine
|
||||
loader cff2CharstringHandler
|
||||
index byte = 0
|
||||
err error
|
||||
)
|
||||
if f.fdSelect != nil {
|
||||
index, err = f.fdSelect.fontDictIndex(glyph)
|
||||
if err != nil {
|
||||
return nil, ps.PathBounds{}, err
|
||||
}
|
||||
}
|
||||
|
||||
font := f.fonts[index]
|
||||
|
||||
loader.coords = coords
|
||||
loader.vars = f.VarStore
|
||||
loader.setVSIndex(int(font.defaultVSIndex))
|
||||
|
||||
err = psi.Run(f.Charstrings[glyph], font.localSubrs, f.globalSubrs, &loader)
|
||||
|
||||
return loader.cs.Segments, loader.cs.Bounds, err
|
||||
}
|
||||
|
||||
// cff2CharstringHandler implements operators needed to fetch CFF2 charstring metrics
|
||||
type cff2CharstringHandler struct {
|
||||
cs ps.CharstringReader
|
||||
|
||||
coords []tables.Coord // normalized variation coordinates
|
||||
vars tables.ItemVarStore
|
||||
|
||||
// the currently active ItemVariationData subtable (default to 0)
|
||||
scalars []float32 // computed from the currently active ItemVariationData subtable
|
||||
}
|
||||
|
||||
func (cff2CharstringHandler) Context() ps.Context { return ps.Type2Charstring }
|
||||
|
||||
func (met *cff2CharstringHandler) setVSIndex(index int) error {
|
||||
// if the font has variations, always build the scalar
|
||||
// slice, even if no variations are activated by the user:
|
||||
// the blend operator needs to know how many args to skip.
|
||||
if len(met.vars.ItemVariationDatas) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
if index >= len(met.vars.ItemVariationDatas) {
|
||||
return fmt.Errorf("invalid 'vsindex' %d", index)
|
||||
}
|
||||
|
||||
vars := met.vars.ItemVariationDatas[index]
|
||||
k := int32(len(vars.RegionIndexes)) // number of regions
|
||||
met.scalars = append(met.scalars[:0], make([]float32, k)...)
|
||||
for i, regionIndex := range vars.RegionIndexes {
|
||||
region := met.vars.VariationRegionList.VariationRegions[regionIndex]
|
||||
met.scalars[i] = region.Evaluate(met.coords)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (met *cff2CharstringHandler) blend(state *ps.Machine) error {
|
||||
// blend requires n*(k+1) + 1 arguments
|
||||
if state.ArgStack.Top < 1 {
|
||||
return errors.New("missing n argument for blend operator")
|
||||
}
|
||||
n := int32(state.ArgStack.Pop())
|
||||
k := int32(len(met.scalars))
|
||||
if state.ArgStack.Top < n*(k+1) {
|
||||
return errors.New("missing arguments for blend operator")
|
||||
}
|
||||
|
||||
// actually apply the deltas only if the user has activated variations
|
||||
if len(met.coords) != 0 {
|
||||
args := state.ArgStack.Vals[state.ArgStack.Top-n*(k+1) : state.ArgStack.Top]
|
||||
// the first n values are the 'default' arguments
|
||||
for i := int32(0); i < n; i++ {
|
||||
baseValue := args[i]
|
||||
deltas := args[n+i*k : n+(i+1)*k] // all the regions, for one operand
|
||||
v := 0.
|
||||
for ik, delta := range deltas {
|
||||
v += float64(met.scalars[ik]) * delta
|
||||
}
|
||||
args[i] = baseValue + v // update the stack with the blended value
|
||||
}
|
||||
}
|
||||
|
||||
// clear the stack, keeping only n arguments
|
||||
state.ArgStack.Top -= n * k
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (met *cff2CharstringHandler) Apply(state *ps.Machine, op ps.Operator) error {
|
||||
var err error
|
||||
if !op.IsEscaped {
|
||||
switch op.Operator {
|
||||
case 1, 18: // hstem, hstemhm
|
||||
met.cs.Hstem(state)
|
||||
case 3, 23: // vstem, vstemhm
|
||||
met.cs.Vstem(state)
|
||||
case 4: // vmoveto
|
||||
err = met.cs.Vmoveto(state)
|
||||
case 5: // rlineto
|
||||
met.cs.Rlineto(state)
|
||||
case 6: // hlineto
|
||||
met.cs.Hlineto(state)
|
||||
case 7: // vlineto
|
||||
met.cs.Vlineto(state)
|
||||
case 8: // rrcurveto
|
||||
met.cs.Rrcurveto(state)
|
||||
case 10: // callsubr
|
||||
return ps.LocalSubr(state) // do not clear the arg stack
|
||||
case 15: // vsindex
|
||||
if state.ArgStack.Top < 1 {
|
||||
return errors.New("missing argument for vsindex operator")
|
||||
}
|
||||
err = met.setVSIndex(int(state.ArgStack.Pop()))
|
||||
case 16: // blend
|
||||
return met.blend(state) // do not clear the arg stack
|
||||
case 19, 20: // hintmask, cntrmask
|
||||
// variable number of arguments, but always even
|
||||
// for xxxmask, if there are arguments on the stack, then this is an impliied stem
|
||||
met.cs.Hintmask(state)
|
||||
// the stack is managed by the previous call
|
||||
return nil
|
||||
case 21: // rmoveto
|
||||
err = met.cs.Rmoveto(state)
|
||||
case 22: // hmoveto
|
||||
err = met.cs.Hmoveto(state)
|
||||
case 24: // rcurveline
|
||||
err = met.cs.Rcurveline(state)
|
||||
case 25: // rlinecurve
|
||||
err = met.cs.Rlinecurve(state)
|
||||
case 26: // vvcurveto
|
||||
met.cs.Vvcurveto(state)
|
||||
case 27: // hhcurveto
|
||||
met.cs.Hhcurveto(state)
|
||||
case 29: // callgsubr
|
||||
return ps.GlobalSubr(state) // do not clear the arg stack
|
||||
case 30: // vhcurveto
|
||||
met.cs.Vhcurveto(state)
|
||||
case 31: // hvcurveto
|
||||
met.cs.Hvcurveto(state)
|
||||
default:
|
||||
// no other operands are allowed before the ones handled above
|
||||
err = fmt.Errorf("invalid operator %s in charstring", op)
|
||||
}
|
||||
} else {
|
||||
switch op.Operator {
|
||||
case 34: // hflex
|
||||
err = met.cs.Hflex(state)
|
||||
case 35: // flex
|
||||
err = met.cs.Flex(state)
|
||||
case 36: // hflex1
|
||||
err = met.cs.Hflex1(state)
|
||||
case 37: // flex1
|
||||
err = met.cs.Flex1(state)
|
||||
default:
|
||||
// no other operands are allowed before the ones handled above
|
||||
err = fmt.Errorf("invalid operator %s in charstring", op)
|
||||
}
|
||||
}
|
||||
state.ArgStack.Clear()
|
||||
return err
|
||||
}
|
||||
+586
@@ -0,0 +1,586 @@
|
||||
// SPDX-License-Identifier: Unlicense OR BSD-3-Clause
|
||||
|
||||
package psinterpreter
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
|
||||
ot "github.com/go-text/typesetting/font/opentype"
|
||||
)
|
||||
|
||||
// PathBounds represents a control bounds for
|
||||
// a glyph outline (in font units).
|
||||
type PathBounds struct {
|
||||
Min, Max Point
|
||||
}
|
||||
|
||||
// Enlarge enlarges the bounds to include pt
|
||||
func (b *PathBounds) Enlarge(pt Point) {
|
||||
if pt.X < b.Min.X {
|
||||
b.Min.X = pt.X
|
||||
}
|
||||
if pt.X > b.Max.X {
|
||||
b.Max.X = pt.X
|
||||
}
|
||||
if pt.Y < b.Min.Y {
|
||||
b.Min.Y = pt.Y
|
||||
}
|
||||
if pt.Y > b.Max.Y {
|
||||
b.Max.Y = pt.Y
|
||||
}
|
||||
}
|
||||
|
||||
// ToExtents converts a path bounds to the corresponding glyph extents.
|
||||
func (b *PathBounds) ToExtents() ot.GlyphExtents {
|
||||
xBearing, yBearing := math.Round(b.Min.X), math.Round(b.Max.Y)
|
||||
return ot.GlyphExtents{
|
||||
XBearing: float32(xBearing),
|
||||
YBearing: float32(yBearing),
|
||||
Width: float32(math.Round(b.Max.X - xBearing)),
|
||||
Height: float32(math.Round(b.Min.Y - yBearing)),
|
||||
}
|
||||
}
|
||||
|
||||
// Point is a 2D Point in font units.
|
||||
type Point struct{ X, Y float64 }
|
||||
|
||||
// Move translates the Point.
|
||||
func (p *Point) Move(dx, dy float64) {
|
||||
p.X += dx
|
||||
p.Y += dy
|
||||
}
|
||||
|
||||
func (p Point) toSP() ot.SegmentPoint {
|
||||
return ot.SegmentPoint{X: float32(p.X), Y: float32(p.Y)}
|
||||
}
|
||||
|
||||
// CharstringReader provides implementation
|
||||
// of the operators found in a font charstring.
|
||||
type CharstringReader struct {
|
||||
// Acumulated segments for the glyph outlines
|
||||
Segments []ot.Segment
|
||||
// Acumulated bounds for the glyph outlines
|
||||
Bounds PathBounds
|
||||
|
||||
vstemCount int32
|
||||
hstemCount int32
|
||||
hintmaskSize int32
|
||||
|
||||
CurrentPoint Point
|
||||
firstPoint Point // first point in path, required to check if a path is closed
|
||||
isPathOpen bool
|
||||
|
||||
seenHintmask bool
|
||||
|
||||
// bounds for an empty path is {0,0,0,0}
|
||||
// however, for the first point in the path,
|
||||
// we must not compare the coordinates with {0,0,0,0}
|
||||
seenPoint bool
|
||||
}
|
||||
|
||||
// enlarges the current bounds to include the Point (x,y).
|
||||
func (out *CharstringReader) updateBounds(pt Point) {
|
||||
if !out.seenPoint {
|
||||
out.Bounds.Min, out.Bounds.Max = pt, pt
|
||||
out.seenPoint = true
|
||||
return
|
||||
}
|
||||
out.Bounds.Enlarge(pt)
|
||||
}
|
||||
|
||||
func (out *CharstringReader) Hstem(state *Machine) {
|
||||
out.hstemCount += state.ArgStack.Top / 2
|
||||
}
|
||||
|
||||
func (out *CharstringReader) Vstem(state *Machine) {
|
||||
out.vstemCount += state.ArgStack.Top / 2
|
||||
}
|
||||
|
||||
func (out *CharstringReader) determineHintmaskSize(state *Machine) {
|
||||
if !out.seenHintmask {
|
||||
out.vstemCount += state.ArgStack.Top / 2
|
||||
out.hintmaskSize = (out.hstemCount + out.vstemCount + 7) >> 3
|
||||
out.seenHintmask = true
|
||||
}
|
||||
}
|
||||
|
||||
func (out *CharstringReader) Hintmask(state *Machine) {
|
||||
out.determineHintmaskSize(state)
|
||||
state.SkipBytes(out.hintmaskSize)
|
||||
}
|
||||
|
||||
func (out *CharstringReader) move(pt Point) {
|
||||
out.ensureClosePath()
|
||||
|
||||
out.CurrentPoint.Move(pt.X, pt.Y)
|
||||
out.isPathOpen = false
|
||||
out.firstPoint = out.CurrentPoint
|
||||
out.Segments = append(out.Segments, ot.Segment{
|
||||
Op: ot.SegmentOpMoveTo,
|
||||
Args: [3]ot.SegmentPoint{out.CurrentPoint.toSP()},
|
||||
})
|
||||
}
|
||||
|
||||
// pt is in absolute coordinates
|
||||
func (out *CharstringReader) line(pt Point) {
|
||||
if !out.isPathOpen {
|
||||
out.isPathOpen = true
|
||||
out.updateBounds(out.CurrentPoint)
|
||||
}
|
||||
out.CurrentPoint = pt
|
||||
out.updateBounds(pt)
|
||||
out.Segments = append(out.Segments, ot.Segment{
|
||||
Op: ot.SegmentOpLineTo,
|
||||
Args: [3]ot.SegmentPoint{pt.toSP()},
|
||||
})
|
||||
}
|
||||
|
||||
func (out *CharstringReader) curve(pt1, pt2, pt3 Point) {
|
||||
if !out.isPathOpen {
|
||||
out.isPathOpen = true
|
||||
out.updateBounds(out.CurrentPoint)
|
||||
}
|
||||
/* include control Points */
|
||||
out.updateBounds(pt1)
|
||||
out.updateBounds(pt2)
|
||||
out.CurrentPoint = pt3
|
||||
out.updateBounds(pt3)
|
||||
out.Segments = append(out.Segments, ot.Segment{
|
||||
Op: ot.SegmentOpCubeTo,
|
||||
Args: [3]ot.SegmentPoint{pt1.toSP(), pt2.toSP(), pt3.toSP()},
|
||||
})
|
||||
}
|
||||
|
||||
func (out *CharstringReader) doubleCurve(pt1, pt2, pt3, pt4, pt5, pt6 Point) {
|
||||
out.curve(pt1, pt2, pt3)
|
||||
out.curve(pt4, pt5, pt6)
|
||||
}
|
||||
|
||||
func (out *CharstringReader) ensureClosePath() {
|
||||
if out.firstPoint != out.CurrentPoint {
|
||||
out.Segments = append(out.Segments, ot.Segment{
|
||||
Op: ot.SegmentOpLineTo,
|
||||
Args: [3]ot.SegmentPoint{out.firstPoint.toSP()},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------
|
||||
|
||||
// LocalSubr pops the subroutine index and call it
|
||||
func LocalSubr(state *Machine) error {
|
||||
if state.ArgStack.Top < 1 {
|
||||
return errors.New("invalid callsubr operator (empty stack)")
|
||||
}
|
||||
index := int32(state.ArgStack.Pop())
|
||||
return state.CallSubroutine(index, true)
|
||||
}
|
||||
|
||||
// GlobalSubr pops the subroutine index and call it
|
||||
func GlobalSubr(state *Machine) error {
|
||||
if state.ArgStack.Top < 1 {
|
||||
return errors.New("invalid callgsubr operator (empty stack)")
|
||||
}
|
||||
index := int32(state.ArgStack.Pop())
|
||||
return state.CallSubroutine(index, false)
|
||||
}
|
||||
|
||||
// ClosePath closes the current contour, adding
|
||||
// a segment to the first point if needed.
|
||||
func (out *CharstringReader) ClosePath() {
|
||||
out.ensureClosePath()
|
||||
out.isPathOpen = false
|
||||
}
|
||||
|
||||
func (out *CharstringReader) Rmoveto(state *Machine) error {
|
||||
if state.ArgStack.Top < 2 {
|
||||
return errors.New("invalid rmoveto operator")
|
||||
}
|
||||
y := state.ArgStack.Pop()
|
||||
x := state.ArgStack.Pop()
|
||||
out.move(Point{x, y})
|
||||
return nil
|
||||
}
|
||||
|
||||
func (out *CharstringReader) Vmoveto(state *Machine) error {
|
||||
if state.ArgStack.Top < 1 {
|
||||
return errors.New("invalid vmoveto operator")
|
||||
}
|
||||
y := state.ArgStack.Pop()
|
||||
out.move(Point{0, y})
|
||||
return nil
|
||||
}
|
||||
|
||||
func (out *CharstringReader) Hmoveto(state *Machine) error {
|
||||
if state.ArgStack.Top < 1 {
|
||||
return errors.New("invalid hmoveto operator")
|
||||
}
|
||||
x := state.ArgStack.Pop()
|
||||
out.move(Point{x, 0})
|
||||
return nil
|
||||
}
|
||||
|
||||
func (out *CharstringReader) Rlineto(state *Machine) {
|
||||
for i := int32(0); i+2 <= state.ArgStack.Top; i += 2 {
|
||||
newPoint := out.CurrentPoint
|
||||
newPoint.Move(state.ArgStack.Vals[i], state.ArgStack.Vals[i+1])
|
||||
out.line(newPoint)
|
||||
}
|
||||
state.ArgStack.Clear()
|
||||
}
|
||||
|
||||
func (out *CharstringReader) Hlineto(state *Machine) {
|
||||
var i int32
|
||||
for ; i+2 <= state.ArgStack.Top; i += 2 {
|
||||
newPoint := out.CurrentPoint
|
||||
newPoint.X += state.ArgStack.Vals[i]
|
||||
out.line(newPoint)
|
||||
newPoint.Y += state.ArgStack.Vals[i+1]
|
||||
out.line(newPoint)
|
||||
}
|
||||
if i < state.ArgStack.Top {
|
||||
newPoint := out.CurrentPoint
|
||||
newPoint.X += state.ArgStack.Vals[i]
|
||||
out.line(newPoint)
|
||||
}
|
||||
}
|
||||
|
||||
func (out *CharstringReader) Vlineto(state *Machine) {
|
||||
var i int32
|
||||
for ; i+2 <= state.ArgStack.Top; i += 2 {
|
||||
newPoint := out.CurrentPoint
|
||||
newPoint.Y += state.ArgStack.Vals[i]
|
||||
out.line(newPoint)
|
||||
newPoint.X += state.ArgStack.Vals[i+1]
|
||||
out.line(newPoint)
|
||||
}
|
||||
if i < state.ArgStack.Top {
|
||||
newPoint := out.CurrentPoint
|
||||
newPoint.Y += state.ArgStack.Vals[i]
|
||||
out.line(newPoint)
|
||||
}
|
||||
}
|
||||
|
||||
// RelativeCurveTo draws a curve with controls points computed from
|
||||
// the current point and `arg1`, `arg2`, `arg3`
|
||||
func (out *CharstringReader) RelativeCurveTo(arg1, arg2, arg3 Point) {
|
||||
pt1 := out.CurrentPoint
|
||||
pt1.Move(arg1.X, arg1.Y)
|
||||
pt2 := pt1
|
||||
pt2.Move(arg2.X, arg2.Y)
|
||||
pt3 := pt2
|
||||
pt3.Move(arg3.X, arg3.Y)
|
||||
out.curve(pt1, pt2, pt3)
|
||||
}
|
||||
|
||||
func (out *CharstringReader) Rrcurveto(state *Machine) {
|
||||
for i := int32(0); i+6 <= state.ArgStack.Top; i += 6 {
|
||||
out.RelativeCurveTo(
|
||||
Point{state.ArgStack.Vals[i], state.ArgStack.Vals[i+1]},
|
||||
Point{state.ArgStack.Vals[i+2], state.ArgStack.Vals[i+3]},
|
||||
Point{state.ArgStack.Vals[i+4], state.ArgStack.Vals[i+5]},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func (out *CharstringReader) Hhcurveto(state *Machine) {
|
||||
var (
|
||||
i int32
|
||||
pt1 = out.CurrentPoint
|
||||
)
|
||||
if (state.ArgStack.Top & 1) != 0 {
|
||||
pt1.Y += (state.ArgStack.Vals[i])
|
||||
i++
|
||||
}
|
||||
for ; i+4 <= state.ArgStack.Top; i += 4 {
|
||||
pt1.X += state.ArgStack.Vals[i]
|
||||
pt2 := pt1
|
||||
pt2.Move(state.ArgStack.Vals[i+1], state.ArgStack.Vals[i+2])
|
||||
pt3 := pt2
|
||||
pt3.X += state.ArgStack.Vals[i+3]
|
||||
out.curve(pt1, pt2, pt3)
|
||||
pt1 = out.CurrentPoint
|
||||
}
|
||||
}
|
||||
|
||||
func (out *CharstringReader) Vhcurveto(state *Machine) {
|
||||
var i int32
|
||||
if (state.ArgStack.Top % 8) >= 4 {
|
||||
pt1 := out.CurrentPoint
|
||||
pt1.Y += state.ArgStack.Vals[i]
|
||||
pt2 := pt1
|
||||
pt2.Move(state.ArgStack.Vals[i+1], state.ArgStack.Vals[i+2])
|
||||
pt3 := pt2
|
||||
pt3.X += state.ArgStack.Vals[i+3]
|
||||
i += 4
|
||||
|
||||
for ; i+8 <= state.ArgStack.Top; i += 8 {
|
||||
out.curve(pt1, pt2, pt3)
|
||||
pt1 = out.CurrentPoint
|
||||
pt1.X += (state.ArgStack.Vals[i])
|
||||
pt2 = pt1
|
||||
pt2.Move(state.ArgStack.Vals[i+1], state.ArgStack.Vals[i+2])
|
||||
pt3 = pt2
|
||||
pt3.Y += (state.ArgStack.Vals[i+3])
|
||||
out.curve(pt1, pt2, pt3)
|
||||
|
||||
pt1 = pt3
|
||||
pt1.Y += (state.ArgStack.Vals[i+4])
|
||||
pt2 = pt1
|
||||
pt2.Move(state.ArgStack.Vals[i+5], state.ArgStack.Vals[i+6])
|
||||
pt3 = pt2
|
||||
pt3.X += (state.ArgStack.Vals[i+7])
|
||||
}
|
||||
if i < state.ArgStack.Top {
|
||||
pt3.Y += (state.ArgStack.Vals[i])
|
||||
}
|
||||
out.curve(pt1, pt2, pt3)
|
||||
} else {
|
||||
for ; i+8 <= state.ArgStack.Top; i += 8 {
|
||||
pt1 := out.CurrentPoint
|
||||
pt1.Y += (state.ArgStack.Vals[i])
|
||||
pt2 := pt1
|
||||
pt2.Move(state.ArgStack.Vals[i+1], state.ArgStack.Vals[i+2])
|
||||
pt3 := pt2
|
||||
pt3.X += (state.ArgStack.Vals[i+3])
|
||||
out.curve(pt1, pt2, pt3)
|
||||
|
||||
pt1 = pt3
|
||||
pt1.X += (state.ArgStack.Vals[i+4])
|
||||
pt2 = pt1
|
||||
pt2.Move(state.ArgStack.Vals[i+5], state.ArgStack.Vals[i+6])
|
||||
pt3 = pt2
|
||||
pt3.Y += (state.ArgStack.Vals[i+7])
|
||||
if (state.ArgStack.Top-i < 16) && ((state.ArgStack.Top & 1) != 0) {
|
||||
pt3.X += (state.ArgStack.Vals[i+8])
|
||||
}
|
||||
out.curve(pt1, pt2, pt3)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (out *CharstringReader) Hvcurveto(state *Machine) {
|
||||
var i int32
|
||||
if (state.ArgStack.Top % 8) >= 4 {
|
||||
pt1 := out.CurrentPoint
|
||||
pt1.X += (state.ArgStack.Vals[i])
|
||||
pt2 := pt1
|
||||
pt2.Move(state.ArgStack.Vals[i+1], state.ArgStack.Vals[i+2])
|
||||
pt3 := pt2
|
||||
pt3.Y += (state.ArgStack.Vals[i+3])
|
||||
i += 4
|
||||
|
||||
for ; i+8 <= state.ArgStack.Top; i += 8 {
|
||||
out.curve(pt1, pt2, pt3)
|
||||
pt1 = out.CurrentPoint
|
||||
pt1.Y += (state.ArgStack.Vals[i])
|
||||
pt2 = pt1
|
||||
pt2.Move(state.ArgStack.Vals[i+1], state.ArgStack.Vals[i+2])
|
||||
pt3 = pt2
|
||||
pt3.X += (state.ArgStack.Vals[i+3])
|
||||
out.curve(pt1, pt2, pt3)
|
||||
|
||||
pt1 = pt3
|
||||
pt1.X += state.ArgStack.Vals[i+4]
|
||||
pt2 = pt1
|
||||
pt2.Move(state.ArgStack.Vals[i+5], state.ArgStack.Vals[i+6])
|
||||
pt3 = pt2
|
||||
pt3.Y += state.ArgStack.Vals[i+7]
|
||||
}
|
||||
if i < state.ArgStack.Top {
|
||||
pt3.X += (state.ArgStack.Vals[i])
|
||||
}
|
||||
out.curve(pt1, pt2, pt3)
|
||||
} else {
|
||||
for ; i+8 <= state.ArgStack.Top; i += 8 {
|
||||
pt1 := out.CurrentPoint
|
||||
pt1.X += (state.ArgStack.Vals[i])
|
||||
pt2 := pt1
|
||||
pt2.Move(state.ArgStack.Vals[i+1], state.ArgStack.Vals[i+2])
|
||||
pt3 := pt2
|
||||
pt3.Y += (state.ArgStack.Vals[i+3])
|
||||
out.curve(pt1, pt2, pt3)
|
||||
|
||||
pt1 = pt3
|
||||
pt1.Y += (state.ArgStack.Vals[i+4])
|
||||
pt2 = pt1
|
||||
pt2.Move(state.ArgStack.Vals[i+5], state.ArgStack.Vals[i+6])
|
||||
pt3 = pt2
|
||||
pt3.X += (state.ArgStack.Vals[i+7])
|
||||
if (state.ArgStack.Top-i < 16) && ((state.ArgStack.Top & 1) != 0) {
|
||||
pt3.Y += state.ArgStack.Vals[i+8]
|
||||
}
|
||||
out.curve(pt1, pt2, pt3)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (out *CharstringReader) Rcurveline(state *Machine) error {
|
||||
argCount := state.ArgStack.Top
|
||||
if argCount < 8 {
|
||||
return fmt.Errorf("expected at least 8 operands for <rcurveline>, got %d", argCount)
|
||||
}
|
||||
|
||||
var i int32
|
||||
curveLimit := argCount - 2
|
||||
for ; i+6 <= curveLimit; i += 6 {
|
||||
pt1 := out.CurrentPoint
|
||||
pt1.Move(state.ArgStack.Vals[i], state.ArgStack.Vals[i+1])
|
||||
pt2 := pt1
|
||||
pt2.Move(state.ArgStack.Vals[i+2], state.ArgStack.Vals[i+3])
|
||||
pt3 := pt2
|
||||
pt3.Move(state.ArgStack.Vals[i+4], state.ArgStack.Vals[i+5])
|
||||
out.curve(pt1, pt2, pt3)
|
||||
}
|
||||
|
||||
pt1 := out.CurrentPoint
|
||||
pt1.Move(state.ArgStack.Vals[i], state.ArgStack.Vals[i+1])
|
||||
out.line(pt1)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (out *CharstringReader) Rlinecurve(state *Machine) error {
|
||||
argCount := state.ArgStack.Top
|
||||
if argCount < 8 {
|
||||
return fmt.Errorf("expected at least 8 operands for <rlinecurve>, got %d", argCount)
|
||||
}
|
||||
var i int32
|
||||
lineLimit := argCount - 6
|
||||
for ; i+2 <= lineLimit; i += 2 {
|
||||
pt1 := out.CurrentPoint
|
||||
pt1.Move(state.ArgStack.Vals[i], state.ArgStack.Vals[i+1])
|
||||
out.line(pt1)
|
||||
}
|
||||
|
||||
pt1 := out.CurrentPoint
|
||||
pt1.Move(state.ArgStack.Vals[i], state.ArgStack.Vals[i+1])
|
||||
pt2 := pt1
|
||||
pt2.Move(state.ArgStack.Vals[i+2], state.ArgStack.Vals[i+3])
|
||||
pt3 := pt2
|
||||
pt3.Move(state.ArgStack.Vals[i+4], state.ArgStack.Vals[i+5])
|
||||
out.curve(pt1, pt2, pt3)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (out *CharstringReader) Vvcurveto(state *Machine) {
|
||||
var i int32
|
||||
pt1 := out.CurrentPoint
|
||||
if (state.ArgStack.Top & 1) != 0 {
|
||||
pt1.X += state.ArgStack.Vals[i]
|
||||
i++
|
||||
}
|
||||
for ; i+4 <= state.ArgStack.Top; i += 4 {
|
||||
pt1.Y += state.ArgStack.Vals[i]
|
||||
pt2 := pt1
|
||||
pt2.Move(state.ArgStack.Vals[i+1], state.ArgStack.Vals[i+2])
|
||||
pt3 := pt2
|
||||
pt3.Y += state.ArgStack.Vals[i+3]
|
||||
out.curve(pt1, pt2, pt3)
|
||||
pt1 = out.CurrentPoint
|
||||
}
|
||||
}
|
||||
|
||||
func (out *CharstringReader) Hflex(state *Machine) error {
|
||||
if state.ArgStack.Top != 7 {
|
||||
return fmt.Errorf("expected 7 operands for <hflex>, got %d", state.ArgStack.Top)
|
||||
}
|
||||
|
||||
pt1 := out.CurrentPoint
|
||||
pt1.X += state.ArgStack.Vals[0]
|
||||
pt2 := pt1
|
||||
pt2.Move(state.ArgStack.Vals[1], state.ArgStack.Vals[2])
|
||||
pt3 := pt2
|
||||
pt3.X += state.ArgStack.Vals[3]
|
||||
pt4 := pt3
|
||||
pt4.X += state.ArgStack.Vals[4]
|
||||
pt5 := pt4
|
||||
pt5.X += state.ArgStack.Vals[5]
|
||||
pt5.Y = pt1.Y
|
||||
pt6 := pt5
|
||||
pt6.X += state.ArgStack.Vals[6]
|
||||
|
||||
out.doubleCurve(pt1, pt2, pt3, pt4, pt5, pt6)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (out *CharstringReader) Flex(state *Machine) error {
|
||||
if state.ArgStack.Top != 13 {
|
||||
return fmt.Errorf("expected 13 operands for <flex>, got %d", state.ArgStack.Top)
|
||||
}
|
||||
|
||||
pt1 := out.CurrentPoint
|
||||
pt1.Move(state.ArgStack.Vals[0], state.ArgStack.Vals[1])
|
||||
pt2 := pt1
|
||||
pt2.Move(state.ArgStack.Vals[2], state.ArgStack.Vals[3])
|
||||
pt3 := pt2
|
||||
pt3.Move(state.ArgStack.Vals[4], state.ArgStack.Vals[5])
|
||||
pt4 := pt3
|
||||
pt4.Move(state.ArgStack.Vals[6], state.ArgStack.Vals[7])
|
||||
pt5 := pt4
|
||||
pt5.Move(state.ArgStack.Vals[8], state.ArgStack.Vals[9])
|
||||
pt6 := pt5
|
||||
pt6.Move(state.ArgStack.Vals[10], state.ArgStack.Vals[11])
|
||||
|
||||
out.doubleCurve(pt1, pt2, pt3, pt4, pt5, pt6)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (out *CharstringReader) Hflex1(state *Machine) error {
|
||||
if state.ArgStack.Top != 9 {
|
||||
return fmt.Errorf("expected 9 operands for <hflex1>, got %d", state.ArgStack.Top)
|
||||
}
|
||||
pt1 := out.CurrentPoint
|
||||
pt1.Move(state.ArgStack.Vals[0], state.ArgStack.Vals[1])
|
||||
pt2 := pt1
|
||||
pt2.Move(state.ArgStack.Vals[2], state.ArgStack.Vals[3])
|
||||
pt3 := pt2
|
||||
pt3.X += state.ArgStack.Vals[4]
|
||||
pt4 := pt3
|
||||
pt4.X += state.ArgStack.Vals[5]
|
||||
pt5 := pt4
|
||||
pt5.Move(state.ArgStack.Vals[6], state.ArgStack.Vals[7])
|
||||
pt6 := pt5
|
||||
pt6.X += state.ArgStack.Vals[8]
|
||||
pt6.Y = out.CurrentPoint.Y
|
||||
|
||||
out.doubleCurve(pt1, pt2, pt3, pt4, pt5, pt6)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (out *CharstringReader) Flex1(state *Machine) error {
|
||||
if state.ArgStack.Top != 11 {
|
||||
return fmt.Errorf("expected 11 operands for <flex1>, got %d", state.ArgStack.Top)
|
||||
}
|
||||
|
||||
var d Point
|
||||
for i := 0; i < 10; i += 2 {
|
||||
d.Move(state.ArgStack.Vals[i], state.ArgStack.Vals[i+1])
|
||||
}
|
||||
|
||||
pt1 := out.CurrentPoint
|
||||
pt1.Move(state.ArgStack.Vals[0], state.ArgStack.Vals[1])
|
||||
pt2 := pt1
|
||||
pt2.Move(state.ArgStack.Vals[2], state.ArgStack.Vals[3])
|
||||
pt3 := pt2
|
||||
pt3.Move(state.ArgStack.Vals[4], state.ArgStack.Vals[5])
|
||||
pt4 := pt3
|
||||
pt4.Move(state.ArgStack.Vals[6], state.ArgStack.Vals[7])
|
||||
pt5 := pt4
|
||||
pt5.Move(state.ArgStack.Vals[8], state.ArgStack.Vals[9])
|
||||
pt6 := pt5
|
||||
|
||||
if math.Abs(d.X) > math.Abs(d.Y) {
|
||||
pt6.X += state.ArgStack.Vals[10]
|
||||
pt6.Y = out.CurrentPoint.Y
|
||||
} else {
|
||||
pt6.X = out.CurrentPoint.X
|
||||
pt6.Y += state.ArgStack.Vals[10]
|
||||
}
|
||||
|
||||
out.doubleCurve(pt1, pt2, pt3, pt4, pt5, pt6)
|
||||
return nil
|
||||
}
|
||||
+370
@@ -0,0 +1,370 @@
|
||||
// SPDX-License-Identifier: Unlicense OR BSD-3-Clause
|
||||
|
||||
// Package psinterpreter implement a Postscript interpreter
|
||||
// required to parse .CFF files, and Type1 and Type2 Charstrings.
|
||||
// This package provides the low-level mechanisms needed to
|
||||
// read such formats; the data is consumed in higher level packages,
|
||||
// which implement `PsOperatorHandler`.
|
||||
// It also provides helpers to interpret glyph outline descriptions,
|
||||
// shared between Type1 and CFF font formats.
|
||||
//
|
||||
// See https://adobe-type-tools.github.io/font-tech-notes/pdfs/5177.Type2.pdf
|
||||
package psinterpreter
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
var (
|
||||
|
||||
// ErrInterrupt signals the interpreter to stop early, without erroring.
|
||||
ErrInterrupt = errors.New("interruption")
|
||||
|
||||
errInvalidCFFTable = errors.New("invalid ps instructions")
|
||||
errUnsupportedRealNumberEncoding = errors.New("unsupported real number encoding")
|
||||
|
||||
be = binary.BigEndian
|
||||
)
|
||||
|
||||
const (
|
||||
// psArgStackSize is the argument stack size for a PostScript interpreter,
|
||||
// set to 513 in CFF2
|
||||
// See https://learn.microsoft.com/en-us/typography/opentype/spec/cff2#appendixD
|
||||
psArgStackSize = 513
|
||||
|
||||
// Similarly, Appendix B says "Subr nesting, stack limit 10".
|
||||
psCallStackSize = 10
|
||||
|
||||
maxRealNumberStrLen = 64 // Maximum length in bytes of the "-123.456E-7" representation.
|
||||
)
|
||||
|
||||
// Context is the flavour of the Postcript language.
|
||||
type Context uint32
|
||||
|
||||
const (
|
||||
TopDict Context = iota // Top dict in CFF files
|
||||
PrivateDict // Private dict in CFF files
|
||||
Type2Charstring // Charstring in CFF files
|
||||
Type1Charstring // Charstring in Type1 font files
|
||||
)
|
||||
|
||||
type ArgStack struct {
|
||||
Vals [psArgStackSize]float64 // we have to use float64 to properly store floats and int32 values
|
||||
// Effecive size currently in use. The first value to
|
||||
// pop is at index Top-1
|
||||
Top int32
|
||||
}
|
||||
|
||||
// Uint16 returns the top level value as uint16,
|
||||
// without popping the stack.
|
||||
func (a *ArgStack) Uint16() uint16 { return uint16(a.Vals[a.Top-1]) }
|
||||
|
||||
// Pop returns the top level value and decrease `Top`
|
||||
// It will panic if the stack is empty.
|
||||
func (a *ArgStack) Pop() float64 {
|
||||
a.Top--
|
||||
return a.Vals[a.Top]
|
||||
}
|
||||
|
||||
// Clear clears the stack
|
||||
func (a *ArgStack) Clear() { a.Top = 0 }
|
||||
|
||||
// PopN check and remove the n top levels entries.
|
||||
// Passing a negative `numPop` clears all the stack.
|
||||
func (a *ArgStack) PopN(numPop int32) error {
|
||||
if a.Top < numPop {
|
||||
return fmt.Errorf("invalid number of operands in PS stack: %d", numPop)
|
||||
}
|
||||
if numPop < 0 { // pop all
|
||||
a.Top = 0
|
||||
} else {
|
||||
a.Top -= numPop
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Machine is a PostScript interpreter.
|
||||
// A same interpreter may be re-used using muliples `Run` calls.
|
||||
type Machine struct {
|
||||
localSubrs [][]byte
|
||||
globalSubrs [][]byte
|
||||
|
||||
instructions []byte
|
||||
|
||||
callStack struct {
|
||||
vals [psCallStackSize][]byte // parent instructions
|
||||
top int32 // effecive size currently in use
|
||||
}
|
||||
ArgStack ArgStack
|
||||
|
||||
parseNumberBuf [maxRealNumberStrLen]byte
|
||||
ctx Context
|
||||
}
|
||||
|
||||
// SkipBytes skips the next `count` bytes from the instructions, and clears the argument stack.
|
||||
// It does nothing if `count` exceed the length of the instructions.
|
||||
func (p *Machine) SkipBytes(count int32) {
|
||||
if int(count) >= len(p.instructions) {
|
||||
return
|
||||
}
|
||||
p.instructions = p.instructions[count:]
|
||||
p.ArgStack.Clear()
|
||||
}
|
||||
|
||||
// 5176.CFF.pdf section 4 "DICT Data" says that "Two-byte operators have an
|
||||
// initial escape byte of 12".
|
||||
const escapeByte = 12
|
||||
|
||||
// Run runs the instructions in the PostScript context asked by `handler`.
|
||||
// `localSubrs` and `globalSubrs` contains the subroutines that may be called in the instructions.
|
||||
func (p *Machine) Run(instructions []byte, localSubrs, globalSubrs [][]byte, handler OperatorHandler) error {
|
||||
p.ctx = handler.Context()
|
||||
p.instructions = instructions
|
||||
p.localSubrs = localSubrs
|
||||
p.globalSubrs = globalSubrs
|
||||
p.ArgStack.Top = 0
|
||||
p.callStack.top = 0
|
||||
|
||||
for len(p.instructions) > 0 {
|
||||
// Push a numeric operand on the stack, if applicable.
|
||||
if hasResult, err := p.parseNumber(); hasResult {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// Otherwise, execute an operator.
|
||||
b := p.instructions[0]
|
||||
p.instructions = p.instructions[1:]
|
||||
|
||||
// check for the escape byte
|
||||
escaped := b == escapeByte
|
||||
if escaped {
|
||||
if len(p.instructions) <= 0 {
|
||||
return errInvalidCFFTable
|
||||
}
|
||||
b = p.instructions[0]
|
||||
p.instructions = p.instructions[1:]
|
||||
}
|
||||
|
||||
err := handler.Apply(p, Operator{Operator: b, IsEscaped: escaped})
|
||||
if err == ErrInterrupt { // stop cleanly
|
||||
return nil
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// See 5176.CFF.pdf section 4 "DICT Data".
|
||||
func (p *Machine) parseNumber() (hasResult bool, err error) {
|
||||
number := 0.
|
||||
switch b := p.instructions[0]; {
|
||||
case b == 28:
|
||||
if len(p.instructions) < 3 {
|
||||
return true, errInvalidCFFTable
|
||||
}
|
||||
number, hasResult = float64(int16(be.Uint16(p.instructions[1:]))), true
|
||||
p.instructions = p.instructions[3:]
|
||||
|
||||
case b == 29 && p.ctx != Type2Charstring:
|
||||
if len(p.instructions) < 5 {
|
||||
return true, errInvalidCFFTable
|
||||
}
|
||||
number, hasResult = float64(int32(be.Uint32(p.instructions[1:]))), true
|
||||
p.instructions = p.instructions[5:]
|
||||
|
||||
case b == 30 && p.ctx != Type2Charstring && p.ctx != Type1Charstring:
|
||||
// Parse a real number. This isn't listed in 5176.CFF.pdf Table 3
|
||||
// "Operand Encoding" but that table lists integer encodings. Further
|
||||
// down the page it says "A real number operand is provided in addition
|
||||
// to integer operands. This operand begins with a byte value of 30
|
||||
// followed by a variable-length sequence of bytes."
|
||||
|
||||
s := p.parseNumberBuf[:0]
|
||||
p.instructions = p.instructions[1:]
|
||||
loop:
|
||||
for {
|
||||
if len(p.instructions) == 0 {
|
||||
return true, errInvalidCFFTable
|
||||
}
|
||||
by := p.instructions[0]
|
||||
p.instructions = p.instructions[1:]
|
||||
// Process by's two nibbles, high then low.
|
||||
for i := 0; i < 2; i++ {
|
||||
nib := by >> 4
|
||||
by = by << 4
|
||||
if nib == 0x0f {
|
||||
f, err := strconv.ParseFloat(string(s), 32)
|
||||
if err != nil {
|
||||
return true, errInvalidCFFTable
|
||||
}
|
||||
number, hasResult = float64(f), true
|
||||
break loop
|
||||
}
|
||||
if nib == 0x0d {
|
||||
return true, errInvalidCFFTable
|
||||
}
|
||||
if len(s)+maxNibbleDefsLength > len(p.parseNumberBuf) {
|
||||
return true, errUnsupportedRealNumberEncoding
|
||||
}
|
||||
s = append(s, nibbleDefs[nib]...)
|
||||
}
|
||||
}
|
||||
|
||||
case b < 32:
|
||||
// not a number: no-op.
|
||||
case b < 247:
|
||||
p.instructions = p.instructions[1:]
|
||||
number, hasResult = float64(b)-139, true
|
||||
case b < 251:
|
||||
if len(p.instructions) < 2 {
|
||||
return true, errInvalidCFFTable
|
||||
}
|
||||
b1 := p.instructions[1]
|
||||
p.instructions = p.instructions[2:]
|
||||
number, hasResult = float64(+int32(b-247)*256+int32(b1)+108), true
|
||||
case b < 255:
|
||||
if len(p.instructions) < 2 {
|
||||
return true, errInvalidCFFTable
|
||||
}
|
||||
b1 := p.instructions[1]
|
||||
p.instructions = p.instructions[2:]
|
||||
number, hasResult = float64(-int32(b-251)*256-int32(b1)-108), true
|
||||
case b == 255 && (p.ctx == Type2Charstring || p.ctx == Type1Charstring):
|
||||
if len(p.instructions) < 5 {
|
||||
return true, errInvalidCFFTable
|
||||
}
|
||||
intValue := int32(be.Uint32(p.instructions[1:]))
|
||||
if p.ctx == Type2Charstring {
|
||||
// 5177.Type2.pdf section 3.2 "Charstring Number Encoding" says "If the
|
||||
// charstring byte contains the value 255... [this] number is
|
||||
// interpreted as a Fixed; that is, a signed number with 16 bits of
|
||||
// fraction".
|
||||
//
|
||||
// we just round the 16.16 fixed point number to the closest integer value
|
||||
number = float64(intValue) / (1 << 16)
|
||||
hasResult = true
|
||||
} else {
|
||||
number, hasResult = float64(intValue), true
|
||||
}
|
||||
p.instructions = p.instructions[5:]
|
||||
}
|
||||
|
||||
if hasResult {
|
||||
if p.ArgStack.Top == psArgStackSize {
|
||||
return true, errInvalidCFFTable
|
||||
}
|
||||
p.ArgStack.Vals[p.ArgStack.Top] = number
|
||||
p.ArgStack.Top++
|
||||
}
|
||||
return hasResult, nil
|
||||
}
|
||||
|
||||
const maxNibbleDefsLength = len("E-")
|
||||
|
||||
// nibbleDefs encodes 5176.CFF.pdf Table 5 "Nibble Definitions".
|
||||
var nibbleDefs = [16]string{
|
||||
0x00: "0",
|
||||
0x01: "1",
|
||||
0x02: "2",
|
||||
0x03: "3",
|
||||
0x04: "4",
|
||||
0x05: "5",
|
||||
0x06: "6",
|
||||
0x07: "7",
|
||||
0x08: "8",
|
||||
0x09: "9",
|
||||
0x0a: ".",
|
||||
0x0b: "E",
|
||||
0x0c: "E-",
|
||||
0x0d: "",
|
||||
0x0e: "-",
|
||||
0x0f: "",
|
||||
}
|
||||
|
||||
// subrBias returns the subroutine index bias as per 5177.Type2.pdf section 4.7
|
||||
// "Subroutine Operators".
|
||||
func subrBias(numSubroutines int) int32 {
|
||||
if numSubroutines < 1240 {
|
||||
return 107
|
||||
}
|
||||
if numSubroutines < 33900 {
|
||||
return 1131
|
||||
}
|
||||
return 32768
|
||||
}
|
||||
|
||||
// CallSubroutine calls the subroutine, identified by its index, as found
|
||||
// in the instructions (that is, before applying the subroutine biased).
|
||||
// `isLocal` controls whether the local or global subroutines are used.
|
||||
// No argument stack modification is performed.
|
||||
func (p *Machine) CallSubroutine(index int32, isLocal bool) error {
|
||||
subrs := p.globalSubrs
|
||||
if isLocal {
|
||||
subrs = p.localSubrs
|
||||
}
|
||||
|
||||
// no bias in type1 fonts
|
||||
if p.ctx == Type2Charstring {
|
||||
index += subrBias(len(subrs))
|
||||
}
|
||||
|
||||
if index < 0 || int(index) >= len(subrs) {
|
||||
return fmt.Errorf("invalid subroutine index %d (for length %d)", index, len(subrs))
|
||||
}
|
||||
if p.callStack.top == psCallStackSize {
|
||||
return errors.New("maximum call stack size reached")
|
||||
}
|
||||
// save the current instructions
|
||||
p.callStack.vals[p.callStack.top] = p.instructions
|
||||
p.callStack.top++
|
||||
|
||||
// activate the subroutine
|
||||
p.instructions = subrs[index]
|
||||
return nil
|
||||
}
|
||||
|
||||
// Return returns from a subroutine call.
|
||||
func (p *Machine) Return() error {
|
||||
if p.callStack.top <= 0 {
|
||||
return errors.New("no subroutine has been called")
|
||||
}
|
||||
p.callStack.top--
|
||||
// restore the previous instructions
|
||||
p.instructions = p.callStack.vals[p.callStack.top]
|
||||
return nil
|
||||
}
|
||||
|
||||
// Operator is a postcript command, which may be escaped.
|
||||
type Operator struct {
|
||||
Operator byte
|
||||
IsEscaped bool
|
||||
}
|
||||
|
||||
func (p Operator) String() string {
|
||||
if p.IsEscaped {
|
||||
return fmt.Sprintf("2-byte operator (12 %d)", p.Operator)
|
||||
}
|
||||
return fmt.Sprintf("1-byte operator (%d)", p.Operator)
|
||||
}
|
||||
|
||||
// OperatorHandler defines the behaviour of an operator.
|
||||
type OperatorHandler interface {
|
||||
// Context defines the precise behaviour of the interpreter,
|
||||
// which has small nuances depending on the context.
|
||||
Context() Context
|
||||
|
||||
// Apply implements the operator defined by `operator` (which is the second byte if `escaped` is true).
|
||||
//
|
||||
// Returning `ErrInterrupt` stop the parsing of the instructions, without reporting an error.
|
||||
// It can be used as an optimization.
|
||||
Apply(state *Machine, operator Operator) error
|
||||
}
|
||||
+709
@@ -0,0 +1,709 @@
|
||||
// SPDX-License-Identifier: Unlicense OR BSD-3-Clause
|
||||
|
||||
package cff
|
||||
|
||||
// code is adapted from golang.org/x/image/font/sfnt
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
ps "github.com/go-text/typesetting/font/cff/interpreter"
|
||||
"github.com/go-text/typesetting/font/opentype"
|
||||
)
|
||||
|
||||
var errUnsupportedCFFVersion = errors.New("unsupported CFF version")
|
||||
|
||||
// CFF represents a parsed CFF font, as found in the 'CFF ' Opentype table.
|
||||
type CFF struct {
|
||||
userStrings userStrings
|
||||
fdSelect fdSelect // only valid for CIDFonts
|
||||
charset []uint16 // indexed by glyph ID
|
||||
|
||||
cidFontName string
|
||||
|
||||
// Charstrings contains the actual glyph definition.
|
||||
// It has a length of numGlyphs and is indexed by glyph ID.
|
||||
// See `LoadGlyph` for a way to intepret the glyph data.
|
||||
Charstrings [][]byte
|
||||
|
||||
fontName []byte // name from the Name INDEX
|
||||
globalSubrs [][]byte
|
||||
|
||||
// array of length 1 for non CIDFonts
|
||||
// For CIDFonts, it can be safely indexed by `fdSelect` output
|
||||
localSubrs [][][]byte
|
||||
}
|
||||
|
||||
// Parse parses a .cff font file.
|
||||
// Although CFF enables multiple font or CIDFont programs to be bundled together in a
|
||||
// single file, embedded CFF font file in PDF or in TrueType/OpenType fonts
|
||||
// shall consist of exactly one font or CIDFont. Thus, this function
|
||||
// returns an error if the file contains more than one font.
|
||||
func Parse(file []byte) (*CFF, error) {
|
||||
// read 4 bytes to check if its a supported CFF file
|
||||
if L := len(file); L < 4 {
|
||||
return nil, fmt.Errorf("EOF: expected length: %d, got %d", 4, L)
|
||||
}
|
||||
if file[0] != 1 || file[1] != 0 || file[2] != 4 {
|
||||
return nil, errUnsupportedCFFVersion
|
||||
}
|
||||
p := cffParser{src: file, offset: 4}
|
||||
out, err := p.parse()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(out) > 1 {
|
||||
return nil, errors.New("only one font is allowed CFF table")
|
||||
}
|
||||
|
||||
return &out[0], nil
|
||||
}
|
||||
|
||||
// GlyphName returns the name of the glyph or an empty string if not found.
|
||||
func (f *CFF) GlyphName(glyph opentype.GID) string {
|
||||
if f.fdSelect != nil || int(glyph) >= len(f.charset) {
|
||||
return ""
|
||||
}
|
||||
out, _ := f.userStrings.getString(f.charset[glyph])
|
||||
return out
|
||||
}
|
||||
|
||||
// since SID = 0 means .notdef, we use a reserved value
|
||||
// to mean unset
|
||||
const unsetSID = uint16(0xFFFF)
|
||||
|
||||
type userStrings [][]byte
|
||||
|
||||
// return either the predefined string or the user defined one
|
||||
func (u userStrings) getString(sid uint16) (string, error) {
|
||||
if sid == unsetSID {
|
||||
return "", nil
|
||||
}
|
||||
if sid < 391 {
|
||||
return stdStrings[sid], nil
|
||||
}
|
||||
sid -= 391
|
||||
if int(sid) >= len(u) {
|
||||
return "", fmt.Errorf("invalid glyph index %d", sid)
|
||||
}
|
||||
return string(u[sid]), nil
|
||||
}
|
||||
|
||||
// Compact Font Format (CFF) fonts are written in PostScript, a stack-based
|
||||
// programming language.
|
||||
//
|
||||
// A fundamental concept is a DICT, or a key-value map, expressed in reverse
|
||||
// Polish notation. For example, this sequence of operations:
|
||||
// - push the number 379
|
||||
// - version operator
|
||||
// - push the number 392
|
||||
// - Notice operator
|
||||
// - etc
|
||||
// - push the number 100
|
||||
// - push the number 0
|
||||
// - push the number 500
|
||||
// - push the number 800
|
||||
// - FontBBox operator
|
||||
// - etc
|
||||
//
|
||||
// defines a DICT that maps "version" to the String ID (SID) 379, "Notice" to
|
||||
// the SID 392, "FontBBox" to the four numbers [100, 0, 500, 800], etc.
|
||||
//
|
||||
// The first 391 String IDs (starting at 0) are predefined as per the CFF spec
|
||||
// Appendix A, in 5176.CFF.pdf referenced below. For example, 379 means
|
||||
// "001.000". String ID 392 is not predefined, and is mapped by a separate
|
||||
// structure, the "String INDEX", inside the CFF data. (String ID 391 is also
|
||||
// not predefined. Specifically for go-opentype-testdata/data/toys/CFFTest.otf, 391 means
|
||||
// "uni4E2D", as this font contains a glyph for U+4E2D).
|
||||
//
|
||||
// The actual glyph vectors are similarly encoded (in PostScript), in a format
|
||||
// called Type 2 Charstrings. The wire encoding is similar to but not exactly
|
||||
// the same as CFF's. For example, the byte 0x05 means FontBBox for CFF DICTs,
|
||||
// but means rlineto (relative line-to) for Type 2 Charstrings. See
|
||||
// 5176.CFF.pdf Appendix H and 5177.Type2.pdf Appendix A in the PDF files
|
||||
// referenced below.
|
||||
//
|
||||
// The relevant specifications are:
|
||||
// - http://wwwimages.adobe.com/content/dam/Adobe/en/devnet/font/pdfs/5176.CFF.pdf
|
||||
// - http://wwwimages.adobe.com/content/dam/Adobe/en/devnet/font/pdfs/5177.Type2.pdf
|
||||
type cffParser struct {
|
||||
src []byte // whole input
|
||||
offset int // current position
|
||||
}
|
||||
|
||||
func (p *cffParser) parse() ([]CFF, error) {
|
||||
// header was checked prior to this call
|
||||
|
||||
// Parse the Name INDEX.
|
||||
fontNames, err := p.parseNames()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
topDicts, err := p.parseTopDicts()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// 5176.CFF.pdf section 8 "Top DICT INDEX" says that the count here
|
||||
// should match the count of the Name INDEX
|
||||
if len(topDicts) != len(fontNames) {
|
||||
return nil, fmt.Errorf("top DICT length doest not match Names (%d, %d)", len(topDicts),
|
||||
len(fontNames))
|
||||
}
|
||||
|
||||
// parse the String INDEX.
|
||||
strs, err := p.parseUserStrings()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
out := make([]CFF, len(topDicts))
|
||||
|
||||
// use the strings to fetch the PSInfo
|
||||
for i, topDict := range topDicts {
|
||||
out[i].fontName = fontNames[i]
|
||||
out[i].userStrings = strs
|
||||
|
||||
// skip PSInfo, and cidFontName
|
||||
|
||||
out[i].cidFontName, err = strs.getString(topDict.cidFontName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// Parse the Global Subrs [Subroutines] INDEX,
|
||||
// shared among all fonts.
|
||||
globalSubrs, err := p.parseIndex()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for i, topDict := range topDicts {
|
||||
out[i].globalSubrs = globalSubrs
|
||||
|
||||
// Parse the CharStrings INDEX, whose location was found in the Top DICT.
|
||||
if err = p.seek(topDict.charStringsOffset); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out[i].Charstrings, err = p.parseIndex()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
numGlyphs := uint16(len(out[i].Charstrings))
|
||||
|
||||
out[i].charset, err = p.parseCharset(topDict.charsetOffset, numGlyphs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// skip encoding
|
||||
|
||||
if !topDict.isCIDFont {
|
||||
// Parse the Private DICT, whose location was found in the Top DICT.
|
||||
var localSubrs [][]byte
|
||||
localSubrs, err = p.parsePrivateDICT(topDict.privateDictOffset, topDict.privateDictLength)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out[i].localSubrs = [][][]byte{localSubrs}
|
||||
} else {
|
||||
// Parse the Font Dict Select data, whose location was found in the Top
|
||||
// DICT.
|
||||
out[i].fdSelect, err = p.parseFDSelect(topDict.fdSelect, numGlyphs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
indexExtent := out[i].fdSelect.extent()
|
||||
|
||||
// Parse the Font Dicts. Each one contains its own Private DICT.
|
||||
if err = p.seek(topDict.fdArray); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
topDicts, err := p.parseTopDicts()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(topDicts) < indexExtent {
|
||||
return nil, fmt.Errorf("invalid number of font dicts: %d (for %d)",
|
||||
len(topDicts), indexExtent)
|
||||
}
|
||||
multiSubrs := make([][][]byte, len(topDicts))
|
||||
for i, topDict := range topDicts {
|
||||
multiSubrs[i], err = p.parsePrivateDICT(topDict.privateDictOffset, topDict.privateDictLength)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
out[i].localSubrs = multiSubrs
|
||||
}
|
||||
}
|
||||
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (p *cffParser) parseTopDicts() ([]topDict, error) {
|
||||
// Parse the Top DICT INDEX.
|
||||
instructions, err := p.parseIndex()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
out := make([]topDict, len(instructions)) // guarded by uint16 max size
|
||||
var psi ps.Machine
|
||||
for i, buf := range instructions {
|
||||
topDict := &out[i]
|
||||
|
||||
// set default value before parsing
|
||||
topDict.underlinePosition = -100
|
||||
topDict.underlineThickness = 50
|
||||
topDict.version = unsetSID
|
||||
topDict.notice = unsetSID
|
||||
topDict.fullName = unsetSID
|
||||
topDict.familyName = unsetSID
|
||||
topDict.weight = unsetSID
|
||||
topDict.cidFontName = unsetSID
|
||||
|
||||
if err = psi.Run(buf, nil, nil, topDict); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// src does NOT includes header, but starts at the array offset
|
||||
// also returns the length read from 'src'
|
||||
func parseIndexContent(src []byte, header indexStart) ([][]byte, int, error) {
|
||||
if header.count == 0 {
|
||||
return nil, 0, nil
|
||||
}
|
||||
oSize := int(header.offSize)
|
||||
offsetArraySize := int(header.count+1) * oSize
|
||||
if L := len(src); L < offsetArraySize {
|
||||
return nil, 0, fmt.Errorf("reading INDEX offsets: EOF: expected length: %d, got %d", offsetArraySize, L)
|
||||
}
|
||||
out := make([][]byte, header.count)
|
||||
data := src[offsetArraySize:]
|
||||
|
||||
prev := 0
|
||||
for i := range out {
|
||||
// In the same paragraph, "Therefore the first element of the offset
|
||||
// array is always 1" before correcting for the off-by-1.
|
||||
loc := int(bigEndian(src[(i+1)*oSize : (i+2)*oSize]))
|
||||
|
||||
// Locations are off by 1 byte. 5176.CFF.pdf section 5 "INDEX Data"
|
||||
// says that "Offsets in the offset array are relative to the byte that
|
||||
// precedes the object data... This ensures that every object has a
|
||||
// corresponding offset which is always nonzero".
|
||||
if loc == 0 {
|
||||
return nil, 0, errors.New("invalid INDEX locations (0)")
|
||||
}
|
||||
loc--
|
||||
|
||||
if loc < prev { // Check that locations are increasing
|
||||
return nil, 0, errors.New("invalid INDEX locations (not increasing)")
|
||||
}
|
||||
|
||||
// Check that locations are in bounds, that is offsetsLength + loc <= len(src)
|
||||
if int(loc) > len(data) {
|
||||
return nil, 0, errors.New("invalid INDEX locations (out of bounds)")
|
||||
}
|
||||
|
||||
out[i] = data[prev:loc]
|
||||
prev = loc
|
||||
}
|
||||
return out, offsetArraySize + prev, nil
|
||||
}
|
||||
|
||||
// parse the general form of an index
|
||||
func (p *cffParser) parseIndex() ([][]byte, error) {
|
||||
count, offSize, err := p.parseIndexHeader()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
out, read, err := parseIndexContent(p.src[p.offset:], indexStart{count: uint32(count), offSize: offSize})
|
||||
p.offset += read
|
||||
|
||||
return out, err
|
||||
}
|
||||
|
||||
// parse the Name INDEX
|
||||
func (p *cffParser) parseNames() ([][]byte, error) {
|
||||
return p.parseIndex()
|
||||
}
|
||||
|
||||
// parse the String INDEX
|
||||
func (p *cffParser) parseUserStrings() (userStrings, error) {
|
||||
index, err := p.parseIndex()
|
||||
return userStrings(index), err
|
||||
}
|
||||
|
||||
// Parse the charset data, whose location was found in the Top DICT.
|
||||
func (p *cffParser) parseCharset(charsetOffset int32, numGlyphs uint16) ([]uint16, error) {
|
||||
// Predefined charset may have offset of 0 to 2 // Table 22
|
||||
var charset []uint16
|
||||
switch charsetOffset {
|
||||
case 0: // ISOAdobe
|
||||
charset = charsetISOAdobe[:]
|
||||
case 1: // Expert
|
||||
charset = charsetExpert[:]
|
||||
case 2: // ExpertSubset
|
||||
charset = charsetExpertSubset[:]
|
||||
default: // custom
|
||||
if err := p.seek(charsetOffset); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
buf, err := p.read(1)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
charset = make([]uint16, numGlyphs)
|
||||
switch buf[0] { // format
|
||||
case 0:
|
||||
buf, err = p.read(2 * (int(numGlyphs) - 1)) // ".notdef" is omited, and has an implicit SID of 0
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for i := uint16(1); i < numGlyphs; i++ {
|
||||
charset[i] = binary.BigEndian.Uint16(buf[2*i-2:])
|
||||
}
|
||||
case 1:
|
||||
for i := uint16(1); i < numGlyphs; {
|
||||
buf, err = p.read(3)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
first, nLeft := binary.BigEndian.Uint16(buf), uint16(buf[2])
|
||||
for j := uint16(0); j <= nLeft && i < numGlyphs; j++ {
|
||||
charset[i] = first + j
|
||||
i++
|
||||
}
|
||||
}
|
||||
case 2:
|
||||
for i := uint16(1); i < numGlyphs; {
|
||||
buf, err = p.read(4)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
first, nLeft := binary.BigEndian.Uint16(buf), binary.BigEndian.Uint16(buf[2:])
|
||||
for j := uint16(0); j <= nLeft && i < numGlyphs; j++ {
|
||||
charset[i] = first + j
|
||||
i++
|
||||
}
|
||||
}
|
||||
default:
|
||||
return nil, fmt.Errorf("invalid custom charset format %d", buf[0])
|
||||
}
|
||||
}
|
||||
return charset, nil
|
||||
}
|
||||
|
||||
// parseFDSelect parses the Font Dict Select data as per 5176.CFF.pdf section
|
||||
// 19 "FDSelect".
|
||||
func (p *cffParser) parseFDSelect(offset int32, numGlyphs uint16) (fdSelect, error) {
|
||||
if err := p.seek(offset); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out, _, err := parseFdSelect(p.src[offset:], int(numGlyphs))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, err
|
||||
}
|
||||
|
||||
// Parse Private DICT and the Local Subrs [Subroutines] INDEX
|
||||
func (p *cffParser) parsePrivateDICT(offset, length int32) ([][]byte, error) {
|
||||
if length == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
if err := p.seek(offset); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
buf, err := p.read(int(length))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var (
|
||||
psi ps.Machine
|
||||
priv privateDict
|
||||
)
|
||||
if err = psi.Run(buf, nil, nil, &priv); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if priv.subrsOffset == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// "The local subrs offset is relative to the beginning of the Private DICT data"
|
||||
if err = p.seek(offset + priv.subrsOffset); err != nil {
|
||||
return nil, errors.New("invalid local subroutines offset")
|
||||
}
|
||||
subrs, err := p.parseIndex()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return subrs, nil
|
||||
}
|
||||
|
||||
// read returns the n bytes from p.offset and advances p.offset by n.
|
||||
func (p *cffParser) read(n int) ([]byte, error) {
|
||||
if n < 0 || len(p.src) < p.offset+n {
|
||||
return nil, errors.New("invalid CFF font file (EOF)")
|
||||
}
|
||||
out := p.src[p.offset : p.offset+n]
|
||||
p.offset += n
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (p *cffParser) seek(offset int32) error {
|
||||
if offset < 0 || len(p.src) < int(offset) {
|
||||
return errors.New("invalid CFF font file (EOF)")
|
||||
}
|
||||
p.offset = int(offset)
|
||||
return nil
|
||||
}
|
||||
|
||||
func bigEndian(b []byte) uint32 {
|
||||
switch len(b) {
|
||||
case 1:
|
||||
return uint32(b[0])
|
||||
case 2:
|
||||
return uint32(b[0])<<8 | uint32(b[1])
|
||||
case 3:
|
||||
return uint32(b[0])<<16 | uint32(b[1])<<8 | uint32(b[2])
|
||||
case 4:
|
||||
return uint32(b[0])<<24 | uint32(b[1])<<16 | uint32(b[2])<<8 | uint32(b[3])
|
||||
}
|
||||
panic("unreachable")
|
||||
}
|
||||
|
||||
func (p *cffParser) parseIndexHeader() (count uint16, offSize uint8, err error) {
|
||||
buf, err := p.read(2)
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
count = binary.BigEndian.Uint16(buf)
|
||||
// 5176.CFF.pdf section 5 "INDEX Data" says that "An empty INDEX is
|
||||
// represented by a count field with a 0 value and no additional fields.
|
||||
// Thus, the total size of an empty INDEX is 2 bytes".
|
||||
if count == 0 {
|
||||
return count, 0, nil
|
||||
}
|
||||
buf, err = p.read(1)
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
offSize = buf[0]
|
||||
if offSize < 1 || 4 < offSize {
|
||||
return 0, 0, fmt.Errorf("invalid offset size %d", offSize)
|
||||
}
|
||||
return count, offSize, nil
|
||||
}
|
||||
|
||||
// topDict contains fields specific to the Top DICT context.
|
||||
type topDict struct {
|
||||
// SIDs, to be decoded using the string index
|
||||
version, notice, fullName, familyName, weight uint16
|
||||
isFixedPitch bool
|
||||
italicAngle, underlinePosition, underlineThickness float32
|
||||
charsetOffset int32
|
||||
encodingOffset int32
|
||||
charStringsOffset int32
|
||||
fdArray int32
|
||||
fdSelect int32
|
||||
isCIDFont bool
|
||||
cidFontName uint16
|
||||
privateDictOffset int32
|
||||
privateDictLength int32
|
||||
}
|
||||
|
||||
func (tp *topDict) Context() ps.Context { return ps.TopDict }
|
||||
|
||||
func (tp *topDict) Apply(state *ps.Machine, op ps.Operator) error {
|
||||
ops := topDictOperators[0]
|
||||
if op.IsEscaped {
|
||||
ops = topDictOperators[1]
|
||||
}
|
||||
if int(op.Operator) >= len(ops) {
|
||||
return fmt.Errorf("invalid operator %s in Top Dict", op)
|
||||
}
|
||||
opFunc := ops[op.Operator]
|
||||
if opFunc.run == nil {
|
||||
return fmt.Errorf("invalid operator %s in Top Dict", op)
|
||||
}
|
||||
if state.ArgStack.Top < opFunc.numPop {
|
||||
return fmt.Errorf("invalid number of arguments for operator %s in Top Dict", op)
|
||||
}
|
||||
err := opFunc.run(tp, state)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = state.ArgStack.PopN(opFunc.numPop)
|
||||
return err
|
||||
}
|
||||
|
||||
// The Top DICT operators are defined by 5176.CFF.pdf Table 9 "Top DICT
|
||||
// Operator Entries" and Table 10 "CIDFont Operator Extensions".
|
||||
type topDictOperator struct {
|
||||
// run is the function that implements the operator. Nil means that we
|
||||
// ignore the operator, other than popping its arguments off the stack.
|
||||
run func(*topDict, *ps.Machine) error
|
||||
|
||||
// numPop is the number of stack values to pop. -1 means "array" and -2
|
||||
// means "delta" as per 5176.CFF.pdf Table 6 "Operand Types".
|
||||
numPop int32
|
||||
}
|
||||
|
||||
func topDictNoOp(*topDict, *ps.Machine) error { return nil }
|
||||
|
||||
var topDictOperators = [2][]topDictOperator{
|
||||
// 1-byte operators.
|
||||
{
|
||||
0: {func(t *topDict, s *ps.Machine) error {
|
||||
t.version = s.ArgStack.Uint16()
|
||||
return nil
|
||||
}, +1 /*version*/},
|
||||
1: {func(t *topDict, s *ps.Machine) error {
|
||||
t.notice = s.ArgStack.Uint16()
|
||||
return nil
|
||||
}, +1 /*Notice*/},
|
||||
2: {func(t *topDict, s *ps.Machine) error {
|
||||
t.fullName = s.ArgStack.Uint16()
|
||||
return nil
|
||||
}, +1 /*FullName*/},
|
||||
3: {func(t *topDict, s *ps.Machine) error {
|
||||
t.familyName = s.ArgStack.Uint16()
|
||||
return nil
|
||||
}, +1 /*FamilyName*/},
|
||||
4: {func(t *topDict, s *ps.Machine) error {
|
||||
t.weight = s.ArgStack.Uint16()
|
||||
return nil
|
||||
}, +1 /*Weight*/},
|
||||
5: {topDictNoOp, -1 /*FontBBox*/},
|
||||
13: {topDictNoOp, +1 /*UniqueID*/},
|
||||
14: {topDictNoOp, -1 /*XUID*/},
|
||||
15: {func(t *topDict, s *ps.Machine) error {
|
||||
t.charsetOffset = int32(s.ArgStack.Vals[s.ArgStack.Top-1])
|
||||
return nil
|
||||
}, +1 /*charset*/},
|
||||
16: {func(t *topDict, s *ps.Machine) error {
|
||||
t.encodingOffset = int32(s.ArgStack.Vals[s.ArgStack.Top-1])
|
||||
return nil
|
||||
}, +1 /*Encoding*/},
|
||||
17: {func(t *topDict, s *ps.Machine) error {
|
||||
t.charStringsOffset = int32(s.ArgStack.Vals[s.ArgStack.Top-1])
|
||||
return nil
|
||||
}, +1 /*CharStrings*/},
|
||||
18: {func(t *topDict, s *ps.Machine) error {
|
||||
t.privateDictLength = int32(s.ArgStack.Vals[s.ArgStack.Top-2])
|
||||
t.privateDictOffset = int32(s.ArgStack.Vals[s.ArgStack.Top-1])
|
||||
return nil
|
||||
}, +2 /*Private*/},
|
||||
},
|
||||
// 2-byte operators. The first byte is the escape byte.
|
||||
{
|
||||
0: {topDictNoOp, +1 /*Copyright*/},
|
||||
1: {func(t *topDict, s *ps.Machine) error {
|
||||
t.isFixedPitch = s.ArgStack.Vals[s.ArgStack.Top-1] == 1
|
||||
return nil
|
||||
}, +1 /*isFixedPitch*/},
|
||||
2: {func(t *topDict, s *ps.Machine) error {
|
||||
t.italicAngle = float32(s.ArgStack.Vals[s.ArgStack.Top-1])
|
||||
return nil
|
||||
}, +1 /*ItalicAngle*/},
|
||||
3: {func(t *topDict, s *ps.Machine) error {
|
||||
t.underlinePosition = float32(s.ArgStack.Vals[s.ArgStack.Top-1])
|
||||
return nil
|
||||
}, +1 /*UnderlinePosition*/},
|
||||
4: {func(t *topDict, s *ps.Machine) error {
|
||||
t.underlineThickness = float32(s.ArgStack.Vals[s.ArgStack.Top-1])
|
||||
return nil
|
||||
}, +1 /*UnderlineThickness*/},
|
||||
5: {topDictNoOp, +1 /*PaintType*/},
|
||||
6: {func(_ *topDict, i *ps.Machine) error {
|
||||
if version := int(i.ArgStack.Vals[i.ArgStack.Top-1]); version != 2 {
|
||||
return fmt.Errorf("charstring type %d not supported", version)
|
||||
}
|
||||
return nil
|
||||
}, +1 /*CharstringType*/},
|
||||
7: {topDictNoOp, -1 /*FontMatrix*/},
|
||||
8: {topDictNoOp, +1 /*StrokeWidth*/},
|
||||
20: {topDictNoOp, +1 /*SyntheticBase*/},
|
||||
21: {topDictNoOp, +1 /*PostScript*/},
|
||||
22: {topDictNoOp, +1 /*BaseFontName*/},
|
||||
23: {topDictNoOp, -2 /*BaseFontBlend*/},
|
||||
30: {func(t *topDict, _ *ps.Machine) error {
|
||||
t.isCIDFont = true
|
||||
return nil
|
||||
}, +3 /*ROS*/},
|
||||
31: {topDictNoOp, +1 /*CIDFontVersion*/},
|
||||
32: {topDictNoOp, +1 /*CIDFontRevision*/},
|
||||
33: {topDictNoOp, +1 /*CIDFontType*/},
|
||||
34: {topDictNoOp, +1 /*CIDCount*/},
|
||||
35: {topDictNoOp, +1 /*UIDBase*/},
|
||||
36: {func(t *topDict, s *ps.Machine) error {
|
||||
t.fdArray = int32(s.ArgStack.Vals[s.ArgStack.Top-1])
|
||||
return nil
|
||||
}, +1 /*FDArray*/},
|
||||
37: {func(t *topDict, s *ps.Machine) error {
|
||||
t.fdSelect = int32(s.ArgStack.Vals[s.ArgStack.Top-1])
|
||||
return nil
|
||||
}, +1 /*FDSelect*/},
|
||||
38: {func(t *topDict, s *ps.Machine) error {
|
||||
t.cidFontName = s.ArgStack.Uint16()
|
||||
return nil
|
||||
}, +1 /*FontName*/},
|
||||
},
|
||||
}
|
||||
|
||||
// privateDict contains fields specific to the Private DICT context.
|
||||
type privateDict struct {
|
||||
subrsOffset int32
|
||||
defaultWidthX, nominalWidthX float64
|
||||
}
|
||||
|
||||
func (privateDict) Context() ps.Context { return ps.PrivateDict }
|
||||
|
||||
// The Private DICT operators are defined by 5176.CFF.pdf Table 23 "Private
|
||||
// DICT Operators".
|
||||
func (priv *privateDict) Apply(state *ps.Machine, op ps.Operator) error {
|
||||
if !op.IsEscaped { // 1-byte operators.
|
||||
switch op.Operator {
|
||||
case 6, 7, 8, 9: // "BlueValues" "OtherBlues" "FamilyBlues" "FamilyOtherBlues"
|
||||
return state.ArgStack.PopN(-2)
|
||||
case 10, 11: // "StdHW" "StdVW"
|
||||
return state.ArgStack.PopN(1)
|
||||
case 20: // "defaultWidthX"
|
||||
if state.ArgStack.Top < 1 {
|
||||
return errors.New("invalid stack size for 'defaultWidthX' in private Dict charstring")
|
||||
}
|
||||
priv.defaultWidthX = state.ArgStack.Vals[state.ArgStack.Top-1]
|
||||
return state.ArgStack.PopN(1)
|
||||
case 21: // "nominalWidthX"
|
||||
if state.ArgStack.Top < 1 {
|
||||
return errors.New("invalid stack size for 'nominalWidthX' in private Dict charstring")
|
||||
}
|
||||
priv.nominalWidthX = state.ArgStack.Vals[state.ArgStack.Top-1]
|
||||
return state.ArgStack.PopN(1)
|
||||
case 19: // "Subrs" pop 1
|
||||
if state.ArgStack.Top < 1 {
|
||||
return errors.New("invalid stack size for 'subrs' in private Dict charstring")
|
||||
}
|
||||
priv.subrsOffset = int32(state.ArgStack.Vals[state.ArgStack.Top-1])
|
||||
return state.ArgStack.PopN(1)
|
||||
}
|
||||
} else { // 2-byte operators. The first byte is the escape byte.
|
||||
switch op.Operator {
|
||||
case 9, 10, 11, 14, 17, 18, 19: // "BlueScale" "BlueShift" "BlueFuzz" "ForceBold" "LanguageGroup" "ExpansionFactor" "initialRandomSeed"
|
||||
return state.ArgStack.PopN(1)
|
||||
case 12, 13: // "StemSnapH" "StemSnapV"
|
||||
return state.ArgStack.PopN(-2)
|
||||
}
|
||||
}
|
||||
return errors.New("invalid operand in private Dict charstring")
|
||||
}
|
||||
+891
@@ -0,0 +1,891 @@
|
||||
// SPDX-License-Identifier: Unlicense OR BSD-3-Clause
|
||||
|
||||
package font
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"sort"
|
||||
|
||||
"github.com/go-text/typesetting/font/opentype/tables"
|
||||
)
|
||||
|
||||
// This file implements the logic needed to use a cmap.
|
||||
|
||||
var (
|
||||
_ Cmap = cmap0(nil)
|
||||
_ Cmap = cmap4(nil)
|
||||
_ Cmap = (*cmap6or10)(nil)
|
||||
_ Cmap = cmap12(nil)
|
||||
_ Cmap = cmap13(nil)
|
||||
|
||||
_ CmapIter = (*cmap0Iter)(nil)
|
||||
_ CmapIter = (*cmap4Iter)(nil)
|
||||
_ CmapIter = (*cmap6Or10Iter)(nil)
|
||||
_ CmapIter = (*cmap12Iter)(nil)
|
||||
_ CmapIter = (*cmap13Iter)(nil)
|
||||
)
|
||||
|
||||
// CmapIter is an iterator over a Cmap.
|
||||
type CmapIter interface {
|
||||
// Next returns true if the iterator still has data to yield
|
||||
Next() bool
|
||||
|
||||
// Char must be called only when `Next` has returned `true`
|
||||
Char() (rune, GID)
|
||||
}
|
||||
|
||||
// Cmap stores a compact representation of a cmap,
|
||||
// offering both on-demand rune lookup and full rune range.
|
||||
// It is conceptually equivalent to a map[rune]GID, but is often
|
||||
// implemented more efficiently.
|
||||
type Cmap interface {
|
||||
// Iter returns a new iterator over the cmap
|
||||
// Multiple iterators may be used over the same cmap
|
||||
// The returned interface is garanted not to be nil.
|
||||
Iter() CmapIter
|
||||
|
||||
// Lookup avoid the construction of a map and provides
|
||||
// an alternative when only few runes need to be fetched.
|
||||
// It returns a default value and false when no glyph is provided.
|
||||
Lookup(rune) (GID, bool)
|
||||
}
|
||||
|
||||
// ProcessCmap sanitize the given 'cmap' subtable, and select the best encoding
|
||||
// when several subtables are given.
|
||||
// When present, the variation selectors are returned.
|
||||
// [os2FontPage] is used for legacy arabic fonts.
|
||||
//
|
||||
// The returned values are copied from the input 'cmap', meaning they do not
|
||||
// retain any reference on the input storage.
|
||||
func ProcessCmap(cmap tables.Cmap, os2FontPage tables.FontPage) (Cmap, UnicodeVariations, error) {
|
||||
var (
|
||||
candidateIds []cmapID
|
||||
candidates []Cmap
|
||||
uv UnicodeVariations
|
||||
)
|
||||
for _, table := range cmap.Records {
|
||||
id := cmapID{platform: table.PlatformID, encoding: table.EncodingID}
|
||||
switch table := table.Subtable.(type) {
|
||||
case tables.CmapSubtable0:
|
||||
candidates = append(candidates, newCmap0(table))
|
||||
candidateIds = append(candidateIds, id)
|
||||
case tables.CmapSubtable2:
|
||||
// we dont support this deprecated format
|
||||
continue
|
||||
case tables.CmapSubtable4:
|
||||
cmap, err := newCmap4(table)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
candidates = append(candidates, cmap)
|
||||
candidateIds = append(candidateIds, id)
|
||||
case tables.CmapSubtable6:
|
||||
candidates = append(candidates, newCmap6(table))
|
||||
candidateIds = append(candidateIds, id)
|
||||
case tables.CmapSubtable10:
|
||||
candidates = append(candidates, newCmap10(table))
|
||||
candidateIds = append(candidateIds, id)
|
||||
case tables.CmapSubtable12:
|
||||
candidates = append(candidates, newCmap12(table))
|
||||
candidateIds = append(candidateIds, id)
|
||||
case tables.CmapSubtable13:
|
||||
candidates = append(candidates, newCmap13(table))
|
||||
candidateIds = append(candidateIds, id)
|
||||
case tables.CmapSubtable14:
|
||||
// quoting the spec :
|
||||
// This subtable format must only be used under platform ID 0 and encoding ID 5.
|
||||
if !(id.platform == 0 && id.encoding == 5) {
|
||||
return nil, nil, errors.New("invalid cmap subtable format 14 platform or encoding")
|
||||
}
|
||||
uv = newUnicodeVariations(table)
|
||||
}
|
||||
}
|
||||
|
||||
// now find the best cmap, following harfbuzz/src/hb-ot-cmap-table.hh
|
||||
|
||||
// Prefer symbol if available.
|
||||
if index := findSubtable(cmapID{tables.PlatformMicrosoft, tables.PEMicrosoftSymbolCs}, candidateIds); index != -1 {
|
||||
cm := candidates[index]
|
||||
switch os2FontPage {
|
||||
case tables.FPNone:
|
||||
cm = remaperSymbol{cm}
|
||||
case tables.FPSimpArabic:
|
||||
cm = remaperPUASimp{cm}
|
||||
case tables.FPTradArabic:
|
||||
cm = remaperPUATrad{cm}
|
||||
}
|
||||
return cm, uv, nil
|
||||
}
|
||||
|
||||
/* 32-bit subtables. */
|
||||
if index := findSubtable(cmapID{tables.PlatformMicrosoft, tables.PEMicrosoftUcs4}, candidateIds); index != -1 {
|
||||
return candidates[index], uv, nil
|
||||
}
|
||||
if index := findSubtable(cmapID{tables.PlatformUnicode, tables.PEUnicodeFull13}, candidateIds); index != -1 {
|
||||
return candidates[index], uv, nil
|
||||
}
|
||||
if index := findSubtable(cmapID{tables.PlatformUnicode, tables.PEUnicodeFull}, candidateIds); index != -1 {
|
||||
return candidates[index], uv, nil
|
||||
}
|
||||
|
||||
/* 16-bit subtables. */
|
||||
if index := findSubtable(cmapID{tables.PlatformMicrosoft, tables.PEMicrosoftUnicodeCs}, candidateIds); index != -1 {
|
||||
return candidates[index], uv, nil
|
||||
}
|
||||
if index := findSubtable(cmapID{tables.PlatformUnicode, tables.PEUnicodeBMP}, candidateIds); index != -1 {
|
||||
return candidates[index], uv, nil
|
||||
}
|
||||
if index := findSubtable(cmapID{tables.PlatformUnicode, 2}, candidateIds); index != -1 { // deprecated
|
||||
return candidates[index], uv, nil
|
||||
}
|
||||
if index := findSubtable(cmapID{tables.PlatformUnicode, 1}, candidateIds); index != -1 { // deprecated
|
||||
return candidates[index], uv, nil
|
||||
}
|
||||
if index := findSubtable(cmapID{tables.PlatformUnicode, 0}, candidateIds); index != -1 { // deprecated
|
||||
return candidates[index], uv, nil
|
||||
}
|
||||
|
||||
/* MacRoman subtable. */
|
||||
if index := findSubtable(cmapID{tables.PlatformMac, 0}, candidateIds); index != -1 {
|
||||
cm := candidates[index]
|
||||
return remaperMacroman{cm}, uv, nil
|
||||
}
|
||||
/* Any other Mac subtable; we just map ASCII for these. */
|
||||
if index := findSubtable(cmapID{tables.PlatformMac, 0xFFFF}, candidateIds); index != -1 {
|
||||
cm := candidates[index]
|
||||
return remaperAscii{cm}, uv, nil
|
||||
}
|
||||
|
||||
// uuh... fallback to the first cmap and hope for the best
|
||||
if len(candidates) != 0 {
|
||||
return candidates[0], uv, nil
|
||||
}
|
||||
return nil, nil, errors.New("unsupported cmap table")
|
||||
}
|
||||
|
||||
// cmapID groups the platform and encoding of a Cmap subtable.
|
||||
type cmapID struct {
|
||||
platform tables.PlatformID
|
||||
encoding tables.EncodingID
|
||||
}
|
||||
|
||||
func (c cmapID) key(ignoreEncoding bool) uint32 {
|
||||
if ignoreEncoding {
|
||||
c.encoding = 0
|
||||
}
|
||||
return uint32(c.platform)<<16 | uint32(c.encoding)
|
||||
}
|
||||
|
||||
// findSubtable returns the cmap index for the given platform and encoding, or -1 if not found.
|
||||
// as a special case, if [id.encoding] is 0xFFFF, encoding is ignored
|
||||
func findSubtable(id cmapID, cmaps []cmapID) int {
|
||||
ignoreEncoding := id.encoding == 0xFFFF
|
||||
key := id.key(ignoreEncoding)
|
||||
// binary search
|
||||
for i, j := 0, len(cmaps); i < j; {
|
||||
h := i + (j-i)/2
|
||||
entryKey := cmaps[h].key(ignoreEncoding)
|
||||
if key < entryKey {
|
||||
j = h
|
||||
} else if entryKey < key {
|
||||
i = h + 1
|
||||
} else {
|
||||
return h
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
// ---------------------------------- Format 0 ----------------------------------
|
||||
|
||||
// use Macintosh encoding, storing indexIntoEncoding -> glyphIndex
|
||||
type cmap0 map[rune]uint8
|
||||
|
||||
func newCmap0(cm tables.CmapSubtable0) cmap0 {
|
||||
out := make(cmap0)
|
||||
for b, gid := range cm.GlyphIdArray {
|
||||
if b == 0 {
|
||||
continue
|
||||
}
|
||||
out[tables.DecodeMacintoshByte(byte(b))] = gid
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
type cmap0Iter struct {
|
||||
data cmap0
|
||||
keys []rune
|
||||
pos int
|
||||
}
|
||||
|
||||
func (it *cmap0Iter) Next() bool {
|
||||
return it.pos < len(it.keys)
|
||||
}
|
||||
|
||||
func (it *cmap0Iter) Char() (rune, GID) {
|
||||
r := it.keys[it.pos]
|
||||
it.pos++
|
||||
return r, GID(it.data[r])
|
||||
}
|
||||
|
||||
func (s cmap0) Iter() CmapIter {
|
||||
keys := make([]rune, 0, len(s))
|
||||
for k := range s {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
return &cmap0Iter{data: s, keys: keys}
|
||||
}
|
||||
|
||||
func (s cmap0) Lookup(r rune) (GID, bool) {
|
||||
v, ok := s[r] // will be 0 if r is not in s
|
||||
return GID(v), ok
|
||||
}
|
||||
|
||||
// ---------------------------------- Format 4 ----------------------------------
|
||||
|
||||
// if indexes is nil, delta is used
|
||||
type cmapEntry16 struct {
|
||||
// we prefere not to keep a link to a buffer (via an offset)
|
||||
// and eagerly resolve it
|
||||
indexes []tables.GlyphID // length end - start + 1
|
||||
end, start uint16
|
||||
delta uint16 // arithmetic modulo 0xFFFF
|
||||
}
|
||||
|
||||
type cmap4 []cmapEntry16
|
||||
|
||||
func newCmap4(cm tables.CmapSubtable4) (cmap4, error) {
|
||||
segCount := len(cm.EndCode)
|
||||
out := make(cmap4, segCount)
|
||||
for i := range out {
|
||||
entry := cmapEntry16{
|
||||
end: cm.EndCode[i],
|
||||
start: cm.StartCode[i],
|
||||
delta: cm.IdDelta[i],
|
||||
}
|
||||
idRangeOffset := int(cm.IdRangeOffsets[i])
|
||||
|
||||
// some fonts use 0xFFFF for idRangeOff for the last segment
|
||||
if entry.start != 0xFFFF && idRangeOffset != 0 {
|
||||
// we resolve the indexes
|
||||
entry.indexes = make([]tables.GlyphID, entry.end-entry.start+1)
|
||||
indexStart := idRangeOffset/2 + i - segCount
|
||||
if len(cm.GlyphIDArray) < 2*(indexStart+len(entry.indexes)) {
|
||||
return nil, errors.New("invalid cmap subtable format 4 glyphs array length")
|
||||
}
|
||||
for j := range entry.indexes {
|
||||
index := indexStart + j
|
||||
entry.indexes[j] = tables.GlyphID(binary.BigEndian.Uint16(cm.GlyphIDArray[2*index:]))
|
||||
}
|
||||
}
|
||||
out[i] = entry
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
type cmap4Iter struct {
|
||||
data cmap4
|
||||
pos1 int // into data
|
||||
pos2 int // either into data[pos1].indexes or an offset between start and end
|
||||
}
|
||||
|
||||
func (it *cmap4Iter) Next() bool {
|
||||
return it.pos1 < len(it.data)
|
||||
}
|
||||
|
||||
func (it *cmap4Iter) Char() (r rune, gy GID) {
|
||||
entry := it.data[it.pos1]
|
||||
if entry.indexes == nil {
|
||||
r = rune(it.pos2 + int(entry.start))
|
||||
gy = GID(uint16(it.pos2) + entry.start + entry.delta)
|
||||
if uint16(it.pos2) == entry.end-entry.start {
|
||||
// we have read the last glyph in this part
|
||||
it.pos2 = 0
|
||||
it.pos1++
|
||||
} else {
|
||||
it.pos2++
|
||||
}
|
||||
} else { // pos2 is the array index
|
||||
r = rune(it.pos2) + rune(entry.start)
|
||||
gy = GID(entry.indexes[it.pos2])
|
||||
if gy != 0 {
|
||||
gy += GID(entry.delta)
|
||||
}
|
||||
if it.pos2 == len(entry.indexes)-1 {
|
||||
// we have read the last glyph in this part
|
||||
it.pos2 = 0
|
||||
it.pos1++
|
||||
} else {
|
||||
it.pos2++
|
||||
}
|
||||
}
|
||||
|
||||
return r, gy
|
||||
}
|
||||
|
||||
func (s cmap4) Iter() CmapIter { return &cmap4Iter{data: s} }
|
||||
|
||||
func (s cmap4) Lookup(r rune) (GID, bool) {
|
||||
if uint32(r) > 0xffff {
|
||||
return 0, false
|
||||
}
|
||||
// binary search
|
||||
c := uint16(r)
|
||||
for i, j := 0, len(s); i < j; {
|
||||
h := i + (j-i)/2
|
||||
entry := s[h]
|
||||
if c < entry.start {
|
||||
j = h
|
||||
} else if entry.end < c {
|
||||
i = h + 1
|
||||
} else if entry.indexes == nil {
|
||||
return GID(c + entry.delta), true
|
||||
} else {
|
||||
glyph := entry.indexes[c-entry.start]
|
||||
if glyph == 0 {
|
||||
return 0, false
|
||||
}
|
||||
return GID(uint16(glyph) + entry.delta), true
|
||||
}
|
||||
}
|
||||
return 0, false
|
||||
}
|
||||
|
||||
// ---------------------------------- Format 6 and 10 ----------------------------------
|
||||
|
||||
type cmap6or10 struct {
|
||||
entries []tables.GlyphID
|
||||
firstCode rune
|
||||
}
|
||||
|
||||
func newCmap6(cm tables.CmapSubtable6) cmap6or10 {
|
||||
return cmap6or10{entries: cm.GlyphIdArray, firstCode: rune(cm.FirstCode)}
|
||||
}
|
||||
|
||||
func newCmap10(cm tables.CmapSubtable10) cmap6or10 {
|
||||
return cmap6or10{entries: cm.GlyphIdArray, firstCode: rune(cm.StartCharCode)}
|
||||
}
|
||||
|
||||
type cmap6Or10Iter struct {
|
||||
data cmap6or10
|
||||
pos int // index into data.entries
|
||||
}
|
||||
|
||||
func (it *cmap6Or10Iter) Next() bool {
|
||||
return it.pos < len(it.data.entries)
|
||||
}
|
||||
|
||||
func (it *cmap6Or10Iter) Char() (rune, GID) {
|
||||
entry := it.data.entries[it.pos]
|
||||
r := rune(it.pos) + it.data.firstCode
|
||||
gy := GID(entry)
|
||||
it.pos++
|
||||
return r, gy
|
||||
}
|
||||
|
||||
func (s cmap6or10) Iter() CmapIter {
|
||||
return &cmap6Or10Iter{data: s}
|
||||
}
|
||||
|
||||
func (s cmap6or10) Lookup(r rune) (GID, bool) {
|
||||
if r < s.firstCode {
|
||||
return 0, false
|
||||
}
|
||||
c := int(r - s.firstCode)
|
||||
if c >= len(s.entries) {
|
||||
return 0, false
|
||||
}
|
||||
return GID(s.entries[c]), true
|
||||
}
|
||||
|
||||
// ---------------------------------- Format 12 ----------------------------------
|
||||
|
||||
type cmap12 []tables.SequentialMapGroup
|
||||
|
||||
func newCmap12(cm tables.CmapSubtable12) cmap12 { return cm.Groups }
|
||||
|
||||
type cmap12Iter struct {
|
||||
data cmap12
|
||||
pos1 int // into data
|
||||
pos2 int // offset from start
|
||||
}
|
||||
|
||||
func (it *cmap12Iter) Next() bool { return it.pos1 < len(it.data) }
|
||||
|
||||
func (it *cmap12Iter) Char() (r rune, gy GID) {
|
||||
entry := it.data[it.pos1]
|
||||
r = rune(it.pos2 + int(entry.StartCharCode))
|
||||
gy = GID(it.pos2 + int(entry.StartGlyphID))
|
||||
if uint32(it.pos2) == entry.EndCharCode-entry.StartCharCode {
|
||||
// we have read the last glyph in this part
|
||||
it.pos2 = 0
|
||||
it.pos1++
|
||||
} else {
|
||||
it.pos2++
|
||||
}
|
||||
|
||||
return r, gy
|
||||
}
|
||||
|
||||
func (s cmap12) Iter() CmapIter { return &cmap12Iter{data: s} }
|
||||
|
||||
func (s cmap12) Lookup(r rune) (GID, bool) {
|
||||
c := uint32(r)
|
||||
// binary search
|
||||
for i, j := 0, len(s); i < j; {
|
||||
h := i + (j-i)/2
|
||||
entry := s[h]
|
||||
if c < entry.StartCharCode {
|
||||
j = h
|
||||
} else if entry.EndCharCode < c {
|
||||
i = h + 1
|
||||
} else {
|
||||
return GID(c - entry.StartCharCode + entry.StartGlyphID), true
|
||||
}
|
||||
}
|
||||
return 0, false
|
||||
}
|
||||
|
||||
// ---------------------------------- Format 13 ----------------------------------
|
||||
|
||||
type cmap13 []tables.SequentialMapGroup
|
||||
|
||||
func newCmap13(cm tables.CmapSubtable13) cmap13 { return cm.Groups }
|
||||
|
||||
type cmap13Iter struct {
|
||||
data cmap13
|
||||
pos1 int // into data
|
||||
pos2 int // offset from start
|
||||
}
|
||||
|
||||
func (it *cmap13Iter) Next() bool {
|
||||
return it.pos1 < len(it.data)
|
||||
}
|
||||
|
||||
func (it *cmap13Iter) Char() (r rune, gy GID) {
|
||||
entry := it.data[it.pos1]
|
||||
r = rune(it.pos2 + int(entry.StartCharCode))
|
||||
gy = GID(entry.StartGlyphID)
|
||||
if uint32(it.pos2) == entry.EndCharCode-entry.StartCharCode {
|
||||
// we have read the last glyph in this part
|
||||
it.pos2 = 0
|
||||
it.pos1++
|
||||
} else {
|
||||
it.pos2++
|
||||
}
|
||||
|
||||
return r, gy
|
||||
}
|
||||
|
||||
func (s cmap13) Iter() CmapIter { return &cmap13Iter{data: s} }
|
||||
|
||||
func (s cmap13) Lookup(r rune) (GID, bool) {
|
||||
c := uint32(r)
|
||||
// binary search
|
||||
for i, j := 0, len(s); i < j; {
|
||||
h := i + (j-i)/2
|
||||
entry := s[h]
|
||||
if c < entry.StartCharCode {
|
||||
j = h
|
||||
} else if entry.EndCharCode < c {
|
||||
i = h + 1
|
||||
} else {
|
||||
return GID(entry.StartGlyphID), true
|
||||
}
|
||||
}
|
||||
return 0, false
|
||||
}
|
||||
|
||||
// -------------------------------- Unicode selectors --------------------------------
|
||||
|
||||
type unicodeRange struct {
|
||||
start rune
|
||||
additionalCount uint8 // 0 for a singleton range
|
||||
}
|
||||
|
||||
type uvsMapping struct {
|
||||
unicode rune
|
||||
glyphID tables.GlyphID
|
||||
}
|
||||
|
||||
type variationSelector struct {
|
||||
defaultUVS []unicodeRange
|
||||
nonDefaultUVS []uvsMapping
|
||||
varSelector rune
|
||||
}
|
||||
|
||||
func (vs variationSelector) getGlyph(r rune) (GID, uint8) {
|
||||
// binary search
|
||||
for i, j := 0, len(vs.defaultUVS); i < j; {
|
||||
h := i + (j-i)/2
|
||||
entry := vs.defaultUVS[h]
|
||||
if r < entry.start {
|
||||
j = h
|
||||
} else if entry.start+rune(entry.additionalCount) < r {
|
||||
i = h + 1
|
||||
} else {
|
||||
return 0, VariantUseDefault
|
||||
}
|
||||
}
|
||||
|
||||
for i, j := 0, len(vs.nonDefaultUVS); i < j; {
|
||||
h := i + (j-i)/2
|
||||
entry := vs.nonDefaultUVS[h].unicode
|
||||
if r < entry {
|
||||
j = h
|
||||
} else if entry < r {
|
||||
i = h + 1
|
||||
} else {
|
||||
return GID(vs.nonDefaultUVS[h].glyphID), VariantFound
|
||||
}
|
||||
}
|
||||
|
||||
return 0, VariantNotFound
|
||||
}
|
||||
|
||||
// same as binary.BigEndian.Uint32, but for 24 bit uint
|
||||
func parseUint24(b [3]byte) rune {
|
||||
return rune(b[0])<<16 | rune(b[1])<<8 | rune(b[2])
|
||||
}
|
||||
|
||||
type UnicodeVariations []variationSelector
|
||||
|
||||
func newUnicodeVariations(cm tables.CmapSubtable14) UnicodeVariations {
|
||||
out := make([]variationSelector, len(cm.VarSelectors))
|
||||
for i, sel := range cm.VarSelectors {
|
||||
vs := variationSelector{
|
||||
varSelector: parseUint24(sel.VarSelector),
|
||||
defaultUVS: make([]unicodeRange, len(sel.DefaultUVS.Ranges)),
|
||||
nonDefaultUVS: make([]uvsMapping, len(sel.NonDefaultUVS.Ranges)),
|
||||
}
|
||||
for i, r := range sel.DefaultUVS.Ranges {
|
||||
vs.defaultUVS[i] = unicodeRange{start: parseUint24(r.StartUnicodeValue), additionalCount: r.AdditionalCount}
|
||||
}
|
||||
for i, r := range sel.NonDefaultUVS.Ranges {
|
||||
vs.nonDefaultUVS[i] = uvsMapping{unicode: parseUint24(r.UnicodeValue), glyphID: r.GlyphID}
|
||||
}
|
||||
out[i] = vs
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
const (
|
||||
// VariantNotFound is returned when the font does not have a glyph for
|
||||
// the given rune and selector.
|
||||
VariantNotFound = iota
|
||||
// VariantUseDefault is returned when the regular glyph should be used (ignoring the selector).
|
||||
VariantUseDefault
|
||||
// VariantFound is returned when the font has a variant for the glyph and selector.
|
||||
VariantFound
|
||||
)
|
||||
|
||||
// GetGlyphVariant returns the glyph index to used to [r] combined with [selector],
|
||||
// with one of the tri-state flags [VariantNotFound, VariantUseDefault, VariantFound]
|
||||
func (t UnicodeVariations) GetGlyphVariant(r, selector rune) (GID, uint8) {
|
||||
// binary search
|
||||
for i, j := 0, len(t); i < j; {
|
||||
h := i + (j-i)/2
|
||||
entryKey := t[h].varSelector
|
||||
if selector < entryKey {
|
||||
j = h
|
||||
} else if entryKey < selector {
|
||||
i = h + 1
|
||||
} else {
|
||||
return t[h].getGlyph(r)
|
||||
}
|
||||
}
|
||||
return 0, VariantNotFound
|
||||
}
|
||||
|
||||
// Handle legacy font with remap
|
||||
// TODO: the Iter() and RuneRanges() method does not include the additional mapping
|
||||
|
||||
type remaperSymbol struct {
|
||||
Cmap
|
||||
}
|
||||
|
||||
func (rs remaperSymbol) Lookup(r rune) (GID, bool) {
|
||||
// try without map first
|
||||
if g, ok := rs.Cmap.Lookup(r); ok {
|
||||
return g, true
|
||||
}
|
||||
|
||||
if r <= 0x00FF {
|
||||
/* For symbol-encoded OpenType fonts, we duplicate the
|
||||
* U+F000..F0FF range at U+0000..U+00FF. That's what
|
||||
* Windows seems to do, and that's hinted about at:
|
||||
* https://docs.microsoft.com/en-us/typography/opentype/spec/recom
|
||||
* under "Non-Standard (Symbol) Fonts". */
|
||||
mapped := 0xF000 + r
|
||||
return rs.Lookup(mapped)
|
||||
}
|
||||
|
||||
return 0, false
|
||||
}
|
||||
|
||||
type remaperPUASimp struct {
|
||||
Cmap
|
||||
}
|
||||
|
||||
func (rs remaperPUASimp) Lookup(r rune) (GID, bool) {
|
||||
// try without map first
|
||||
if g, ok := rs.Cmap.Lookup(r); ok {
|
||||
return g, true
|
||||
}
|
||||
|
||||
if mapped := puaSimpLookup(r); mapped != 0 {
|
||||
return rs.Lookup(rune(mapped))
|
||||
}
|
||||
|
||||
return 0, false
|
||||
}
|
||||
|
||||
type remaperPUATrad struct {
|
||||
Cmap
|
||||
}
|
||||
|
||||
func (rs remaperPUATrad) Lookup(r rune) (GID, bool) {
|
||||
// try without map first
|
||||
if g, ok := rs.Cmap.Lookup(r); ok {
|
||||
return g, true
|
||||
}
|
||||
|
||||
if mapped := puaTradLookup(r); mapped != 0 {
|
||||
return rs.Lookup(rune(mapped))
|
||||
}
|
||||
|
||||
return 0, false
|
||||
}
|
||||
|
||||
type remaperAscii struct {
|
||||
Cmap
|
||||
}
|
||||
|
||||
func lookupAscii(cmap Cmap, r rune) (GID, bool) {
|
||||
if r < 0x80 {
|
||||
return cmap.Lookup(r)
|
||||
}
|
||||
return 0, false
|
||||
}
|
||||
|
||||
func (rs remaperAscii) Lookup(r rune) (GID, bool) { return lookupAscii(rs.Cmap, r) }
|
||||
|
||||
type remaperMacroman struct {
|
||||
Cmap
|
||||
}
|
||||
|
||||
func (rs remaperMacroman) Lookup(r rune) (GID, bool) {
|
||||
if g, ok := lookupAscii(rs.Cmap, r); ok {
|
||||
return g, ok
|
||||
}
|
||||
if mapped := unicodeToMacroman(r); mapped != 0 {
|
||||
return rs.Cmap.Lookup(mapped)
|
||||
}
|
||||
|
||||
return 0, false
|
||||
}
|
||||
|
||||
// assume u is not in ASCII range
|
||||
func unicodeToMacroman(u rune) rune {
|
||||
mapping := [...]struct {
|
||||
unicode uint16
|
||||
macroman uint8
|
||||
}{
|
||||
{0x00A0, 0xCA},
|
||||
{0x00A1, 0xC1},
|
||||
{0x00A2, 0xA2},
|
||||
{0x00A3, 0xA3},
|
||||
{0x00A5, 0xB4},
|
||||
{0x00A7, 0xA4},
|
||||
{0x00A8, 0xAC},
|
||||
{0x00A9, 0xA9},
|
||||
{0x00AA, 0xBB},
|
||||
{0x00AB, 0xC7},
|
||||
{0x00AC, 0xC2},
|
||||
{0x00AE, 0xA8},
|
||||
{0x00AF, 0xF8},
|
||||
{0x00B0, 0xA1},
|
||||
{0x00B1, 0xB1},
|
||||
{0x00B4, 0xAB},
|
||||
{0x00B5, 0xB5},
|
||||
{0x00B6, 0xA6},
|
||||
{0x00B7, 0xE1},
|
||||
{0x00B8, 0xFC},
|
||||
{0x00BA, 0xBC},
|
||||
{0x00BB, 0xC8},
|
||||
{0x00BF, 0xC0},
|
||||
{0x00C0, 0xCB},
|
||||
{0x00C1, 0xE7},
|
||||
{0x00C2, 0xE5},
|
||||
{0x00C3, 0xCC},
|
||||
{0x00C4, 0x80},
|
||||
{0x00C5, 0x81},
|
||||
{0x00C6, 0xAE},
|
||||
{0x00C7, 0x82},
|
||||
{0x00C8, 0xE9},
|
||||
{0x00C9, 0x83},
|
||||
{0x00CA, 0xE6},
|
||||
{0x00CB, 0xE8},
|
||||
{0x00CC, 0xED},
|
||||
{0x00CD, 0xEA},
|
||||
{0x00CE, 0xEB},
|
||||
{0x00CF, 0xEC},
|
||||
{0x00D1, 0x84},
|
||||
{0x00D2, 0xF1},
|
||||
{0x00D3, 0xEE},
|
||||
{0x00D4, 0xEF},
|
||||
{0x00D5, 0xCD},
|
||||
{0x00D6, 0x85},
|
||||
{0x00D8, 0xAF},
|
||||
{0x00D9, 0xF4},
|
||||
{0x00DA, 0xF2},
|
||||
{0x00DB, 0xF3},
|
||||
{0x00DC, 0x86},
|
||||
{0x00DF, 0xA7},
|
||||
{0x00E0, 0x88},
|
||||
{0x00E1, 0x87},
|
||||
{0x00E2, 0x89},
|
||||
{0x00E3, 0x8B},
|
||||
{0x00E4, 0x8A},
|
||||
{0x00E5, 0x8C},
|
||||
{0x00E6, 0xBE},
|
||||
{0x00E7, 0x8D},
|
||||
{0x00E8, 0x8F},
|
||||
{0x00E9, 0x8E},
|
||||
{0x00EA, 0x90},
|
||||
{0x00EB, 0x91},
|
||||
{0x00EC, 0x93},
|
||||
{0x00ED, 0x92},
|
||||
{0x00EE, 0x94},
|
||||
{0x00EF, 0x95},
|
||||
{0x00F1, 0x96},
|
||||
{0x00F2, 0x98},
|
||||
{0x00F3, 0x97},
|
||||
{0x00F4, 0x99},
|
||||
{0x00F5, 0x9B},
|
||||
{0x00F6, 0x9A},
|
||||
{0x00F7, 0xD6},
|
||||
{0x00F8, 0xBF},
|
||||
{0x00F9, 0x9D},
|
||||
{0x00FA, 0x9C},
|
||||
{0x00FB, 0x9E},
|
||||
{0x00FC, 0x9F},
|
||||
{0x00FF, 0xD8},
|
||||
{0x0131, 0xF5},
|
||||
{0x0152, 0xCE},
|
||||
{0x0153, 0xCF},
|
||||
{0x0178, 0xD9},
|
||||
{0x0192, 0xC4},
|
||||
{0x02C6, 0xF6},
|
||||
{0x02C7, 0xFF},
|
||||
{0x02D8, 0xF9},
|
||||
{0x02D9, 0xFA},
|
||||
{0x02DA, 0xFB},
|
||||
{0x02DB, 0xFE},
|
||||
{0x02DC, 0xF7},
|
||||
{0x02DD, 0xFD},
|
||||
{0x03A9, 0xBD},
|
||||
{0x03C0, 0xB9},
|
||||
{0x2013, 0xD0},
|
||||
{0x2014, 0xD1},
|
||||
{0x2018, 0xD4},
|
||||
{0x2019, 0xD5},
|
||||
{0x201A, 0xE2},
|
||||
{0x201C, 0xD2},
|
||||
{0x201D, 0xD3},
|
||||
{0x201E, 0xE3},
|
||||
{0x2020, 0xA0},
|
||||
{0x2021, 0xE0},
|
||||
{0x2022, 0xA5},
|
||||
{0x2026, 0xC9},
|
||||
{0x2030, 0xE4},
|
||||
{0x2039, 0xDC},
|
||||
{0x203A, 0xDD},
|
||||
{0x2044, 0xDA},
|
||||
{0x20AC, 0xDB},
|
||||
{0x2122, 0xAA},
|
||||
{0x2202, 0xB6},
|
||||
{0x2206, 0xC6},
|
||||
{0x220F, 0xB8},
|
||||
{0x2211, 0xB7},
|
||||
{0x221A, 0xC3},
|
||||
{0x221E, 0xB0},
|
||||
{0x222B, 0xBA},
|
||||
{0x2248, 0xC5},
|
||||
{0x2260, 0xAD},
|
||||
{0x2264, 0xB2},
|
||||
{0x2265, 0xB3},
|
||||
{0x25CA, 0xD7},
|
||||
{0xF8FF, 0xF0},
|
||||
{0xFB01, 0xDE},
|
||||
{0xFB02, 0xDF},
|
||||
}
|
||||
i := sort.Search(len(mapping), func(i int) bool { return u <= rune(mapping[i].unicode) })
|
||||
if i < len(mapping) && rune(mapping[i].unicode) == u {
|
||||
return rune(mapping[i].macroman)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// ---------------------------- efficent rune set support -----------------------------------------
|
||||
|
||||
// CmapRuneRanger is implemented by cmaps whose coverage is defined in terms
|
||||
// of rune ranges
|
||||
type CmapRuneRanger interface {
|
||||
// RuneRanges returns a list of (start, end) rune pairs, both included.
|
||||
// `dst` is an optional buffer used to reduce allocations
|
||||
RuneRanges(dst [][2]rune) [][2]rune
|
||||
}
|
||||
|
||||
var (
|
||||
_ CmapRuneRanger = cmap4(nil)
|
||||
_ CmapRuneRanger = (*cmap6or10)(nil)
|
||||
_ CmapRuneRanger = cmap12(nil)
|
||||
_ CmapRuneRanger = cmap13(nil)
|
||||
)
|
||||
|
||||
func (cm cmap4) RuneRanges(dst [][2]rune) [][2]rune {
|
||||
if cap(dst) < len(cm) {
|
||||
dst = make([][2]rune, 0, len(cm))
|
||||
}
|
||||
dst = dst[:0]
|
||||
for _, e := range cm {
|
||||
start, end := rune(e.start), rune(e.end)
|
||||
if L := len(dst); L != 0 && dst[L-1][1] == start {
|
||||
// grow the previous range
|
||||
dst[L-1][1] = end
|
||||
} else {
|
||||
dst = append(dst, [2]rune{start, end})
|
||||
}
|
||||
}
|
||||
return dst
|
||||
}
|
||||
|
||||
func (cm *cmap6or10) RuneRanges(dst [][2]rune) [][2]rune {
|
||||
if cap(dst) < 1 {
|
||||
dst = [][2]rune{{}}
|
||||
}
|
||||
dst = dst[:1]
|
||||
dst[0] = [2]rune{cm.firstCode, cm.firstCode + rune(len(cm.entries)) - 1}
|
||||
return dst
|
||||
}
|
||||
|
||||
func (cm cmap12) RuneRanges(dst [][2]rune) [][2]rune {
|
||||
if cap(dst) < len(cm) {
|
||||
dst = make([][2]rune, 0, len(cm))
|
||||
}
|
||||
dst = dst[:0]
|
||||
for _, e := range cm {
|
||||
start, end := rune(e.StartCharCode), rune(e.EndCharCode)
|
||||
if L := len(dst); L != 0 && dst[L-1][1] == start {
|
||||
// grow the previous range
|
||||
dst[L-1][1] = end
|
||||
} else {
|
||||
dst = append(dst, [2]rune{start, end})
|
||||
}
|
||||
}
|
||||
return dst
|
||||
}
|
||||
|
||||
func (cm cmap13) RuneRanges(dst [][2]rune) [][2]rune { return cmap12(cm).RuneRanges(dst) }
|
||||
+106
@@ -0,0 +1,106 @@
|
||||
// SPDX-License-Identifier: Unlicense OR BSD-3-Clause
|
||||
|
||||
package font
|
||||
|
||||
// Code generated by typesettings-utils/generators/unicodedata/cmd/main.go DO NOT EDIT.
|
||||
|
||||
var puaSimpUint16 = [320]uint16{
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 61728, 61729, 61730, 0, 0, 61733, 0, 0, 61736, 61737, 61738, 61739,
|
||||
61790, 61741, 61742, 61743, 61872, 61873, 61874, 61875, 61876, 61877, 61878, 61879, 61880, 61881, 61754, 61755, 0, 61757, 0, 61759,
|
||||
0, 0, 0, 61787, 61788, 61789, 0, 0, 0, 0, 0, 61731, 0, 0, 0, 0, 0, 0, 0, 61732,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 61734, 0, 0, 0, 0, 0, 0, 0, 61735,
|
||||
0, 0, 0, 0, 61740, 0, 0, 0, 0, 0, 0, 61755, 0, 0, 0, 61759, 0, 61869, 61765, 61763,
|
||||
61883, 61767, 61882, 61761, 61770, 61865, 61772, 61774, 61777, 61780, 61783, 61784, 61785, 61786, 61792, 61794, 61796, 61798, 61800, 61801,
|
||||
61802, 61806, 61810, 61696, 61696, 61696, 61696, 61696, 61791, 61813, 61816, 61818, 61820, 61822, 61921, 61860, 61861, 61868, 61864, 61895,
|
||||
61896, 61899, 61892, 61893, 61898, 61897, 61894, 61696, 61696, 61696, 61696, 61696, 61696, 61696, 61696, 61696, 61696, 61696, 61696, 0,
|
||||
61744, 61745, 61746, 61747, 61748, 61749, 61750, 61751, 61752, 61753, 0, 61790, 61790, 0, 0, 0, 0, 0, 0, 0,
|
||||
61708, 61709, 61710, 61711, 61756, 61758, 0, 0, 0, 0, 0, 0, 0, 61765, 61766, 61763, 61764, 61883, 61883, 61767,
|
||||
61768, 61882, 61871, 61870, 61870, 61761, 61762, 61770, 61770, 61769, 61769, 61865, 61866, 61772, 61772, 61771, 61771, 61774, 61774, 61773,
|
||||
61773, 61777, 61776, 61775, 61775, 61780, 61779, 61778, 61778, 61783, 61782, 61781, 61781, 61784, 61784, 61785, 61785, 61786, 61786, 61792,
|
||||
61792, 61794, 61794, 61793, 61793, 61796, 61796, 61795, 61795, 61798, 61798, 61797, 61797, 61800, 61800, 61799, 61799, 61801, 61801, 61801,
|
||||
61801, 61802, 61802, 61802, 61802, 61806, 61805, 61803, 61804, 61810, 61809, 61807, 61808, 61813, 61813, 61811, 61812, 61816, 61816, 61814,
|
||||
61815, 61818, 61818, 61817, 61817, 61820, 61820, 61819, 61819, 61822, 61822, 61821, 61821, 61921, 61921, 61823, 61823, 61860, 61859, 61857,
|
||||
61858, 61861, 61861, 61868, 61867, 61864, 61863, 61862, 61862, 61888, 61889, 61886, 61887, 61890, 61891, 61885, 61884, 0, 0, 0,
|
||||
}
|
||||
|
||||
var puaSimpUint8 = [136]uint8{
|
||||
84, 86, 85, 85, 85, 85, 85, 213, 16, 34, 34, 34, 34, 34, 35, 34, 34, 34, 34, 34,
|
||||
34, 34, 34, 34, 36, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 82, 16,
|
||||
0, 0, 0, 0, 1, 2, 3, 4, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 6, 0, 7, 0, 0, 8, 0, 0, 0, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 0, 11, 12, 13, 14, 15, 16, 17, 18, 19,
|
||||
20, 21, 0, 0, 0, 22, 0, 23, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39,
|
||||
}
|
||||
|
||||
// Total size 776 B.
|
||||
|
||||
func puaSimpBits2(a []uint8, i int) uint8 {
|
||||
return (a[i>>2] >> ((i & 3) << 1)) & 0b11
|
||||
}
|
||||
func puaSimpBits4(a []uint8, i int) uint8 {
|
||||
return (a[i>>1] >> ((i & 1) << 2)) & 0b1111
|
||||
}
|
||||
func puaSimpLookup(u rune) uint16 {
|
||||
if 0 <= u && u < 65277 {
|
||||
return puaSimpUint16[int((int(puaSimpUint8[40+int(int((int(puaSimpBits4(puaSimpUint8[8:], int((int(puaSimpBits2(puaSimpUint8[:], int(((u>>3)>>4)>>4))))<<4)+int(((u>>3)>>4)&15))))<<4)+int((u>>3)&15))]))<<3)+int(u&7)]
|
||||
} else {
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
var puaTradUint16 = [400]uint16{
|
||||
0, 0, 0, 0, 61984, 61985, 61986, 0, 0, 61989, 0, 0, 61992, 61993, 61994, 61995, 62046, 61997, 61998, 61999,
|
||||
0, 0, 62010, 62011, 0, 62013, 0, 62015, 0, 0, 0, 62043, 0, 62045, 0, 0, 0, 0, 0, 61987,
|
||||
0, 0, 0, 61988, 0, 0, 0, 61990, 0, 0, 0, 61991, 61996, 0, 0, 0, 0, 0, 0, 62011,
|
||||
0, 0, 0, 62015, 0, 62165, 62021, 62019, 62170, 62023, 62169, 62017, 62028, 62161, 62032, 62036, 62040, 62048, 62052, 62053,
|
||||
62055, 62057, 62059, 62064, 62068, 62072, 62078, 62114, 62115, 62122, 62126, 61952, 61952, 61952, 61952, 61952, 62047, 62130, 62134, 62138,
|
||||
62142, 62146, 62150, 62154, 62155, 62164, 62160, 62183, 62184, 62187, 62180, 62181, 62186, 62185, 62182, 61952, 61952, 61952, 61952, 0,
|
||||
62000, 62001, 62002, 62003, 62004, 62005, 62006, 62007, 62008, 62009, 0, 62046, 62046, 0, 0, 0, 61964, 61965, 61966, 61967,
|
||||
62012, 62014, 0, 0, 61954, 0, 61981, 0, 0, 0, 61955, 0, 61982, 0, 61956, 0, 0, 0, 62111, 0,
|
||||
0, 0, 0, 61970, 61971, 61972, 61957, 0, 61980, 0, 0, 0, 0, 0, 61958, 0, 61983, 0, 0, 0,
|
||||
0, 0, 62191, 0, 62188, 62189, 62192, 0, 0, 0, 61973, 0, 0, 62098, 0, 0, 61974, 0, 0, 62099,
|
||||
0, 0, 62101, 0, 0, 61975, 0, 0, 62100, 0, 0, 0, 62080, 62081, 62082, 62102, 0, 62083, 62084, 62085,
|
||||
62103, 0, 0, 0, 62106, 0, 62107, 0, 62108, 0, 0, 0, 61976, 0, 0, 0, 0, 62086, 62087, 62088,
|
||||
62109, 61978, 62089, 62090, 62091, 62110, 62093, 62094, 0, 62104, 0, 0, 0, 0, 62095, 62096, 62097, 62105, 0, 0,
|
||||
61977, 0, 0, 0, 0, 0, 62075, 62077, 61968, 0, 0, 0, 0, 62021, 62022, 62019, 62020, 62170, 62171, 62023,
|
||||
62024, 62169, 62168, 62166, 62167, 62017, 62018, 62028, 62027, 62025, 62026, 62161, 62162, 62032, 62031, 62029, 62030, 62036, 62035, 62033,
|
||||
62034, 62040, 62039, 62037, 62038, 62048, 62044, 62041, 62042, 62052, 62051, 62049, 62050, 62053, 62054, 62055, 62056, 62057, 62058, 62059,
|
||||
62060, 62064, 62063, 62061, 62062, 62068, 62067, 62065, 62066, 62072, 62071, 62069, 62070, 62078, 62076, 62073, 62074, 62114, 62113, 62079,
|
||||
62193, 62118, 62117, 62115, 62116, 62122, 62121, 62119, 62120, 62126, 62125, 62123, 62124, 62130, 62129, 62127, 62128, 62134, 62133, 62131,
|
||||
62132, 62138, 62137, 62135, 62136, 62142, 62141, 62139, 62140, 62146, 62145, 62143, 62144, 62150, 62149, 62147, 62148, 62154, 62153, 62151,
|
||||
62152, 62155, 62156, 62164, 62163, 62160, 62159, 62157, 62158, 62176, 62177, 62174, 62175, 62178, 62179, 62172, 62173, 0, 0, 0,
|
||||
}
|
||||
|
||||
var puaTradUint8 = [328]uint8{
|
||||
16, 34, 34, 34, 35, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34,
|
||||
34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 66, 16, 50, 68, 68, 68, 68, 68, 68,
|
||||
68, 68, 68, 68, 101, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 71, 68, 68, 68,
|
||||
68, 68, 68, 68, 152, 186, 76, 77, 68, 254, 16, 50, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
1, 2, 3, 4, 0, 0, 5, 6, 0, 0, 0, 0, 0, 0, 7, 8, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 0, 0, 0, 10, 0,
|
||||
0, 0, 0, 0, 0, 11, 0, 0, 0, 0, 0, 0, 0, 12, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 13, 0, 0, 14, 15,
|
||||
16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 23, 23, 29, 30, 31, 32, 33,
|
||||
0, 0, 0, 0, 0, 0, 0, 34, 0, 0, 0, 35, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 36, 37, 38, 0, 0, 0, 0, 0, 0, 0, 39, 0, 0, 40, 41, 42, 0, 43,
|
||||
44, 0, 0, 45, 46, 0, 47, 48, 49, 0, 0, 0, 0, 50, 0, 0, 51, 52, 0, 53,
|
||||
54, 55, 56, 57, 58, 0, 0, 0, 0, 0, 59, 60, 61, 62, 63, 64, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 65, 0, 0, 66,
|
||||
0, 0, 67, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 68, 69, 70, 71,
|
||||
72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91,
|
||||
92, 93, 94, 95, 96, 97, 98, 99,
|
||||
}
|
||||
|
||||
// Total size 1128 B.
|
||||
|
||||
func puaTradBits4(a []uint8, i int) uint8 {
|
||||
return (a[i>>1] >> ((i & 1) << 2)) & 0b1111
|
||||
}
|
||||
func puaTradLookup(u rune) uint16 {
|
||||
if 0 <= u && u < 65277 {
|
||||
return puaTradUint16[int((int(puaTradUint8[72+int(int((int(puaTradBits4(puaTradUint8[32:], int((int(puaTradBits4(puaTradUint8[:], int(((u>>2)>>4)>>4))))<<4)+int(((u>>2)>>4)&15))))<<4)+int((u>>2)&15))]))<<2)+int(u&3)]
|
||||
} else {
|
||||
return 0
|
||||
}
|
||||
}
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
package font
|
||||
|
||||
// Code generated by typesetting-utils/generators/cache/gen.go. DO NOT EDIT.
|
||||
|
||||
/* Implements caches for integers key->value functions.
|
||||
*
|
||||
* The cache is a fixed-size array of 8-bit, 16-bit or 32-bit integers,
|
||||
* typically 256 elements.
|
||||
*
|
||||
* The key is split into two parts: the cache index (high bits)
|
||||
* and the rest (low bits).
|
||||
*
|
||||
* The memory layout is the following :
|
||||
* KEY = <key bits - cache bits><cache bits>
|
||||
* VALUE = <key bits - cache bits><value bits>
|
||||
* with the constraints
|
||||
* KEY in [0, 2^key bits[
|
||||
* VALUE in [0, 2^value bits[
|
||||
*
|
||||
* The cache index is used to index into the array. The array
|
||||
* member is an integer that is used BOTH
|
||||
* to store the low bits of the key, and the value.
|
||||
*
|
||||
* The value is stored in the least significant bits of the integer.
|
||||
* The low bits of the key are stored in the most significant bits
|
||||
* of the integer.
|
||||
*
|
||||
* A cache hit is detected by comparing the low bits of the key
|
||||
* with the high bits of the integer at the array position indexed
|
||||
* by the high bits of the key. If they match, the value is extracted
|
||||
* from the least significant bits of the integer and returned.
|
||||
* Otherwise, a cache miss is reported.
|
||||
*
|
||||
* Cache operations (storage and retrieval) involve just a few
|
||||
* arithmetic operations and a single memory access.
|
||||
*/
|
||||
|
||||
// cache21_19_8 is a cache for integer (key, value) pairs,
|
||||
// with 0 <= key < 2097152 and 0 <= value < 524288
|
||||
type cache21_19_8 [1 << 8]uint32
|
||||
|
||||
// clear should be used as init function
|
||||
func (c *cache21_19_8) clear() {
|
||||
for i := range c {
|
||||
c[i] = ^uint32(0)
|
||||
}
|
||||
}
|
||||
|
||||
func (c cache21_19_8) get(key uint32) (uint32, bool) {
|
||||
k := key & ((1 << 8) - 1)
|
||||
v := c[k]
|
||||
if v == ^uint32(0) || (v>>19) != uint32(key>>8) {
|
||||
return 0, false
|
||||
}
|
||||
return v & ((1 << 19) - 1), true
|
||||
}
|
||||
|
||||
func (c *cache21_19_8) set(key uint32, value uint32) {
|
||||
if (key>>21) != 0 || (value>>19) != 0 { /* overflows */
|
||||
return
|
||||
}
|
||||
c.setUnchecked(key, value)
|
||||
}
|
||||
|
||||
// assumes key < 2097152 and value < 524288
|
||||
func (c *cache21_19_8) setUnchecked(key uint32, value uint32) {
|
||||
k := key & ((1 << 8) - 1)
|
||||
v := (uint32(key>>8) << 19) | value
|
||||
c[k] = v
|
||||
}
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
package font
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/go-text/typesetting/font/opentype/tables"
|
||||
)
|
||||
|
||||
// Support for COLR and CPAL tables
|
||||
|
||||
// CPAL is the 'CPAL' table,
|
||||
// with [numPalettes]x[numPaletteEntries] colors.
|
||||
// CPAL[0] is the default palette
|
||||
type CPAL [][]tables.ColorRecord
|
||||
|
||||
func newCPAL(table tables.CPAL) (CPAL, error) {
|
||||
numPalettes := len(table.ColorRecordIndices)
|
||||
numColors := len(table.ColorRecordsArray)
|
||||
|
||||
// "The first palette, palette index 0, is the default palette.
|
||||
// A minimum of one palette must be provided in the CPAL table if the table is present.
|
||||
// Palettes must have a minimum of one color record. An empty CPAL table,
|
||||
// with no palettes and no color records is not permitted."
|
||||
if numPalettes == 0 {
|
||||
return nil, errors.New("empty CPAL table")
|
||||
}
|
||||
out := make(CPAL, numPalettes)
|
||||
for i, startIndex := range table.ColorRecordIndices {
|
||||
endIndex := int(startIndex) + int(table.NumPaletteEntries)
|
||||
if endIndex > numColors {
|
||||
return nil, fmt.Errorf("invalid CPAL table (expected at least %d colors, got %d)", endIndex, numColors)
|
||||
}
|
||||
out[i] = table.ColorRecordsArray[startIndex:endIndex]
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
+667
@@ -0,0 +1,667 @@
|
||||
// SPDX-License-Identifier: Unlicense OR BSD-3-Clause
|
||||
|
||||
// Package font provides an high level API to access
|
||||
// Opentype font properties.
|
||||
// See packages [opentype] and [opentype/tables] for a lower level, more detailled API.
|
||||
package font
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
|
||||
"github.com/go-text/typesetting/font/cff"
|
||||
ot "github.com/go-text/typesetting/font/opentype"
|
||||
"github.com/go-text/typesetting/font/opentype/tables"
|
||||
)
|
||||
|
||||
type (
|
||||
// GID is used to identify glyphs in a font.
|
||||
// It is mostly internal to the font and should not be confused with
|
||||
// Unicode code points.
|
||||
// Note that, despite Opentype font files using uint16, we choose to use uint32,
|
||||
// to allow room for future extension.
|
||||
GID = ot.GID
|
||||
|
||||
// Tag represents an open-type name.
|
||||
// These are technically uint32's, but are usually
|
||||
// displayed in ASCII as they are all acronyms.
|
||||
// See https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6.html#Overview
|
||||
Tag = ot.Tag
|
||||
|
||||
// VarCoord stores font variation coordinates,
|
||||
// which are real numbers in [-1;1], stored as fixed 2.14 integer.
|
||||
VarCoord = tables.Coord
|
||||
|
||||
// Resource is a combination of io.Reader, io.Seeker and io.ReaderAt.
|
||||
// This interface is satisfied by most things that you'd want
|
||||
// to parse, for example *os.File, io.SectionReader or *bytes.Reader.
|
||||
Resource = ot.Resource
|
||||
|
||||
// GlyphExtents exposes extent values, measured in font units.
|
||||
// Note that height is negative in coordinate systems that grow up.
|
||||
GlyphExtents = ot.GlyphExtents
|
||||
)
|
||||
|
||||
// ParseTTF parse an Opentype font file (.otf, .ttf).
|
||||
// See ParseTTC for support for collections.
|
||||
func ParseTTF(file Resource) (*Face, error) {
|
||||
ld, err := ot.NewLoader(file)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ft, err := NewFont(ld)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return NewFace(ft), nil
|
||||
}
|
||||
|
||||
// ParseTTC parse an Opentype font file, with support for collections.
|
||||
// Single font files are supported, returning a slice with length 1.
|
||||
func ParseTTC(file Resource) ([]*Face, error) {
|
||||
lds, err := ot.NewLoaders(file)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out := make([]*Face, len(lds))
|
||||
for i, ld := range lds {
|
||||
ft, err := NewFont(ld)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("reading font %d of collection: %s", i, err)
|
||||
}
|
||||
out[i] = NewFace(ft)
|
||||
}
|
||||
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// EmptyGlyph represents an invisible glyph, which should not be drawn,
|
||||
// but whose advance and offsets should still be accounted for when rendering.
|
||||
const EmptyGlyph GID = math.MaxUint32
|
||||
|
||||
// FontExtents exposes font-wide extent values, measured in font units.
|
||||
// Note that typically ascender is positive and descender negative in coordinate systems that grow up.
|
||||
type FontExtents struct {
|
||||
Ascender float32 // Typographic ascender.
|
||||
Descender float32 // Typographic descender.
|
||||
LineGap float32 // Suggested line spacing gap.
|
||||
}
|
||||
|
||||
// LineMetric identifies one metric about the font.
|
||||
type LineMetric uint8
|
||||
|
||||
const (
|
||||
// Distance above the baseline of the top of the underline.
|
||||
// Since most fonts have underline positions beneath the baseline, this value is typically negative.
|
||||
UnderlinePosition LineMetric = iota
|
||||
|
||||
// Suggested thickness to draw for the underline.
|
||||
UnderlineThickness
|
||||
|
||||
// Distance above the baseline of the top of the strikethrough.
|
||||
StrikethroughPosition
|
||||
|
||||
// Suggested thickness to draw for the strikethrough.
|
||||
StrikethroughThickness
|
||||
|
||||
SuperscriptEmYSize
|
||||
SuperscriptEmXOffset
|
||||
|
||||
SubscriptEmYSize
|
||||
SubscriptEmYOffset
|
||||
SubscriptEmXOffset
|
||||
|
||||
CapHeight
|
||||
XHeight
|
||||
)
|
||||
|
||||
// FontID represents an identifier of a font (possibly in a collection),
|
||||
// and an optional variable instance.
|
||||
type FontID struct {
|
||||
File string // The filename or identifier of the font file.
|
||||
|
||||
// The index of the face in a collection. It is always 0 for
|
||||
// single font files.
|
||||
Index uint16
|
||||
|
||||
// For variable fonts, stores 1 + the instance index.
|
||||
// It is set to 0 to ignore variations, or for non variable fonts.
|
||||
Instance uint16
|
||||
}
|
||||
|
||||
// Font represents one Opentype font file (or one sub font of a collection).
|
||||
// It is an educated view of the underlying font file, optimized for quick access
|
||||
// to information required by text layout engines.
|
||||
//
|
||||
// All its methods are read-only and a [*Font] object is thus safe for concurrent use.
|
||||
type Font struct {
|
||||
// Cmap is the 'cmap' table
|
||||
Cmap Cmap
|
||||
cmapVar UnicodeVariations
|
||||
|
||||
hhea *tables.Hhea
|
||||
vhea *tables.Vhea
|
||||
vorg *tables.VORG // optional
|
||||
cff *cff.CFF // optional
|
||||
cff2 *cff.CFF2 // optional
|
||||
post post // optional
|
||||
svg svg // optional
|
||||
|
||||
glyf tables.Glyf
|
||||
hmtx tables.Hmtx
|
||||
vmtx tables.Vmtx
|
||||
bitmap bitmap
|
||||
sbix sbix
|
||||
|
||||
STAT *STAT // optional
|
||||
|
||||
COLR *tables.COLR1 // color glyphs, optional
|
||||
CPAL CPAL // color glyphs, optional
|
||||
|
||||
os2 os2
|
||||
names tables.Name
|
||||
head tables.Head
|
||||
|
||||
// Optional, only present in variable fonts
|
||||
|
||||
fvar fvar // optional
|
||||
hvar *tables.HVAR // optional
|
||||
vvar *tables.VVAR // optional
|
||||
avar tables.Avar
|
||||
mvar mvar
|
||||
gvar gvar
|
||||
|
||||
// Advanced layout tables.
|
||||
|
||||
GDEF tables.GDEF // An absent table has a nil GlyphClassDef
|
||||
Trak tables.Trak
|
||||
Ankr tables.Ankr
|
||||
Feat tables.Feat
|
||||
Ltag tables.Ltag
|
||||
Morx Morx
|
||||
Kern Kernx
|
||||
Kerx Kernx
|
||||
GSUB GSUB // An absent table has a nil slice of lookups
|
||||
GPOS GPOS // An absent table has a nil slice of lookups
|
||||
|
||||
upem uint16 // cached value
|
||||
nGlyphs int
|
||||
}
|
||||
|
||||
// NewFont loads all the font tables, sanitizing them.
|
||||
// An error is returned only when required tables 'cmap', 'head', 'maxp' are invalid (or missing).
|
||||
// More control on errors is available by using package [tables].
|
||||
func NewFont(ld *ot.Loader) (*Font, error) {
|
||||
var (
|
||||
out Font
|
||||
err error
|
||||
)
|
||||
|
||||
// 'cmap' handling depend on os2
|
||||
raw, _ := ld.RawTable(ot.MustNewTag("OS/2"))
|
||||
os2, _, _ := tables.ParseOs2(raw)
|
||||
fontPage := os2.FontPage()
|
||||
out.os2, _ = newOs2(os2)
|
||||
|
||||
raw, err = ld.RawTable(ot.MustNewTag("cmap"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tb, _, err := tables.ParseCmap(raw)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out.Cmap, out.cmapVar, err = ProcessCmap(tb, fontPage)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
out.head, _, err = LoadHeadTable(ld, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
raw, err = ld.RawTable(ot.MustNewTag("maxp"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
maxp, _, err := tables.ParseMaxp(raw)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out.nGlyphs = int(maxp.NumGlyphs)
|
||||
|
||||
// We considerer all the following tables as optional,
|
||||
// since, in practice, users won't have much control on the
|
||||
// font files they use
|
||||
//
|
||||
// Ignoring the errors on `RawTable` is OK : it will trigger an error on the next tables.ParseXXX,
|
||||
// which in turn will return a zero value
|
||||
|
||||
raw, _ = ld.RawTable(ot.MustNewTag("fvar"))
|
||||
fvar, _, _ := tables.ParseFvar(raw)
|
||||
out.fvar = newFvar(fvar)
|
||||
|
||||
raw, _ = ld.RawTable(ot.MustNewTag("avar"))
|
||||
out.avar, _, _ = tables.ParseAvar(raw)
|
||||
|
||||
out.upem = out.head.Upem()
|
||||
|
||||
raw, _ = ld.RawTable(ot.MustNewTag("glyf"))
|
||||
locaRaw, _ := ld.RawTable(ot.MustNewTag("loca"))
|
||||
loca, err := tables.ParseLoca(locaRaw, out.nGlyphs, out.head.IndexToLocFormat == 1)
|
||||
if err == nil { // ParseGlyf panics if len(loca) == 0
|
||||
out.glyf, _ = tables.ParseGlyf(raw, loca)
|
||||
}
|
||||
|
||||
out.bitmap = selectBitmapTable(ld)
|
||||
|
||||
raw, _ = ld.RawTable(ot.MustNewTag("sbix"))
|
||||
sbix, _, _ := tables.ParseSbix(raw, out.nGlyphs)
|
||||
out.sbix = newSbix(sbix)
|
||||
|
||||
out.cff, _ = loadCff(ld, out.nGlyphs)
|
||||
out.cff2, _ = loadCff2(ld, out.nGlyphs, len(out.fvar))
|
||||
|
||||
raw, _ = ld.RawTable(ot.MustNewTag("post"))
|
||||
post, _, _ := tables.ParsePost(raw)
|
||||
out.post, _ = newPost(post)
|
||||
|
||||
raw, _ = ld.RawTable(ot.MustNewTag("SVG "))
|
||||
svg, _, _ := tables.ParseSVG(raw)
|
||||
out.svg, _ = newSvg(svg)
|
||||
|
||||
raw, _ = ld.RawTable(ot.MustNewTag("COLR"))
|
||||
if colr, err := tables.ParseCOLR(raw); err == nil {
|
||||
out.COLR = &colr
|
||||
// color table without CPAL is broken
|
||||
raw, _ = ld.RawTable(ot.MustNewTag("CPAL"))
|
||||
cpal, _, _ := tables.ParseCPAL(raw)
|
||||
out.CPAL, err = newCPAL(cpal)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
raw, _ = ld.RawTable(ot.MustNewTag("STAT"))
|
||||
stat, _, err := tables.ParseSTAT(raw)
|
||||
if err == nil {
|
||||
out.STAT = &stat
|
||||
}
|
||||
|
||||
out.hhea, out.hmtx, _ = loadHmtx(ld, out.nGlyphs)
|
||||
out.vhea, out.vmtx, _ = loadVmtx(ld, out.nGlyphs)
|
||||
|
||||
if axisCount := len(out.fvar); axisCount != 0 {
|
||||
raw, _ = ld.RawTable(ot.MustNewTag("MVAR"))
|
||||
mvar, _, _ := tables.ParseMVAR(raw)
|
||||
out.mvar, _ = newMvar(mvar, axisCount)
|
||||
|
||||
raw, _ = ld.RawTable(ot.MustNewTag("gvar"))
|
||||
gvar, _, _ := tables.ParseGvar(raw)
|
||||
out.gvar, _ = newGvar(gvar, out.glyf)
|
||||
|
||||
raw, _ = ld.RawTable(ot.MustNewTag("HVAR"))
|
||||
hvar, _, err := tables.ParseHVAR(raw)
|
||||
if err == nil {
|
||||
out.hvar = &hvar
|
||||
}
|
||||
|
||||
raw, _ = ld.RawTable(ot.MustNewTag("VVAR"))
|
||||
vvar, _, err := tables.ParseVVAR(raw)
|
||||
if err == nil {
|
||||
out.vvar = &vvar
|
||||
}
|
||||
}
|
||||
|
||||
raw, _ = ld.RawTable(ot.MustNewTag("VORG"))
|
||||
vorg, _, err := tables.ParseVORG(raw)
|
||||
if err == nil {
|
||||
out.vorg = &vorg
|
||||
}
|
||||
|
||||
raw, _ = ld.RawTable(ot.MustNewTag("name"))
|
||||
out.names, _, _ = tables.ParseName(raw)
|
||||
|
||||
// layout tables
|
||||
|
||||
gsubRaw, _ := ld.RawTable(ot.MustNewTag("GSUB"))
|
||||
layout, _, err := tables.ParseLayout(gsubRaw)
|
||||
// harfbuzz relies on GSUB.Loookups being nil when the table is absent
|
||||
if err == nil {
|
||||
out.GSUB, _ = newGSUB(layout)
|
||||
}
|
||||
|
||||
gposRaw, _ := ld.RawTable(ot.MustNewTag("GPOS"))
|
||||
layout, _, err = tables.ParseLayout(gposRaw)
|
||||
// harfbuzz relies on GPOS.Loookups being nil when the table is absent
|
||||
if err == nil {
|
||||
out.GPOS, _ = newGPOS(layout)
|
||||
}
|
||||
|
||||
out.GDEF, _ = loadGDEF(ld, len(out.fvar), gsubRaw, gposRaw)
|
||||
|
||||
raw, _ = ld.RawTable(ot.MustNewTag("morx"))
|
||||
morx, _, _ := tables.ParseMorx(raw, out.nGlyphs)
|
||||
out.Morx = newMorx(morx)
|
||||
|
||||
raw, _ = ld.RawTable(ot.MustNewTag("kerx"))
|
||||
kerx, _, _ := tables.ParseKerx(raw, out.nGlyphs)
|
||||
out.Kerx = newKernxFromKerx(kerx)
|
||||
|
||||
raw, _ = ld.RawTable(ot.MustNewTag("kern"))
|
||||
kern, _, _ := tables.ParseKern(raw)
|
||||
out.Kern = newKernxFromKern(kern)
|
||||
|
||||
raw, _ = ld.RawTable(ot.MustNewTag("ankr"))
|
||||
out.Ankr, _, _ = tables.ParseAnkr(raw, out.nGlyphs)
|
||||
|
||||
raw, _ = ld.RawTable(ot.MustNewTag("trak"))
|
||||
out.Trak, _, _ = tables.ParseTrak(raw)
|
||||
|
||||
raw, _ = ld.RawTable(ot.MustNewTag("feat"))
|
||||
out.Feat, _, _ = tables.ParseFeat(raw)
|
||||
|
||||
raw, _ = ld.RawTable(ot.MustNewTag("ltag"))
|
||||
out.Ltag, _, _ = tables.ParseLtag(raw)
|
||||
|
||||
return &out, nil
|
||||
}
|
||||
|
||||
// see harfbuzz/src/hb-ot-layout.cc
|
||||
func isGDEFBlocklisted(gdef, gsub, gpos []byte) bool {
|
||||
id := uint64(len(gdef))<<42 | uint64(len(gsub))<<21 | uint64(len(gpos))
|
||||
switch id {
|
||||
/* sha1sum:c5ee92f0bca4bfb7d06c4d03e8cf9f9cf75d2e8a Windows 7? timesi.ttf */
|
||||
case 442<<42 | 2874<<21 | 42038,
|
||||
/* sha1sum:37fc8c16a0894ab7b749e35579856c73c840867b Windows 7? timesbi.ttf */
|
||||
430<<42 | 2874<<21 | 40662,
|
||||
/* sha1sum:19fc45110ea6cd3cdd0a5faca256a3797a069a80 Windows 7 timesi.ttf */
|
||||
442<<42 | 2874<<21 | 39116,
|
||||
/* sha1sum:6d2d3c9ed5b7de87bc84eae0df95ee5232ecde26 Windows 7 timesbi.ttf */
|
||||
430<<42 | 2874<<21 | 39374,
|
||||
/* sha1sum:8583225a8b49667c077b3525333f84af08c6bcd8 OS X 10.11.3 Times New Roman Italic.ttf */
|
||||
490<<42 | 3046<<21 | 41638,
|
||||
/* sha1sum:ec0f5a8751845355b7c3271d11f9918a966cb8c9 OS X 10.11.3 Times New Roman Bold Italic.ttf */
|
||||
478<<42 | 3046<<21 | 41902,
|
||||
/* sha1sum:96eda93f7d33e79962451c6c39a6b51ee893ce8c tahoma.ttf from Windows 8 */
|
||||
898<<42 | 12554<<21 | 46470,
|
||||
/* sha1sum:20928dc06014e0cd120b6fc942d0c3b1a46ac2bc tahomabd.ttf from Windows 8 */
|
||||
910<<42 | 12566<<21 | 47732,
|
||||
/* sha1sum:4f95b7e4878f60fa3a39ca269618dfde9721a79e tahoma.ttf from Windows 8.1 */
|
||||
928<<42 | 23298<<21 | 59332,
|
||||
/* sha1sum:6d400781948517c3c0441ba42acb309584b73033 tahomabd.ttf from Windows 8.1 */
|
||||
940<<42 | 23310<<21 | 60732,
|
||||
/* tahoma.ttf v6.04 from Windows 8.1 x64, see https://bugzilla.mozilla.org/show_bug.cgi?id=1279925 */
|
||||
964<<42 | 23836<<21 | 60072,
|
||||
/* tahomabd.ttf v6.04 from Windows 8.1 x64, see https://bugzilla.mozilla.org/show_bug.cgi?id=1279925 */
|
||||
976<<42 | 23832<<21 | 61456,
|
||||
/* sha1sum:e55fa2dfe957a9f7ec26be516a0e30b0c925f846 tahoma.ttf from Windows 10 */
|
||||
994<<42 | 24474<<21 | 60336,
|
||||
/* sha1sum:7199385abb4c2cc81c83a151a7599b6368e92343 tahomabd.ttf from Windows 10 */
|
||||
1006<<42 | 24470<<21 | 61740,
|
||||
/* tahoma.ttf v6.91 from Windows 10 x64, see https://bugzilla.mozilla.org/show_bug.cgi?id=1279925 */
|
||||
1006<<42 | 24576<<21 | 61346,
|
||||
/* tahomabd.ttf v6.91 from Windows 10 x64, see https://bugzilla.mozilla.org/show_bug.cgi?id=1279925 */
|
||||
1018<<42 | 24572<<21 | 62828,
|
||||
/* sha1sum:b9c84d820c49850d3d27ec498be93955b82772b5 tahoma.ttf from Windows 10 AU */
|
||||
1006<<42 | 24576<<21 | 61352,
|
||||
/* sha1sum:2bdfaab28174bdadd2f3d4200a30a7ae31db79d2 tahomabd.ttf from Windows 10 AU */
|
||||
1018<<42 | 24572<<21 | 62834,
|
||||
/* sha1sum:b0d36cf5a2fbe746a3dd277bffc6756a820807a7 Tahoma.ttf from Mac OS X 10.9 */
|
||||
832<<42 | 7324<<21 | 47162,
|
||||
/* sha1sum:12fc4538e84d461771b30c18b5eb6bd434e30fba Tahoma Bold.ttf from Mac OS X 10.9 */
|
||||
844<<42 | 7302<<21 | 45474,
|
||||
/* sha1sum:eb8afadd28e9cf963e886b23a30b44ab4fd83acc himalaya.ttf from Windows 7 */
|
||||
180<<42 | 13054<<21 | 7254,
|
||||
/* sha1sum:73da7f025b238a3f737aa1fde22577a6370f77b0 himalaya.ttf from Windows 8 */
|
||||
192<<42 | 12638<<21 | 7254,
|
||||
/* sha1sum:6e80fd1c0b059bbee49272401583160dc1e6a427 himalaya.ttf from Windows 8.1 */
|
||||
192<<42 | 12690<<21 | 7254,
|
||||
/* 8d9267aea9cd2c852ecfb9f12a6e834bfaeafe44 cantarell-fonts-0.0.21/otf/Cantarell-Regular.otf */
|
||||
/* 983988ff7b47439ab79aeaf9a45bd4a2c5b9d371 cantarell-fonts-0.0.21/otf/Cantarell-Oblique.otf */
|
||||
188<<42 | 248<<21 | 3852,
|
||||
/* 2c0c90c6f6087ffbfea76589c93113a9cbb0e75f cantarell-fonts-0.0.21/otf/Cantarell-Bold.otf */
|
||||
/* 55461f5b853c6da88069ffcdf7f4dd3f8d7e3e6b cantarell-fonts-0.0.21/otf/Cantarell-Bold-Oblique.otf */
|
||||
188<<42 | 264<<21 | 3426,
|
||||
/* d125afa82a77a6475ac0e74e7c207914af84b37a padauk-2.80/Padauk.ttf RHEL 7.2 */
|
||||
1058<<42 | 47032<<21 | 11818,
|
||||
/* 0f7b80437227b90a577cc078c0216160ae61b031 padauk-2.80/Padauk-Bold.ttf RHEL 7.2*/
|
||||
1046<<42 | 47030<<21 | 12600,
|
||||
/* d3dde9aa0a6b7f8f6a89ef1002e9aaa11b882290 padauk-2.80/Padauk.ttf Ubuntu 16.04 */
|
||||
1058<<42 | 71796<<21 | 16770,
|
||||
/* 5f3c98ccccae8a953be2d122c1b3a77fd805093f padauk-2.80/Padauk-Bold.ttf Ubuntu 16.04 */
|
||||
1046<<42 | 71790<<21 | 17862,
|
||||
/* 6c93b63b64e8b2c93f5e824e78caca555dc887c7 padauk-2.80/Padauk-book.ttf */
|
||||
1046<<42 | 71788<<21 | 17112,
|
||||
/* d89b1664058359b8ec82e35d3531931125991fb9 padauk-2.80/Padauk-bookbold.ttf */
|
||||
1058<<42 | 71794<<21 | 17514,
|
||||
/* 824cfd193aaf6234b2b4dc0cf3c6ef576c0d00ef padauk-3.0/Padauk-book.ttf */
|
||||
1330<<42 | 109904<<21 | 57938,
|
||||
/* 91fcc10cf15e012d27571e075b3b4dfe31754a8a padauk-3.0/Padauk-bookbold.ttf */
|
||||
1330<<42 | 109904<<21 | 58972,
|
||||
/* sha1sum: c26e41d567ed821bed997e937bc0c41435689e85 Padauk.ttf
|
||||
* "Padauk Regular" "Version 2.5", see https://crbug.com/681813 */
|
||||
1004<<42 | 59092<<21 | 14836,
|
||||
/* 88d2006ca084f04af2df1954ed714a8c71e8400f Courier New.ttf from macOS 15 */
|
||||
588<<42 | 5078<<21 | 14418,
|
||||
/* 608e3ebb6dd1aee521cff08eb07d500a2c59df68 Courier New Bold.ttf from macOS 15 */
|
||||
588<<42 | 5078<<21 | 14238,
|
||||
/* d13221044ff054efd78f1cd8631b853c3ce85676 cour.ttf from Windows 10 */
|
||||
894<<42 | 17162<<21 | 33960,
|
||||
/* 68ed4a22d8067fcf1622ac6f6e2f4d3a2e3ec394 courbd.ttf from Windows 10 */
|
||||
894<<42 | 17154<<21 | 34472,
|
||||
/* 4cdb0259c96b7fd7c103821bb8f08f7cc6b211d7 cour.ttf from Windows 8.1 */
|
||||
816<<42 | 7868<<21 | 17052,
|
||||
/* 920483d8a8ed37f7f0afdabbe7f679aece7c75d8 courbd.ttf from Windows 8.1 */
|
||||
816<<42 | 7868<<21 | 17138:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
var bhedTag = ot.MustNewTag("bhed")
|
||||
|
||||
// LoadHeadTable loads the 'head' or the 'bhed' table.
|
||||
//
|
||||
// If a 'bhed' Apple table is present, it replaces the 'head' one.
|
||||
//
|
||||
// [buffer] may be provided to reduce allocations; the returned [tables.Head] is guaranteed
|
||||
// not to retain any reference on [buffer].
|
||||
// If [buffer] is nil or has not enough capacity, a new slice is allocated (and returned).
|
||||
func LoadHeadTable(ld *ot.Loader, buffer []byte) (tables.Head, []byte, error) {
|
||||
var err error
|
||||
// check 'bhed' first
|
||||
if ld.HasTable(bhedTag) {
|
||||
buffer, err = ld.RawTableTo(bhedTag, buffer)
|
||||
} else {
|
||||
buffer, err = ld.RawTableTo(ot.MustNewTag("head"), buffer)
|
||||
}
|
||||
if err != nil {
|
||||
return tables.Head{}, nil, errors.New("missing required head (or bhed) table")
|
||||
}
|
||||
out, _, err := tables.ParseHead(buffer)
|
||||
return out, buffer, err
|
||||
}
|
||||
|
||||
// return nil if no table is valid (or present)
|
||||
func selectBitmapTable(ld *ot.Loader) bitmap {
|
||||
color, err := loadBitmap(ld, ot.MustNewTag("CBLC"), ot.MustNewTag("CBDT"))
|
||||
if err == nil {
|
||||
return color
|
||||
}
|
||||
|
||||
gray, err := loadBitmap(ld, ot.MustNewTag("EBLC"), ot.MustNewTag("EBDT"))
|
||||
if err == nil {
|
||||
return gray
|
||||
}
|
||||
|
||||
apple, err := loadBitmap(ld, ot.MustNewTag("bloc"), ot.MustNewTag("bdat"))
|
||||
if err == nil {
|
||||
return apple
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// return nil if the table is missing or invalid
|
||||
func loadCff(ld *ot.Loader, numGlyphs int) (*cff.CFF, error) {
|
||||
raw, err := ld.RawTable(ot.MustNewTag("CFF "))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cff, err := cff.Parse(raw)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if N := len(cff.Charstrings); N != numGlyphs {
|
||||
return nil, fmt.Errorf("invalid number of glyphs in CFF table (%d != %d)", N, numGlyphs)
|
||||
}
|
||||
return cff, nil
|
||||
}
|
||||
|
||||
// return nil if the table is missing or invalid
|
||||
func loadCff2(ld *ot.Loader, numGlyphs, axisCount int) (*cff.CFF2, error) {
|
||||
raw, err := ld.RawTable(ot.MustNewTag("CFF2"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cff2, err := cff.ParseCFF2(raw)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if N := len(cff2.Charstrings); N != numGlyphs {
|
||||
return nil, fmt.Errorf("invalid number of glyphs in CFF table (%d != %d)", N, numGlyphs)
|
||||
}
|
||||
|
||||
if got := cff2.VarStore.AxisCount(); got != -1 && got != axisCount {
|
||||
return nil, fmt.Errorf("invalid number of axis in CFF table (%d != %d)", got, axisCount)
|
||||
}
|
||||
return cff2, nil
|
||||
}
|
||||
|
||||
func loadHVtmx(hheaRaw, htmxRaw []byte, numGlyphs int) (*tables.Hhea, tables.Hmtx, error) {
|
||||
hhea, _, err := tables.ParseHhea(hheaRaw)
|
||||
if err != nil {
|
||||
return nil, tables.Hmtx{}, err
|
||||
}
|
||||
|
||||
hmtx, _, err := tables.ParseHmtx(htmxRaw, int(hhea.NumOfLongMetrics), numGlyphs-int(hhea.NumOfLongMetrics))
|
||||
if err != nil {
|
||||
return nil, tables.Hmtx{}, err
|
||||
}
|
||||
return &hhea, hmtx, nil
|
||||
}
|
||||
|
||||
func loadHmtx(ld *ot.Loader, numGlyphs int) (*tables.Hhea, tables.Hmtx, error) {
|
||||
rawHead, err := ld.RawTable(ot.MustNewTag("hhea"))
|
||||
if err != nil {
|
||||
return nil, tables.Hmtx{}, err
|
||||
}
|
||||
|
||||
rawMetrics, err := ld.RawTable(ot.MustNewTag("hmtx"))
|
||||
if err != nil {
|
||||
return nil, tables.Hmtx{}, err
|
||||
}
|
||||
|
||||
return loadHVtmx(rawHead, rawMetrics, numGlyphs)
|
||||
}
|
||||
|
||||
func loadVmtx(ld *ot.Loader, numGlyphs int) (*tables.Hhea, tables.Hmtx, error) {
|
||||
rawHead, err := ld.RawTable(ot.MustNewTag("vhea"))
|
||||
if err != nil {
|
||||
return nil, tables.Hmtx{}, err
|
||||
}
|
||||
|
||||
rawMetrics, err := ld.RawTable(ot.MustNewTag("vmtx"))
|
||||
if err != nil {
|
||||
return nil, tables.Hmtx{}, err
|
||||
}
|
||||
|
||||
return loadHVtmx(rawHead, rawMetrics, numGlyphs)
|
||||
}
|
||||
|
||||
func loadGDEF(ld *ot.Loader, axisCount int, gsub, gpos []byte) (tables.GDEF, error) {
|
||||
raw, err := ld.RawTable(ot.MustNewTag("GDEF"))
|
||||
if err != nil {
|
||||
return tables.GDEF{}, err
|
||||
}
|
||||
|
||||
// Nuke the GDEF tables of to avoid unwanted width-zeroing.
|
||||
if isGDEFBlocklisted(raw, gsub, gpos) {
|
||||
return tables.GDEF{}, nil
|
||||
}
|
||||
|
||||
GDEF, _, err := tables.ParseGDEF(raw)
|
||||
if err != nil {
|
||||
return tables.GDEF{}, err
|
||||
}
|
||||
|
||||
err = sanitizeGDEF(GDEF, axisCount)
|
||||
if err != nil {
|
||||
return tables.GDEF{}, err
|
||||
}
|
||||
return GDEF, nil
|
||||
}
|
||||
|
||||
// Face is a font with user-provided settings.
|
||||
// Contrary to the [*Font] objects, Faces are NOT safe for concurrent use.
|
||||
// A Face caches glyph extents and rune to glyph mapping, and should be reused when possible.
|
||||
//
|
||||
// Also note that an empty [Face] is invalid : the [NewFace] constructor is required to properly init caches.
|
||||
type Face struct {
|
||||
*Font
|
||||
|
||||
extentsCache extentsCache
|
||||
cmapCache cache21_19_8
|
||||
|
||||
coords []tables.Coord
|
||||
xPpem, yPpem uint16
|
||||
}
|
||||
|
||||
// NewFace wraps [font] and initializes glyph caches.
|
||||
func NewFace(font *Font) *Face {
|
||||
out := &Face{Font: font, extentsCache: make(extentsCache, font.nGlyphs)}
|
||||
out.cmapCache.clear()
|
||||
return out
|
||||
}
|
||||
|
||||
// NominalGlyph returns the glyph used to represent the given rune,
|
||||
// or false if not found.
|
||||
// Note that it only looks into the cmap, without taking account substitutions
|
||||
// nor variation selectors.
|
||||
func (f *Face) NominalGlyph(ch rune) (GID, bool) {
|
||||
if g, ok := f.cmapCache.get(uint32(ch)); ok {
|
||||
return GID(g), ok
|
||||
}
|
||||
g, ok := f.Cmap.Lookup(ch)
|
||||
if ok {
|
||||
f.cmapCache.set(uint32(ch), uint32(g))
|
||||
}
|
||||
return g, ok
|
||||
}
|
||||
|
||||
// Ppem returns the horizontal and vertical pixels-per-em (ppem), used to select bitmap sizes.
|
||||
func (f *Face) Ppem() (x, y uint16) { return f.xPpem, f.yPpem }
|
||||
|
||||
// SetPpem applies horizontal and vertical pixels-per-em (ppem).
|
||||
func (f *Face) SetPpem(x, y uint16) {
|
||||
f.xPpem, f.yPpem = x, y
|
||||
// invalid the cache
|
||||
f.extentsCache.reset()
|
||||
}
|
||||
|
||||
// Coords return a read-only slice of the current variable coordinates, expressed in normalized units.
|
||||
// It is empty for non variable fonts.
|
||||
func (f *Face) Coords() []tables.Coord { return f.coords }
|
||||
|
||||
// SetCoords applies a list of variation coordinates, expressed in normalized units.
|
||||
// Use [NormalizeVariations] to convert from design (user) space units.
|
||||
func (f *Face) SetCoords(coords []tables.Coord) {
|
||||
f.coords = coords
|
||||
// invalid the cache
|
||||
f.extentsCache.reset()
|
||||
}
|
||||
+367
@@ -0,0 +1,367 @@
|
||||
// SPDX-License-Identifier: Unlicense OR BSD-3-Clause
|
||||
|
||||
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 // this point is the last of the current contour
|
||||
isExplicit bool // this point is referenced, i.e., explicit deltas specified */
|
||||
}
|
||||
|
||||
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) // used to deny loops
|
||||
f.getPointsForGlyphRec(gid, 0, seenGlyphs, &out)
|
||||
return out
|
||||
}
|
||||
|
||||
const maxCompositeNesting = 20 // protect against malicious fonts
|
||||
|
||||
// use the `glyf` table to fetch the contour points,
|
||||
// applying variation if needed.
|
||||
// for composite, recursively calls itself; allPoints includes phantom points and will be at least of length 4
|
||||
func (f *Face) getPointsForGlyphRec(gid tables.GlyphID, currentDepth int, currentGlyphs glyphSet, allPoints *[]contourPoint /* OUT */) {
|
||||
// adapted from harfbuzz/src/OT/glyf/Glyph.hh
|
||||
|
||||
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) // fetch the "real" points
|
||||
} else { // zeros values are enough
|
||||
points = make([]contourPoint, pointNumbersCount(g))
|
||||
}
|
||||
|
||||
// init phantom point
|
||||
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{}{}
|
||||
|
||||
// recurse on component
|
||||
var compPoints []contourPoint
|
||||
|
||||
f.getPointsForGlyphRec(item.GlyphIndex, currentDepth+1, currentGlyphs, &compPoints)
|
||||
|
||||
LC := len(compPoints)
|
||||
if LC < phantomCount { // in case of max depth reached
|
||||
delete(currentGlyphs, item.GlyphIndex)
|
||||
return
|
||||
}
|
||||
|
||||
/* Copy phantom points from component if USE_MY_METRICS flag set */
|
||||
if item.HasUseMyMetrics() {
|
||||
copy(phantoms, compPoints[LC-phantomCount:])
|
||||
}
|
||||
|
||||
/* Apply component transformation & translation */
|
||||
transformPoints(&item, compPoints)
|
||||
|
||||
/* Apply translation from gvar */
|
||||
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: // no data for the glyph
|
||||
*allPoints = append(*allPoints, phantoms...)
|
||||
}
|
||||
|
||||
// apply at top level
|
||||
if currentDepth == 0 {
|
||||
/* Undocumented rasterizer behavior:
|
||||
* Shift points horizontally by the updated left side bearing */
|
||||
tx := -phantoms[phantomLeft].X
|
||||
for i := range *allPoints {
|
||||
(*allPoints)[i].translate(tx, 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// does not includes phantom points
|
||||
func pointNumbersCount(g tables.Glyph) int {
|
||||
switch g := g.Data.(type) {
|
||||
case tables.SimpleGlyph:
|
||||
return len(g.Points)
|
||||
case tables.CompositeGlyph:
|
||||
/* pseudo component points for each component in composite glyph */
|
||||
return len(g.Glyphs)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// return all the contour points, without phantoms
|
||||
func getContourPoints(sg tables.SimpleGlyph) []contourPoint {
|
||||
const flagOnCurve = 1 << 0 // 0x0001
|
||||
|
||||
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 {
|
||||
// zero extent for the empty glyph
|
||||
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
|
||||
}
|
||||
|
||||
// walk through the contour points of the given glyph to compute its extends and its phantom points
|
||||
// As an optimization, if `computeExtents` is false, the extents computation is skipped (a zero value is returned).
|
||||
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
|
||||
// shortcut identity transform
|
||||
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
|
||||
/* Undocumented rasterizer behavior: shift glyph to the left by (lsb - xMin), i.e., xMin = lsb */
|
||||
/* extents.XBearing = hb_min (glyph_header.xMin, glyph_header.xMax); */
|
||||
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
|
||||
}
|
||||
|
||||
// sbix
|
||||
|
||||
var (
|
||||
dupe = ot.MustNewTag("dupe")
|
||||
// tagPNG identifies bitmap glyph with png format
|
||||
tagPNG = ot.MustNewTag("png ")
|
||||
// tagTIFF identifies bitmap glyph with tiff format
|
||||
tagTIFF = ot.MustNewTag("tiff")
|
||||
// tagJPG identifies bitmap glyph with jpg format
|
||||
tagJPG = ot.MustNewTag("jpg ")
|
||||
)
|
||||
|
||||
// strikeGlyph return the data for [glyph], or a zero value if not found.
|
||||
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
|
||||
}
|
||||
|
||||
// decodeBitmapConfig parse the data to find the width and height
|
||||
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
|
||||
}
|
||||
|
||||
// return the extents computed from the data
|
||||
// should only be called on valid, non nil glyph data
|
||||
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
|
||||
}
|
||||
+455
@@ -0,0 +1,455 @@
|
||||
package font
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
ot "github.com/go-text/typesetting/font/opentype"
|
||||
"github.com/go-text/typesetting/font/opentype/tables"
|
||||
)
|
||||
|
||||
// name values corresponding to the xxxConsts arrays
|
||||
var (
|
||||
styleStrings [len(styleConsts)]string
|
||||
weightStrings [len(weightConsts)]string
|
||||
stretchStrings [len(stretchConsts)]string
|
||||
)
|
||||
|
||||
func init() {
|
||||
for i, v := range styleConsts {
|
||||
styleStrings[i] = v.name
|
||||
}
|
||||
for i, v := range weightConsts {
|
||||
weightStrings[i] = v.name
|
||||
}
|
||||
for i, v := range stretchConsts {
|
||||
stretchStrings[i] = v.name
|
||||
}
|
||||
}
|
||||
|
||||
var styleConsts = [...]struct {
|
||||
name string
|
||||
value Style
|
||||
}{
|
||||
{"italic", StyleItalic},
|
||||
{"kursiv", StyleItalic},
|
||||
{"oblique", StyleItalic}, // map Oblique to Italic
|
||||
}
|
||||
|
||||
var weightConsts = [...]struct {
|
||||
name string
|
||||
value Weight
|
||||
}{
|
||||
{"thin", WeightThin},
|
||||
{"extralight", WeightExtraLight},
|
||||
{"ultralight", WeightExtraLight},
|
||||
{"light", WeightLight},
|
||||
{"demilight", (WeightLight + WeightNormal) / 2},
|
||||
{"semilight", (WeightLight + WeightNormal) / 2},
|
||||
{"book", WeightNormal - 20},
|
||||
{"regular", WeightNormal},
|
||||
{"normal", WeightNormal},
|
||||
{"medium", WeightMedium},
|
||||
{"demibold", WeightSemibold},
|
||||
{"demi", WeightSemibold},
|
||||
{"semibold", WeightSemibold},
|
||||
{"extrabold", WeightExtraBold},
|
||||
{"superbold", WeightExtraBold},
|
||||
{"ultrabold", WeightExtraBold},
|
||||
{"bold", WeightBold},
|
||||
{"ultrablack", WeightBlack + 20},
|
||||
{"superblack", WeightBlack + 20},
|
||||
{"extrablack", WeightBlack + 20},
|
||||
{"black", WeightBlack},
|
||||
{"heavy", WeightBlack},
|
||||
}
|
||||
|
||||
var stretchConsts = [...]struct {
|
||||
name string
|
||||
value Stretch
|
||||
}{
|
||||
{"ultracondensed", StretchUltraCondensed},
|
||||
{"extracondensed", StretchExtraCondensed},
|
||||
{"semicondensed", StretchSemiCondensed},
|
||||
{"condensed", StretchCondensed},
|
||||
{"normal", StretchNormal},
|
||||
{"semiexpanded", StretchSemiExpanded},
|
||||
{"extraexpanded", StretchExtraExpanded},
|
||||
{"ultraexpanded", StretchUltraExpanded},
|
||||
{"expanded", StretchExpanded},
|
||||
{"extended", StretchExpanded},
|
||||
}
|
||||
|
||||
// Style (also called slant) allows italic or oblique faces to be selected.
|
||||
type Style uint8
|
||||
|
||||
// note that we use the 0 value to indicate no style has been found yet
|
||||
const (
|
||||
// A face that is neither italic not obliqued.
|
||||
StyleNormal Style = iota + 1
|
||||
// A form that is generally cursive in nature or slanted.
|
||||
// This groups what is usually called Italic or Oblique.
|
||||
StyleItalic
|
||||
)
|
||||
|
||||
// Weight is the degree of blackness or stroke thickness of a font.
|
||||
// This value ranges from 100.0 to 900.0, with 400.0 as normal.
|
||||
type Weight float32
|
||||
|
||||
const (
|
||||
// Thin weight (100), the thinnest value.
|
||||
WeightThin Weight = 100
|
||||
// Extra light weight (200).
|
||||
WeightExtraLight Weight = 200
|
||||
// Light weight (300).
|
||||
WeightLight Weight = 300
|
||||
// Normal (400).
|
||||
WeightNormal Weight = 400
|
||||
// Medium weight (500, higher than normal).
|
||||
WeightMedium Weight = 500
|
||||
// Semibold weight (600).
|
||||
WeightSemibold Weight = 600
|
||||
// Bold weight (700).
|
||||
WeightBold Weight = 700
|
||||
// Extra-bold weight (800).
|
||||
WeightExtraBold Weight = 800
|
||||
// Black weight (900), the thickest value.
|
||||
WeightBlack Weight = 900
|
||||
)
|
||||
|
||||
// Stretch is the width of a font as an approximate fraction of the normal width.
|
||||
// Widths range from 0.5 to 2.0 inclusive, with 1.0 as the normal width.
|
||||
type Stretch float32
|
||||
|
||||
const (
|
||||
// Ultra-condensed width (50%), the narrowest possible.
|
||||
StretchUltraCondensed Stretch = 0.5
|
||||
// Extra-condensed width (62.5%).
|
||||
StretchExtraCondensed Stretch = 0.625
|
||||
// Condensed width (75%).
|
||||
StretchCondensed Stretch = 0.75
|
||||
// Semi-condensed width (87.5%).
|
||||
StretchSemiCondensed Stretch = 0.875
|
||||
// Normal width (100%).
|
||||
StretchNormal Stretch = 1.0
|
||||
// Semi-expanded width (112.5%).
|
||||
StretchSemiExpanded Stretch = 1.125
|
||||
// Expanded width (125%).
|
||||
StretchExpanded Stretch = 1.25
|
||||
// Extra-expanded width (150%).
|
||||
StretchExtraExpanded Stretch = 1.5
|
||||
// Ultra-expanded width (200%), the widest possible.
|
||||
StretchUltraExpanded Stretch = 2.0
|
||||
)
|
||||
|
||||
// Aspect stores the properties that specify which font in a family to use:
|
||||
// style, weight, and stretchiness.
|
||||
type Aspect struct {
|
||||
Style Style
|
||||
Weight Weight
|
||||
Stretch Stretch
|
||||
}
|
||||
|
||||
// aspect returns the [aspect] of the font,
|
||||
// defaulting to regular style.
|
||||
func (fd *fontDescriptor) aspect() Aspect {
|
||||
// use rawAspect and additionalStyle to infer the Aspect
|
||||
|
||||
out := fd.rawAspect() // load the aspect properties ...
|
||||
|
||||
// ... try to fill the missing one with the "style"
|
||||
out.inferFromStyle(fd.additionalStyle())
|
||||
|
||||
// ... and finally add default to regular values :
|
||||
// StyleNormal, WeightNormal, StretchNormal
|
||||
out.SetDefaults()
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
// some fonts includes aspect information in a string description,
|
||||
// usually called "style"
|
||||
// inferFromStyle scans such a string and fills the missing fields,
|
||||
func (as *Aspect) inferFromStyle(additionalStyle string) {
|
||||
additionalStyle = NormalizeFamily(additionalStyle)
|
||||
|
||||
if as.Style == 0 {
|
||||
if index := stringContainsConst(additionalStyle, styleStrings[:]); index != -1 {
|
||||
as.Style = styleConsts[index].value
|
||||
}
|
||||
}
|
||||
|
||||
if as.Weight == 0 {
|
||||
if index := stringContainsConst(additionalStyle, weightStrings[:]); index != -1 {
|
||||
as.Weight = weightConsts[index].value
|
||||
}
|
||||
}
|
||||
|
||||
if as.Stretch == 0 {
|
||||
if index := stringContainsConst(additionalStyle, stretchStrings[:]); index != -1 {
|
||||
as.Stretch = stretchConsts[index].value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// SetDefaults replace unspecified values by the default values: StyleNormal, WeightNormal, StretchNormal
|
||||
func (as *Aspect) SetDefaults() {
|
||||
if as.Style == 0 {
|
||||
as.Style = StyleNormal
|
||||
}
|
||||
|
||||
if as.Stretch == 0 {
|
||||
as.Stretch = StretchNormal
|
||||
}
|
||||
|
||||
if as.Weight == 0 {
|
||||
as.Weight = WeightNormal
|
||||
}
|
||||
}
|
||||
|
||||
func (fd *fontDescriptor) additionalStyle() string {
|
||||
var style string
|
||||
if fd.os2 != nil && fd.os2.fsSelection&256 != 0 {
|
||||
style = fd.names.Name(namePreferredSubfamily)
|
||||
if style == "" {
|
||||
style = fd.names.Name(nameFontSubfamily)
|
||||
}
|
||||
} else {
|
||||
style = fd.names.Name(nameWWSSubfamily)
|
||||
if style == "" {
|
||||
style = fd.names.Name(namePreferredSubfamily)
|
||||
}
|
||||
if style == "" {
|
||||
style = fd.names.Name(nameFontSubfamily)
|
||||
}
|
||||
}
|
||||
style = strings.TrimSpace(style)
|
||||
return style
|
||||
}
|
||||
|
||||
func (fd *fontDescriptor) rawAspect() Aspect {
|
||||
var (
|
||||
style Style
|
||||
weight Weight
|
||||
stretch Stretch
|
||||
)
|
||||
|
||||
if fd.os2 != nil {
|
||||
// We have an OS/2 table; use the `fsSelection' field. Bit 9
|
||||
// indicates an oblique font face. This flag has been
|
||||
// introduced in version 1.5 of the OpenType specification.
|
||||
if fd.os2.fsSelection&(1<<9) != 0 || fd.os2.fsSelection&1 != 0 {
|
||||
style = StyleItalic
|
||||
}
|
||||
|
||||
weight = Weight(fd.os2.usWeightClass)
|
||||
|
||||
switch fd.os2.usWidthClass {
|
||||
case 1:
|
||||
stretch = StretchUltraCondensed
|
||||
case 2:
|
||||
stretch = StretchExtraCondensed
|
||||
case 3:
|
||||
stretch = StretchCondensed
|
||||
case 4:
|
||||
stretch = StretchSemiCondensed
|
||||
case 5:
|
||||
stretch = StretchNormal
|
||||
case 6:
|
||||
stretch = StretchSemiExpanded
|
||||
case 7:
|
||||
stretch = StretchExpanded
|
||||
case 8:
|
||||
stretch = StretchExtraExpanded
|
||||
case 9:
|
||||
stretch = StretchUltraExpanded
|
||||
}
|
||||
|
||||
} else {
|
||||
// this is an old Mac font, use the header field
|
||||
if isItalic := fd.head.MacStyle&2 != 0; isItalic {
|
||||
style = StyleItalic
|
||||
}
|
||||
if isBold := fd.head.MacStyle&1 != 0; isBold {
|
||||
weight = WeightBold
|
||||
}
|
||||
}
|
||||
|
||||
return Aspect{style, weight, stretch}
|
||||
}
|
||||
|
||||
var rp = strings.NewReplacer(" ", "", "\t", "")
|
||||
|
||||
// NormalizeFamily removes spaces and lower the given string.
|
||||
func NormalizeFamily(family string) string { return rp.Replace(strings.ToLower(family)) }
|
||||
|
||||
// returns the index in `constants` of a constant contained in `str`,
|
||||
// or -1
|
||||
func stringContainsConst(str string, constants []string) int {
|
||||
for i, c := range constants {
|
||||
if strings.Contains(str, c) {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
const (
|
||||
nameFontFamily tables.NameID = 1
|
||||
nameFontSubfamily tables.NameID = 2
|
||||
namePreferredFamily tables.NameID = 16 // or Typographic Family
|
||||
namePreferredSubfamily tables.NameID = 17 // or Typographic Subfamily
|
||||
nameWWSFamily tables.NameID = 21 //
|
||||
nameWWSSubfamily tables.NameID = 22 //
|
||||
)
|
||||
|
||||
type os2Desc struct {
|
||||
usWeightClass uint16
|
||||
usWidthClass uint16
|
||||
fsSelection uint16
|
||||
}
|
||||
|
||||
func newOS2Desc(os tables.Os2) *os2Desc {
|
||||
return &os2Desc{
|
||||
usWeightClass: os.USWeightClass,
|
||||
usWidthClass: os.USWidthClass,
|
||||
fsSelection: os.FsSelection,
|
||||
}
|
||||
}
|
||||
|
||||
// fontDescriptor provides access to family and aspect
|
||||
type fontDescriptor struct {
|
||||
// these tables are required both in Family
|
||||
// and Aspect
|
||||
os2 *os2Desc // optional
|
||||
names tables.Name
|
||||
head tables.Head
|
||||
}
|
||||
|
||||
func newFontDescriptor(ld *ot.Loader, buffer []byte) (fontDescriptor, []byte) {
|
||||
var desc fontDescriptor
|
||||
|
||||
// load tables, all considered optional
|
||||
buffer, _ = ld.RawTableTo(ot.MustNewTag("OS/2"), buffer)
|
||||
if os2, _, err := tables.ParseOs2(buffer); err == nil {
|
||||
desc.os2 = newOS2Desc(os2)
|
||||
}
|
||||
|
||||
desc.head, buffer, _ = LoadHeadTable(ld, buffer)
|
||||
|
||||
buffer, _ = ld.RawTableTo(ot.MustNewTag("name"), buffer)
|
||||
desc.names, _, _ = tables.ParseName(buffer)
|
||||
|
||||
return desc, buffer
|
||||
}
|
||||
|
||||
// family returns the font family name.
|
||||
func (fd *fontDescriptor) family() string {
|
||||
var family string
|
||||
if fd.os2 != nil && fd.os2.fsSelection&256 != 0 {
|
||||
family = fd.names.Name(namePreferredFamily)
|
||||
if family == "" {
|
||||
family = fd.names.Name(nameFontFamily)
|
||||
}
|
||||
} else {
|
||||
family = fd.names.Name(nameWWSFamily)
|
||||
if family == "" {
|
||||
family = fd.names.Name(namePreferredFamily)
|
||||
}
|
||||
if family == "" {
|
||||
family = fd.names.Name(nameFontFamily)
|
||||
}
|
||||
}
|
||||
return family
|
||||
}
|
||||
|
||||
func max(a, b int) int {
|
||||
if a > b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func abs(x int) int {
|
||||
if x < 0 {
|
||||
return -x
|
||||
}
|
||||
return x
|
||||
}
|
||||
|
||||
func approximatelyEqual(x, y int) bool { return abs(x-y)*33 <= max(abs(x), abs(y)) }
|
||||
|
||||
// IsMonospace returns 'true' if the font is monospace,
|
||||
// by inspecting the horizontal advances of its glyphs.
|
||||
func (fd *Font) IsMonospace() bool {
|
||||
// code adapted from fontconfig
|
||||
|
||||
// try the fast shortcuts
|
||||
if fd.post.isFixedPitch {
|
||||
return true
|
||||
}
|
||||
|
||||
if fd.hmtx.IsEmpty() {
|
||||
// we can't be sure, so be conservative
|
||||
return false
|
||||
}
|
||||
|
||||
if len(fd.hmtx.Metrics) == 1 {
|
||||
return true
|
||||
}
|
||||
|
||||
// directly read the advances in the 'hmtx' table
|
||||
var firstAdvance int
|
||||
for gid, metric := range fd.hmtx.Metrics {
|
||||
if gid == 0 { // ignore the 'unset' glyph, which may be different
|
||||
continue
|
||||
}
|
||||
advance := int(metric.AdvanceWidth)
|
||||
if advance == 0 { // do not count zero as a proper width
|
||||
continue
|
||||
}
|
||||
|
||||
if firstAdvance == 0 {
|
||||
firstAdvance = advance
|
||||
continue
|
||||
}
|
||||
|
||||
if approximatelyEqual(advance, firstAdvance) {
|
||||
continue
|
||||
}
|
||||
|
||||
// two distinct advances : the font is not monospace
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// Description provides font metadata.
|
||||
type Description struct {
|
||||
Family string
|
||||
Aspect Aspect
|
||||
}
|
||||
|
||||
// Describe provides access to family and aspect.
|
||||
//
|
||||
// 'buffer' may be provided to reduce allocations.
|
||||
//
|
||||
// It provides an efficient API, loading only the mininum
|
||||
// tables required. See also the method [Font.Describe]
|
||||
// if you already have loaded the font.
|
||||
func Describe(ld *ot.Loader, buffer []byte) (Description, []byte) {
|
||||
desc, buffer := newFontDescriptor(ld, buffer)
|
||||
return Description{desc.family(), desc.aspect()}, buffer
|
||||
}
|
||||
|
||||
// Describe provides access to family and aspect.
|
||||
//
|
||||
// See also the package level function [Describe],
|
||||
// which is more efficient if you only need the font
|
||||
// metadata.
|
||||
func (ft *Font) Describe() Description {
|
||||
desc := fontDescriptor{ft.os2.os2Desc, ft.names, ft.head}
|
||||
return Description{desc.family(), desc.aspect()}
|
||||
}
|
||||
|
||||
type STAT = tables.STAT
|
||||
+419
@@ -0,0 +1,419 @@
|
||||
// SPDX-License-Identifier: Unlicense OR BSD-3-Clause
|
||||
|
||||
package font
|
||||
|
||||
import (
|
||||
"math"
|
||||
|
||||
ot "github.com/go-text/typesetting/font/opentype"
|
||||
"github.com/go-text/typesetting/font/opentype/tables"
|
||||
)
|
||||
|
||||
type gID = tables.GlyphID
|
||||
|
||||
func (f *Font) GetGlyphContourPoint(glyph GID, pointIndex uint16) (x, y int32, ok bool) {
|
||||
// harfbuzz seems not to implement this feature
|
||||
return 0, 0, false
|
||||
}
|
||||
|
||||
// GlyphName returns the name of the given glyph, or an empty
|
||||
// string if the glyph is invalid or has no name.
|
||||
func (f *Font) GlyphName(glyph GID) string {
|
||||
if postNames := f.post.names; postNames != nil {
|
||||
if name := postNames.glyphName(glyph); name != "" {
|
||||
return name
|
||||
}
|
||||
}
|
||||
if f.cff != nil {
|
||||
return f.cff.GlyphName(glyph)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// Upem returns the units per em of the font file.
|
||||
// This value is only relevant for scalable fonts.
|
||||
func (f *Font) Upem() uint16 { return f.upem }
|
||||
|
||||
var (
|
||||
metricsTagHorizontalAscender = ot.MustNewTag("hasc")
|
||||
metricsTagHorizontalDescender = ot.MustNewTag("hdsc")
|
||||
metricsTagHorizontalLineGap = ot.MustNewTag("hlgp")
|
||||
metricsTagVerticalAscender = ot.MustNewTag("vasc")
|
||||
metricsTagVerticalDescender = ot.MustNewTag("vdsc")
|
||||
metricsTagVerticalLineGap = ot.MustNewTag("vlgp")
|
||||
)
|
||||
|
||||
func fixAscenderDescender(value float32, metricsTag Tag) float32 {
|
||||
if metricsTag == metricsTagHorizontalAscender || metricsTag == metricsTagVerticalAscender {
|
||||
return float32(math.Abs(float64(value)))
|
||||
}
|
||||
if metricsTag == metricsTagHorizontalDescender || metricsTag == metricsTagVerticalDescender {
|
||||
return float32(-math.Abs(float64(value)))
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
func (f *Font) getPositionCommon(metricTag Tag, varCoords []VarCoord) (float32, bool) {
|
||||
deltaVar := f.mvar.getVar(metricTag, varCoords)
|
||||
switch metricTag {
|
||||
case metricsTagHorizontalAscender:
|
||||
if f.os2.useTypoMetrics {
|
||||
return fixAscenderDescender(float32(f.os2.sTypoAscender)+deltaVar, metricTag), true
|
||||
} else if f.hhea != nil {
|
||||
return fixAscenderDescender(float32(f.hhea.Ascender)+deltaVar, metricTag), true
|
||||
}
|
||||
|
||||
case metricsTagHorizontalDescender:
|
||||
if f.os2.useTypoMetrics {
|
||||
return fixAscenderDescender(float32(f.os2.sTypoDescender)+deltaVar, metricTag), true
|
||||
} else if f.hhea != nil {
|
||||
return fixAscenderDescender(float32(f.hhea.Descender)+deltaVar, metricTag), true
|
||||
}
|
||||
case metricsTagHorizontalLineGap:
|
||||
if f.os2.useTypoMetrics {
|
||||
return fixAscenderDescender(float32(f.os2.sTypoLineGap)+deltaVar, metricTag), true
|
||||
} else if f.hhea != nil {
|
||||
return fixAscenderDescender(float32(f.hhea.LineGap)+deltaVar, metricTag), true
|
||||
}
|
||||
case metricsTagVerticalAscender:
|
||||
if f.vhea != nil {
|
||||
return fixAscenderDescender(float32(f.vhea.Ascender)+deltaVar, metricTag), true
|
||||
}
|
||||
case metricsTagVerticalDescender:
|
||||
if f.vhea != nil {
|
||||
return fixAscenderDescender(float32(f.vhea.Descender)+deltaVar, metricTag), true
|
||||
}
|
||||
case metricsTagVerticalLineGap:
|
||||
if f.vhea != nil {
|
||||
return fixAscenderDescender(float32(f.vhea.LineGap)+deltaVar, metricTag), true
|
||||
}
|
||||
}
|
||||
return 0, false
|
||||
}
|
||||
|
||||
// FontHExtents returns the extents of the font for horizontal text, or false
|
||||
// it not available, in font units.
|
||||
func (f *Face) FontHExtents() (FontExtents, bool) {
|
||||
var (
|
||||
out FontExtents
|
||||
ok1, ok2, ok3 bool
|
||||
)
|
||||
out.Ascender, ok1 = f.Font.getPositionCommon(metricsTagHorizontalAscender, f.coords)
|
||||
out.Descender, ok2 = f.Font.getPositionCommon(metricsTagHorizontalDescender, f.coords)
|
||||
out.LineGap, ok3 = f.Font.getPositionCommon(metricsTagHorizontalLineGap, f.coords)
|
||||
return out, ok1 && ok2 && ok3
|
||||
}
|
||||
|
||||
// FontVExtents is the same as `FontHExtents`, but for vertical text.
|
||||
func (f *Face) FontVExtents() (FontExtents, bool) {
|
||||
var (
|
||||
out FontExtents
|
||||
ok1, ok2, ok3 bool
|
||||
)
|
||||
out.Ascender, ok1 = f.Font.getPositionCommon(metricsTagVerticalAscender, f.coords)
|
||||
out.Descender, ok2 = f.Font.getPositionCommon(metricsTagVerticalDescender, f.coords)
|
||||
out.LineGap, ok3 = f.Font.getPositionCommon(metricsTagVerticalLineGap, f.coords)
|
||||
return out, ok1 && ok2 && ok3
|
||||
}
|
||||
|
||||
var (
|
||||
tagStrikeoutSize = ot.MustNewTag("strs")
|
||||
tagStrikeoutOffset = ot.MustNewTag("stro")
|
||||
tagUnderlineSize = ot.MustNewTag("unds")
|
||||
tagUnderlineOffset = ot.MustNewTag("undo")
|
||||
tagSuperscriptYSize = ot.MustNewTag("spys")
|
||||
tagSuperscriptXOffset = ot.MustNewTag("spxo")
|
||||
tagSubscriptYSize = ot.MustNewTag("sbys")
|
||||
tagSubscriptYOffset = ot.MustNewTag("sbyo")
|
||||
tagSubscriptXOffset = ot.MustNewTag("sbxo")
|
||||
tagXHeight = ot.MustNewTag("xhgt")
|
||||
tagCapHeight = ot.MustNewTag("cpht")
|
||||
)
|
||||
|
||||
// return the height from baseline (in font units)
|
||||
func (f *Face) runeHeight(r rune) float32 {
|
||||
gid, ok := f.NominalGlyph(r)
|
||||
if !ok {
|
||||
return 0
|
||||
}
|
||||
extents, ok := f.GlyphExtents(gid)
|
||||
if !ok {
|
||||
return 0
|
||||
}
|
||||
return extents.YBearing
|
||||
}
|
||||
|
||||
// LineMetric returns the metric identified by `metric` (in fonts units).
|
||||
func (f *Face) LineMetric(metric LineMetric) float32 {
|
||||
switch metric {
|
||||
case UnderlinePosition:
|
||||
return f.post.underlinePosition + f.mvar.getVar(tagUnderlineOffset, f.coords)
|
||||
case UnderlineThickness:
|
||||
return f.post.underlineThickness + f.mvar.getVar(tagUnderlineSize, f.coords)
|
||||
case StrikethroughPosition:
|
||||
return float32(f.os2.yStrikeoutPosition) + f.mvar.getVar(tagStrikeoutOffset, f.coords)
|
||||
case StrikethroughThickness:
|
||||
return float32(f.os2.yStrikeoutSize) + f.mvar.getVar(tagStrikeoutSize, f.coords)
|
||||
case SuperscriptEmYSize:
|
||||
return float32(f.os2.ySuperscriptYSize) + f.mvar.getVar(tagSuperscriptYSize, f.coords)
|
||||
case SuperscriptEmXOffset:
|
||||
return float32(f.os2.ySuperscriptXOffset) + f.mvar.getVar(tagSuperscriptXOffset, f.coords)
|
||||
case SubscriptEmYSize:
|
||||
return float32(f.os2.ySubscriptYSize) + f.mvar.getVar(tagSubscriptYSize, f.coords)
|
||||
case SubscriptEmYOffset:
|
||||
return float32(f.os2.ySubscriptYOffset) + f.mvar.getVar(tagSubscriptYOffset, f.coords)
|
||||
case SubscriptEmXOffset:
|
||||
return float32(f.os2.ySubscriptXOffset) + f.mvar.getVar(tagSubscriptXOffset, f.coords)
|
||||
case CapHeight:
|
||||
if f.os2.version < 2 {
|
||||
// sCapHeight may be set equal to the top of the unscaled and unhinted glyph
|
||||
// bounding box of the glyph encoded at U+0048 (LATIN CAPITAL LETTER H).
|
||||
return f.runeHeight('H')
|
||||
}
|
||||
return float32(f.os2.sCapHeight) + f.mvar.getVar(tagCapHeight, f.coords)
|
||||
case XHeight:
|
||||
if f.os2.version < 2 {
|
||||
// sxHeight equal to the top of the unscaled and unhinted glyph bounding box
|
||||
// of the glyph encoded at U+0078 (LATIN SMALL LETTER X).
|
||||
return f.runeHeight('x')
|
||||
}
|
||||
return float32(f.os2.sxHeigh) + f.mvar.getVar(tagXHeight, f.coords)
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
// NominalGlyph returns the glyph used to represent the given rune,
|
||||
// or false if not found.
|
||||
// Note that it only looks into the cmap, without taking account substitutions
|
||||
// nor variation selectors.
|
||||
func (f *Font) NominalGlyph(ch rune) (GID, bool) { return f.Cmap.Lookup(ch) }
|
||||
|
||||
// VariationGlyph retrieves the glyph ID for a specified Unicode code point
|
||||
// followed by a specified Variation Selector code point, or false if not found
|
||||
func (f *Font) VariationGlyph(ch, varSelector rune) (GID, bool) {
|
||||
gid, kind := f.cmapVar.GetGlyphVariant(ch, varSelector)
|
||||
switch kind {
|
||||
case VariantNotFound:
|
||||
return 0, false
|
||||
case VariantFound:
|
||||
return gid, true
|
||||
default: // VariantUseDefault
|
||||
return f.NominalGlyph(ch)
|
||||
}
|
||||
}
|
||||
|
||||
// do not take into account variations
|
||||
func (f *Font) getBaseAdvance(gid gID, table tables.Hmtx, isVertical bool) int16 {
|
||||
/* If `table` is empty, it means we don't have the metrics table
|
||||
* for this direction: return default advance. Otherwise, it means that the
|
||||
* glyph index is out of bound: return zero. */
|
||||
if table.IsEmpty() {
|
||||
if isVertical {
|
||||
return int16(f.upem)
|
||||
}
|
||||
return int16(f.upem / 2)
|
||||
}
|
||||
|
||||
return table.Advance(gid)
|
||||
}
|
||||
|
||||
func clamp(v float32) float32 {
|
||||
if v < 0 {
|
||||
v = 0
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
func (f *Face) getGlyphAdvanceVar(gid gID, isVertical bool) float32 {
|
||||
_, phantoms := f.getGlyfPoints(gid, false)
|
||||
if isVertical {
|
||||
return clamp(phantoms[phantomTop].Y - phantoms[phantomBottom].Y)
|
||||
}
|
||||
return clamp(phantoms[phantomRight].X - phantoms[phantomLeft].X)
|
||||
}
|
||||
|
||||
func (f *Face) HorizontalAdvance(gid GID) float32 {
|
||||
advance := f.getBaseAdvance(gID(gid), f.hmtx, false)
|
||||
if !f.isVar() {
|
||||
return float32(advance)
|
||||
}
|
||||
if f.hvar != nil {
|
||||
return float32(advance) + f.hvar.AdvanceDelta(gID(gid), f.coords)
|
||||
}
|
||||
return f.getGlyphAdvanceVar(gID(gid), false)
|
||||
}
|
||||
|
||||
// return `true` is the font is variable and `Coords` is valid
|
||||
func (f *Face) isVar() bool {
|
||||
return len(f.coords) != 0 && len(f.coords) == len(f.Font.fvar)
|
||||
}
|
||||
|
||||
// HasVerticalMetrics returns true if a the 'vmtx' table is present.
|
||||
// If not, client should avoid calls to [VerticalAdvance], which will returns a
|
||||
// defaut value.
|
||||
func (f *Font) HasVerticalMetrics() bool { return !f.vmtx.IsEmpty() }
|
||||
|
||||
func (f *Face) VerticalAdvance(gid GID) float32 {
|
||||
// return the opposite of the advance from the font
|
||||
advance := f.getBaseAdvance(gID(gid), f.vmtx, true)
|
||||
if !f.isVar() {
|
||||
return -float32(advance)
|
||||
}
|
||||
if f.vvar != nil {
|
||||
return -float32(advance) - f.vvar.AdvanceDelta(gID(gid), f.coords)
|
||||
}
|
||||
return -f.getGlyphAdvanceVar(gID(gid), true)
|
||||
}
|
||||
|
||||
func (f *Font) GlyphHOrigin(GID) (x, y int32, found bool) {
|
||||
// zero is the right value here
|
||||
return 0, 0, true
|
||||
}
|
||||
|
||||
func (f *Face) GlyphVOrigin(glyph GID) (x, y float32) {
|
||||
// First, set the x value to half the advance width.
|
||||
x = f.HorizontalAdvance(glyph) / 2
|
||||
|
||||
// If there is VORG, always use it. It uses VVAR for variations if necessary.
|
||||
if f.vorg != nil {
|
||||
y = float32(f.vorg.YOrigin(gID(glyph)))
|
||||
if f.isVar() && f.vvar != nil {
|
||||
y += f.vvar.VorgDelta(gID(glyph), f.coords)
|
||||
}
|
||||
return x, y
|
||||
}
|
||||
|
||||
// If and only if `vmtx` is present and it's a `glyf` font,
|
||||
// we use the top phantom point, deduced from vmtx,glyf[,gvar].
|
||||
if !f.vmtx.IsEmpty() && f.glyf != nil {
|
||||
y = f.getVOriginWithVar(gID(glyph))
|
||||
return x, y
|
||||
}
|
||||
|
||||
// Otherwise, use glyph extents to center the glyph vertically.
|
||||
// If getting glyph extents failed, just use the font ascender.
|
||||
fontExtents, _ := f.FontHExtents()
|
||||
fontAdvance := fontExtents.Ascender - fontExtents.Descender
|
||||
|
||||
if extents, ok := f.getExtentsFromGlyf(gID(glyph)); ok {
|
||||
diff := fontAdvance - -extents.Height
|
||||
y = extents.YBearing + float32(int(diff)/2)
|
||||
return x, y
|
||||
}
|
||||
|
||||
y = fontExtents.Ascender
|
||||
return x, y
|
||||
}
|
||||
|
||||
func (f *Face) getVOriginWithVar(gid gID) float32 {
|
||||
if int(gid) >= f.nGlyphs {
|
||||
return 0
|
||||
}
|
||||
_, phantoms := f.getGlyfPoints(gid, false)
|
||||
return phantoms[phantomTop].Y
|
||||
}
|
||||
|
||||
func (f *Face) getExtentsFromGlyf(glyph gID) (GlyphExtents, bool) {
|
||||
if int(glyph) >= len(f.glyf) {
|
||||
return GlyphExtents{}, false
|
||||
}
|
||||
if f.isVar() { // we have to compute the outline points and apply variations
|
||||
extents, _ := f.getGlyfPoints(glyph, true)
|
||||
return extents, true
|
||||
}
|
||||
return getGlyphExtents(f.glyf[glyph], f.hmtx, glyph), true
|
||||
}
|
||||
|
||||
func (f *Font) getExtentsFromBitmap(glyph gID, xPpem, yPpem uint16) (GlyphExtents, bool) {
|
||||
strike := f.bitmap.chooseStrike(xPpem, yPpem)
|
||||
if strike == nil || strike.ppemX == 0 || strike.ppemY == 0 {
|
||||
return GlyphExtents{}, false
|
||||
}
|
||||
subtable := strike.findTable(glyph)
|
||||
if subtable == nil {
|
||||
return GlyphExtents{}, false
|
||||
}
|
||||
image := subtable.image(glyph)
|
||||
if image == nil {
|
||||
return GlyphExtents{}, false
|
||||
}
|
||||
extents := GlyphExtents{
|
||||
XBearing: float32(image.metrics.BearingX),
|
||||
YBearing: float32(image.metrics.BearingY),
|
||||
Width: float32(image.metrics.Width),
|
||||
Height: -float32(image.metrics.Height),
|
||||
}
|
||||
|
||||
/* convert to font units. */
|
||||
xScale := float32(f.upem) / float32(strike.ppemX)
|
||||
yScale := float32(f.upem) / float32(strike.ppemY)
|
||||
extents.XBearing *= xScale
|
||||
extents.YBearing *= yScale
|
||||
extents.Width *= xScale
|
||||
extents.Height *= yScale
|
||||
return extents, true
|
||||
}
|
||||
|
||||
func (f *Font) getExtentsFromSbix(glyph gID, xPpem, yPpem uint16) (GlyphExtents, bool) {
|
||||
strike := f.sbix.chooseStrike(xPpem, yPpem)
|
||||
if strike == nil || strike.Ppem == 0 {
|
||||
return GlyphExtents{}, false
|
||||
}
|
||||
data := strikeGlyph(strike, glyph, 0)
|
||||
if data.GraphicType == 0 {
|
||||
return GlyphExtents{}, false
|
||||
}
|
||||
extents, ok := bitmapGlyphExtents(data)
|
||||
|
||||
/* convert to font units. */
|
||||
scale := float32(f.upem) / float32(strike.Ppem)
|
||||
extents.XBearing *= scale
|
||||
extents.YBearing *= scale
|
||||
extents.Width *= scale
|
||||
extents.Height *= scale
|
||||
return extents, ok
|
||||
}
|
||||
|
||||
func (f *Font) getExtentsFromCff1(glyph gID) (GlyphExtents, bool) {
|
||||
if f.cff == nil {
|
||||
return GlyphExtents{}, false
|
||||
}
|
||||
_, bounds, err := f.cff.LoadGlyph(glyph)
|
||||
if err != nil {
|
||||
return GlyphExtents{}, false
|
||||
}
|
||||
return bounds.ToExtents(), true
|
||||
}
|
||||
|
||||
func (f *Face) getExtentsFromCff2(glyph gID) (GlyphExtents, bool) {
|
||||
if f.cff2 == nil {
|
||||
return GlyphExtents{}, false
|
||||
}
|
||||
_, bounds, err := f.cff2.LoadGlyph(glyph, f.coords)
|
||||
if err != nil {
|
||||
return GlyphExtents{}, false
|
||||
}
|
||||
return bounds.ToExtents(), true
|
||||
}
|
||||
|
||||
func (f *Face) glyphExtentsRaw(glyph GID) (GlyphExtents, bool) {
|
||||
out, ok := f.getExtentsFromSbix(gID(glyph), f.xPpem, f.yPpem)
|
||||
if ok {
|
||||
return out, ok
|
||||
}
|
||||
out, ok = f.getExtentsFromBitmap(gID(glyph), f.xPpem, f.yPpem)
|
||||
if ok {
|
||||
return out, ok
|
||||
}
|
||||
out, ok = f.getExtentsFromGlyf(gID(glyph))
|
||||
if ok {
|
||||
return out, ok
|
||||
}
|
||||
out, ok = f.getExtentsFromCff2(gID(glyph))
|
||||
if ok {
|
||||
return out, ok
|
||||
}
|
||||
out, ok = f.getExtentsFromCff1(gID(glyph))
|
||||
return out, ok
|
||||
}
|
||||
+87
@@ -0,0 +1,87 @@
|
||||
// SPDX-License-Identifier: Unlicense OR BSD-3-Clause
|
||||
|
||||
// Package opentype provides the low level routines
|
||||
// required to read and write Opentype font files, including collections.
|
||||
//
|
||||
// This package is designed to provide an efficient, lazy, reading API.
|
||||
//
|
||||
// For the parsing of the various tables, see package [tables].
|
||||
package opentype
|
||||
|
||||
type Tag uint32
|
||||
|
||||
// NewTag returns the tag for <abcd>.
|
||||
func NewTag(a, b, c, d byte) Tag {
|
||||
return Tag(uint32(d) | uint32(c)<<8 | uint32(b)<<16 | uint32(a)<<24)
|
||||
}
|
||||
|
||||
// MustNewTag gives you the Tag corresponding to the acronym.
|
||||
// This function will panic if the string passed in is not 4 bytes long.
|
||||
func MustNewTag(str string) Tag {
|
||||
if len(str) != 4 {
|
||||
panic("invalid tag: must be exactly 4 bytes")
|
||||
}
|
||||
_ = str[3]
|
||||
return NewTag(str[0], str[1], str[2], str[3])
|
||||
}
|
||||
|
||||
// String return the ASCII form of the tag.
|
||||
func (t Tag) String() string {
|
||||
return string([]byte{
|
||||
byte(t >> 24),
|
||||
byte(t >> 16),
|
||||
byte(t >> 8),
|
||||
byte(t),
|
||||
})
|
||||
}
|
||||
|
||||
type GID uint32
|
||||
|
||||
type GlyphExtents struct {
|
||||
XBearing float32 // Left side of glyph from origin
|
||||
YBearing float32 // Top side of glyph from origin
|
||||
Width float32 // Distance from left to right side
|
||||
Height float32 // Distance from top to bottom side
|
||||
}
|
||||
|
||||
type SegmentOp uint8
|
||||
|
||||
const (
|
||||
SegmentOpMoveTo SegmentOp = iota
|
||||
SegmentOpLineTo
|
||||
SegmentOpQuadTo
|
||||
SegmentOpCubeTo
|
||||
)
|
||||
|
||||
type SegmentPoint struct {
|
||||
X, Y float32 // expressed in fonts units
|
||||
}
|
||||
|
||||
// Move translates the point.
|
||||
func (pt *SegmentPoint) Move(dx, dy float32) {
|
||||
pt.X += dx
|
||||
pt.Y += dy
|
||||
}
|
||||
|
||||
type Segment struct {
|
||||
Op SegmentOp
|
||||
// Args is up to three (x, y) coordinates, depending on the
|
||||
// operation.
|
||||
// The Y axis increases up.
|
||||
Args [3]SegmentPoint
|
||||
}
|
||||
|
||||
// ArgsSlice returns the effective slice of points
|
||||
// used (whose length is between 1 and 3).
|
||||
func (s *Segment) ArgsSlice() []SegmentPoint {
|
||||
switch s.Op {
|
||||
case SegmentOpMoveTo, SegmentOpLineTo:
|
||||
return s.Args[0:1]
|
||||
case SegmentOpQuadTo:
|
||||
return s.Args[0:2]
|
||||
case SegmentOpCubeTo:
|
||||
return s.Args[0:3]
|
||||
default:
|
||||
panic("unreachable")
|
||||
}
|
||||
}
|
||||
+371
@@ -0,0 +1,371 @@
|
||||
// SPDX-License-Identifier: Unlicense OR BSD-3-Clause
|
||||
|
||||
package opentype
|
||||
|
||||
import (
|
||||
"compress/zlib"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"sort"
|
||||
)
|
||||
|
||||
var (
|
||||
// TrueType is the first four bytes of an OpenType file containing a TrueType font
|
||||
TrueType = Tag(0x00010000)
|
||||
// AppleTrueType is the first four bytes of an OpenType file containing a TrueType font
|
||||
// (specifically one designed for Apple products, it's recommended to use TrueType instead)
|
||||
AppleTrueType = MustNewTag("true")
|
||||
// PostScript1 is the first four bytes of an OpenType file containing a PostScript 1 font
|
||||
PostScript1 = MustNewTag("typ1")
|
||||
// OpenType is the first four bytes of an OpenType file containing a PostScript Type 2 font
|
||||
// as specified by OpenType
|
||||
OpenType = MustNewTag("OTTO")
|
||||
|
||||
// signatureWOFF is the magic number at the start of a WOFF file.
|
||||
signatureWOFF = MustNewTag("wOFF")
|
||||
|
||||
ttcTag = MustNewTag("ttcf")
|
||||
|
||||
errInvalidDfont = errors.New("invalid dfont")
|
||||
)
|
||||
|
||||
// dfontResourceDataOffset is the assumed value of a dfont file's resource data
|
||||
// offset.
|
||||
//
|
||||
// https://github.com/kreativekorp/ksfl/wiki/Macintosh-Resource-File-Format
|
||||
// says that "A Mac OS resource file... [starts with an] offset from start of
|
||||
// file to start of resource data section... [usually] 0x0100". In theory,
|
||||
// 0x00000100 isn't always a magic number for identifying dfont files. In
|
||||
// practice, it seems to work.
|
||||
const dfontResourceDataOffset = 0x00000100
|
||||
|
||||
type Resource interface {
|
||||
Read([]byte) (int, error)
|
||||
ReadAt([]byte, int64) (int, error)
|
||||
Seek(int64, int) (int64, error)
|
||||
}
|
||||
|
||||
// tableSection represents a table within the font file.
|
||||
type tableSection struct {
|
||||
offset uint32 // Offset into the file this table starts.
|
||||
length uint32 // Length of this table within the file.
|
||||
zLength uint32 // Uncompressed length of this table.
|
||||
}
|
||||
|
||||
// Loader is the low level font reader, providing
|
||||
// full control over table loading.
|
||||
type Loader struct {
|
||||
file Resource // source, needed to parse each table
|
||||
tables map[Tag]tableSection // header only, contents is processed on demand
|
||||
|
||||
// Type represents the kind of this font being loaded.
|
||||
// It is one of TrueType, TrueTypeApple, PostScript1, OpenType
|
||||
Type Tag
|
||||
}
|
||||
|
||||
// NewLoader reads the `file` header and returns
|
||||
// a new lazy ot.
|
||||
// `file` will be used to parse tables, and should not be close.
|
||||
func NewLoader(file Resource) (*Loader, error) {
|
||||
return parseOneFont(file, 0, false)
|
||||
}
|
||||
|
||||
// NewLoaders is the same as `NewLoader`, but supports collections.
|
||||
func NewLoaders(file Resource) ([]*Loader, error) {
|
||||
_, err := file.Seek(0, io.SeekStart) // file might have been used before
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var bytes [4]byte
|
||||
_, err = file.Read(bytes[:])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
magic := NewTag(bytes[0], bytes[1], bytes[2], bytes[3])
|
||||
|
||||
file.Seek(0, io.SeekStart)
|
||||
|
||||
var (
|
||||
pr *Loader
|
||||
offsets []uint32
|
||||
relativeOffset bool
|
||||
)
|
||||
switch magic {
|
||||
case signatureWOFF, TrueType, OpenType, PostScript1, AppleTrueType:
|
||||
pr, err = parseOneFont(file, 0, false)
|
||||
case ttcTag:
|
||||
offsets, err = parseTTCHeader(file)
|
||||
case dfontResourceDataOffset:
|
||||
offsets, err = parseDfont(file)
|
||||
relativeOffset = true
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported font format %v", bytes)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// only one font
|
||||
if pr != nil {
|
||||
return []*Loader{pr}, nil
|
||||
}
|
||||
|
||||
// collection
|
||||
out := make([]*Loader, len(offsets))
|
||||
for i, o := range offsets {
|
||||
out[i], err = parseOneFont(file, o, relativeOffset)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// dst is an optional storage which may be provided to reduce allocations.
|
||||
func (pr *Loader) findTableBuffer(s tableSection, dst []byte) ([]byte, error) {
|
||||
if s.length != 0 && s.length < s.zLength {
|
||||
zbuf := io.NewSectionReader(pr.file, int64(s.offset), int64(s.length))
|
||||
r, err := zlib.NewReader(zbuf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer r.Close()
|
||||
|
||||
if cap(dst) < int(s.zLength) {
|
||||
dst = make([]byte, s.zLength)
|
||||
}
|
||||
dst = dst[0:s.zLength]
|
||||
if _, err := io.ReadFull(r, dst); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
if cap(dst) < int(s.length) {
|
||||
dst = make([]byte, s.length)
|
||||
}
|
||||
dst = dst[0:s.length]
|
||||
if _, err := pr.file.ReadAt(dst, int64(s.offset)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return dst, nil
|
||||
}
|
||||
|
||||
// HasTable returns true if [table] is present.
|
||||
func (pr *Loader) HasTable(table Tag) bool {
|
||||
_, has := pr.tables[table]
|
||||
return has
|
||||
}
|
||||
|
||||
// Tables returns all the tables found in the file,
|
||||
// as a sorted slice.
|
||||
func (ld *Loader) Tables() []Tag {
|
||||
out := make([]Tag, 0, len(ld.tables))
|
||||
for tag := range ld.tables {
|
||||
out = append(out, tag)
|
||||
}
|
||||
sort.Slice(out, func(i, j int) bool { return out[i] < out[j] })
|
||||
return out
|
||||
}
|
||||
|
||||
// RawTable returns the binary content of the given table,
|
||||
// or an error if not found.
|
||||
func (pr *Loader) RawTable(tag Tag) ([]byte, error) {
|
||||
return pr.RawTableTo(tag, nil)
|
||||
}
|
||||
|
||||
// RawTable writes the binary content of the given table to [dst], returning it,
|
||||
// or an error if not found.
|
||||
func (pr *Loader) RawTableTo(tag Tag, dst []byte) ([]byte, error) {
|
||||
s, found := pr.tables[tag]
|
||||
if !found {
|
||||
return nil, fmt.Errorf("missing table %s", tag)
|
||||
}
|
||||
|
||||
return pr.findTableBuffer(s, dst)
|
||||
}
|
||||
|
||||
func parseOneFont(file Resource, offset uint32, relativeOffset bool) (parser *Loader, err error) {
|
||||
_, err = file.Seek(int64(offset), io.SeekStart)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid offset: %s", err)
|
||||
}
|
||||
|
||||
var bytes [4]byte
|
||||
_, err = file.Read(bytes[:])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
magic := NewTag(bytes[0], bytes[1], bytes[2], bytes[3])
|
||||
|
||||
switch magic {
|
||||
case signatureWOFF:
|
||||
parser, err = parseWOFF(file, offset, relativeOffset)
|
||||
case TrueType, OpenType, PostScript1, AppleTrueType:
|
||||
parser, err = parseOTF(file, offset, relativeOffset)
|
||||
case ttcTag, dfontResourceDataOffset: // no more collections allowed here
|
||||
return nil, errors.New("collections not allowed")
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown font format tag %v", bytes)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return parser, nil
|
||||
}
|
||||
|
||||
// support for collections
|
||||
|
||||
const maxNumFonts = 2048 // security implementation limit
|
||||
|
||||
// returns the offsets of each font
|
||||
func parseTTCHeader(r io.Reader) ([]uint32, error) {
|
||||
// The https://www.microsoft.com/typography/otspec/otff.htm "Font
|
||||
// Collections" section describes the TTC header.
|
||||
var buf [12]byte
|
||||
if _, err := r.Read(buf[:]); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// skip versions
|
||||
numFonts := binary.BigEndian.Uint32(buf[8:])
|
||||
if numFonts == 0 {
|
||||
return nil, errors.New("empty font collection")
|
||||
}
|
||||
if numFonts > maxNumFonts {
|
||||
return nil, fmt.Errorf("number of fonts (%d) in collection exceed implementation limit (%d)",
|
||||
numFonts, maxNumFonts)
|
||||
}
|
||||
|
||||
offsetsBytes := make([]byte, numFonts*4)
|
||||
_, err := io.ReadFull(r, offsetsBytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return parseUint32s(offsetsBytes, int(numFonts)), nil
|
||||
}
|
||||
|
||||
// parseDfont parses a dfont resource map, as per
|
||||
// https://github.com/kreativekorp/ksfl/wiki/Macintosh-Resource-File-Format
|
||||
//
|
||||
// That unofficial wiki page lists all of its fields as *signed* integers,
|
||||
// which looks unusual. The actual file format might use *unsigned* integers in
|
||||
// various places, but until we have either an official specification or an
|
||||
// actual dfont file where this matters, we'll use signed integers and treat
|
||||
// negative values as invalid.
|
||||
func parseDfont(r Resource) ([]uint32, error) {
|
||||
var buf [16]byte
|
||||
if _, err := r.Read(buf[:]); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resourceMapOffset := binary.BigEndian.Uint32(buf[4:])
|
||||
resourceMapLength := binary.BigEndian.Uint32(buf[12:])
|
||||
|
||||
const (
|
||||
// (maxTableOffset + maxTableLength) will not overflow an int32.
|
||||
maxTableLength = 1 << 29
|
||||
maxTableOffset = 1 << 29
|
||||
)
|
||||
if resourceMapOffset > maxTableOffset || resourceMapLength > maxTableLength {
|
||||
return nil, errors.New("unsupported table offset or length")
|
||||
}
|
||||
|
||||
const headerSize = 28
|
||||
if resourceMapLength < headerSize {
|
||||
return nil, errInvalidDfont
|
||||
}
|
||||
_, err := r.ReadAt(buf[:2], int64(resourceMapOffset+24))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
typeListOffset := int64(int16(binary.BigEndian.Uint16(buf[:])))
|
||||
if typeListOffset < headerSize || resourceMapLength < uint32(typeListOffset)+2 {
|
||||
return nil, errInvalidDfont
|
||||
}
|
||||
_, err = r.ReadAt(buf[:2], int64(resourceMapOffset)+typeListOffset)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
typeCount := int(binary.BigEndian.Uint16(buf[:])) // The number of types, minus one.
|
||||
if typeCount == 0xFFFF {
|
||||
return nil, errInvalidDfont
|
||||
}
|
||||
typeCount += 1
|
||||
|
||||
const tSize = 8
|
||||
if tSize*uint32(typeCount) > resourceMapLength-uint32(typeListOffset)-2 {
|
||||
return nil, errInvalidDfont
|
||||
}
|
||||
|
||||
typeList := make([]byte, tSize*typeCount)
|
||||
_, err = r.ReadAt(typeList, int64(resourceMapOffset)+typeListOffset+2)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
numFonts, resourceListOffset := 0, 0
|
||||
for i := 0; i < typeCount; i++ {
|
||||
if binary.BigEndian.Uint32(typeList[tSize*i:]) != 0x73666e74 { // "sfnt".
|
||||
continue
|
||||
}
|
||||
|
||||
numFonts = int(int16(binary.BigEndian.Uint16(typeList[tSize*i+4:])))
|
||||
if numFonts < 0 {
|
||||
return nil, errInvalidDfont
|
||||
}
|
||||
// https://github.com/kreativekorp/ksfl/wiki/Macintosh-Resource-File-Format
|
||||
// says that the value in the wire format is "the number of
|
||||
// resources of this type, minus one."
|
||||
numFonts++
|
||||
|
||||
resourceListOffset = int(int16(binary.BigEndian.Uint16((typeList[tSize*i+6:]))))
|
||||
if resourceListOffset < 0 {
|
||||
return nil, errInvalidDfont
|
||||
}
|
||||
}
|
||||
if numFonts == 0 {
|
||||
return nil, errInvalidDfont
|
||||
}
|
||||
if numFonts > maxNumFonts {
|
||||
return nil, fmt.Errorf("number of fonts (%d) in collection exceed implementation limit (%d)",
|
||||
numFonts, maxNumFonts)
|
||||
}
|
||||
|
||||
const rSize = 12
|
||||
o, n := uint32(int(typeListOffset)+resourceListOffset), rSize*uint32(numFonts)
|
||||
if o > resourceMapLength || n > resourceMapLength-o {
|
||||
return nil, errInvalidDfont
|
||||
}
|
||||
|
||||
offsetsBytes := make([]byte, n)
|
||||
_, err = r.ReadAt(offsetsBytes, int64(resourceMapOffset+o))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
offsets := make([]uint32, numFonts)
|
||||
for i := range offsets {
|
||||
o := 0xffffff & binary.BigEndian.Uint32(offsetsBytes[rSize*i+4:])
|
||||
// Offsets are relative to the resource data start, not the file start.
|
||||
// A particular resource's data also starts with a 4-byte length, which
|
||||
// we skip.
|
||||
o += dfontResourceDataOffset + 4
|
||||
if o > maxTableOffset {
|
||||
return nil, errors.New("unsupported table offset or length")
|
||||
}
|
||||
offsets[i] = o
|
||||
}
|
||||
|
||||
return offsets, nil
|
||||
}
|
||||
|
||||
// data length must have been checked
|
||||
func parseUint32s(data []byte, count int) []uint32 {
|
||||
out := make([]uint32, count)
|
||||
for i := range out {
|
||||
out[i] = binary.BigEndian.Uint32(data[4*i:])
|
||||
}
|
||||
return out
|
||||
}
|
||||
+99
@@ -0,0 +1,99 @@
|
||||
// SPDX-License-Identifier: Unlicense OR BSD-3-Clause
|
||||
|
||||
package opentype
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
)
|
||||
|
||||
// An Entry in an OpenType table.
|
||||
type otfEntry struct {
|
||||
Tag Tag
|
||||
CheckSum uint32
|
||||
Offset uint32
|
||||
Length uint32
|
||||
}
|
||||
|
||||
const (
|
||||
otfHeaderSize = 12
|
||||
otfEntrySize = 16
|
||||
)
|
||||
|
||||
func readOTFHeader(r io.Reader) (flavor Tag, numTables uint16, err error) {
|
||||
var buf [otfHeaderSize]byte
|
||||
if _, err := r.Read(buf[:]); err != nil {
|
||||
return 0, 0, fmt.Errorf("invalid OpenType header: %s", err)
|
||||
}
|
||||
|
||||
return NewTag(buf[0], buf[1], buf[2], buf[3]), binary.BigEndian.Uint16(buf[4:6]), nil
|
||||
}
|
||||
|
||||
func readOTFEntry(r io.Reader) (otfEntry, error) {
|
||||
var (
|
||||
buf [otfEntrySize]byte
|
||||
entry otfEntry
|
||||
)
|
||||
if _, err := io.ReadFull(r, buf[:]); err != nil {
|
||||
return entry, fmt.Errorf("invalid directory entry: %s", err)
|
||||
}
|
||||
|
||||
entry.Tag = Tag(binary.BigEndian.Uint32(buf[0:4]))
|
||||
entry.CheckSum = binary.BigEndian.Uint32(buf[4:8])
|
||||
entry.Offset = binary.BigEndian.Uint32(buf[8:12])
|
||||
entry.Length = binary.BigEndian.Uint32(buf[12:16])
|
||||
|
||||
return entry, nil
|
||||
}
|
||||
|
||||
// parseOTF reads an OpenTyp (.otf) or TrueType (.ttf) file and returns a Font.
|
||||
// If the parsing fails, then an error is returned and Font will be nil.
|
||||
// `offset` is the beginning of the ressource in the file (non zero for collections)
|
||||
// `relativeOffset` is true when the table offset are expresed relatively to the ressource start
|
||||
// (that is, `offset`) rather than to the file start.
|
||||
func parseOTF(file Resource, offset uint32, relativeOffset bool) (*Loader, error) {
|
||||
_, err := file.Seek(int64(offset), io.SeekStart)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid offset: %s", err)
|
||||
}
|
||||
|
||||
flavor, numTables, err := readOTFHeader(file)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pr := &Loader{
|
||||
file: file,
|
||||
tables: make(map[Tag]tableSection, numTables),
|
||||
Type: flavor,
|
||||
}
|
||||
|
||||
for i := 0; i < int(numTables); i++ {
|
||||
entry, err := readOTFEntry(file)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if _, found := pr.tables[entry.Tag]; found {
|
||||
// ignore duplicate tables – the first one wins
|
||||
continue
|
||||
}
|
||||
|
||||
sec := tableSection{
|
||||
offset: entry.Offset,
|
||||
length: entry.Length,
|
||||
}
|
||||
// adapt the relative offsets
|
||||
if relativeOffset {
|
||||
sec.offset += offset
|
||||
if sec.offset < offset { // check for overflow
|
||||
return nil, errors.New("unsupported table offset or length")
|
||||
}
|
||||
}
|
||||
pr.tables[entry.Tag] = sec
|
||||
}
|
||||
|
||||
return pr, nil
|
||||
}
|
||||
+96
@@ -0,0 +1,96 @@
|
||||
// SPDX-License-Identifier: Unlicense OR BSD-3-Clause
|
||||
|
||||
package opentype
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"io"
|
||||
)
|
||||
|
||||
type woffEntry struct {
|
||||
Tag Tag
|
||||
Offset uint32
|
||||
CompLength uint32
|
||||
OrigLength uint32
|
||||
OrigChecksum uint32
|
||||
}
|
||||
|
||||
const (
|
||||
woffHeaderSize = 44 // for the full header, but we only read Flavor and NumTables
|
||||
woffEntrySize = 20
|
||||
)
|
||||
|
||||
func readWOFFHeader(r io.Reader) (flavor Tag, numTables uint16, err error) {
|
||||
var buf [woffHeaderSize]byte
|
||||
if _, err := io.ReadFull(r, buf[:]); err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
|
||||
return NewTag(buf[4], buf[5], buf[6], buf[7]), binary.BigEndian.Uint16(buf[12:14]), nil
|
||||
}
|
||||
|
||||
func readWOFFEntry(r io.Reader) (woffEntry, error) {
|
||||
var (
|
||||
buf [woffEntrySize]byte
|
||||
entry woffEntry
|
||||
)
|
||||
if _, err := io.ReadFull(r, buf[:]); err != nil {
|
||||
return entry, err
|
||||
}
|
||||
entry.Tag = NewTag(buf[0], buf[1], buf[2], buf[3])
|
||||
entry.Offset = binary.BigEndian.Uint32(buf[4:8])
|
||||
entry.CompLength = binary.BigEndian.Uint32(buf[8:12])
|
||||
entry.OrigLength = binary.BigEndian.Uint32(buf[12:16])
|
||||
entry.OrigChecksum = binary.BigEndian.Uint32(buf[16:20])
|
||||
return entry, nil
|
||||
}
|
||||
|
||||
// `offset` is the beginning of the ressource in the file (non zero for collections)
|
||||
// `relativeOffset` is true when the table offset are expresed relatively ot the ressource
|
||||
// (that is, `offset`) rather than to the file
|
||||
func parseWOFF(file Resource, offset uint32, relativeOffset bool) (*Loader, error) {
|
||||
_, err := file.Seek(int64(offset), io.SeekStart)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
flavor, numTables, err := readWOFFHeader(file)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
fontParser := &Loader{
|
||||
file: file,
|
||||
tables: make(map[Tag]tableSection, numTables),
|
||||
Type: flavor,
|
||||
}
|
||||
for i := 0; i < int(numTables); i++ {
|
||||
entry, err := readWOFFEntry(file)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if _, found := fontParser.tables[entry.Tag]; found {
|
||||
// ignore duplicate tables – the first one wins
|
||||
continue
|
||||
}
|
||||
|
||||
sec := tableSection{
|
||||
offset: entry.Offset,
|
||||
length: entry.CompLength,
|
||||
zLength: entry.OrigLength,
|
||||
}
|
||||
// adapt the relative offsets
|
||||
if relativeOffset {
|
||||
sec.offset += offset
|
||||
if sec.offset < offset { // check for overflow
|
||||
return nil, errors.New("unsupported table offset or length")
|
||||
}
|
||||
}
|
||||
|
||||
fontParser.tables[entry.Tag] = sec
|
||||
}
|
||||
|
||||
return fontParser, nil
|
||||
}
|
||||
+342
@@ -0,0 +1,342 @@
|
||||
// SPDX-License-Identifier: Unlicense OR BSD-3-Clause
|
||||
|
||||
package tables
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// Code generated by binarygen from aat_ankr_src.go. DO NOT EDIT
|
||||
|
||||
func (item *AnkrAnchor) mustParse(src []byte) {
|
||||
_ = src[3] // early bound checking
|
||||
item.X = int16(binary.BigEndian.Uint16(src[0:]))
|
||||
item.Y = int16(binary.BigEndian.Uint16(src[2:]))
|
||||
}
|
||||
|
||||
func (item *LookupRecord2) mustParse(src []byte) {
|
||||
_ = src[5] // early bound checking
|
||||
item.LastGlyph = binary.BigEndian.Uint16(src[0:])
|
||||
item.FirstGlyph = binary.BigEndian.Uint16(src[2:])
|
||||
item.Value = binary.BigEndian.Uint16(src[4:])
|
||||
}
|
||||
|
||||
func ParseAATLookup(src []byte, valuesCount int) (AATLookup, int, error) {
|
||||
var item AATLookup
|
||||
|
||||
if L := len(src); L < 2 {
|
||||
return item, 0, fmt.Errorf("reading AATLookup: "+"EOF: expected length: 2, got %d", L)
|
||||
}
|
||||
format := uint16(binary.BigEndian.Uint16(src[0:]))
|
||||
var (
|
||||
read int
|
||||
err error
|
||||
)
|
||||
switch format {
|
||||
case 0:
|
||||
item, read, err = ParseAATLoopkup0(src[0:], valuesCount)
|
||||
case 10:
|
||||
item, read, err = ParseAATLoopkup10(src[0:])
|
||||
case 2:
|
||||
item, read, err = ParseAATLoopkup2(src[0:])
|
||||
case 4:
|
||||
item, read, err = ParseAATLoopkup4(src[0:])
|
||||
case 6:
|
||||
item, read, err = ParseAATLoopkup6(src[0:])
|
||||
case 8:
|
||||
item, read, err = ParseAATLoopkup8(src[0:])
|
||||
default:
|
||||
err = fmt.Errorf("unsupported AATLookup format %d", format)
|
||||
}
|
||||
if err != nil {
|
||||
return item, 0, fmt.Errorf("reading AATLookup: %s", err)
|
||||
}
|
||||
|
||||
return item, read, nil
|
||||
}
|
||||
|
||||
func ParseAATLookupRecord4(src []byte, parentSrc []byte) (AATLookupRecord4, int, error) {
|
||||
var item AATLookupRecord4
|
||||
n := 0
|
||||
if L := len(src); L < 6 {
|
||||
return item, 0, fmt.Errorf("reading AATLookupRecord4: "+"EOF: expected length: 6, got %d", L)
|
||||
}
|
||||
_ = src[5] // early bound checking
|
||||
item.LastGlyph = binary.BigEndian.Uint16(src[0:])
|
||||
item.FirstGlyph = binary.BigEndian.Uint16(src[2:])
|
||||
offsetValues := int(binary.BigEndian.Uint16(src[4:]))
|
||||
n += 6
|
||||
|
||||
{
|
||||
if offsetValues != 0 { // ignore null offset
|
||||
if L := len(parentSrc); L < offsetValues {
|
||||
return item, 0, fmt.Errorf("reading AATLookupRecord4: "+"EOF: expected length: %d, got %d", offsetValues, L)
|
||||
}
|
||||
|
||||
arrayLength := int(item.nValues())
|
||||
|
||||
if L := len(parentSrc); L < offsetValues+arrayLength*2 {
|
||||
return item, 0, fmt.Errorf("reading AATLookupRecord4: "+"EOF: expected length: %d, got %d", offsetValues+arrayLength*2, L)
|
||||
}
|
||||
|
||||
item.Values = make([]uint16, arrayLength) // allocation guarded by the previous check
|
||||
for i := range item.Values {
|
||||
item.Values[i] = binary.BigEndian.Uint16(parentSrc[offsetValues+i*2:])
|
||||
}
|
||||
offsetValues += arrayLength * 2
|
||||
}
|
||||
}
|
||||
return item, n, nil
|
||||
}
|
||||
|
||||
func ParseAATLoopkup0(src []byte, valuesCount int) (AATLoopkup0, int, error) {
|
||||
var item AATLoopkup0
|
||||
n := 0
|
||||
if L := len(src); L < 2 {
|
||||
return item, 0, fmt.Errorf("reading AATLoopkup0: "+"EOF: expected length: 2, got %d", L)
|
||||
}
|
||||
item.version = binary.BigEndian.Uint16(src[0:])
|
||||
n += 2
|
||||
|
||||
{
|
||||
|
||||
if L := len(src); L < 2+valuesCount*2 {
|
||||
return item, 0, fmt.Errorf("reading AATLoopkup0: "+"EOF: expected length: %d, got %d", 2+valuesCount*2, L)
|
||||
}
|
||||
|
||||
item.Values = make([]uint16, valuesCount) // allocation guarded by the previous check
|
||||
for i := range item.Values {
|
||||
item.Values[i] = binary.BigEndian.Uint16(src[2+i*2:])
|
||||
}
|
||||
n += valuesCount * 2
|
||||
}
|
||||
return item, n, nil
|
||||
}
|
||||
|
||||
func ParseAATLoopkup10(src []byte) (AATLoopkup10, int, error) {
|
||||
var item AATLoopkup10
|
||||
n := 0
|
||||
if L := len(src); L < 8 {
|
||||
return item, 0, fmt.Errorf("reading AATLoopkup10: "+"EOF: expected length: 8, got %d", L)
|
||||
}
|
||||
_ = src[7] // early bound checking
|
||||
item.version = binary.BigEndian.Uint16(src[0:])
|
||||
item.unitSize = binary.BigEndian.Uint16(src[2:])
|
||||
item.FirstGlyph = binary.BigEndian.Uint16(src[4:])
|
||||
arrayLengthValues := int(binary.BigEndian.Uint16(src[6:]))
|
||||
n += 8
|
||||
|
||||
{
|
||||
|
||||
if L := len(src); L < 8+arrayLengthValues*2 {
|
||||
return item, 0, fmt.Errorf("reading AATLoopkup10: "+"EOF: expected length: %d, got %d", 8+arrayLengthValues*2, L)
|
||||
}
|
||||
|
||||
item.Values = make([]uint16, arrayLengthValues) // allocation guarded by the previous check
|
||||
for i := range item.Values {
|
||||
item.Values[i] = binary.BigEndian.Uint16(src[8+i*2:])
|
||||
}
|
||||
n += arrayLengthValues * 2
|
||||
}
|
||||
return item, n, nil
|
||||
}
|
||||
|
||||
func ParseAATLoopkup2(src []byte) (AATLoopkup2, int, error) {
|
||||
var item AATLoopkup2
|
||||
n := 0
|
||||
if L := len(src); L < 12 {
|
||||
return item, 0, fmt.Errorf("reading AATLoopkup2: "+"EOF: expected length: 12, got %d", L)
|
||||
}
|
||||
_ = src[11] // early bound checking
|
||||
item.version = binary.BigEndian.Uint16(src[0:])
|
||||
item.binSearchHeader.mustParse(src[2:])
|
||||
n += 12
|
||||
|
||||
{
|
||||
arrayLength := int(item.nUnits)
|
||||
|
||||
if L := len(src); L < 12+arrayLength*6 {
|
||||
return item, 0, fmt.Errorf("reading AATLoopkup2: "+"EOF: expected length: %d, got %d", 12+arrayLength*6, L)
|
||||
}
|
||||
|
||||
item.Records = make([]LookupRecord2, arrayLength) // allocation guarded by the previous check
|
||||
for i := range item.Records {
|
||||
item.Records[i].mustParse(src[12+i*6:])
|
||||
}
|
||||
n += arrayLength * 6
|
||||
}
|
||||
return item, n, nil
|
||||
}
|
||||
|
||||
func ParseAATLoopkup4(src []byte) (AATLoopkup4, int, error) {
|
||||
var item AATLoopkup4
|
||||
n := 0
|
||||
if L := len(src); L < 12 {
|
||||
return item, 0, fmt.Errorf("reading AATLoopkup4: "+"EOF: expected length: 12, got %d", L)
|
||||
}
|
||||
_ = src[11] // early bound checking
|
||||
item.version = binary.BigEndian.Uint16(src[0:])
|
||||
item.binSearchHeader.mustParse(src[2:])
|
||||
n += 12
|
||||
|
||||
{
|
||||
arrayLength := int(item.nUnits - 1)
|
||||
|
||||
offset := 12
|
||||
for i := 0; i < arrayLength; i++ {
|
||||
elem, read, err := ParseAATLookupRecord4(src[offset:], src)
|
||||
if err != nil {
|
||||
return item, 0, fmt.Errorf("reading AATLoopkup4: %s", err)
|
||||
}
|
||||
item.Records = append(item.Records, elem)
|
||||
offset += read
|
||||
}
|
||||
n = offset
|
||||
}
|
||||
return item, n, nil
|
||||
}
|
||||
|
||||
func ParseAATLoopkup6(src []byte) (AATLoopkup6, int, error) {
|
||||
var item AATLoopkup6
|
||||
n := 0
|
||||
if L := len(src); L < 12 {
|
||||
return item, 0, fmt.Errorf("reading AATLoopkup6: "+"EOF: expected length: 12, got %d", L)
|
||||
}
|
||||
_ = src[11] // early bound checking
|
||||
item.version = binary.BigEndian.Uint16(src[0:])
|
||||
item.binSearchHeader.mustParse(src[2:])
|
||||
n += 12
|
||||
|
||||
{
|
||||
arrayLength := int(item.nUnits)
|
||||
|
||||
if L := len(src); L < 12+arrayLength*4 {
|
||||
return item, 0, fmt.Errorf("reading AATLoopkup6: "+"EOF: expected length: %d, got %d", 12+arrayLength*4, L)
|
||||
}
|
||||
|
||||
item.Records = make([]loopkupRecord6, arrayLength) // allocation guarded by the previous check
|
||||
for i := range item.Records {
|
||||
item.Records[i].mustParse(src[12+i*4:])
|
||||
}
|
||||
n += arrayLength * 4
|
||||
}
|
||||
return item, n, nil
|
||||
}
|
||||
|
||||
func ParseAATLoopkup8(src []byte) (AATLoopkup8, int, error) {
|
||||
var item AATLoopkup8
|
||||
n := 0
|
||||
if L := len(src); L < 2 {
|
||||
return item, 0, fmt.Errorf("reading AATLoopkup8: "+"EOF: expected length: 2, got %d", L)
|
||||
}
|
||||
item.version = binary.BigEndian.Uint16(src[0:])
|
||||
n += 2
|
||||
|
||||
{
|
||||
var (
|
||||
err error
|
||||
read int
|
||||
)
|
||||
item.AATLoopkup8Data, read, err = ParseAATLoopkup8Data(src[2:])
|
||||
if err != nil {
|
||||
return item, 0, fmt.Errorf("reading AATLoopkup8: %s", err)
|
||||
}
|
||||
n += read
|
||||
}
|
||||
return item, n, nil
|
||||
}
|
||||
|
||||
func ParseAATLoopkup8Data(src []byte) (AATLoopkup8Data, int, error) {
|
||||
var item AATLoopkup8Data
|
||||
n := 0
|
||||
if L := len(src); L < 4 {
|
||||
return item, 0, fmt.Errorf("reading AATLoopkup8Data: "+"EOF: expected length: 4, got %d", L)
|
||||
}
|
||||
_ = src[3] // early bound checking
|
||||
item.FirstGlyph = binary.BigEndian.Uint16(src[0:])
|
||||
arrayLengthValues := int(binary.BigEndian.Uint16(src[2:]))
|
||||
n += 4
|
||||
|
||||
{
|
||||
|
||||
if L := len(src); L < 4+arrayLengthValues*2 {
|
||||
return item, 0, fmt.Errorf("reading AATLoopkup8Data: "+"EOF: expected length: %d, got %d", 4+arrayLengthValues*2, L)
|
||||
}
|
||||
|
||||
item.Values = make([]uint16, arrayLengthValues) // allocation guarded by the previous check
|
||||
for i := range item.Values {
|
||||
item.Values[i] = binary.BigEndian.Uint16(src[4+i*2:])
|
||||
}
|
||||
n += arrayLengthValues * 2
|
||||
}
|
||||
return item, n, nil
|
||||
}
|
||||
|
||||
func ParseAnkr(src []byte, valuesCount int) (Ankr, int, error) {
|
||||
var item Ankr
|
||||
n := 0
|
||||
if L := len(src); L < 12 {
|
||||
return item, 0, fmt.Errorf("reading Ankr: "+"EOF: expected length: 12, got %d", L)
|
||||
}
|
||||
_ = src[11] // early bound checking
|
||||
item.version = binary.BigEndian.Uint16(src[0:])
|
||||
item.flags = binary.BigEndian.Uint16(src[2:])
|
||||
offsetLookupTable := int(binary.BigEndian.Uint32(src[4:]))
|
||||
offsetGlyphDataTable := int(binary.BigEndian.Uint32(src[8:]))
|
||||
n += 12
|
||||
|
||||
{
|
||||
if offsetLookupTable != 0 { // ignore null offset
|
||||
if L := len(src); L < offsetLookupTable {
|
||||
return item, 0, fmt.Errorf("reading Ankr: "+"EOF: expected length: %d, got %d", offsetLookupTable, L)
|
||||
}
|
||||
|
||||
var (
|
||||
err error
|
||||
read int
|
||||
)
|
||||
item.lookupTable, read, err = ParseAATLookup(src[offsetLookupTable:], valuesCount)
|
||||
if err != nil {
|
||||
return item, 0, fmt.Errorf("reading Ankr: %s", err)
|
||||
}
|
||||
offsetLookupTable += read
|
||||
}
|
||||
}
|
||||
{
|
||||
if offsetGlyphDataTable != 0 { // ignore null offset
|
||||
if L := len(src); L < offsetGlyphDataTable {
|
||||
return item, 0, fmt.Errorf("reading Ankr: "+"EOF: expected length: %d, got %d", offsetGlyphDataTable, L)
|
||||
}
|
||||
|
||||
item.glyphDataTable = src[offsetGlyphDataTable:]
|
||||
}
|
||||
}
|
||||
return item, n, nil
|
||||
}
|
||||
|
||||
func ParseAnkrAnchor(src []byte) (AnkrAnchor, int, error) {
|
||||
var item AnkrAnchor
|
||||
n := 0
|
||||
if L := len(src); L < 4 {
|
||||
return item, 0, fmt.Errorf("reading AnkrAnchor: "+"EOF: expected length: 4, got %d", L)
|
||||
}
|
||||
item.mustParse(src)
|
||||
n += 4
|
||||
return item, n, nil
|
||||
}
|
||||
|
||||
func (item *binSearchHeader) mustParse(src []byte) {
|
||||
_ = src[9] // early bound checking
|
||||
item.unitSize = binary.BigEndian.Uint16(src[0:])
|
||||
item.nUnits = binary.BigEndian.Uint16(src[2:])
|
||||
item.searchRange = binary.BigEndian.Uint16(src[4:])
|
||||
item.entrySelector = binary.BigEndian.Uint16(src[6:])
|
||||
item.rangeShift = binary.BigEndian.Uint16(src[8:])
|
||||
}
|
||||
|
||||
func (item *loopkupRecord6) mustParse(src []byte) {
|
||||
_ = src[3] // early bound checking
|
||||
item.Glyph = binary.BigEndian.Uint16(src[0:])
|
||||
item.Value = binary.BigEndian.Uint16(src[2:])
|
||||
}
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
// SPDX-License-Identifier: Unlicense OR BSD-3-Clause
|
||||
|
||||
package tables
|
||||
|
||||
import "encoding/binary"
|
||||
|
||||
// Ankr is the anchor point table
|
||||
// See - https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6ankr.html
|
||||
type Ankr struct {
|
||||
version uint16 // Version number (set to zero)
|
||||
flags uint16 // Flags (currently unused; set to zero)
|
||||
// Offset to the table's lookup table; currently this is always 0x0000000C
|
||||
// The lookup table returns uint16 offset from the beginning of the glyph data table, not indices.
|
||||
lookupTable AATLookup `offsetSize:"Offset32"`
|
||||
// Offset to the glyph data table
|
||||
glyphDataTable []byte `offsetSize:"Offset32" arrayCount:"ToEnd"`
|
||||
}
|
||||
|
||||
// GetAnchor return the i-th anchor for `glyph`, or {0,0} if not found.
|
||||
func (ank Ankr) GetAnchor(glyph GlyphID, index int) (anchor AnkrAnchor) {
|
||||
offset, ok := ank.lookupTable.Class(glyph)
|
||||
if !ok || int(offset)+4 >= len(ank.glyphDataTable) {
|
||||
return anchor
|
||||
}
|
||||
|
||||
count := int(binary.BigEndian.Uint32(ank.glyphDataTable[offset:]))
|
||||
if index >= count {
|
||||
return anchor // invalid index
|
||||
}
|
||||
|
||||
indexStart := int(offset) + 4 + 4*index
|
||||
if len(ank.glyphDataTable) < indexStart+4 {
|
||||
return anchor // invalid table
|
||||
}
|
||||
anchor.X = int16(binary.BigEndian.Uint16(ank.glyphDataTable[indexStart:]))
|
||||
anchor.Y = int16(binary.BigEndian.Uint16(ank.glyphDataTable[indexStart+2:]))
|
||||
return anchor
|
||||
}
|
||||
|
||||
// AnkrAnchor is a point within the coordinate space of a given glyph
|
||||
// independent of the control points used to render the glyph
|
||||
type AnkrAnchor struct {
|
||||
X, Y int16
|
||||
}
|
||||
+452
@@ -0,0 +1,452 @@
|
||||
// SPDX-License-Identifier: Unlicense OR BSD-3-Clause
|
||||
|
||||
package tables
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// AAT layout
|
||||
|
||||
// State table header, without the actual data
|
||||
// See https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6Tables.html
|
||||
type AATStateTable struct {
|
||||
StateSize uint16 // Size of a state, in bytes. The size is limited to 8 bits, although the field is 16 bits for alignment.
|
||||
ClassTable ClassTable `offsetSize:"Offset16"` // Byte offset from the beginning of the state table to the class subtable.
|
||||
stateArray Offset16 // Byte offset from the beginning of the state table to the state array.
|
||||
entryTable Offset16 // Byte offset from the beginning of the state table to the entry subtable.
|
||||
States [][]uint8 `isOpaque:""`
|
||||
Entries []AATStateEntry `isOpaque:""` // entry data are empty
|
||||
}
|
||||
|
||||
func (state *AATStateTable) parseStates(src []byte) error {
|
||||
if state.stateArray > state.entryTable {
|
||||
return fmt.Errorf("invalid AAT state offsets (%d > %d)", state.stateArray, state.entryTable)
|
||||
}
|
||||
if L := len(src); L < int(state.entryTable) {
|
||||
return fmt.Errorf("EOF: expected length: %d, got %d", state.entryTable, L)
|
||||
}
|
||||
states := src[state.stateArray:state.entryTable]
|
||||
|
||||
nC := int(state.StateSize)
|
||||
// Ensure pre-defined classes fit.
|
||||
if nC < 4 {
|
||||
return fmt.Errorf("invalid number of classes in AAT state table: %d", nC)
|
||||
}
|
||||
state.States = make([][]uint8, len(states)/nC)
|
||||
for i := range state.States {
|
||||
state.States[i] = states[i*nC : (i+1)*nC]
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (state *AATStateTable) parseEntries(src []byte) (int, error) {
|
||||
// find max index
|
||||
var maxi uint8
|
||||
for _, l := range state.States {
|
||||
for _, stateIndex := range l {
|
||||
if stateIndex > maxi {
|
||||
maxi = stateIndex
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
src = src[state.entryTable:] // checked in parseStates
|
||||
count := int(maxi) + 1
|
||||
var err error
|
||||
state.Entries, err = parseAATStateEntries(src, count, 0)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
// newState is an offset: convert back to index
|
||||
for i, entry := range state.Entries {
|
||||
state.Entries[i].NewState = uint16((int(entry.NewState) - int(state.stateArray)) / int(state.StateSize))
|
||||
}
|
||||
|
||||
// the own header data stop at the entryTable offset
|
||||
return 8, err
|
||||
}
|
||||
|
||||
// src starts at the entryTable
|
||||
func parseAATStateEntries(src []byte, count, entryDataSize int) ([]AATStateEntry, error) {
|
||||
entrySize := 4 + entryDataSize
|
||||
if L := len(src); L < count*entrySize {
|
||||
return nil, fmt.Errorf("EOF: expected length: %d, got %d", count*entrySize, L)
|
||||
}
|
||||
out := make([]AATStateEntry, count)
|
||||
for i := range out {
|
||||
out[i].NewState = binary.BigEndian.Uint16(src[i*entrySize:])
|
||||
out[i].Flags = binary.BigEndian.Uint16(src[i*entrySize+2:])
|
||||
copy(out[i].data[:], src[i*entrySize+4:(i+1)*entrySize])
|
||||
}
|
||||
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// ClassTable is the same as AATLookup8, but with no format and with bytes instead of uint16s
|
||||
type ClassTable struct {
|
||||
StartGlyph GlyphID
|
||||
Values []byte `arrayCount:"FirstUint16"`
|
||||
}
|
||||
|
||||
// Extended state table, including the data
|
||||
// See https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6Tables.html - State tables
|
||||
// binarygen: argument=entryDataSize int
|
||||
type AATStateTableExt struct {
|
||||
StateSize uint32 // Size of a state, in bytes. The size is limited to 8 bits, although the field is 16 bits for alignment.
|
||||
Class AATLookup `offsetSize:"Offset32"` // Byte offset from the beginning of the state table to the class subtable.
|
||||
stateArray Offset32 // Byte offset from the beginning of the state table to the state array.
|
||||
entryTable Offset32 // Byte offset from the beginning of the state table to the entry subtable.
|
||||
States [][]uint16 `isOpaque:""` // each sub array has length stateSize
|
||||
Entries []AATStateEntry `isOpaque:""` // length is the maximum state + 1
|
||||
}
|
||||
|
||||
func (state *AATStateTableExt) parseStates(src []byte, _, _ int) error {
|
||||
if state.stateArray > state.entryTable {
|
||||
return fmt.Errorf("invalid AAT state offsets (%d > %d)", state.stateArray, state.entryTable)
|
||||
}
|
||||
if L := len(src); L < int(state.entryTable) {
|
||||
return fmt.Errorf("EOF: expected length: %d, got %d", state.entryTable, L)
|
||||
}
|
||||
|
||||
statesArray := src[state.stateArray:state.entryTable]
|
||||
states, err := ParseUint16s(statesArray, len(statesArray)/2)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
nC := int(state.StateSize)
|
||||
// Ensure pre-defined classes fit.
|
||||
if nC < 4 {
|
||||
return fmt.Errorf("invalid number of classes in AAT state table: %d", nC)
|
||||
}
|
||||
state.States = make([][]uint16, len(states)/nC)
|
||||
for i := range state.States {
|
||||
state.States[i] = states[i*nC : (i+1)*nC]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (state *AATStateTableExt) parseEntries(src []byte, _, entryDataSize int) (int, error) {
|
||||
// find max index
|
||||
var maxi uint16
|
||||
for _, l := range state.States {
|
||||
for _, stateIndex := range l {
|
||||
if stateIndex > maxi {
|
||||
maxi = stateIndex
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
src = src[state.entryTable:] // checked in parseStates
|
||||
count := int(maxi) + 1
|
||||
var err error
|
||||
state.Entries, err = parseAATStateEntries(src, count, entryDataSize)
|
||||
|
||||
// the own header data stop at the entryTable offset
|
||||
return 16, err
|
||||
}
|
||||
|
||||
// AATStateEntry is shared between old and extended state tables,
|
||||
// and between the different kind of entries.
|
||||
// See the various AsXXX() methods.
|
||||
type AATStateEntry struct {
|
||||
NewState uint16
|
||||
Flags uint16 // Table specific.
|
||||
data [4]byte // Table specific.
|
||||
}
|
||||
|
||||
// AsMorxContextual reads the internal data for entries in morx contextual subtable.
|
||||
// The returned indexes use 0xFFFF as empty value.
|
||||
func (e AATStateEntry) AsMorxContextual() (markIndex, currentIndex uint16) {
|
||||
markIndex = binary.BigEndian.Uint16(e.data[:])
|
||||
currentIndex = binary.BigEndian.Uint16(e.data[2:])
|
||||
return
|
||||
}
|
||||
|
||||
// AsMorxInsertion reads the internal data for entries in morx insertion subtable.
|
||||
// The returned indexes use 0xFFFF as empty value.
|
||||
func (e AATStateEntry) AsMorxInsertion() (currentIndex, markedIndex uint16) {
|
||||
currentIndex = binary.BigEndian.Uint16(e.data[:])
|
||||
markedIndex = binary.BigEndian.Uint16(e.data[2:])
|
||||
return
|
||||
}
|
||||
|
||||
// AsMorxLigature reads the internal data for entries in morx ligature subtable.
|
||||
func (e AATStateEntry) AsMorxLigature() (ligActionIndex uint16) {
|
||||
return binary.BigEndian.Uint16(e.data[:])
|
||||
}
|
||||
|
||||
// AsKernxIndex reads the internal data for entries in 'kern/x' subtable format 1 or 4.
|
||||
// An entry with no index returns 0xFFFF
|
||||
func (e AATStateEntry) AsKernxIndex() uint16 {
|
||||
// for kern table, during parsing, we store the resolved index
|
||||
// at the same place as kerx tables
|
||||
return binary.BigEndian.Uint16(e.data[:])
|
||||
}
|
||||
|
||||
type binSearchHeader struct {
|
||||
unitSize uint16
|
||||
nUnits uint16
|
||||
searchRange uint16 // The value of unitSize times the largest power of 2 that is less than or equal to the value of nUnits.
|
||||
entrySelector uint16 // The log base 2 of the largest power of 2 less than or equal to the value of nUnits.
|
||||
rangeShift uint16 // The value of unitSize times the difference of the value of nUnits minus the largest power of 2 less than or equal to the value of nUnits.
|
||||
}
|
||||
|
||||
// AATLookup is conceptually a map[GlyphID]uint16, but it may
|
||||
// be implemented more efficiently.
|
||||
type AATLookup interface {
|
||||
AatLookupMixed
|
||||
|
||||
isAATLookup()
|
||||
|
||||
// Class returns the class ID for the provided glyph, or (0, false)
|
||||
// for glyphs not covered by this class.
|
||||
Class(g GlyphID) (uint16, bool)
|
||||
}
|
||||
|
||||
func (AATLoopkup0) isAATLookup() {}
|
||||
func (AATLoopkup2) isAATLookup() {}
|
||||
func (AATLoopkup4) isAATLookup() {}
|
||||
func (AATLoopkup6) isAATLookup() {}
|
||||
func (AATLoopkup8) isAATLookup() {}
|
||||
func (AATLoopkup10) isAATLookup() {}
|
||||
|
||||
type AATLoopkup0 struct {
|
||||
version uint16 `unionTag:"0"`
|
||||
Values []uint16 `arrayCount:""`
|
||||
}
|
||||
|
||||
type AATLoopkup2 struct {
|
||||
version uint16 `unionTag:"2"`
|
||||
binSearchHeader
|
||||
Records []LookupRecord2 `arrayCount:"ComputedField-nUnits"`
|
||||
}
|
||||
|
||||
type LookupRecord2 struct {
|
||||
LastGlyph GlyphID
|
||||
FirstGlyph GlyphID
|
||||
Value uint16
|
||||
}
|
||||
|
||||
type AATLoopkup4 struct {
|
||||
version uint16 `unionTag:"4"`
|
||||
binSearchHeader
|
||||
// Do not include the termination segment
|
||||
Records []AATLookupRecord4 `arrayCount:"ComputedField-nUnits-1"`
|
||||
}
|
||||
|
||||
type AATLookupRecord4 struct {
|
||||
LastGlyph GlyphID
|
||||
FirstGlyph GlyphID
|
||||
// offset to an array of []uint16 (or []uint32 for extended) with length last - first + 1
|
||||
Values []uint16 `offsetSize:"Offset16" offsetRelativeTo:"Parent" arrayCount:"ComputedField-nValues()"`
|
||||
}
|
||||
|
||||
func (lk AATLookupRecord4) nValues() int { return int(lk.LastGlyph) - int(lk.FirstGlyph) + 1 }
|
||||
|
||||
type AATLoopkup6 struct {
|
||||
version uint16 `unionTag:"6"`
|
||||
binSearchHeader
|
||||
Records []loopkupRecord6 `arrayCount:"ComputedField-nUnits"`
|
||||
}
|
||||
|
||||
type loopkupRecord6 struct {
|
||||
Glyph GlyphID
|
||||
Value uint16
|
||||
}
|
||||
|
||||
type AATLoopkup8 struct {
|
||||
version uint16 `unionTag:"8"`
|
||||
AATLoopkup8Data
|
||||
}
|
||||
|
||||
type AATLoopkup8Data struct {
|
||||
FirstGlyph GlyphID
|
||||
Values []uint16 `arrayCount:"FirstUint16"`
|
||||
}
|
||||
|
||||
type AATLoopkup10 struct {
|
||||
version uint16 `unionTag:"10"`
|
||||
unitSize uint16
|
||||
FirstGlyph GlyphID
|
||||
Values []uint16 `arrayCount:"FirstUint16"`
|
||||
}
|
||||
|
||||
// extended versions
|
||||
|
||||
// AATLookupExt is the same as AATLookup, but class values are uint32
|
||||
type AATLookupExt interface {
|
||||
AatLookupMixed
|
||||
|
||||
isAATLookupExt()
|
||||
|
||||
// Class returns the class ID for the provided glyph, or (0, false)
|
||||
// for glyphs not covered by this class.
|
||||
Class(g GlyphID) (uint32, bool)
|
||||
}
|
||||
|
||||
func (AATLoopkupExt0) isAATLookupExt() {}
|
||||
func (AATLoopkupExt2) isAATLookupExt() {}
|
||||
func (AATLoopkupExt4) isAATLookupExt() {}
|
||||
func (AATLoopkupExt6) isAATLookupExt() {}
|
||||
func (AATLoopkupExt8) isAATLookupExt() {}
|
||||
func (AATLoopkupExt10) isAATLookupExt() {}
|
||||
|
||||
type AATLoopkupExt0 struct {
|
||||
version uint16 `unionTag:"0"`
|
||||
Values []uint32 `arrayCount:""`
|
||||
}
|
||||
|
||||
type AATLoopkupExt2 struct {
|
||||
version uint16 `unionTag:"2"`
|
||||
binSearchHeader
|
||||
Records []lookupRecordExt2 `arrayCount:"ComputedField-nUnits"`
|
||||
}
|
||||
|
||||
type lookupRecordExt2 struct {
|
||||
LastGlyph GlyphID
|
||||
FirstGlyph GlyphID
|
||||
Value uint32
|
||||
}
|
||||
|
||||
type AATLoopkupExt4 struct {
|
||||
version uint16 `unionTag:"4"`
|
||||
binSearchHeader
|
||||
// the values pointed by the record are uint32
|
||||
Records []loopkupRecordExt4 `arrayCount:"ComputedField-nUnits"`
|
||||
}
|
||||
|
||||
type loopkupRecordExt4 struct {
|
||||
LastGlyph GlyphID
|
||||
FirstGlyph GlyphID
|
||||
// offset to an array of []uint16 (or []uint32 for extended) with length last - first + 1
|
||||
Values []uint32 `offsetSize:"Offset16" offsetRelativeTo:"Parent" arrayCount:"ComputedField-nValues()"`
|
||||
}
|
||||
|
||||
func (lk loopkupRecordExt4) nValues() int { return int(lk.LastGlyph) - int(lk.FirstGlyph) + 1 }
|
||||
|
||||
type AATLoopkupExt6 struct {
|
||||
version uint16 `unionTag:"6"`
|
||||
binSearchHeader
|
||||
Records []loopkupRecordExt6 `arrayCount:"ComputedField-nUnits"`
|
||||
}
|
||||
|
||||
type loopkupRecordExt6 struct {
|
||||
Glyph GlyphID
|
||||
Value uint32
|
||||
}
|
||||
|
||||
type AATLoopkupExt8 AATLoopkup8
|
||||
|
||||
type AATLoopkupExt10 struct {
|
||||
version uint16 `unionTag:"10"`
|
||||
unitSize uint16
|
||||
FirstGlyph GlyphID
|
||||
Values []uint32 `arrayCount:"FirstUint16"`
|
||||
}
|
||||
|
||||
func (src AATLoopkup0) Coverage() [][2]GlyphID {
|
||||
return [][2]GlyphID{{0, GlyphID(len(src.Values) - 1)}}
|
||||
}
|
||||
|
||||
func (src AATLoopkupExt0) Coverage() [][2]GlyphID {
|
||||
return [][2]GlyphID{{0, GlyphID(len(src.Values) - 1)}}
|
||||
}
|
||||
|
||||
func (src AATLoopkup2) Coverage() [][2]GlyphID {
|
||||
out := make([][2]GlyphID, 0, len(src.Records))
|
||||
for _, record := range src.Records {
|
||||
if record.FirstGlyph == 0xFFFF {
|
||||
continue
|
||||
}
|
||||
out = append(out, [2]GlyphID{record.FirstGlyph, record.LastGlyph})
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func (src AATLoopkupExt2) Coverage() [][2]GlyphID {
|
||||
out := make([][2]GlyphID, 0, len(src.Records))
|
||||
for _, record := range src.Records {
|
||||
if record.FirstGlyph == 0xFFFF {
|
||||
continue
|
||||
}
|
||||
out = append(out, [2]GlyphID{record.FirstGlyph, record.LastGlyph})
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func (src AATLoopkup4) Coverage() [][2]GlyphID {
|
||||
out := make([][2]GlyphID, 0, len(src.Records))
|
||||
for _, record := range src.Records {
|
||||
if record.FirstGlyph == 0xFFFF {
|
||||
continue
|
||||
}
|
||||
out = append(out, [2]GlyphID{record.FirstGlyph, record.LastGlyph})
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func (src AATLoopkupExt4) Coverage() [][2]GlyphID {
|
||||
out := make([][2]GlyphID, 0, len(src.Records))
|
||||
for _, record := range src.Records {
|
||||
if record.FirstGlyph == 0xFFFF {
|
||||
continue
|
||||
}
|
||||
out = append(out, [2]GlyphID{record.FirstGlyph, record.LastGlyph})
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func (src AATLoopkup6) Coverage() [][2]GlyphID {
|
||||
out := make([][2]GlyphID, 0, len(src.Records))
|
||||
for _, record := range src.Records {
|
||||
if record.Glyph == 0xFFFF {
|
||||
continue
|
||||
}
|
||||
out = append(out, [2]GlyphID{record.Glyph, record.Glyph})
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func (src AATLoopkupExt6) Coverage() [][2]GlyphID {
|
||||
out := make([][2]GlyphID, 0, len(src.Records))
|
||||
for _, record := range src.Records {
|
||||
if record.Glyph == 0xFFFF {
|
||||
continue
|
||||
}
|
||||
out = append(out, [2]GlyphID{record.Glyph, record.Glyph})
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func (src AATLoopkup8) Coverage() [][2]GlyphID {
|
||||
if len(src.Values) == 0 || src.FirstGlyph == 0xFFFF {
|
||||
return nil
|
||||
}
|
||||
return [][2]GlyphID{{src.FirstGlyph, src.FirstGlyph + GlyphID(len(src.Values)-1)}}
|
||||
}
|
||||
|
||||
func (src AATLoopkupExt8) Coverage() [][2]GlyphID {
|
||||
if len(src.Values) == 0 || src.FirstGlyph == 0xFFFF {
|
||||
return nil
|
||||
}
|
||||
return [][2]GlyphID{{src.FirstGlyph, src.FirstGlyph + GlyphID(len(src.Values)-1)}}
|
||||
}
|
||||
|
||||
func (src AATLoopkup10) Coverage() [][2]GlyphID {
|
||||
if len(src.Values) == 0 || src.FirstGlyph == 0xFFFF {
|
||||
return nil
|
||||
}
|
||||
return [][2]GlyphID{{src.FirstGlyph, src.FirstGlyph + GlyphID(len(src.Values)-1)}}
|
||||
}
|
||||
|
||||
func (src AATLoopkupExt10) Coverage() [][2]GlyphID {
|
||||
if len(src.Values) == 0 || src.FirstGlyph == 0xFFFF {
|
||||
return nil
|
||||
}
|
||||
return [][2]GlyphID{{src.FirstGlyph, src.FirstGlyph + GlyphID(len(src.Values)-1)}}
|
||||
}
|
||||
+82
@@ -0,0 +1,82 @@
|
||||
// SPDX-License-Identifier: Unlicense OR BSD-3-Clause
|
||||
|
||||
package tables
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// Code generated by binarygen from aat_feat_src.go. DO NOT EDIT
|
||||
|
||||
func (item *FeatureSettingName) mustParse(src []byte) {
|
||||
_ = src[3] // early bound checking
|
||||
item.Setting = binary.BigEndian.Uint16(src[0:])
|
||||
item.NameIndex = binary.BigEndian.Uint16(src[2:])
|
||||
}
|
||||
|
||||
func ParseFeat(src []byte) (Feat, int, error) {
|
||||
var item Feat
|
||||
n := 0
|
||||
if L := len(src); L < 12 {
|
||||
return item, 0, fmt.Errorf("reading Feat: "+"EOF: expected length: 12, got %d", L)
|
||||
}
|
||||
_ = src[11] // early bound checking
|
||||
item.version = binary.BigEndian.Uint32(src[0:])
|
||||
item.featureNameCount = binary.BigEndian.Uint16(src[4:])
|
||||
item.none1 = binary.BigEndian.Uint16(src[6:])
|
||||
item.none2 = binary.BigEndian.Uint32(src[8:])
|
||||
n += 12
|
||||
|
||||
{
|
||||
arrayLength := int(item.featureNameCount)
|
||||
|
||||
offset := 12
|
||||
for i := 0; i < arrayLength; i++ {
|
||||
elem, read, err := ParseFeatureName(src[offset:], src)
|
||||
if err != nil {
|
||||
return item, 0, fmt.Errorf("reading Feat: %s", err)
|
||||
}
|
||||
item.Names = append(item.Names, elem)
|
||||
offset += read
|
||||
}
|
||||
n = offset
|
||||
}
|
||||
return item, n, nil
|
||||
}
|
||||
|
||||
func ParseFeatureName(src []byte, parentSrc []byte) (FeatureName, int, error) {
|
||||
var item FeatureName
|
||||
n := 0
|
||||
if L := len(src); L < 12 {
|
||||
return item, 0, fmt.Errorf("reading FeatureName: "+"EOF: expected length: 12, got %d", L)
|
||||
}
|
||||
_ = src[11] // early bound checking
|
||||
item.Feature = binary.BigEndian.Uint16(src[0:])
|
||||
item.nSettings = binary.BigEndian.Uint16(src[2:])
|
||||
offsetSettingTable := int(binary.BigEndian.Uint32(src[4:]))
|
||||
item.FeatureFlags = binary.BigEndian.Uint16(src[8:])
|
||||
item.NameIndex = binary.BigEndian.Uint16(src[10:])
|
||||
n += 12
|
||||
|
||||
{
|
||||
if offsetSettingTable != 0 { // ignore null offset
|
||||
if L := len(parentSrc); L < offsetSettingTable {
|
||||
return item, 0, fmt.Errorf("reading FeatureName: "+"EOF: expected length: %d, got %d", offsetSettingTable, L)
|
||||
}
|
||||
|
||||
arrayLength := int(item.nSettings)
|
||||
|
||||
if L := len(parentSrc); L < offsetSettingTable+arrayLength*4 {
|
||||
return item, 0, fmt.Errorf("reading FeatureName: "+"EOF: expected length: %d, got %d", offsetSettingTable+arrayLength*4, L)
|
||||
}
|
||||
|
||||
item.SettingTable = make([]FeatureSettingName, arrayLength) // allocation guarded by the previous check
|
||||
for i := range item.SettingTable {
|
||||
item.SettingTable[i].mustParse(parentSrc[offsetSettingTable+i*4:])
|
||||
}
|
||||
offsetSettingTable += arrayLength * 4
|
||||
}
|
||||
}
|
||||
return item, n, nil
|
||||
}
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
// SPDX-License-Identifier: Unlicense OR BSD-3-Clause
|
||||
|
||||
package tables
|
||||
|
||||
// Feat is the feature name table.
|
||||
// See - https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6feat.html
|
||||
type Feat struct {
|
||||
version uint32 // Version number of the feature name table (0x00010000 for the current version).
|
||||
featureNameCount uint16 // The number of entries in the feature name array.
|
||||
none1 uint16 // Reserved (set to zero).
|
||||
none2 uint32 // Reserved (set to zero).
|
||||
Names []FeatureName `arrayCount:"ComputedField-featureNameCount"` // The feature name array.
|
||||
}
|
||||
|
||||
type FeatureName struct {
|
||||
Feature uint16 // Feature type.
|
||||
nSettings uint16 // The number of records in the setting name array.
|
||||
SettingTable []FeatureSettingName `offsetSize:"Offset32" offsetRelativeTo:"Parent" arrayCount:"ComputedField-nSettings"` // Offset in bytes from the beginning of the 'feat' table to this feature's setting name array. The actual type of record this offset refers to will depend on the exclusivity value, as described below.
|
||||
FeatureFlags uint16 // Single-bit flags associated with the feature type.
|
||||
NameIndex uint16 // The name table index for the feature's name. This index has values greater than 255 and less than 32768.
|
||||
}
|
||||
|
||||
type FeatureSettingName struct {
|
||||
Setting uint16 // The setting.
|
||||
NameIndex uint16 // The name table index for the setting's name. The nameIndex must be greater than 255 and less than 32768.
|
||||
}
|
||||
+642
@@ -0,0 +1,642 @@
|
||||
// SPDX-License-Identifier: Unlicense OR BSD-3-Clause
|
||||
|
||||
package tables
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// Code generated by binarygen from aat_kerx_src.go. DO NOT EDIT
|
||||
|
||||
func (item *KAAnchor) mustParse(src []byte) {
|
||||
_ = src[3] // early bound checking
|
||||
item.Mark = binary.BigEndian.Uint16(src[0:])
|
||||
item.Current = binary.BigEndian.Uint16(src[2:])
|
||||
}
|
||||
|
||||
func (item *KAControl) mustParse(src []byte) {
|
||||
_ = src[3] // early bound checking
|
||||
item.Mark = binary.BigEndian.Uint16(src[0:])
|
||||
item.Current = binary.BigEndian.Uint16(src[2:])
|
||||
}
|
||||
|
||||
func (item *KACoordinates) mustParse(src []byte) {
|
||||
_ = src[7] // early bound checking
|
||||
item.MarkX = int16(binary.BigEndian.Uint16(src[0:]))
|
||||
item.MarkY = int16(binary.BigEndian.Uint16(src[2:]))
|
||||
item.CurrentX = int16(binary.BigEndian.Uint16(src[4:]))
|
||||
item.CurrentY = int16(binary.BigEndian.Uint16(src[6:]))
|
||||
}
|
||||
|
||||
func (item *Kernx0Record) mustParse(src []byte) {
|
||||
_ = src[5] // early bound checking
|
||||
item.Left = binary.BigEndian.Uint16(src[0:])
|
||||
item.Right = binary.BigEndian.Uint16(src[2:])
|
||||
item.Value = int16(binary.BigEndian.Uint16(src[4:]))
|
||||
}
|
||||
|
||||
func ParseAATLookupExt(src []byte, valuesCount int) (AATLookupExt, int, error) {
|
||||
var item AATLookupExt
|
||||
|
||||
if L := len(src); L < 2 {
|
||||
return item, 0, fmt.Errorf("reading AATLookupExt: "+"EOF: expected length: 2, got %d", L)
|
||||
}
|
||||
format := uint16(binary.BigEndian.Uint16(src[0:]))
|
||||
var (
|
||||
read int
|
||||
err error
|
||||
)
|
||||
switch format {
|
||||
case 0:
|
||||
item, read, err = ParseAATLoopkupExt0(src[0:], valuesCount)
|
||||
case 10:
|
||||
item, read, err = ParseAATLoopkupExt10(src[0:])
|
||||
case 2:
|
||||
item, read, err = ParseAATLoopkupExt2(src[0:])
|
||||
case 4:
|
||||
item, read, err = ParseAATLoopkupExt4(src[0:])
|
||||
case 6:
|
||||
item, read, err = ParseAATLoopkupExt6(src[0:])
|
||||
case 8:
|
||||
item, read, err = ParseAATLoopkupExt8(src[0:])
|
||||
default:
|
||||
err = fmt.Errorf("unsupported AATLookupExt format %d", format)
|
||||
}
|
||||
if err != nil {
|
||||
return item, 0, fmt.Errorf("reading AATLookupExt: %s", err)
|
||||
}
|
||||
|
||||
return item, read, nil
|
||||
}
|
||||
|
||||
func ParseAATLoopkupExt0(src []byte, valuesCount int) (AATLoopkupExt0, int, error) {
|
||||
var item AATLoopkupExt0
|
||||
n := 0
|
||||
if L := len(src); L < 2 {
|
||||
return item, 0, fmt.Errorf("reading AATLoopkupExt0: "+"EOF: expected length: 2, got %d", L)
|
||||
}
|
||||
item.version = binary.BigEndian.Uint16(src[0:])
|
||||
n += 2
|
||||
|
||||
{
|
||||
|
||||
if L := len(src); L < 2+valuesCount*4 {
|
||||
return item, 0, fmt.Errorf("reading AATLoopkupExt0: "+"EOF: expected length: %d, got %d", 2+valuesCount*4, L)
|
||||
}
|
||||
|
||||
item.Values = make([]uint32, valuesCount) // allocation guarded by the previous check
|
||||
for i := range item.Values {
|
||||
item.Values[i] = binary.BigEndian.Uint32(src[2+i*4:])
|
||||
}
|
||||
n += valuesCount * 4
|
||||
}
|
||||
return item, n, nil
|
||||
}
|
||||
|
||||
func ParseAATLoopkupExt10(src []byte) (AATLoopkupExt10, int, error) {
|
||||
var item AATLoopkupExt10
|
||||
n := 0
|
||||
if L := len(src); L < 8 {
|
||||
return item, 0, fmt.Errorf("reading AATLoopkupExt10: "+"EOF: expected length: 8, got %d", L)
|
||||
}
|
||||
_ = src[7] // early bound checking
|
||||
item.version = binary.BigEndian.Uint16(src[0:])
|
||||
item.unitSize = binary.BigEndian.Uint16(src[2:])
|
||||
item.FirstGlyph = binary.BigEndian.Uint16(src[4:])
|
||||
arrayLengthValues := int(binary.BigEndian.Uint16(src[6:]))
|
||||
n += 8
|
||||
|
||||
{
|
||||
|
||||
if L := len(src); L < 8+arrayLengthValues*4 {
|
||||
return item, 0, fmt.Errorf("reading AATLoopkupExt10: "+"EOF: expected length: %d, got %d", 8+arrayLengthValues*4, L)
|
||||
}
|
||||
|
||||
item.Values = make([]uint32, arrayLengthValues) // allocation guarded by the previous check
|
||||
for i := range item.Values {
|
||||
item.Values[i] = binary.BigEndian.Uint32(src[8+i*4:])
|
||||
}
|
||||
n += arrayLengthValues * 4
|
||||
}
|
||||
return item, n, nil
|
||||
}
|
||||
|
||||
func ParseAATLoopkupExt2(src []byte) (AATLoopkupExt2, int, error) {
|
||||
var item AATLoopkupExt2
|
||||
n := 0
|
||||
if L := len(src); L < 12 {
|
||||
return item, 0, fmt.Errorf("reading AATLoopkupExt2: "+"EOF: expected length: 12, got %d", L)
|
||||
}
|
||||
_ = src[11] // early bound checking
|
||||
item.version = binary.BigEndian.Uint16(src[0:])
|
||||
item.binSearchHeader.mustParse(src[2:])
|
||||
n += 12
|
||||
|
||||
{
|
||||
arrayLength := int(item.nUnits)
|
||||
|
||||
if L := len(src); L < 12+arrayLength*8 {
|
||||
return item, 0, fmt.Errorf("reading AATLoopkupExt2: "+"EOF: expected length: %d, got %d", 12+arrayLength*8, L)
|
||||
}
|
||||
|
||||
item.Records = make([]lookupRecordExt2, arrayLength) // allocation guarded by the previous check
|
||||
for i := range item.Records {
|
||||
item.Records[i].mustParse(src[12+i*8:])
|
||||
}
|
||||
n += arrayLength * 8
|
||||
}
|
||||
return item, n, nil
|
||||
}
|
||||
|
||||
func ParseAATLoopkupExt4(src []byte) (AATLoopkupExt4, int, error) {
|
||||
var item AATLoopkupExt4
|
||||
n := 0
|
||||
if L := len(src); L < 12 {
|
||||
return item, 0, fmt.Errorf("reading AATLoopkupExt4: "+"EOF: expected length: 12, got %d", L)
|
||||
}
|
||||
_ = src[11] // early bound checking
|
||||
item.version = binary.BigEndian.Uint16(src[0:])
|
||||
item.binSearchHeader.mustParse(src[2:])
|
||||
n += 12
|
||||
|
||||
{
|
||||
arrayLength := int(item.nUnits)
|
||||
|
||||
offset := 12
|
||||
for i := 0; i < arrayLength; i++ {
|
||||
elem, read, err := parseLoopkupRecordExt4(src[offset:], src)
|
||||
if err != nil {
|
||||
return item, 0, fmt.Errorf("reading AATLoopkupExt4: %s", err)
|
||||
}
|
||||
item.Records = append(item.Records, elem)
|
||||
offset += read
|
||||
}
|
||||
n = offset
|
||||
}
|
||||
return item, n, nil
|
||||
}
|
||||
|
||||
func ParseAATLoopkupExt6(src []byte) (AATLoopkupExt6, int, error) {
|
||||
var item AATLoopkupExt6
|
||||
n := 0
|
||||
if L := len(src); L < 12 {
|
||||
return item, 0, fmt.Errorf("reading AATLoopkupExt6: "+"EOF: expected length: 12, got %d", L)
|
||||
}
|
||||
_ = src[11] // early bound checking
|
||||
item.version = binary.BigEndian.Uint16(src[0:])
|
||||
item.binSearchHeader.mustParse(src[2:])
|
||||
n += 12
|
||||
|
||||
{
|
||||
arrayLength := int(item.nUnits)
|
||||
|
||||
if L := len(src); L < 12+arrayLength*6 {
|
||||
return item, 0, fmt.Errorf("reading AATLoopkupExt6: "+"EOF: expected length: %d, got %d", 12+arrayLength*6, L)
|
||||
}
|
||||
|
||||
item.Records = make([]loopkupRecordExt6, arrayLength) // allocation guarded by the previous check
|
||||
for i := range item.Records {
|
||||
item.Records[i].mustParse(src[12+i*6:])
|
||||
}
|
||||
n += arrayLength * 6
|
||||
}
|
||||
return item, n, nil
|
||||
}
|
||||
|
||||
func ParseAATLoopkupExt8(src []byte) (AATLoopkupExt8, int, error) {
|
||||
var item AATLoopkupExt8
|
||||
n := 0
|
||||
if L := len(src); L < 2 {
|
||||
return item, 0, fmt.Errorf("reading AATLoopkupExt8: "+"EOF: expected length: 2, got %d", L)
|
||||
}
|
||||
item.version = binary.BigEndian.Uint16(src[0:])
|
||||
n += 2
|
||||
|
||||
{
|
||||
var (
|
||||
err error
|
||||
read int
|
||||
)
|
||||
item.AATLoopkup8Data, read, err = ParseAATLoopkup8Data(src[2:])
|
||||
if err != nil {
|
||||
return item, 0, fmt.Errorf("reading AATLoopkupExt8: %s", err)
|
||||
}
|
||||
n += read
|
||||
}
|
||||
return item, n, nil
|
||||
}
|
||||
|
||||
func ParseAATStateTableExt(src []byte, valuesCount int, entryDataSize int) (AATStateTableExt, int, error) {
|
||||
var item AATStateTableExt
|
||||
n := 0
|
||||
if L := len(src); L < 16 {
|
||||
return item, 0, fmt.Errorf("reading AATStateTableExt: "+"EOF: expected length: 16, got %d", L)
|
||||
}
|
||||
_ = src[15] // early bound checking
|
||||
item.StateSize = binary.BigEndian.Uint32(src[0:])
|
||||
offsetClass := int(binary.BigEndian.Uint32(src[4:]))
|
||||
item.stateArray = Offset32(binary.BigEndian.Uint32(src[8:]))
|
||||
item.entryTable = Offset32(binary.BigEndian.Uint32(src[12:]))
|
||||
n += 16
|
||||
|
||||
{
|
||||
if offsetClass != 0 { // ignore null offset
|
||||
if L := len(src); L < offsetClass {
|
||||
return item, 0, fmt.Errorf("reading AATStateTableExt: "+"EOF: expected length: %d, got %d", offsetClass, L)
|
||||
}
|
||||
|
||||
var (
|
||||
err error
|
||||
read int
|
||||
)
|
||||
item.Class, read, err = ParseAATLookup(src[offsetClass:], valuesCount)
|
||||
if err != nil {
|
||||
return item, 0, fmt.Errorf("reading AATStateTableExt: %s", err)
|
||||
}
|
||||
offsetClass += read
|
||||
}
|
||||
}
|
||||
{
|
||||
|
||||
err := item.parseStates(src[:], valuesCount, entryDataSize)
|
||||
if err != nil {
|
||||
return item, 0, fmt.Errorf("reading AATStateTableExt: %s", err)
|
||||
}
|
||||
}
|
||||
{
|
||||
|
||||
read, err := item.parseEntries(src[:], valuesCount, entryDataSize)
|
||||
if err != nil {
|
||||
return item, 0, fmt.Errorf("reading AATStateTableExt: %s", err)
|
||||
}
|
||||
n = read
|
||||
}
|
||||
return item, n, nil
|
||||
}
|
||||
|
||||
func ParseKerx(src []byte, valuesCount int) (Kerx, int, error) {
|
||||
var item Kerx
|
||||
n := 0
|
||||
if L := len(src); L < 8 {
|
||||
return item, 0, fmt.Errorf("reading Kerx: "+"EOF: expected length: 8, got %d", L)
|
||||
}
|
||||
_ = src[7] // early bound checking
|
||||
item.version = binary.BigEndian.Uint16(src[0:])
|
||||
item.padding = binary.BigEndian.Uint16(src[2:])
|
||||
item.nTables = binary.BigEndian.Uint32(src[4:])
|
||||
n += 8
|
||||
|
||||
{
|
||||
arrayLength := int(item.nTables)
|
||||
|
||||
offset := 8
|
||||
for i := 0; i < arrayLength; i++ {
|
||||
elem, read, err := ParseKerxSubtable(src[offset:], valuesCount)
|
||||
if err != nil {
|
||||
return item, 0, fmt.Errorf("reading Kerx: %s", err)
|
||||
}
|
||||
item.Tables = append(item.Tables, elem)
|
||||
offset += read
|
||||
}
|
||||
n = offset
|
||||
}
|
||||
return item, n, nil
|
||||
}
|
||||
|
||||
func ParseKerxAnchorAnchors(src []byte, anchorsCount int) (KerxAnchorAnchors, int, error) {
|
||||
var item KerxAnchorAnchors
|
||||
n := 0
|
||||
{
|
||||
|
||||
if L := len(src); L < anchorsCount*4 {
|
||||
return item, 0, fmt.Errorf("reading KerxAnchorAnchors: "+"EOF: expected length: %d, got %d", anchorsCount*4, L)
|
||||
}
|
||||
|
||||
item.Anchors = make([]KAAnchor, anchorsCount) // allocation guarded by the previous check
|
||||
for i := range item.Anchors {
|
||||
item.Anchors[i].mustParse(src[i*4:])
|
||||
}
|
||||
n += anchorsCount * 4
|
||||
}
|
||||
return item, n, nil
|
||||
}
|
||||
|
||||
func ParseKerxAnchorControls(src []byte, anchorsCount int) (KerxAnchorControls, int, error) {
|
||||
var item KerxAnchorControls
|
||||
n := 0
|
||||
{
|
||||
|
||||
if L := len(src); L < anchorsCount*4 {
|
||||
return item, 0, fmt.Errorf("reading KerxAnchorControls: "+"EOF: expected length: %d, got %d", anchorsCount*4, L)
|
||||
}
|
||||
|
||||
item.Anchors = make([]KAControl, anchorsCount) // allocation guarded by the previous check
|
||||
for i := range item.Anchors {
|
||||
item.Anchors[i].mustParse(src[i*4:])
|
||||
}
|
||||
n += anchorsCount * 4
|
||||
}
|
||||
return item, n, nil
|
||||
}
|
||||
|
||||
func ParseKerxAnchorCoordinates(src []byte, anchorsCount int) (KerxAnchorCoordinates, int, error) {
|
||||
var item KerxAnchorCoordinates
|
||||
n := 0
|
||||
{
|
||||
|
||||
if L := len(src); L < anchorsCount*8 {
|
||||
return item, 0, fmt.Errorf("reading KerxAnchorCoordinates: "+"EOF: expected length: %d, got %d", anchorsCount*8, L)
|
||||
}
|
||||
|
||||
item.Anchors = make([]KACoordinates, anchorsCount) // allocation guarded by the previous check
|
||||
for i := range item.Anchors {
|
||||
item.Anchors[i].mustParse(src[i*8:])
|
||||
}
|
||||
n += anchorsCount * 8
|
||||
}
|
||||
return item, n, nil
|
||||
}
|
||||
|
||||
func ParseKerxData0(src []byte, tupleCount int) (KerxData0, int, error) {
|
||||
var item KerxData0
|
||||
n := 0
|
||||
if L := len(src); L < 16 {
|
||||
return item, 0, fmt.Errorf("reading KerxData0: "+"EOF: expected length: 16, got %d", L)
|
||||
}
|
||||
_ = src[15] // early bound checking
|
||||
item.nPairs = binary.BigEndian.Uint32(src[0:])
|
||||
item.searchRange = binary.BigEndian.Uint32(src[4:])
|
||||
item.entrySelector = binary.BigEndian.Uint32(src[8:])
|
||||
item.rangeShift = binary.BigEndian.Uint32(src[12:])
|
||||
n += 16
|
||||
|
||||
{
|
||||
arrayLength := int(item.nPairs)
|
||||
|
||||
if L := len(src); L < 16+arrayLength*6 {
|
||||
return item, 0, fmt.Errorf("reading KerxData0: "+"EOF: expected length: %d, got %d", 16+arrayLength*6, L)
|
||||
}
|
||||
|
||||
item.Pairs = make([]Kernx0Record, arrayLength) // allocation guarded by the previous check
|
||||
for i := range item.Pairs {
|
||||
item.Pairs[i].mustParse(src[16+i*6:])
|
||||
}
|
||||
n += arrayLength * 6
|
||||
}
|
||||
var err error
|
||||
n, err = item.parseEnd(src, tupleCount)
|
||||
if err != nil {
|
||||
return item, 0, fmt.Errorf("reading KerxData0: %s", err)
|
||||
}
|
||||
|
||||
return item, n, nil
|
||||
}
|
||||
|
||||
func ParseKerxData1(src []byte, tupleCount int, valuesCount int) (KerxData1, int, error) {
|
||||
var item KerxData1
|
||||
n := 0
|
||||
{
|
||||
var (
|
||||
err error
|
||||
read int
|
||||
)
|
||||
item.AATStateTableExt, read, err = ParseAATStateTableExt(src[0:], int(valuesCount), int(2))
|
||||
if err != nil {
|
||||
return item, 0, fmt.Errorf("reading KerxData1: %s", err)
|
||||
}
|
||||
n += read
|
||||
}
|
||||
if L := len(src); L < n+4 {
|
||||
return item, 0, fmt.Errorf("reading KerxData1: "+"EOF: expected length: n + 4, got %d", L)
|
||||
}
|
||||
item.valueTable = Offset32(binary.BigEndian.Uint32(src[n:]))
|
||||
n += 4
|
||||
|
||||
{
|
||||
|
||||
err := item.parseValues(src[:], tupleCount, valuesCount)
|
||||
if err != nil {
|
||||
return item, 0, fmt.Errorf("reading KerxData1: %s", err)
|
||||
}
|
||||
}
|
||||
return item, n, nil
|
||||
}
|
||||
|
||||
func ParseKerxData2(src []byte, parentSrc []byte, valuesCount int) (KerxData2, int, error) {
|
||||
var item KerxData2
|
||||
n := 0
|
||||
if L := len(src); L < 16 {
|
||||
return item, 0, fmt.Errorf("reading KerxData2: "+"EOF: expected length: 16, got %d", L)
|
||||
}
|
||||
_ = src[15] // early bound checking
|
||||
item.rowWidth = binary.BigEndian.Uint32(src[0:])
|
||||
offsetLeft := int(binary.BigEndian.Uint32(src[4:]))
|
||||
offsetRight := int(binary.BigEndian.Uint32(src[8:]))
|
||||
item.KerningStart = Offset32(binary.BigEndian.Uint32(src[12:]))
|
||||
n += 16
|
||||
|
||||
{
|
||||
if offsetLeft != 0 { // ignore null offset
|
||||
if L := len(parentSrc); L < offsetLeft {
|
||||
return item, 0, fmt.Errorf("reading KerxData2: "+"EOF: expected length: %d, got %d", offsetLeft, L)
|
||||
}
|
||||
|
||||
var (
|
||||
err error
|
||||
read int
|
||||
)
|
||||
item.Left, read, err = ParseAATLookup(parentSrc[offsetLeft:], valuesCount)
|
||||
if err != nil {
|
||||
return item, 0, fmt.Errorf("reading KerxData2: %s", err)
|
||||
}
|
||||
offsetLeft += read
|
||||
}
|
||||
}
|
||||
{
|
||||
if offsetRight != 0 { // ignore null offset
|
||||
if L := len(parentSrc); L < offsetRight {
|
||||
return item, 0, fmt.Errorf("reading KerxData2: "+"EOF: expected length: %d, got %d", offsetRight, L)
|
||||
}
|
||||
|
||||
var (
|
||||
err error
|
||||
read int
|
||||
)
|
||||
item.Right, read, err = ParseAATLookup(parentSrc[offsetRight:], valuesCount)
|
||||
if err != nil {
|
||||
return item, 0, fmt.Errorf("reading KerxData2: %s", err)
|
||||
}
|
||||
offsetRight += read
|
||||
}
|
||||
}
|
||||
{
|
||||
|
||||
item.KerningData = src[0:]
|
||||
}
|
||||
return item, n, nil
|
||||
}
|
||||
|
||||
func ParseKerxData4(src []byte, valuesCount int) (KerxData4, int, error) {
|
||||
var item KerxData4
|
||||
n := 0
|
||||
{
|
||||
var (
|
||||
err error
|
||||
read int
|
||||
)
|
||||
item.AATStateTableExt, read, err = ParseAATStateTableExt(src[0:], int(valuesCount), int(2))
|
||||
if err != nil {
|
||||
return item, 0, fmt.Errorf("reading KerxData4: %s", err)
|
||||
}
|
||||
n += read
|
||||
}
|
||||
if L := len(src); L < n+4 {
|
||||
return item, 0, fmt.Errorf("reading KerxData4: "+"EOF: expected length: n + 4, got %d", L)
|
||||
}
|
||||
item.Flags = binary.BigEndian.Uint32(src[n:])
|
||||
n += 4
|
||||
|
||||
{
|
||||
|
||||
err := item.parseAnchors(src[:], valuesCount)
|
||||
if err != nil {
|
||||
return item, 0, fmt.Errorf("reading KerxData4: %s", err)
|
||||
}
|
||||
}
|
||||
return item, n, nil
|
||||
}
|
||||
|
||||
func ParseKerxData6(src []byte, parentSrc []byte, tupleCount int, valuesCount int) (KerxData6, int, error) {
|
||||
var item KerxData6
|
||||
n := 0
|
||||
if L := len(src); L < 24 {
|
||||
return item, 0, fmt.Errorf("reading KerxData6: "+"EOF: expected length: 24, got %d", L)
|
||||
}
|
||||
_ = src[23] // early bound checking
|
||||
item.flags = binary.BigEndian.Uint32(src[0:])
|
||||
item.rowCount = binary.BigEndian.Uint16(src[4:])
|
||||
item.columnCount = binary.BigEndian.Uint16(src[6:])
|
||||
item.rowIndexTableOffset = binary.BigEndian.Uint32(src[8:])
|
||||
item.columnIndexTableOffset = binary.BigEndian.Uint32(src[12:])
|
||||
item.kerningArrayOffset = binary.BigEndian.Uint32(src[16:])
|
||||
item.kerningVectorOffset = binary.BigEndian.Uint32(src[20:])
|
||||
n += 24
|
||||
|
||||
{
|
||||
|
||||
err := item.parseRow(src[:], parentSrc, tupleCount, valuesCount)
|
||||
if err != nil {
|
||||
return item, 0, fmt.Errorf("reading KerxData6: %s", err)
|
||||
}
|
||||
}
|
||||
{
|
||||
|
||||
err := item.parseColumn(src[:], parentSrc, tupleCount, valuesCount)
|
||||
if err != nil {
|
||||
return item, 0, fmt.Errorf("reading KerxData6: %s", err)
|
||||
}
|
||||
}
|
||||
{
|
||||
|
||||
err := item.parseKernings(src[:], parentSrc, tupleCount, valuesCount)
|
||||
if err != nil {
|
||||
return item, 0, fmt.Errorf("reading KerxData6: %s", err)
|
||||
}
|
||||
}
|
||||
return item, n, nil
|
||||
}
|
||||
|
||||
func ParseKerxSubtable(src []byte, valuesCount int) (KerxSubtable, int, error) {
|
||||
var item KerxSubtable
|
||||
n := 0
|
||||
if L := len(src); L < 12 {
|
||||
return item, 0, fmt.Errorf("reading KerxSubtable: "+"EOF: expected length: 12, got %d", L)
|
||||
}
|
||||
_ = src[11] // early bound checking
|
||||
item.length = binary.BigEndian.Uint32(src[0:])
|
||||
item.Coverage = binary.BigEndian.Uint16(src[4:])
|
||||
item.padding = src[6]
|
||||
item.version = kerxSTVersion(src[7])
|
||||
item.TupleCount = binary.BigEndian.Uint32(src[8:])
|
||||
n += 12
|
||||
|
||||
{
|
||||
var (
|
||||
read int
|
||||
err error
|
||||
)
|
||||
switch item.version {
|
||||
case kerxSTVersion0:
|
||||
item.Data, read, err = ParseKerxData0(src[12:], int(item.TupleCount))
|
||||
case kerxSTVersion1:
|
||||
item.Data, read, err = ParseKerxData1(src[12:], int(item.TupleCount), int(valuesCount))
|
||||
case kerxSTVersion2:
|
||||
item.Data, read, err = ParseKerxData2(src[12:], src, int(valuesCount))
|
||||
case kerxSTVersion4:
|
||||
item.Data, read, err = ParseKerxData4(src[12:], int(valuesCount))
|
||||
case kerxSTVersion6:
|
||||
item.Data, read, err = ParseKerxData6(src[12:], src, int(item.TupleCount), int(valuesCount))
|
||||
default:
|
||||
err = fmt.Errorf("unsupported KerxDataVersion %d", item.version)
|
||||
}
|
||||
if err != nil {
|
||||
return item, 0, fmt.Errorf("reading KerxSubtable: %s", err)
|
||||
}
|
||||
n += read
|
||||
}
|
||||
var err error
|
||||
n, err = item.parseEnd(src, valuesCount)
|
||||
if err != nil {
|
||||
return item, 0, fmt.Errorf("reading KerxSubtable: %s", err)
|
||||
}
|
||||
|
||||
return item, n, nil
|
||||
}
|
||||
|
||||
func (item *lookupRecordExt2) mustParse(src []byte) {
|
||||
_ = src[7] // early bound checking
|
||||
item.LastGlyph = binary.BigEndian.Uint16(src[0:])
|
||||
item.FirstGlyph = binary.BigEndian.Uint16(src[2:])
|
||||
item.Value = binary.BigEndian.Uint32(src[4:])
|
||||
}
|
||||
|
||||
func (item *loopkupRecordExt6) mustParse(src []byte) {
|
||||
_ = src[5] // early bound checking
|
||||
item.Glyph = binary.BigEndian.Uint16(src[0:])
|
||||
item.Value = binary.BigEndian.Uint32(src[2:])
|
||||
}
|
||||
|
||||
func parseLoopkupRecordExt4(src []byte, parentSrc []byte) (loopkupRecordExt4, int, error) {
|
||||
var item loopkupRecordExt4
|
||||
n := 0
|
||||
if L := len(src); L < 6 {
|
||||
return item, 0, fmt.Errorf("reading loopkupRecordExt4: "+"EOF: expected length: 6, got %d", L)
|
||||
}
|
||||
_ = src[5] // early bound checking
|
||||
item.LastGlyph = binary.BigEndian.Uint16(src[0:])
|
||||
item.FirstGlyph = binary.BigEndian.Uint16(src[2:])
|
||||
offsetValues := int(binary.BigEndian.Uint16(src[4:]))
|
||||
n += 6
|
||||
|
||||
{
|
||||
if offsetValues != 0 { // ignore null offset
|
||||
if L := len(parentSrc); L < offsetValues {
|
||||
return item, 0, fmt.Errorf("reading loopkupRecordExt4: "+"EOF: expected length: %d, got %d", offsetValues, L)
|
||||
}
|
||||
|
||||
arrayLength := int(item.nValues())
|
||||
|
||||
if L := len(parentSrc); L < offsetValues+arrayLength*4 {
|
||||
return item, 0, fmt.Errorf("reading loopkupRecordExt4: "+"EOF: expected length: %d, got %d", offsetValues+arrayLength*4, L)
|
||||
}
|
||||
|
||||
item.Values = make([]uint32, arrayLength) // allocation guarded by the previous check
|
||||
for i := range item.Values {
|
||||
item.Values[i] = binary.BigEndian.Uint32(parentSrc[offsetValues+i*4:])
|
||||
}
|
||||
offsetValues += arrayLength * 4
|
||||
}
|
||||
}
|
||||
return item, n, nil
|
||||
}
|
||||
+320
@@ -0,0 +1,320 @@
|
||||
// SPDX-License-Identifier: Unlicense OR BSD-3-Clause
|
||||
|
||||
package tables
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// Kerx is the extended kerning table
|
||||
// See https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6kerx.html
|
||||
type Kerx struct {
|
||||
version uint16 // The version number of the extended kerning table (currently 2, 3, or 4).
|
||||
padding uint16 // Unused; set to zero.
|
||||
nTables uint32 // The number of subtables included in the extended kerning table.
|
||||
Tables []KerxSubtable `arrayCount:"ComputedField-nTables"`
|
||||
}
|
||||
|
||||
// extended versions
|
||||
|
||||
// binarygen: argument=valuesCount int
|
||||
type KerxSubtable struct {
|
||||
length uint32 // The length of this subtable in bytes, including this header.
|
||||
Coverage uint16 // Circumstances under which this table is used.
|
||||
padding byte // unused
|
||||
version kerxSTVersion
|
||||
TupleCount uint32 // The tuple count. This value is only used with variation fonts and should be 0 for all other fonts. The subtable's tupleCount will be ignored if the 'kerx' table version is less than 4.
|
||||
Data KerxData `unionField:"version" arguments:"tupleCount=.TupleCount, valuesCount=valuesCount"`
|
||||
}
|
||||
|
||||
// check and return the subtable length
|
||||
func (ks *KerxSubtable) parseEnd(src []byte, _ int) (int, error) {
|
||||
if L := len(src); L < int(ks.length) {
|
||||
return 0, fmt.Errorf("EOF: expected length: %d, got %d", ks.length, L)
|
||||
}
|
||||
return int(ks.length), nil
|
||||
}
|
||||
|
||||
type kerxSTVersion byte
|
||||
|
||||
const (
|
||||
kerxSTVersion0 kerxSTVersion = iota
|
||||
kerxSTVersion1
|
||||
kerxSTVersion2
|
||||
_
|
||||
kerxSTVersion4
|
||||
_
|
||||
kerxSTVersion6
|
||||
)
|
||||
|
||||
type KerxData interface {
|
||||
isKerxData()
|
||||
}
|
||||
|
||||
func (KerxData0) isKerxData() {}
|
||||
func (KerxData1) isKerxData() {}
|
||||
func (KerxData2) isKerxData() {}
|
||||
func (KerxData4) isKerxData() {}
|
||||
func (KerxData6) isKerxData() {}
|
||||
|
||||
// binarygen: argument=tupleCount int
|
||||
type KerxData0 struct {
|
||||
nPairs uint32
|
||||
searchRange uint32
|
||||
entrySelector uint32
|
||||
rangeShift uint32
|
||||
Pairs []Kernx0Record `arrayCount:"ComputedField-nPairs"`
|
||||
}
|
||||
|
||||
// resolve offset for variable fonts
|
||||
func (kd *KerxData0) parseEnd(src []byte, tupleCount int) (int, error) {
|
||||
if tupleCount != 0 { // interpret values as offset
|
||||
for i, pair := range kd.Pairs {
|
||||
if L, E := len(src), int(uint16(pair.Value))+2; L < E {
|
||||
return 0, fmt.Errorf("EOF: expected length: %d, got %d", E, L)
|
||||
}
|
||||
kd.Pairs[i].Value = int16(binary.BigEndian.Uint16(src[pair.Value:]))
|
||||
}
|
||||
}
|
||||
return len(src), nil
|
||||
}
|
||||
|
||||
type Kernx0Record struct {
|
||||
Left, Right GlyphID
|
||||
Value int16
|
||||
}
|
||||
|
||||
// Kernx1 state entry flags
|
||||
const (
|
||||
Kerx1Push = 0x8000 // If set, push this glyph on the kerning stack.
|
||||
Kerx1DontAdvance = 0x4000 // If set, don't advance to the next glyph before going to the new state.
|
||||
Kerx1Reset = 0x2000 // If set, reset the kerning data (clear the stack)
|
||||
Kern1Offset = 0x3FFF // Byte offset from beginning of subtable to the value table for the glyphs on the kerning stack.
|
||||
)
|
||||
|
||||
// binarygen: argument=tupleCount int
|
||||
// binarygen: argument=valuesCount int
|
||||
type KerxData1 struct {
|
||||
AATStateTableExt `arguments:"valuesCount=valuesCount, entryDataSize=2"`
|
||||
valueTable Offset32
|
||||
Values []int16 `isOpaque:""`
|
||||
}
|
||||
|
||||
// From Apple 'kern' spec:
|
||||
// Each pops one glyph from the kerning stack and applies the kerning value to it.
|
||||
// The end of the list is marked by an odd value...
|
||||
func parseKernx1Values(src []byte, entries []AATStateEntry, valueTableOffset, tupleCount int) ([]int16, error) {
|
||||
// find the maximum index need in the values array
|
||||
var maxi uint16
|
||||
for _, entry := range entries {
|
||||
if index := entry.AsKernxIndex(); index != 0xFFFF && index > maxi {
|
||||
maxi = index
|
||||
}
|
||||
}
|
||||
|
||||
if tupleCount == 0 {
|
||||
tupleCount = 1
|
||||
}
|
||||
nbUint16Min := tupleCount * int(maxi+1)
|
||||
if L, E := len(src), valueTableOffset+2*nbUint16Min; L < E {
|
||||
return nil, fmt.Errorf("EOF: expected length: %d, got %d", E, L)
|
||||
}
|
||||
|
||||
src = src[valueTableOffset:]
|
||||
out := make([]int16, 0, nbUint16Min)
|
||||
for len(src) >= 2 { // gracefully handle missing odd value
|
||||
v := int16(binary.BigEndian.Uint16(src))
|
||||
out = append(out, v)
|
||||
src = src[2:]
|
||||
if len(out) >= nbUint16Min && v&1 != 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (kx *KerxData1) parseValues(src []byte, tupleCount, _ int) error {
|
||||
var err error
|
||||
kx.Values, err = parseKernx1Values(src, kx.Entries, int(kx.valueTable), tupleCount)
|
||||
return err
|
||||
}
|
||||
|
||||
type KerxData2 struct {
|
||||
rowWidth uint32 // The number of bytes in each row of the kerning value array
|
||||
Left AATLookup `offsetSize:"Offset32" offsetRelativeTo:"Parent"` // Offset from beginning of this subtable to the left-hand offset table.
|
||||
Right AATLookup `offsetSize:"Offset32" offsetRelativeTo:"Parent"` // Offset from beginning of this subtable to right-hand offset table.
|
||||
KerningStart Offset32 // Offset from beginning of this subtable to the start of the kerning array.
|
||||
KerningData []byte `subsliceStart:"AtStart" arrayCount:"ToEnd"` // indexed by Left + Right
|
||||
}
|
||||
|
||||
// binarygen: argument=valuesCount int
|
||||
type KerxData4 struct {
|
||||
AATStateTableExt `arguments:"valuesCount=valuesCount,entryDataSize=2"`
|
||||
Flags uint32
|
||||
Anchors KerxAnchors `isOpaque:""`
|
||||
}
|
||||
|
||||
func (kd KerxData4) nAnchors() int {
|
||||
// find the maximum index need in the actions array
|
||||
var maxi uint16
|
||||
for _, entry := range kd.Entries {
|
||||
if index := entry.AsKernxIndex(); index != 0xFFFF && index > maxi {
|
||||
maxi = index
|
||||
}
|
||||
}
|
||||
return int(maxi) + 1
|
||||
}
|
||||
|
||||
func (kd *KerxData4) parseAnchors(src []byte, _ int) error {
|
||||
nAnchors := kd.nAnchors()
|
||||
const Offset = 0x00FFFFFF // Masks the offset in bytes from the beginning of the subtable to the beginning of the control point table.
|
||||
controlOffset := int(kd.Flags & Offset)
|
||||
if L := len(src); L < controlOffset {
|
||||
return fmt.Errorf("EOF: expected length: %d, got %d", controlOffset, L)
|
||||
}
|
||||
var err error
|
||||
switch kd.ActionType() {
|
||||
case 0:
|
||||
kd.Anchors, _, err = ParseKerxAnchorControls(src[controlOffset:], nAnchors)
|
||||
case 1:
|
||||
kd.Anchors, _, err = ParseKerxAnchorAnchors(src[controlOffset:], nAnchors)
|
||||
case 2:
|
||||
kd.Anchors, _, err = ParseKerxAnchorCoordinates(src[controlOffset:], nAnchors)
|
||||
default:
|
||||
return fmt.Errorf("invalid Kerx4 anchor format %d", kd.ActionType())
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// ActionType returns 0, 1 or 2, according to the anchor format :
|
||||
// - 0 : KerxAnchorControls
|
||||
// - 1 : KerxAnchorAnchors
|
||||
// - 2 : KerxAnchorCoordinates
|
||||
func (kd KerxData4) ActionType() uint8 {
|
||||
const ActionType = 0xC0000000 // A two-bit field containing the action type.
|
||||
return uint8((kd.Flags & ActionType) >> 30)
|
||||
}
|
||||
|
||||
type KerxAnchors interface {
|
||||
isKerxAnchors()
|
||||
}
|
||||
|
||||
func (KerxAnchorControls) isKerxAnchors() {}
|
||||
func (KerxAnchorAnchors) isKerxAnchors() {}
|
||||
func (KerxAnchorCoordinates) isKerxAnchors() {}
|
||||
|
||||
type KerxAnchorControls struct {
|
||||
Anchors []KAControl
|
||||
}
|
||||
type KerxAnchorAnchors struct {
|
||||
Anchors []KAAnchor
|
||||
}
|
||||
type KerxAnchorCoordinates struct {
|
||||
Anchors []KACoordinates
|
||||
}
|
||||
|
||||
type KAControl struct {
|
||||
Mark, Current uint16
|
||||
}
|
||||
|
||||
type KAAnchor struct {
|
||||
Mark, Current uint16
|
||||
}
|
||||
|
||||
type KACoordinates struct {
|
||||
MarkX, MarkY, CurrentX, CurrentY int16
|
||||
}
|
||||
|
||||
// binarygen: argument=tupleCount int
|
||||
// binarygen: argument=valuesCount int
|
||||
type KerxData6 struct {
|
||||
flags uint32 // Flags for this subtable. See below.
|
||||
rowCount uint16 // The number of rows in the kerning value array
|
||||
columnCount uint16 // The number of columns in the kerning value array
|
||||
rowIndexTableOffset uint32 // Offset from beginning of this subtable to the row index lookup table.
|
||||
columnIndexTableOffset uint32 // Offset from beginning of this subtable to column index offset table.
|
||||
kerningArrayOffset uint32 // Offset from beginning of this subtable to the start of the kerning array.
|
||||
kerningVectorOffset uint32 // Offset from beginning of this subtable to the start of the kerning vectors. This value is only present if the tupleCount for this subtable is 1 or more.
|
||||
Row AatLookupMixed `isOpaque:"" offsetRelativeTo:"Parent"` // Values are pre-multiplied by `columnCount`
|
||||
Column AatLookupMixed `isOpaque:"" offsetRelativeTo:"Parent"`
|
||||
// with rowCount * columnCount
|
||||
// for tuples the values are estParseKerx (Not yet run).the first element of the tuple
|
||||
Kernings []int16 `isOpaque:"" offsetRelativeTo:"Parent"`
|
||||
}
|
||||
|
||||
func (kd *KerxData6) parseRow(_, parentSrc []byte, _, valuesCount int) error {
|
||||
isExtended := kd.flags&1 != 0
|
||||
if L := len(parentSrc); L < int(kd.rowIndexTableOffset) {
|
||||
return fmt.Errorf("EOF: expected length: %d, got %d", kd.rowIndexTableOffset, L)
|
||||
}
|
||||
var err error
|
||||
if isExtended {
|
||||
kd.Row, _, err = ParseAATLookupExt(parentSrc[kd.rowIndexTableOffset:], valuesCount)
|
||||
} else {
|
||||
kd.Row, _, err = ParseAATLookup(parentSrc[kd.rowIndexTableOffset:], valuesCount)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (kd *KerxData6) parseColumn(_, parentSrc []byte, _, valuesCount int) error {
|
||||
isExtended := kd.flags&1 != 0
|
||||
if L := len(parentSrc); L < int(kd.columnIndexTableOffset) {
|
||||
return fmt.Errorf("EOF: expected length: %d, got %d", kd.columnIndexTableOffset, L)
|
||||
}
|
||||
var err error
|
||||
if isExtended {
|
||||
kd.Column, _, err = ParseAATLookupExt(parentSrc[kd.columnIndexTableOffset:], valuesCount)
|
||||
} else {
|
||||
kd.Column, _, err = ParseAATLookup(parentSrc[kd.columnIndexTableOffset:], valuesCount)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (kd *KerxData6) parseKernings(_, parentSrc []byte, tupleCount, _ int) error {
|
||||
isExtended := kd.flags&1 != 0
|
||||
|
||||
length := int(kd.rowCount) * int(kd.columnCount)
|
||||
var tmp []uint32
|
||||
if isExtended {
|
||||
if L, E := len(parentSrc), int(kd.kerningArrayOffset)+length*4; L < E {
|
||||
return fmt.Errorf("EOF: expected length: %d, got %d", E, L)
|
||||
}
|
||||
tmp = make([]uint32, length)
|
||||
for i := range tmp {
|
||||
tmp[i] = binary.BigEndian.Uint32(parentSrc[int(kd.kerningArrayOffset)+4*i:])
|
||||
}
|
||||
} else {
|
||||
if L, E := len(parentSrc), int(kd.kerningArrayOffset)+length*2; L < E {
|
||||
return fmt.Errorf("EOF: expected length: %d, got %d", E, L)
|
||||
}
|
||||
tmp = make([]uint32, length)
|
||||
for i := range tmp {
|
||||
tmp[i] = uint32(binary.BigEndian.Uint16(parentSrc[int(kd.kerningArrayOffset)+2*i:]))
|
||||
}
|
||||
}
|
||||
|
||||
kd.Kernings = make([]int16, len(tmp))
|
||||
if tupleCount != 0 { // interpret kern values as offset
|
||||
// If the tupleCount is 1 or more, then the kerning array contains offsets from the beginning
|
||||
// of the kerningVectors table to a tupleCount-dimensional vector of FUnits controlling the kerning.
|
||||
for i, v := range tmp {
|
||||
kerningOffset := int(kd.kerningVectorOffset) + int(v)
|
||||
if L := len(parentSrc); L < kerningOffset+2 {
|
||||
return fmt.Errorf("EOF: expected length: %d, got %d", kerningOffset+2, L)
|
||||
}
|
||||
kd.Kernings[i] = int16(binary.BigEndian.Uint16(parentSrc[kerningOffset:]))
|
||||
}
|
||||
} else {
|
||||
// a kerning value greater than an int16 should not happen
|
||||
for i, v := range tmp {
|
||||
kd.Kernings[i] = int16(v)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
//lint:ignore U1000 this type is required so that the code generator add a ParseAATLookupExt function
|
||||
type dummy struct {
|
||||
A AATLookupExt
|
||||
}
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
// SPDX-License-Identifier: Unlicense OR BSD-3-Clause
|
||||
|
||||
package tables
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// Code generated by binarygen from aat_ltag_src.go. DO NOT EDIT
|
||||
|
||||
func ParseLtag(src []byte) (Ltag, int, error) {
|
||||
var item Ltag
|
||||
n := 0
|
||||
if L := len(src); L < 12 {
|
||||
return item, 0, fmt.Errorf("reading Ltag: "+"EOF: expected length: 12, got %d", L)
|
||||
}
|
||||
_ = src[11] // early bound checking
|
||||
item.version = binary.BigEndian.Uint32(src[0:])
|
||||
item.flags = binary.BigEndian.Uint32(src[4:])
|
||||
item.numTags = binary.BigEndian.Uint32(src[8:])
|
||||
n += 12
|
||||
|
||||
{
|
||||
arrayLength := int(item.numTags)
|
||||
|
||||
if L := len(src); L < 12+arrayLength*4 {
|
||||
return item, 0, fmt.Errorf("reading Ltag: "+"EOF: expected length: %d, got %d", 12+arrayLength*4, L)
|
||||
}
|
||||
|
||||
item.tagRange = make([]stringRange, arrayLength) // allocation guarded by the previous check
|
||||
for i := range item.tagRange {
|
||||
item.tagRange[i].mustParse(src[12+i*4:])
|
||||
}
|
||||
n += arrayLength * 4
|
||||
}
|
||||
{
|
||||
|
||||
item.stringData = src[0:]
|
||||
n = len(src)
|
||||
}
|
||||
return item, n, nil
|
||||
}
|
||||
|
||||
func (item *stringRange) mustParse(src []byte) {
|
||||
_ = src[3] // early bound checking
|
||||
item.offset = binary.BigEndian.Uint16(src[0:])
|
||||
item.length = binary.BigEndian.Uint16(src[2:])
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
// SPDX-License-Identifier: Unlicense OR BSD-3-Clause
|
||||
|
||||
package tables
|
||||
|
||||
import "github.com/go-text/typesetting/language"
|
||||
|
||||
// Ltag is the language tags table
|
||||
// See https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6ltag.html
|
||||
type Ltag struct {
|
||||
version uint32 // Table version; currently 1
|
||||
flags uint32 // Table flags; currently none defined
|
||||
numTags uint32 // Number of language tags which follow
|
||||
tagRange []stringRange `arrayCount:"ComputedField-numTags"` // Range for each tag's string
|
||||
stringData []byte `subsliceStart:"AtStart" arrayCount:"ToEnd"`
|
||||
}
|
||||
|
||||
type stringRange struct {
|
||||
offset uint16 // Offset from the start of the table to the beginning of the string
|
||||
length uint16 // String length (in bytes)
|
||||
}
|
||||
|
||||
func (lt Ltag) Language(i uint16) language.Language {
|
||||
r := lt.tagRange[i]
|
||||
return language.NewLanguage(string(lt.stringData[r.offset : r.offset+r.length]))
|
||||
}
|
||||
+330
@@ -0,0 +1,330 @@
|
||||
// SPDX-License-Identifier: Unlicense OR BSD-3-Clause
|
||||
|
||||
package tables
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// Code generated by binarygen from aat_mortx_src.go. DO NOT EDIT
|
||||
|
||||
func (item *AATFeature) mustParse(src []byte) {
|
||||
_ = src[11] // early bound checking
|
||||
item.FeatureType = binary.BigEndian.Uint16(src[0:])
|
||||
item.FeatureSetting = binary.BigEndian.Uint16(src[2:])
|
||||
item.EnableFlags = binary.BigEndian.Uint32(src[4:])
|
||||
item.DisableFlags = binary.BigEndian.Uint32(src[8:])
|
||||
}
|
||||
|
||||
func ParseMorx(src []byte, valuesCount int) (Morx, int, error) {
|
||||
var item Morx
|
||||
n := 0
|
||||
if L := len(src); L < 8 {
|
||||
return item, 0, fmt.Errorf("reading Morx: "+"EOF: expected length: 8, got %d", L)
|
||||
}
|
||||
_ = src[7] // early bound checking
|
||||
item.version = binary.BigEndian.Uint16(src[0:])
|
||||
item.unused = binary.BigEndian.Uint16(src[2:])
|
||||
item.nChains = binary.BigEndian.Uint32(src[4:])
|
||||
n += 8
|
||||
|
||||
{
|
||||
arrayLength := int(item.nChains)
|
||||
|
||||
offset := 8
|
||||
for i := 0; i < arrayLength; i++ {
|
||||
elem, read, err := ParseMorxChain(src[offset:], valuesCount)
|
||||
if err != nil {
|
||||
return item, 0, fmt.Errorf("reading Morx: %s", err)
|
||||
}
|
||||
item.Chains = append(item.Chains, elem)
|
||||
offset += read
|
||||
}
|
||||
n = offset
|
||||
}
|
||||
return item, n, nil
|
||||
}
|
||||
|
||||
func ParseMorxChain(src []byte, valuesCount int) (MorxChain, int, error) {
|
||||
var item MorxChain
|
||||
n := 0
|
||||
if L := len(src); L < 16 {
|
||||
return item, 0, fmt.Errorf("reading MorxChain: "+"EOF: expected length: 16, got %d", L)
|
||||
}
|
||||
_ = src[15] // early bound checking
|
||||
item.Flags = binary.BigEndian.Uint32(src[0:])
|
||||
item.chainLength = binary.BigEndian.Uint32(src[4:])
|
||||
item.nFeatureEntries = binary.BigEndian.Uint32(src[8:])
|
||||
item.nSubtable = binary.BigEndian.Uint32(src[12:])
|
||||
n += 16
|
||||
|
||||
{
|
||||
arrayLength := int(item.nFeatureEntries)
|
||||
|
||||
if L := len(src); L < 16+arrayLength*12 {
|
||||
return item, 0, fmt.Errorf("reading MorxChain: "+"EOF: expected length: %d, got %d", 16+arrayLength*12, L)
|
||||
}
|
||||
|
||||
item.Features = make([]AATFeature, arrayLength) // allocation guarded by the previous check
|
||||
for i := range item.Features {
|
||||
item.Features[i].mustParse(src[16+i*12:])
|
||||
}
|
||||
n += arrayLength * 12
|
||||
}
|
||||
{
|
||||
arrayLength := int(item.nSubtable)
|
||||
|
||||
offset := n
|
||||
for i := 0; i < arrayLength; i++ {
|
||||
elem, read, err := ParseMorxChainSubtable(src[offset:], valuesCount)
|
||||
if err != nil {
|
||||
return item, 0, fmt.Errorf("reading MorxChain: %s", err)
|
||||
}
|
||||
item.Subtables = append(item.Subtables, elem)
|
||||
offset += read
|
||||
}
|
||||
n = offset
|
||||
}
|
||||
return item, n, nil
|
||||
}
|
||||
|
||||
func ParseMorxChainSubtable(src []byte, valuesCount int) (MorxChainSubtable, int, error) {
|
||||
var item MorxChainSubtable
|
||||
n := 0
|
||||
if L := len(src); L < 12 {
|
||||
return item, 0, fmt.Errorf("reading MorxChainSubtable: "+"EOF: expected length: 12, got %d", L)
|
||||
}
|
||||
_ = src[11] // early bound checking
|
||||
item.length = binary.BigEndian.Uint32(src[0:])
|
||||
item.Coverage = src[4]
|
||||
item.ignored[0] = src[5]
|
||||
item.ignored[1] = src[6]
|
||||
item.version = MorxSubtableVersion(src[7])
|
||||
item.SubFeatureFlags = binary.BigEndian.Uint32(src[8:])
|
||||
n += 12
|
||||
|
||||
{
|
||||
var (
|
||||
read int
|
||||
err error
|
||||
)
|
||||
switch item.version {
|
||||
case MorxSubtableVersionContextual:
|
||||
item.Data, read, err = ParseMorxSubtableContextual(src[12:], valuesCount)
|
||||
case MorxSubtableVersionInsertion:
|
||||
item.Data, read, err = ParseMorxSubtableInsertion(src[12:], valuesCount)
|
||||
case MorxSubtableVersionLigature:
|
||||
item.Data, read, err = ParseMorxSubtableLigature(src[12:], valuesCount)
|
||||
case MorxSubtableVersionNonContextual:
|
||||
item.Data, read, err = ParseMorxSubtableNonContextual(src[12:], valuesCount)
|
||||
case MorxSubtableVersionRearrangement:
|
||||
item.Data, read, err = ParseMorxSubtableRearrangement(src[12:], valuesCount)
|
||||
default:
|
||||
err = fmt.Errorf("unsupported MorxSubtableVersion %d", item.version)
|
||||
}
|
||||
if err != nil {
|
||||
return item, 0, fmt.Errorf("reading MorxChainSubtable: %s", err)
|
||||
}
|
||||
n += read
|
||||
}
|
||||
var err error
|
||||
n, err = item.parseEnd(src, valuesCount)
|
||||
if err != nil {
|
||||
return item, 0, fmt.Errorf("reading MorxChainSubtable: %s", err)
|
||||
}
|
||||
|
||||
return item, n, nil
|
||||
}
|
||||
|
||||
func ParseMorxSubtableContextual(src []byte, valuesCount int) (MorxSubtableContextual, int, error) {
|
||||
var item MorxSubtableContextual
|
||||
n := 0
|
||||
{
|
||||
var (
|
||||
err error
|
||||
read int
|
||||
)
|
||||
item.AATStateTableExt, read, err = ParseAATStateTableExt(src[0:], int(valuesCount), int(4))
|
||||
if err != nil {
|
||||
return item, 0, fmt.Errorf("reading MorxSubtableContextual: %s", err)
|
||||
}
|
||||
n += read
|
||||
}
|
||||
if L := len(src); L < n+4 {
|
||||
return item, 0, fmt.Errorf("reading MorxSubtableContextual: "+"EOF: expected length: n + 4, got %d", L)
|
||||
}
|
||||
offsetSubstitutions := int(binary.BigEndian.Uint32(src[n:]))
|
||||
n += 4
|
||||
|
||||
{
|
||||
if offsetSubstitutions != 0 { // ignore null offset
|
||||
if L := len(src); L < offsetSubstitutions {
|
||||
return item, 0, fmt.Errorf("reading MorxSubtableContextual: "+"EOF: expected length: %d, got %d", offsetSubstitutions, L)
|
||||
}
|
||||
|
||||
var err error
|
||||
item.Substitutions, _, err = ParseSubstitutionsTable(src[offsetSubstitutions:], int(item.nSubs()), int(valuesCount))
|
||||
if err != nil {
|
||||
return item, 0, fmt.Errorf("reading MorxSubtableContextual: %s", err)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
return item, n, nil
|
||||
}
|
||||
|
||||
func ParseMorxSubtableInsertion(src []byte, valuesCount int) (MorxSubtableInsertion, int, error) {
|
||||
var item MorxSubtableInsertion
|
||||
n := 0
|
||||
{
|
||||
var (
|
||||
err error
|
||||
read int
|
||||
)
|
||||
item.AATStateTableExt, read, err = ParseAATStateTableExt(src[0:], int(valuesCount), int(4))
|
||||
if err != nil {
|
||||
return item, 0, fmt.Errorf("reading MorxSubtableInsertion: %s", err)
|
||||
}
|
||||
n += read
|
||||
}
|
||||
if L := len(src); L < n+4 {
|
||||
return item, 0, fmt.Errorf("reading MorxSubtableInsertion: "+"EOF: expected length: n + 4, got %d", L)
|
||||
}
|
||||
offsetInsertions := int(binary.BigEndian.Uint32(src[n:]))
|
||||
n += 4
|
||||
|
||||
{
|
||||
if offsetInsertions != 0 { // ignore null offset
|
||||
if L := len(src); L < offsetInsertions {
|
||||
return item, 0, fmt.Errorf("reading MorxSubtableInsertion: "+"EOF: expected length: %d, got %d", offsetInsertions, L)
|
||||
}
|
||||
|
||||
arrayLength := int(item.nInsertions())
|
||||
|
||||
if L := len(src); L < offsetInsertions+arrayLength*2 {
|
||||
return item, 0, fmt.Errorf("reading MorxSubtableInsertion: "+"EOF: expected length: %d, got %d", offsetInsertions+arrayLength*2, L)
|
||||
}
|
||||
|
||||
item.Insertions = make([]uint16, arrayLength) // allocation guarded by the previous check
|
||||
for i := range item.Insertions {
|
||||
item.Insertions[i] = binary.BigEndian.Uint16(src[offsetInsertions+i*2:])
|
||||
}
|
||||
offsetInsertions += arrayLength * 2
|
||||
}
|
||||
}
|
||||
return item, n, nil
|
||||
}
|
||||
|
||||
func ParseMorxSubtableLigature(src []byte, valuesCount int) (MorxSubtableLigature, int, error) {
|
||||
var item MorxSubtableLigature
|
||||
n := 0
|
||||
{
|
||||
var (
|
||||
err error
|
||||
read int
|
||||
)
|
||||
item.AATStateTableExt, read, err = ParseAATStateTableExt(src[0:], int(valuesCount), int(2))
|
||||
if err != nil {
|
||||
return item, 0, fmt.Errorf("reading MorxSubtableLigature: %s", err)
|
||||
}
|
||||
n += read
|
||||
}
|
||||
if L := len(src); L < n+12 {
|
||||
return item, 0, fmt.Errorf("reading MorxSubtableLigature: "+"EOF: expected length: n + 12, got %d", L)
|
||||
}
|
||||
_ = src[n+11] // early bound checking
|
||||
item.ligActionOffset = Offset32(binary.BigEndian.Uint32(src[n:]))
|
||||
item.componentOffset = Offset32(binary.BigEndian.Uint32(src[n+4:]))
|
||||
item.ligatureOffset = Offset32(binary.BigEndian.Uint32(src[n+8:]))
|
||||
n += 12
|
||||
|
||||
{
|
||||
|
||||
err := item.parseLigActions(src[:], valuesCount)
|
||||
if err != nil {
|
||||
return item, 0, fmt.Errorf("reading MorxSubtableLigature: %s", err)
|
||||
}
|
||||
}
|
||||
{
|
||||
|
||||
err := item.parseComponents(src[:], valuesCount)
|
||||
if err != nil {
|
||||
return item, 0, fmt.Errorf("reading MorxSubtableLigature: %s", err)
|
||||
}
|
||||
}
|
||||
{
|
||||
|
||||
err := item.parseLigatures(src[:], valuesCount)
|
||||
if err != nil {
|
||||
return item, 0, fmt.Errorf("reading MorxSubtableLigature: %s", err)
|
||||
}
|
||||
}
|
||||
return item, n, nil
|
||||
}
|
||||
|
||||
func ParseMorxSubtableNonContextual(src []byte, valuesCount int) (MorxSubtableNonContextual, int, error) {
|
||||
var item MorxSubtableNonContextual
|
||||
n := 0
|
||||
{
|
||||
var (
|
||||
err error
|
||||
read int
|
||||
)
|
||||
item.Class, read, err = ParseAATLookup(src[0:], valuesCount)
|
||||
if err != nil {
|
||||
return item, 0, fmt.Errorf("reading MorxSubtableNonContextual: %s", err)
|
||||
}
|
||||
n += read
|
||||
}
|
||||
return item, n, nil
|
||||
}
|
||||
|
||||
func ParseMorxSubtableRearrangement(src []byte, valuesCount int) (MorxSubtableRearrangement, int, error) {
|
||||
var item MorxSubtableRearrangement
|
||||
n := 0
|
||||
{
|
||||
var (
|
||||
err error
|
||||
read int
|
||||
)
|
||||
item.AATStateTableExt, read, err = ParseAATStateTableExt(src[0:], int(valuesCount), int(0))
|
||||
if err != nil {
|
||||
return item, 0, fmt.Errorf("reading MorxSubtableRearrangement: %s", err)
|
||||
}
|
||||
n += read
|
||||
}
|
||||
return item, n, nil
|
||||
}
|
||||
|
||||
func ParseSubstitutionsTable(src []byte, substitutionsCount int, valuesCount int) (SubstitutionsTable, int, error) {
|
||||
var item SubstitutionsTable
|
||||
n := 0
|
||||
{
|
||||
|
||||
if L := len(src); L < substitutionsCount*4 {
|
||||
return item, 0, fmt.Errorf("reading SubstitutionsTable: "+"EOF: expected length: %d, got %d", substitutionsCount*4, L)
|
||||
}
|
||||
|
||||
item.Substitutions = make([]AATLookup, substitutionsCount) // allocation guarded by the previous check
|
||||
for i := range item.Substitutions {
|
||||
offset := int(binary.BigEndian.Uint32(src[i*4:]))
|
||||
// ignore null offsets
|
||||
if offset == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
if L := len(src); L < offset {
|
||||
return item, 0, fmt.Errorf("reading SubstitutionsTable: "+"EOF: expected length: %d, got %d", offset, L)
|
||||
}
|
||||
|
||||
var err error
|
||||
item.Substitutions[i], _, err = ParseAATLookup(src[offset:], valuesCount)
|
||||
if err != nil {
|
||||
return item, 0, fmt.Errorf("reading SubstitutionsTable: %s", err)
|
||||
}
|
||||
}
|
||||
n += substitutionsCount * 4
|
||||
}
|
||||
return item, n, nil
|
||||
}
|
||||
+302
@@ -0,0 +1,302 @@
|
||||
// SPDX-License-Identifier: Unlicense OR BSD-3-Clause
|
||||
|
||||
package tables
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// Morx is the extended glyph metamorphosis table
|
||||
// See https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6morx.html
|
||||
type Morx struct {
|
||||
version uint16 // Version number of the extended glyph metamorphosis table (either 2 or 3)
|
||||
unused uint16 // Set to 0
|
||||
nChains uint32 // Number of metamorphosis chains contained in this table.
|
||||
Chains []MorxChain `arrayCount:"ComputedField-nChains"`
|
||||
}
|
||||
|
||||
// MorxChain is a set of subtables
|
||||
type MorxChain struct {
|
||||
Flags uint32 // The default specification for subtables.
|
||||
chainLength uint32 // Total byte count, including this header; must be a multiple of 4.
|
||||
nFeatureEntries uint32 // Number of feature subtable entries.
|
||||
nSubtable uint32 // The number of subtables in the chain.
|
||||
Features []AATFeature `arrayCount:"ComputedField-nFeatureEntries"`
|
||||
Subtables []MorxChainSubtable `arrayCount:"ComputedField-nSubtable"`
|
||||
}
|
||||
|
||||
type AATFeature struct {
|
||||
FeatureType uint16
|
||||
FeatureSetting uint16
|
||||
EnableFlags uint32 // Flags for the settings that this feature and setting enables.
|
||||
DisableFlags uint32 // Complement of flags for the settings that this feature and setting disable.
|
||||
}
|
||||
|
||||
type MorxChainSubtable struct {
|
||||
length uint32 // Total subtable length, including this header.
|
||||
|
||||
// Coverage flags and subtable type.
|
||||
Coverage byte
|
||||
ignored [2]byte
|
||||
version MorxSubtableVersion
|
||||
|
||||
SubFeatureFlags uint32 // The 32-bit mask identifying which subtable this is (the subtable being executed if the AND of this value and the processed defaultFlags is nonzero)
|
||||
|
||||
Data MorxSubtable `unionField:"version"`
|
||||
}
|
||||
|
||||
// check and return the subtable length
|
||||
func (mc *MorxChainSubtable) parseEnd(src []byte, _ int) (int, error) {
|
||||
if L := len(src); L < int(mc.length) {
|
||||
return 0, fmt.Errorf("EOF: expected length: %d, got %d", mc.length, L)
|
||||
}
|
||||
return int(mc.length), nil
|
||||
}
|
||||
|
||||
// MorxSubtableVersion indicates the kind of 'morx' subtable.
|
||||
// See the constants.
|
||||
type MorxSubtableVersion uint8
|
||||
|
||||
const (
|
||||
MorxSubtableVersionRearrangement MorxSubtableVersion = iota
|
||||
MorxSubtableVersionContextual
|
||||
MorxSubtableVersionLigature
|
||||
_ // reserved
|
||||
MorxSubtableVersionNonContextual
|
||||
MorxSubtableVersionInsertion
|
||||
)
|
||||
|
||||
type MorxSubtable interface {
|
||||
isMorxSubtable()
|
||||
}
|
||||
|
||||
func (MorxSubtableRearrangement) isMorxSubtable() {}
|
||||
func (MorxSubtableContextual) isMorxSubtable() {}
|
||||
func (MorxSubtableLigature) isMorxSubtable() {}
|
||||
func (MorxSubtableNonContextual) isMorxSubtable() {}
|
||||
func (MorxSubtableInsertion) isMorxSubtable() {}
|
||||
|
||||
// binarygen: argument=valuesCount int
|
||||
type MorxSubtableRearrangement struct {
|
||||
AATStateTableExt `arguments:"valuesCount=valuesCount,entryDataSize=0"`
|
||||
}
|
||||
|
||||
// binarygen: argument=valuesCount int
|
||||
type MorxSubtableContextual struct {
|
||||
AATStateTableExt `arguments:"valuesCount=valuesCount,entryDataSize=4"`
|
||||
// Byte offset from the beginning of the state subtable to the beginning of the substitution tables :
|
||||
// each value of the array is itself an offet to a aatLookupTable, and the number of
|
||||
// items is computed from the header
|
||||
Substitutions SubstitutionsTable `offsetSize:"Offset32" arguments:"substitutionsCount=.nSubs(), valuesCount=valuesCount"`
|
||||
}
|
||||
|
||||
type SubstitutionsTable struct {
|
||||
Substitutions []AATLookup `offsetsArray:"Offset32"`
|
||||
}
|
||||
|
||||
func (ct *MorxSubtableContextual) nSubs() int {
|
||||
// find the maximum index need in the substitution array
|
||||
var maxi uint16
|
||||
for _, entry := range ct.Entries {
|
||||
markIndex, currentIndex := entry.AsMorxContextual()
|
||||
if markIndex != 0xFFFF && markIndex > maxi {
|
||||
maxi = markIndex
|
||||
}
|
||||
if currentIndex != 0xFFFF && currentIndex > maxi {
|
||||
maxi = currentIndex
|
||||
}
|
||||
}
|
||||
return int(maxi) + 1
|
||||
}
|
||||
|
||||
// binarygen: argument=valuesCount int
|
||||
type MorxSubtableLigature struct {
|
||||
AATStateTableExt `arguments:"valuesCount=valuesCount, entryDataSize=2"`
|
||||
ligActionOffset Offset32 // Byte offset from stateHeader to the start of the ligature action table.
|
||||
componentOffset Offset32 // Byte offset from stateHeader to the start of the component table.
|
||||
ligatureOffset Offset32 // Byte offset from stateHeader to the start of the actual ligature lists.
|
||||
LigActions []uint32 `isOpaque:""`
|
||||
Components []uint16 `isOpaque:""`
|
||||
Ligatures []GlyphID `isOpaque:""`
|
||||
}
|
||||
|
||||
// MorxLigatureSubtable flags
|
||||
const (
|
||||
// Push this glyph onto the component stack for
|
||||
// eventual processing.
|
||||
MLSetComponent = 0x8000
|
||||
// Leave the glyph pointer at this glyph for the
|
||||
// next iteration.
|
||||
MLDontAdvance = 0x4000
|
||||
// Use the ligActionIndex to process a ligature group.
|
||||
MLPerformAction = 0x2000
|
||||
// Byte offset from beginning of subtable to the
|
||||
// ligature action list. This value must be a
|
||||
// multiple of 4.
|
||||
MLOffset = 0x3FFF
|
||||
|
||||
// This is the last action in the list. This also
|
||||
// implies storage.
|
||||
MLActionLast = 1 << 31
|
||||
// Store the ligature at the current cumulated index
|
||||
// in the ligature table in place of the marked
|
||||
// (i.e. currently-popped) glyph.
|
||||
MLActionStore = 1 << 30
|
||||
// A 30-bit value which is sign-extended to 32-bits
|
||||
// and added to the glyph ID, resulting in an index
|
||||
// into the component table.
|
||||
MLActionOffset = 0x3FFFFFFF
|
||||
)
|
||||
|
||||
// the LigActions length is not specified. Instead, we have to parse uint32 one by one
|
||||
// until we reach last action or reach EOF
|
||||
func (lig *MorxSubtableLigature) parseLigActions(src []byte, _ int) error {
|
||||
// fetch the maximum start index
|
||||
maxIndex := -1
|
||||
for _, entry := range lig.Entries {
|
||||
if entry.Flags&MLPerformAction == 0 {
|
||||
continue
|
||||
}
|
||||
if index := int(entry.AsMorxLigature()); index > maxIndex {
|
||||
maxIndex = index
|
||||
}
|
||||
}
|
||||
|
||||
if L := len(src); L < int(lig.ligActionOffset)+4*int(maxIndex+1) {
|
||||
return fmt.Errorf("EOF: expected length: %d, got %d", lig.ligActionOffset, L)
|
||||
}
|
||||
|
||||
// fetch the action table, up to the last entry
|
||||
src = src[lig.ligActionOffset:]
|
||||
for len(src) >= 4 { // stop gracefully if the last action was not found
|
||||
action := binary.BigEndian.Uint32(src)
|
||||
lig.LigActions = append(lig.LigActions, action)
|
||||
src = src[4:]
|
||||
// dont break before maxIndex
|
||||
if len(lig.LigActions) > maxIndex && action&MLActionLast != 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (lig *MorxSubtableLigature) parseComponents(src []byte, _ int) error {
|
||||
// we rely on offset being sorted, which seems to be the case in practice
|
||||
if lig.componentOffset > lig.ligatureOffset {
|
||||
return errors.New("unsupported non sorted offsets")
|
||||
}
|
||||
if L := len(src); L < int(lig.componentOffset) {
|
||||
return fmt.Errorf("EOF: expected length: %d, got %d", lig.componentOffset, L)
|
||||
}
|
||||
src = src[lig.componentOffset:]
|
||||
componentCount := (lig.ligatureOffset - lig.componentOffset) / 2
|
||||
lig.Components = make([]uint16, componentCount)
|
||||
for i := range lig.Components {
|
||||
lig.Components[i] = binary.BigEndian.Uint16(src[2*i:])
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (lig *MorxSubtableLigature) parseLigatures(src []byte, _ int) error {
|
||||
if L := len(src); L < int(lig.ligatureOffset) {
|
||||
return fmt.Errorf("EOF: expected length: %d, got %d", lig.ligatureOffset, L)
|
||||
}
|
||||
src = src[lig.ligatureOffset:]
|
||||
ligatureCount := len(src) / 2
|
||||
lig.Ligatures = make([]GlyphID, ligatureCount)
|
||||
for i := range lig.Ligatures {
|
||||
lig.Ligatures[i] = GlyphID(binary.BigEndian.Uint16(src[2*i:]))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type MorxSubtableNonContextual struct {
|
||||
// The lookup value is interpreted as a GlyphIndex
|
||||
Class AATLookup
|
||||
}
|
||||
|
||||
// binarygen: argument=valuesCount int
|
||||
type MorxSubtableInsertion struct {
|
||||
AATStateTableExt `arguments:"valuesCount=valuesCount,entryDataSize=4"`
|
||||
Insertions []GlyphID `offsetSize:"Offset32" arrayCount:"ComputedField-nInsertions()"` // Byte offset from stateHeader to the start of the insertion glyph table.
|
||||
}
|
||||
|
||||
// MorxInsertionSubtable flags
|
||||
const (
|
||||
// If set, mark the current glyph.
|
||||
MISetMark = 0x8000
|
||||
// If set, don't advance to the next glyph before
|
||||
// going to the new state. This does not mean
|
||||
// that the glyph pointed to is the same one as
|
||||
// before. If you've made insertions immediately
|
||||
// downstream of the current glyph, the next glyph
|
||||
// processed would in fact be the first one
|
||||
// inserted.
|
||||
MIDontAdvance = 0x4000
|
||||
// If set, and the currentInsertList is nonzero,
|
||||
// then the specified glyph list will be inserted
|
||||
// as a kashida-like insertion, either before or
|
||||
// after the current glyph (depending on the state
|
||||
// of the currentInsertBefore flag). If clear, and
|
||||
// the currentInsertList is nonzero, then the
|
||||
// specified glyph list will be inserted as a
|
||||
// split-vowel-like insertion, either before or
|
||||
// after the current glyph (depending on the state
|
||||
// of the currentInsertBefore flag).
|
||||
MICurrentIsKashidaLike = 0x2000
|
||||
// If set, and the markedInsertList is nonzero,
|
||||
// then the specified glyph list will be inserted
|
||||
// as a kashida-like insertion, either before or
|
||||
// after the marked glyph (depending on the state
|
||||
// of the markedInsertBefore flag). If clear, and
|
||||
// the markedInsertList is nonzero, then the
|
||||
// specified glyph list will be inserted as a
|
||||
// split-vowel-like insertion, either before or
|
||||
// after the marked glyph (depending on the state
|
||||
// of the markedInsertBefore flag).
|
||||
MIMarkedIsKashidaLike = 0x1000
|
||||
// If set, specifies that insertions are to be made
|
||||
// to the left of the current glyph. If clear,
|
||||
// they're made to the right of the current glyph.
|
||||
MICurrentInsertBefore = 0x0800
|
||||
// If set, specifies that insertions are to be
|
||||
// made to the left of the marked glyph. If clear,
|
||||
// they're made to the right of the marked glyph.
|
||||
MIMarkedInsertBefore = 0x0400
|
||||
// This 5-bit field is treated as a count of the
|
||||
// number of glyphs to insert at the current
|
||||
// position. Since zero means no insertions, the
|
||||
// largest number of insertions at any given
|
||||
// current location is 31 glyphs.
|
||||
MICurrentInsertCount = 0x3E0
|
||||
// This 5-bit field is treated as a count of the
|
||||
// number of glyphs to insert at the marked
|
||||
// position. Since zero means no insertions, the
|
||||
// largest number of insertions at any given
|
||||
// marked location is 31 glyphs.
|
||||
MIMarkedInsertCount = 0x001F
|
||||
)
|
||||
|
||||
func (msi *MorxSubtableInsertion) nInsertions() int {
|
||||
// find the maximum index needed in the insertions array,
|
||||
// taking into account the number of insertions
|
||||
var maxi uint16
|
||||
for _, entry := range msi.Entries {
|
||||
currentIndex, markedIndex := entry.AsMorxInsertion()
|
||||
if currentIndex != 0xFFFF {
|
||||
indexEnd := currentIndex + (entry.Flags&MICurrentInsertCount)>>5
|
||||
if indexEnd > maxi {
|
||||
maxi = indexEnd
|
||||
}
|
||||
}
|
||||
if markedIndex != 0xFFFF {
|
||||
indexEnd := markedIndex + entry.Flags&MIMarkedInsertCount
|
||||
if indexEnd > maxi {
|
||||
maxi = indexEnd
|
||||
}
|
||||
}
|
||||
}
|
||||
return int(maxi)
|
||||
}
|
||||
+272
@@ -0,0 +1,272 @@
|
||||
// SPDX-License-Identifier: Unlicense OR BSD-3-Clause
|
||||
|
||||
package tables
|
||||
|
||||
import (
|
||||
"sort"
|
||||
)
|
||||
|
||||
// This file implements routines used to simplify acces to the tables
|
||||
// data.
|
||||
|
||||
func (lk AATLoopkup0) Class(g GlyphID) (uint16, bool) {
|
||||
if int(g) >= len(lk.Values) {
|
||||
return 0, false
|
||||
}
|
||||
return lk.Values[g], true
|
||||
}
|
||||
|
||||
func (lk AATLoopkup2) Class(g GlyphID) (uint16, bool) {
|
||||
// 'adapted' from golang/x/image/font/sfnt
|
||||
c := lk.Records
|
||||
num := len(c)
|
||||
if num == 0 {
|
||||
return 0, false
|
||||
}
|
||||
|
||||
// classRange is an array of startGlyphID, endGlyphID and target class ID.
|
||||
// Ranges are non-overlapping.
|
||||
// E.g. 130, 135, 1 137, 137, 5 etc
|
||||
|
||||
idx := sort.Search(num, func(i int) bool { return g <= c[i].FirstGlyph })
|
||||
// idx either points to a matching start, or to the next range (or idx==num)
|
||||
// e.g. with the range example from above: 130 points to 130-135 range, 133 points to 137-137 range
|
||||
|
||||
// check if gi is the start of a range, but only if sort.Search returned a valid result
|
||||
if idx < num {
|
||||
if class := c[idx]; g == c[idx].FirstGlyph {
|
||||
return class.Value, true
|
||||
}
|
||||
}
|
||||
// check if gi is in previous range
|
||||
if idx > 0 {
|
||||
idx--
|
||||
if class := c[idx]; g >= class.FirstGlyph && g <= class.LastGlyph {
|
||||
return class.Value, true
|
||||
}
|
||||
}
|
||||
|
||||
return 0, false
|
||||
}
|
||||
|
||||
func (lk AATLoopkup4) Class(g GlyphID) (uint16, bool) {
|
||||
// binary search
|
||||
for i, j := 0, len(lk.Records); i < j; {
|
||||
h := i + (j-i)/2
|
||||
entry := lk.Records[h]
|
||||
if g < entry.FirstGlyph {
|
||||
j = h
|
||||
} else if entry.LastGlyph < g {
|
||||
i = h + 1
|
||||
} else {
|
||||
return entry.Values[g-entry.FirstGlyph], true
|
||||
}
|
||||
}
|
||||
return 0, false
|
||||
}
|
||||
|
||||
func (lk AATLoopkup6) Class(g GlyphID) (uint16, bool) {
|
||||
// binary search
|
||||
for i, j := 0, len(lk.Records); i < j; {
|
||||
h := i + (j-i)/2
|
||||
entry := lk.Records[h]
|
||||
if g < entry.Glyph {
|
||||
j = h
|
||||
} else if entry.Glyph < g {
|
||||
i = h + 1
|
||||
} else {
|
||||
return entry.Value, true
|
||||
}
|
||||
}
|
||||
return 0, false
|
||||
}
|
||||
|
||||
func (lk AATLoopkup8Data) Class(g GlyphID) (uint16, bool) {
|
||||
if g < lk.FirstGlyph || g >= lk.FirstGlyph+GlyphID(len(lk.Values)) {
|
||||
return 0, false
|
||||
}
|
||||
return lk.Values[g-lk.FirstGlyph], true
|
||||
}
|
||||
|
||||
func (lk AATLoopkup10) Class(g GlyphID) (uint16, bool) {
|
||||
if g < lk.FirstGlyph || g >= lk.FirstGlyph+GlyphID(len(lk.Values)) {
|
||||
return 0, false
|
||||
}
|
||||
return lk.Values[g-lk.FirstGlyph], true
|
||||
}
|
||||
|
||||
func (lk AATLoopkupExt0) Class(g GlyphID) (uint32, bool) {
|
||||
if int(g) >= len(lk.Values) {
|
||||
return 0, false
|
||||
}
|
||||
return lk.Values[g], true
|
||||
}
|
||||
|
||||
func (lk AATLoopkupExt2) Class(g GlyphID) (uint32, bool) {
|
||||
// 'adapted' from golang/x/image/font/sfnt
|
||||
c := lk.Records
|
||||
num := len(c)
|
||||
if num == 0 {
|
||||
return 0, false
|
||||
}
|
||||
|
||||
// classRange is an array of startGlyphID, endGlyphID and target class ID.
|
||||
// Ranges are non-overlapping.
|
||||
// E.g. 130, 135, 1 137, 137, 5 etc
|
||||
|
||||
idx := sort.Search(num, func(i int) bool { return g <= c[i].FirstGlyph })
|
||||
// idx either points to a matching start, or to the next range (or idx==num)
|
||||
// e.g. with the range example from above: 130 points to 130-135 range, 133 points to 137-137 range
|
||||
|
||||
// check if gi is the start of a range, but only if sort.Search returned a valid result
|
||||
if idx < num {
|
||||
if class := c[idx]; g == c[idx].FirstGlyph {
|
||||
return class.Value, true
|
||||
}
|
||||
}
|
||||
// check if gi is in previous range
|
||||
if idx > 0 {
|
||||
idx--
|
||||
if class := c[idx]; g >= class.FirstGlyph && g <= class.LastGlyph {
|
||||
return class.Value, true
|
||||
}
|
||||
}
|
||||
|
||||
return 0, false
|
||||
}
|
||||
|
||||
func (lk AATLoopkupExt4) Class(g GlyphID) (uint32, bool) {
|
||||
// binary search
|
||||
for i, j := 0, len(lk.Records); i < j; {
|
||||
h := i + (j-i)/2
|
||||
entry := lk.Records[h]
|
||||
if g < entry.FirstGlyph {
|
||||
j = h
|
||||
} else if entry.LastGlyph < g {
|
||||
i = h + 1
|
||||
} else {
|
||||
return entry.Values[g-entry.FirstGlyph], true
|
||||
}
|
||||
}
|
||||
return 0, false
|
||||
}
|
||||
|
||||
func (lk AATLoopkupExt6) Class(g GlyphID) (uint32, bool) {
|
||||
// binary search
|
||||
for i, j := 0, len(lk.Records); i < j; {
|
||||
h := i + (j-i)/2
|
||||
entry := lk.Records[h]
|
||||
if g < entry.Glyph {
|
||||
j = h
|
||||
} else if entry.Glyph < g {
|
||||
i = h + 1
|
||||
} else {
|
||||
return entry.Value, true
|
||||
}
|
||||
}
|
||||
return 0, false
|
||||
}
|
||||
|
||||
func (lk AATLoopkupExt8) Class(g GlyphID) (uint32, bool) {
|
||||
v, ok := AATLoopkup8(lk).Class(g)
|
||||
return uint32(v), ok
|
||||
}
|
||||
|
||||
func (lk AATLoopkupExt10) Class(g GlyphID) (uint32, bool) {
|
||||
if g < lk.FirstGlyph || g >= lk.FirstGlyph+GlyphID(len(lk.Values)) {
|
||||
return 0, false
|
||||
}
|
||||
return lk.Values[g-lk.FirstGlyph], true
|
||||
}
|
||||
|
||||
type AatLookupMixed interface {
|
||||
// Returns 0 if not supported
|
||||
ClassUint32(GlyphID) uint32
|
||||
|
||||
// Coverage returns the glyphs covered by this lookup as
|
||||
// a list of inclusive ranges
|
||||
Coverage() [][2]GlyphID
|
||||
}
|
||||
|
||||
func (lk AATLoopkup0) ClassUint32(g GlyphID) uint32 {
|
||||
v, _ := lk.Class(g)
|
||||
return uint32(v)
|
||||
}
|
||||
|
||||
func (lk AATLoopkup2) ClassUint32(g GlyphID) uint32 {
|
||||
v, _ := lk.Class(g)
|
||||
return uint32(v)
|
||||
}
|
||||
|
||||
func (lk AATLoopkup4) ClassUint32(g GlyphID) uint32 {
|
||||
v, _ := lk.Class(g)
|
||||
return uint32(v)
|
||||
}
|
||||
|
||||
func (lk AATLoopkup6) ClassUint32(g GlyphID) uint32 {
|
||||
v, _ := lk.Class(g)
|
||||
return uint32(v)
|
||||
}
|
||||
|
||||
func (lk AATLoopkup8) ClassUint32(g GlyphID) uint32 {
|
||||
v, _ := lk.Class(g)
|
||||
return uint32(v)
|
||||
}
|
||||
|
||||
func (lk AATLoopkup10) ClassUint32(g GlyphID) uint32 {
|
||||
v, _ := lk.Class(g)
|
||||
return uint32(v)
|
||||
}
|
||||
|
||||
func (lk AATLoopkupExt0) ClassUint32(g GlyphID) uint32 {
|
||||
v, _ := lk.Class(g)
|
||||
return v
|
||||
}
|
||||
|
||||
func (lk AATLoopkupExt2) ClassUint32(g GlyphID) uint32 {
|
||||
v, _ := lk.Class(g)
|
||||
return v
|
||||
}
|
||||
|
||||
func (lk AATLoopkupExt4) ClassUint32(g GlyphID) uint32 {
|
||||
v, _ := lk.Class(g)
|
||||
return v
|
||||
}
|
||||
|
||||
func (lk AATLoopkupExt6) ClassUint32(g GlyphID) uint32 {
|
||||
v, _ := lk.Class(g)
|
||||
return v
|
||||
}
|
||||
|
||||
func (lk AATLoopkupExt8) ClassUint32(g GlyphID) uint32 {
|
||||
v, _ := lk.Class(g)
|
||||
return v
|
||||
}
|
||||
|
||||
func (lk AATLoopkupExt10) ClassUint32(g GlyphID) uint32 {
|
||||
v, _ := lk.Class(g)
|
||||
return v
|
||||
}
|
||||
|
||||
// GetFeature performs a binary seach into the names, using `Feature` as key,
|
||||
// returning `nil` if not found.
|
||||
func (ft Feat) GetFeature(feature uint16) *FeatureName {
|
||||
for i, j := 0, len(ft.Names); i < j; {
|
||||
h := i + (j-i)/2
|
||||
entry := ft.Names[h].Feature
|
||||
if feature < entry {
|
||||
j = h
|
||||
} else if entry < feature {
|
||||
i = h + 1
|
||||
} else {
|
||||
return &ft.Names[h]
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// IsExclusive returns true if the feature settings are mutually exclusive.
|
||||
func (feature *FeatureName) IsExclusive() bool {
|
||||
const Exclusive = 0x8000
|
||||
return feature.FeatureFlags&Exclusive != 0
|
||||
}
|
||||
+135
@@ -0,0 +1,135 @@
|
||||
// SPDX-License-Identifier: Unlicense OR BSD-3-Clause
|
||||
|
||||
package tables
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// Code generated by binarygen from aat_trak_src.go. DO NOT EDIT
|
||||
|
||||
func ParseTrackData(src []byte, parentSrc []byte) (TrackData, int, error) {
|
||||
var item TrackData
|
||||
n := 0
|
||||
if L := len(src); L < 8 {
|
||||
return item, 0, fmt.Errorf("reading TrackData: "+"EOF: expected length: 8, got %d", L)
|
||||
}
|
||||
_ = src[7] // early bound checking
|
||||
item.nTracks = binary.BigEndian.Uint16(src[0:])
|
||||
item.nSizes = binary.BigEndian.Uint16(src[2:])
|
||||
offsetSizeTable := int(binary.BigEndian.Uint32(src[4:]))
|
||||
n += 8
|
||||
|
||||
{
|
||||
if offsetSizeTable != 0 { // ignore null offset
|
||||
if L := len(parentSrc); L < offsetSizeTable {
|
||||
return item, 0, fmt.Errorf("reading TrackData: "+"EOF: expected length: %d, got %d", offsetSizeTable, L)
|
||||
}
|
||||
|
||||
arrayLength := int(item.nSizes)
|
||||
|
||||
if L := len(parentSrc); L < offsetSizeTable+arrayLength*4 {
|
||||
return item, 0, fmt.Errorf("reading TrackData: "+"EOF: expected length: %d, got %d", offsetSizeTable+arrayLength*4, L)
|
||||
}
|
||||
|
||||
item.SizeTable = make([]float32, arrayLength) // allocation guarded by the previous check
|
||||
for i := range item.SizeTable {
|
||||
item.SizeTable[i] = Float1616FromUint(binary.BigEndian.Uint32(parentSrc[offsetSizeTable+i*4:]))
|
||||
}
|
||||
offsetSizeTable += arrayLength * 4
|
||||
}
|
||||
}
|
||||
{
|
||||
arrayLength := int(item.nTracks)
|
||||
|
||||
offset := 8
|
||||
for i := 0; i < arrayLength; i++ {
|
||||
elem, read, err := ParseTrackTableEntry(src[offset:], parentSrc, int(item.nSizes))
|
||||
if err != nil {
|
||||
return item, 0, fmt.Errorf("reading TrackData: %s", err)
|
||||
}
|
||||
item.TrackTable = append(item.TrackTable, elem)
|
||||
offset += read
|
||||
}
|
||||
n = offset
|
||||
}
|
||||
return item, n, nil
|
||||
}
|
||||
|
||||
func ParseTrackTableEntry(src []byte, grandParentSrc []byte, perSizeTrackingCount int) (TrackTableEntry, int, error) {
|
||||
var item TrackTableEntry
|
||||
n := 0
|
||||
if L := len(src); L < 8 {
|
||||
return item, 0, fmt.Errorf("reading TrackTableEntry: "+"EOF: expected length: 8, got %d", L)
|
||||
}
|
||||
_ = src[7] // early bound checking
|
||||
item.Track = Float1616FromUint(binary.BigEndian.Uint32(src[0:]))
|
||||
item.NameIndex = binary.BigEndian.Uint16(src[4:])
|
||||
offsetPerSizeTracking := int(binary.BigEndian.Uint16(src[6:]))
|
||||
n += 8
|
||||
|
||||
{
|
||||
if offsetPerSizeTracking != 0 { // ignore null offset
|
||||
if L := len(grandParentSrc); L < offsetPerSizeTracking {
|
||||
return item, 0, fmt.Errorf("reading TrackTableEntry: "+"EOF: expected length: %d, got %d", offsetPerSizeTracking, L)
|
||||
}
|
||||
|
||||
if L := len(grandParentSrc); L < offsetPerSizeTracking+perSizeTrackingCount*2 {
|
||||
return item, 0, fmt.Errorf("reading TrackTableEntry: "+"EOF: expected length: %d, got %d", offsetPerSizeTracking+perSizeTrackingCount*2, L)
|
||||
}
|
||||
|
||||
item.PerSizeTracking = make([]int16, perSizeTrackingCount) // allocation guarded by the previous check
|
||||
for i := range item.PerSizeTracking {
|
||||
item.PerSizeTracking[i] = int16(binary.BigEndian.Uint16(grandParentSrc[offsetPerSizeTracking+i*2:]))
|
||||
}
|
||||
offsetPerSizeTracking += perSizeTrackingCount * 2
|
||||
}
|
||||
}
|
||||
return item, n, nil
|
||||
}
|
||||
|
||||
func ParseTrak(src []byte) (Trak, int, error) {
|
||||
var item Trak
|
||||
n := 0
|
||||
if L := len(src); L < 12 {
|
||||
return item, 0, fmt.Errorf("reading Trak: "+"EOF: expected length: 12, got %d", L)
|
||||
}
|
||||
_ = src[11] // early bound checking
|
||||
item.version = binary.BigEndian.Uint32(src[0:])
|
||||
item.format = binary.BigEndian.Uint16(src[4:])
|
||||
offsetHoriz := int(binary.BigEndian.Uint16(src[6:]))
|
||||
offsetVert := int(binary.BigEndian.Uint16(src[8:]))
|
||||
item.reserved = binary.BigEndian.Uint16(src[10:])
|
||||
n += 12
|
||||
|
||||
{
|
||||
if offsetHoriz != 0 { // ignore null offset
|
||||
if L := len(src); L < offsetHoriz {
|
||||
return item, 0, fmt.Errorf("reading Trak: "+"EOF: expected length: %d, got %d", offsetHoriz, L)
|
||||
}
|
||||
|
||||
var err error
|
||||
item.Horiz, _, err = ParseTrackData(src[offsetHoriz:], src)
|
||||
if err != nil {
|
||||
return item, 0, fmt.Errorf("reading Trak: %s", err)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
{
|
||||
if offsetVert != 0 { // ignore null offset
|
||||
if L := len(src); L < offsetVert {
|
||||
return item, 0, fmt.Errorf("reading Trak: "+"EOF: expected length: %d, got %d", offsetVert, L)
|
||||
}
|
||||
|
||||
var err error
|
||||
item.Vert, _, err = ParseTrackData(src[offsetVert:], src)
|
||||
if err != nil {
|
||||
return item, 0, fmt.Errorf("reading Trak: %s", err)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
return item, n, nil
|
||||
}
|
||||
+137
@@ -0,0 +1,137 @@
|
||||
// SPDX-License-Identifier: Unlicense OR BSD-3-Clause
|
||||
|
||||
package tables
|
||||
|
||||
// Trak is the tracking table.
|
||||
// See - https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6trak.html
|
||||
type Trak struct {
|
||||
version uint32 // Version number of the tracking table (0x00010000 for the current version).
|
||||
format uint16 // Format of the tracking table (set to 0).
|
||||
Horiz TrackData `offsetSize:"Offset16"` // Offset from start of tracking table to TrackData for horizontal text (or 0 if none).
|
||||
Vert TrackData `offsetSize:"Offset16"` // Offset from start of tracking table to TrackData for vertical text (or 0 if none).
|
||||
reserved uint16 // Reserved. Set to 0.
|
||||
}
|
||||
|
||||
// IsEmpty return `true` it the table has no entries.
|
||||
func (t Trak) IsEmpty() bool {
|
||||
return len(t.Horiz.TrackTable)+len(t.Vert.TrackTable) == 0
|
||||
}
|
||||
|
||||
type TrackData struct {
|
||||
nTracks uint16 // Number of separate tracks included in this table.
|
||||
nSizes uint16 // Number of point sizes included in this table.
|
||||
SizeTable []Float1616 `offsetSize:"Offset32" offsetRelativeTo:"Parent" arrayCount:"ComputedField-nSizes"` // Offset from start of the tracking table to the start of the size subtable.
|
||||
TrackTable []TrackTableEntry `arrayCount:"ComputedField-nTracks" arguments:"perSizeTrackingCount=.nSizes"` // Array[nTracks] of TrackTableEntry records.
|
||||
}
|
||||
|
||||
// GetTracking selects the tracking for the given `track` and applies it
|
||||
// for `ptem`. It returns 0 if not found.
|
||||
func (td TrackData) GetTracking(ptem float32, track float32) float32 {
|
||||
count := len(td.TrackTable)
|
||||
if count == 0 {
|
||||
return 0
|
||||
} else if count == 1 {
|
||||
return td.TrackTable[0].value(ptem, td.SizeTable)
|
||||
}
|
||||
|
||||
// At least two entries.
|
||||
|
||||
i := 0
|
||||
j := count - 1
|
||||
|
||||
// Find the two entries that track is between.
|
||||
for i+1 < count && td.TrackTable[i+1].Track <= track {
|
||||
i++
|
||||
}
|
||||
for j > 0 && td.TrackTable[j-1].Track >= track {
|
||||
j--
|
||||
}
|
||||
|
||||
// Exact match.
|
||||
if i == j {
|
||||
return td.TrackTable[i].value(ptem, td.SizeTable)
|
||||
}
|
||||
|
||||
// Interpolate.
|
||||
|
||||
t0 := td.TrackTable[i].Track
|
||||
t1 := td.TrackTable[j].Track
|
||||
|
||||
t := (track - t0) / (t1 - t0)
|
||||
|
||||
a := td.TrackTable[i].value(ptem, td.SizeTable)
|
||||
b := td.TrackTable[j].value(ptem, td.SizeTable)
|
||||
return a + t*(b-a)
|
||||
}
|
||||
|
||||
type TrackTableEntry struct {
|
||||
Track Float1616 // Track value for this record.
|
||||
NameIndex uint16 // The 'name' table index for this track (a short word or phrase like "loose" or "very tight"). NameIndex has a value greater than 255 and less than 32768.
|
||||
PerSizeTracking []int16 `offsetSize:"Offset16" offsetRelativeTo:"GrandParent"` // in font units, with length len(SizeTable)
|
||||
}
|
||||
|
||||
func (entry *TrackTableEntry) value(ptem float32, sizeTable []Float1616) float32 {
|
||||
values := entry.PerSizeTracking
|
||||
nSizes := len(sizeTable)
|
||||
|
||||
// Choose size.
|
||||
if nSizes == 0 {
|
||||
return 0
|
||||
}
|
||||
if nSizes == 1 {
|
||||
return float32(values[0])
|
||||
}
|
||||
|
||||
// At least two entries.
|
||||
|
||||
var i int
|
||||
for i = 0; i < nSizes; i++ {
|
||||
if sizeTable[i] >= ptem {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Boundary conditions.
|
||||
if i == 0 {
|
||||
return float32(values[0])
|
||||
}
|
||||
if i == nSizes {
|
||||
return float32(values[nSizes-1])
|
||||
}
|
||||
|
||||
// Exact match.
|
||||
if sizeTable[i] == ptem {
|
||||
return float32(values[i])
|
||||
}
|
||||
|
||||
// Interpolate.
|
||||
return entry.interpolateAt(i-1, ptem, sizeTable)
|
||||
}
|
||||
|
||||
// idx is assumed to verify idx <= len(sizeTable) - 2
|
||||
func (td *TrackTableEntry) interpolateAt(idx int, ptem float32, sizeTable []Float1616) float32 {
|
||||
values := td.PerSizeTracking
|
||||
|
||||
s0 := sizeTable[idx]
|
||||
s1 := sizeTable[idx+1]
|
||||
v0 := float32(values[idx])
|
||||
v1 := float32(values[idx+1])
|
||||
|
||||
// Deal with font bugs.
|
||||
if s1 < s0 {
|
||||
s0, s1 = s1, s0
|
||||
v0, v1 = v1, v0
|
||||
}
|
||||
if ptem < s0 {
|
||||
return v0
|
||||
}
|
||||
if ptem > s1 {
|
||||
return v1
|
||||
}
|
||||
if s0 == s1 {
|
||||
return (v0 + v1) * 0.5
|
||||
}
|
||||
|
||||
t := (ptem - s0) / (s1 - s0)
|
||||
return v0 + t*(v1-v0)
|
||||
}
|
||||
+742
@@ -0,0 +1,742 @@
|
||||
// SPDX-License-Identifier: Unlicense OR BSD-3-Clause
|
||||
|
||||
package tables
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// Code generated by binarygen from cmap_src.go. DO NOT EDIT
|
||||
|
||||
func (item *CmapSubtable0) mustParse(src []byte) {
|
||||
_ = src[261] // early bound checking
|
||||
item.format = binary.BigEndian.Uint16(src[0:])
|
||||
item.length = binary.BigEndian.Uint16(src[2:])
|
||||
item.language = binary.BigEndian.Uint16(src[4:])
|
||||
item.GlyphIdArray[0] = src[6]
|
||||
item.GlyphIdArray[1] = src[7]
|
||||
item.GlyphIdArray[2] = src[8]
|
||||
item.GlyphIdArray[3] = src[9]
|
||||
item.GlyphIdArray[4] = src[10]
|
||||
item.GlyphIdArray[5] = src[11]
|
||||
item.GlyphIdArray[6] = src[12]
|
||||
item.GlyphIdArray[7] = src[13]
|
||||
item.GlyphIdArray[8] = src[14]
|
||||
item.GlyphIdArray[9] = src[15]
|
||||
item.GlyphIdArray[10] = src[16]
|
||||
item.GlyphIdArray[11] = src[17]
|
||||
item.GlyphIdArray[12] = src[18]
|
||||
item.GlyphIdArray[13] = src[19]
|
||||
item.GlyphIdArray[14] = src[20]
|
||||
item.GlyphIdArray[15] = src[21]
|
||||
item.GlyphIdArray[16] = src[22]
|
||||
item.GlyphIdArray[17] = src[23]
|
||||
item.GlyphIdArray[18] = src[24]
|
||||
item.GlyphIdArray[19] = src[25]
|
||||
item.GlyphIdArray[20] = src[26]
|
||||
item.GlyphIdArray[21] = src[27]
|
||||
item.GlyphIdArray[22] = src[28]
|
||||
item.GlyphIdArray[23] = src[29]
|
||||
item.GlyphIdArray[24] = src[30]
|
||||
item.GlyphIdArray[25] = src[31]
|
||||
item.GlyphIdArray[26] = src[32]
|
||||
item.GlyphIdArray[27] = src[33]
|
||||
item.GlyphIdArray[28] = src[34]
|
||||
item.GlyphIdArray[29] = src[35]
|
||||
item.GlyphIdArray[30] = src[36]
|
||||
item.GlyphIdArray[31] = src[37]
|
||||
item.GlyphIdArray[32] = src[38]
|
||||
item.GlyphIdArray[33] = src[39]
|
||||
item.GlyphIdArray[34] = src[40]
|
||||
item.GlyphIdArray[35] = src[41]
|
||||
item.GlyphIdArray[36] = src[42]
|
||||
item.GlyphIdArray[37] = src[43]
|
||||
item.GlyphIdArray[38] = src[44]
|
||||
item.GlyphIdArray[39] = src[45]
|
||||
item.GlyphIdArray[40] = src[46]
|
||||
item.GlyphIdArray[41] = src[47]
|
||||
item.GlyphIdArray[42] = src[48]
|
||||
item.GlyphIdArray[43] = src[49]
|
||||
item.GlyphIdArray[44] = src[50]
|
||||
item.GlyphIdArray[45] = src[51]
|
||||
item.GlyphIdArray[46] = src[52]
|
||||
item.GlyphIdArray[47] = src[53]
|
||||
item.GlyphIdArray[48] = src[54]
|
||||
item.GlyphIdArray[49] = src[55]
|
||||
item.GlyphIdArray[50] = src[56]
|
||||
item.GlyphIdArray[51] = src[57]
|
||||
item.GlyphIdArray[52] = src[58]
|
||||
item.GlyphIdArray[53] = src[59]
|
||||
item.GlyphIdArray[54] = src[60]
|
||||
item.GlyphIdArray[55] = src[61]
|
||||
item.GlyphIdArray[56] = src[62]
|
||||
item.GlyphIdArray[57] = src[63]
|
||||
item.GlyphIdArray[58] = src[64]
|
||||
item.GlyphIdArray[59] = src[65]
|
||||
item.GlyphIdArray[60] = src[66]
|
||||
item.GlyphIdArray[61] = src[67]
|
||||
item.GlyphIdArray[62] = src[68]
|
||||
item.GlyphIdArray[63] = src[69]
|
||||
item.GlyphIdArray[64] = src[70]
|
||||
item.GlyphIdArray[65] = src[71]
|
||||
item.GlyphIdArray[66] = src[72]
|
||||
item.GlyphIdArray[67] = src[73]
|
||||
item.GlyphIdArray[68] = src[74]
|
||||
item.GlyphIdArray[69] = src[75]
|
||||
item.GlyphIdArray[70] = src[76]
|
||||
item.GlyphIdArray[71] = src[77]
|
||||
item.GlyphIdArray[72] = src[78]
|
||||
item.GlyphIdArray[73] = src[79]
|
||||
item.GlyphIdArray[74] = src[80]
|
||||
item.GlyphIdArray[75] = src[81]
|
||||
item.GlyphIdArray[76] = src[82]
|
||||
item.GlyphIdArray[77] = src[83]
|
||||
item.GlyphIdArray[78] = src[84]
|
||||
item.GlyphIdArray[79] = src[85]
|
||||
item.GlyphIdArray[80] = src[86]
|
||||
item.GlyphIdArray[81] = src[87]
|
||||
item.GlyphIdArray[82] = src[88]
|
||||
item.GlyphIdArray[83] = src[89]
|
||||
item.GlyphIdArray[84] = src[90]
|
||||
item.GlyphIdArray[85] = src[91]
|
||||
item.GlyphIdArray[86] = src[92]
|
||||
item.GlyphIdArray[87] = src[93]
|
||||
item.GlyphIdArray[88] = src[94]
|
||||
item.GlyphIdArray[89] = src[95]
|
||||
item.GlyphIdArray[90] = src[96]
|
||||
item.GlyphIdArray[91] = src[97]
|
||||
item.GlyphIdArray[92] = src[98]
|
||||
item.GlyphIdArray[93] = src[99]
|
||||
item.GlyphIdArray[94] = src[100]
|
||||
item.GlyphIdArray[95] = src[101]
|
||||
item.GlyphIdArray[96] = src[102]
|
||||
item.GlyphIdArray[97] = src[103]
|
||||
item.GlyphIdArray[98] = src[104]
|
||||
item.GlyphIdArray[99] = src[105]
|
||||
item.GlyphIdArray[100] = src[106]
|
||||
item.GlyphIdArray[101] = src[107]
|
||||
item.GlyphIdArray[102] = src[108]
|
||||
item.GlyphIdArray[103] = src[109]
|
||||
item.GlyphIdArray[104] = src[110]
|
||||
item.GlyphIdArray[105] = src[111]
|
||||
item.GlyphIdArray[106] = src[112]
|
||||
item.GlyphIdArray[107] = src[113]
|
||||
item.GlyphIdArray[108] = src[114]
|
||||
item.GlyphIdArray[109] = src[115]
|
||||
item.GlyphIdArray[110] = src[116]
|
||||
item.GlyphIdArray[111] = src[117]
|
||||
item.GlyphIdArray[112] = src[118]
|
||||
item.GlyphIdArray[113] = src[119]
|
||||
item.GlyphIdArray[114] = src[120]
|
||||
item.GlyphIdArray[115] = src[121]
|
||||
item.GlyphIdArray[116] = src[122]
|
||||
item.GlyphIdArray[117] = src[123]
|
||||
item.GlyphIdArray[118] = src[124]
|
||||
item.GlyphIdArray[119] = src[125]
|
||||
item.GlyphIdArray[120] = src[126]
|
||||
item.GlyphIdArray[121] = src[127]
|
||||
item.GlyphIdArray[122] = src[128]
|
||||
item.GlyphIdArray[123] = src[129]
|
||||
item.GlyphIdArray[124] = src[130]
|
||||
item.GlyphIdArray[125] = src[131]
|
||||
item.GlyphIdArray[126] = src[132]
|
||||
item.GlyphIdArray[127] = src[133]
|
||||
item.GlyphIdArray[128] = src[134]
|
||||
item.GlyphIdArray[129] = src[135]
|
||||
item.GlyphIdArray[130] = src[136]
|
||||
item.GlyphIdArray[131] = src[137]
|
||||
item.GlyphIdArray[132] = src[138]
|
||||
item.GlyphIdArray[133] = src[139]
|
||||
item.GlyphIdArray[134] = src[140]
|
||||
item.GlyphIdArray[135] = src[141]
|
||||
item.GlyphIdArray[136] = src[142]
|
||||
item.GlyphIdArray[137] = src[143]
|
||||
item.GlyphIdArray[138] = src[144]
|
||||
item.GlyphIdArray[139] = src[145]
|
||||
item.GlyphIdArray[140] = src[146]
|
||||
item.GlyphIdArray[141] = src[147]
|
||||
item.GlyphIdArray[142] = src[148]
|
||||
item.GlyphIdArray[143] = src[149]
|
||||
item.GlyphIdArray[144] = src[150]
|
||||
item.GlyphIdArray[145] = src[151]
|
||||
item.GlyphIdArray[146] = src[152]
|
||||
item.GlyphIdArray[147] = src[153]
|
||||
item.GlyphIdArray[148] = src[154]
|
||||
item.GlyphIdArray[149] = src[155]
|
||||
item.GlyphIdArray[150] = src[156]
|
||||
item.GlyphIdArray[151] = src[157]
|
||||
item.GlyphIdArray[152] = src[158]
|
||||
item.GlyphIdArray[153] = src[159]
|
||||
item.GlyphIdArray[154] = src[160]
|
||||
item.GlyphIdArray[155] = src[161]
|
||||
item.GlyphIdArray[156] = src[162]
|
||||
item.GlyphIdArray[157] = src[163]
|
||||
item.GlyphIdArray[158] = src[164]
|
||||
item.GlyphIdArray[159] = src[165]
|
||||
item.GlyphIdArray[160] = src[166]
|
||||
item.GlyphIdArray[161] = src[167]
|
||||
item.GlyphIdArray[162] = src[168]
|
||||
item.GlyphIdArray[163] = src[169]
|
||||
item.GlyphIdArray[164] = src[170]
|
||||
item.GlyphIdArray[165] = src[171]
|
||||
item.GlyphIdArray[166] = src[172]
|
||||
item.GlyphIdArray[167] = src[173]
|
||||
item.GlyphIdArray[168] = src[174]
|
||||
item.GlyphIdArray[169] = src[175]
|
||||
item.GlyphIdArray[170] = src[176]
|
||||
item.GlyphIdArray[171] = src[177]
|
||||
item.GlyphIdArray[172] = src[178]
|
||||
item.GlyphIdArray[173] = src[179]
|
||||
item.GlyphIdArray[174] = src[180]
|
||||
item.GlyphIdArray[175] = src[181]
|
||||
item.GlyphIdArray[176] = src[182]
|
||||
item.GlyphIdArray[177] = src[183]
|
||||
item.GlyphIdArray[178] = src[184]
|
||||
item.GlyphIdArray[179] = src[185]
|
||||
item.GlyphIdArray[180] = src[186]
|
||||
item.GlyphIdArray[181] = src[187]
|
||||
item.GlyphIdArray[182] = src[188]
|
||||
item.GlyphIdArray[183] = src[189]
|
||||
item.GlyphIdArray[184] = src[190]
|
||||
item.GlyphIdArray[185] = src[191]
|
||||
item.GlyphIdArray[186] = src[192]
|
||||
item.GlyphIdArray[187] = src[193]
|
||||
item.GlyphIdArray[188] = src[194]
|
||||
item.GlyphIdArray[189] = src[195]
|
||||
item.GlyphIdArray[190] = src[196]
|
||||
item.GlyphIdArray[191] = src[197]
|
||||
item.GlyphIdArray[192] = src[198]
|
||||
item.GlyphIdArray[193] = src[199]
|
||||
item.GlyphIdArray[194] = src[200]
|
||||
item.GlyphIdArray[195] = src[201]
|
||||
item.GlyphIdArray[196] = src[202]
|
||||
item.GlyphIdArray[197] = src[203]
|
||||
item.GlyphIdArray[198] = src[204]
|
||||
item.GlyphIdArray[199] = src[205]
|
||||
item.GlyphIdArray[200] = src[206]
|
||||
item.GlyphIdArray[201] = src[207]
|
||||
item.GlyphIdArray[202] = src[208]
|
||||
item.GlyphIdArray[203] = src[209]
|
||||
item.GlyphIdArray[204] = src[210]
|
||||
item.GlyphIdArray[205] = src[211]
|
||||
item.GlyphIdArray[206] = src[212]
|
||||
item.GlyphIdArray[207] = src[213]
|
||||
item.GlyphIdArray[208] = src[214]
|
||||
item.GlyphIdArray[209] = src[215]
|
||||
item.GlyphIdArray[210] = src[216]
|
||||
item.GlyphIdArray[211] = src[217]
|
||||
item.GlyphIdArray[212] = src[218]
|
||||
item.GlyphIdArray[213] = src[219]
|
||||
item.GlyphIdArray[214] = src[220]
|
||||
item.GlyphIdArray[215] = src[221]
|
||||
item.GlyphIdArray[216] = src[222]
|
||||
item.GlyphIdArray[217] = src[223]
|
||||
item.GlyphIdArray[218] = src[224]
|
||||
item.GlyphIdArray[219] = src[225]
|
||||
item.GlyphIdArray[220] = src[226]
|
||||
item.GlyphIdArray[221] = src[227]
|
||||
item.GlyphIdArray[222] = src[228]
|
||||
item.GlyphIdArray[223] = src[229]
|
||||
item.GlyphIdArray[224] = src[230]
|
||||
item.GlyphIdArray[225] = src[231]
|
||||
item.GlyphIdArray[226] = src[232]
|
||||
item.GlyphIdArray[227] = src[233]
|
||||
item.GlyphIdArray[228] = src[234]
|
||||
item.GlyphIdArray[229] = src[235]
|
||||
item.GlyphIdArray[230] = src[236]
|
||||
item.GlyphIdArray[231] = src[237]
|
||||
item.GlyphIdArray[232] = src[238]
|
||||
item.GlyphIdArray[233] = src[239]
|
||||
item.GlyphIdArray[234] = src[240]
|
||||
item.GlyphIdArray[235] = src[241]
|
||||
item.GlyphIdArray[236] = src[242]
|
||||
item.GlyphIdArray[237] = src[243]
|
||||
item.GlyphIdArray[238] = src[244]
|
||||
item.GlyphIdArray[239] = src[245]
|
||||
item.GlyphIdArray[240] = src[246]
|
||||
item.GlyphIdArray[241] = src[247]
|
||||
item.GlyphIdArray[242] = src[248]
|
||||
item.GlyphIdArray[243] = src[249]
|
||||
item.GlyphIdArray[244] = src[250]
|
||||
item.GlyphIdArray[245] = src[251]
|
||||
item.GlyphIdArray[246] = src[252]
|
||||
item.GlyphIdArray[247] = src[253]
|
||||
item.GlyphIdArray[248] = src[254]
|
||||
item.GlyphIdArray[249] = src[255]
|
||||
item.GlyphIdArray[250] = src[256]
|
||||
item.GlyphIdArray[251] = src[257]
|
||||
item.GlyphIdArray[252] = src[258]
|
||||
item.GlyphIdArray[253] = src[259]
|
||||
item.GlyphIdArray[254] = src[260]
|
||||
item.GlyphIdArray[255] = src[261]
|
||||
}
|
||||
|
||||
func ParseCmap(src []byte) (Cmap, int, error) {
|
||||
var item Cmap
|
||||
n := 0
|
||||
if L := len(src); L < 4 {
|
||||
return item, 0, fmt.Errorf("reading Cmap: "+"EOF: expected length: 4, got %d", L)
|
||||
}
|
||||
_ = src[3] // early bound checking
|
||||
item.version = binary.BigEndian.Uint16(src[0:])
|
||||
item.numTables = binary.BigEndian.Uint16(src[2:])
|
||||
n += 4
|
||||
|
||||
{
|
||||
arrayLength := int(item.numTables)
|
||||
|
||||
offset := 4
|
||||
for i := 0; i < arrayLength; i++ {
|
||||
elem, read, err := ParseEncodingRecord(src[offset:], src)
|
||||
if err != nil {
|
||||
return item, 0, fmt.Errorf("reading Cmap: %s", err)
|
||||
}
|
||||
item.Records = append(item.Records, elem)
|
||||
offset += read
|
||||
}
|
||||
n = offset
|
||||
}
|
||||
return item, n, nil
|
||||
}
|
||||
|
||||
func ParseCmapSubtable(src []byte) (CmapSubtable, int, error) {
|
||||
var item CmapSubtable
|
||||
|
||||
if L := len(src); L < 2 {
|
||||
return item, 0, fmt.Errorf("reading CmapSubtable: "+"EOF: expected length: 2, got %d", L)
|
||||
}
|
||||
format := uint16(binary.BigEndian.Uint16(src[0:]))
|
||||
var (
|
||||
read int
|
||||
err error
|
||||
)
|
||||
switch format {
|
||||
case 0:
|
||||
item, read, err = ParseCmapSubtable0(src[0:])
|
||||
case 10:
|
||||
item, read, err = ParseCmapSubtable10(src[0:])
|
||||
case 12:
|
||||
item, read, err = ParseCmapSubtable12(src[0:])
|
||||
case 13:
|
||||
item, read, err = ParseCmapSubtable13(src[0:])
|
||||
case 14:
|
||||
item, read, err = ParseCmapSubtable14(src[0:])
|
||||
case 2:
|
||||
item, read, err = ParseCmapSubtable2(src[0:])
|
||||
case 4:
|
||||
item, read, err = ParseCmapSubtable4(src[0:])
|
||||
case 6:
|
||||
item, read, err = ParseCmapSubtable6(src[0:])
|
||||
default:
|
||||
err = fmt.Errorf("unsupported CmapSubtable format %d", format)
|
||||
}
|
||||
if err != nil {
|
||||
return item, 0, fmt.Errorf("reading CmapSubtable: %s", err)
|
||||
}
|
||||
|
||||
return item, read, nil
|
||||
}
|
||||
|
||||
func ParseCmapSubtable0(src []byte) (CmapSubtable0, int, error) {
|
||||
var item CmapSubtable0
|
||||
n := 0
|
||||
if L := len(src); L < 262 {
|
||||
return item, 0, fmt.Errorf("reading CmapSubtable0: "+"EOF: expected length: 262, got %d", L)
|
||||
}
|
||||
item.mustParse(src)
|
||||
n += 262
|
||||
return item, n, nil
|
||||
}
|
||||
|
||||
func ParseCmapSubtable10(src []byte) (CmapSubtable10, int, error) {
|
||||
var item CmapSubtable10
|
||||
n := 0
|
||||
if L := len(src); L < 20 {
|
||||
return item, 0, fmt.Errorf("reading CmapSubtable10: "+"EOF: expected length: 20, got %d", L)
|
||||
}
|
||||
_ = src[19] // early bound checking
|
||||
item.format = binary.BigEndian.Uint16(src[0:])
|
||||
item.reserved = binary.BigEndian.Uint16(src[2:])
|
||||
item.length = binary.BigEndian.Uint32(src[4:])
|
||||
item.language = binary.BigEndian.Uint32(src[8:])
|
||||
item.StartCharCode = binary.BigEndian.Uint32(src[12:])
|
||||
arrayLengthGlyphIdArray := int(binary.BigEndian.Uint32(src[16:]))
|
||||
n += 20
|
||||
|
||||
{
|
||||
|
||||
if L := len(src); L < 20+arrayLengthGlyphIdArray*2 {
|
||||
return item, 0, fmt.Errorf("reading CmapSubtable10: "+"EOF: expected length: %d, got %d", 20+arrayLengthGlyphIdArray*2, L)
|
||||
}
|
||||
|
||||
item.GlyphIdArray = make([]uint16, arrayLengthGlyphIdArray) // allocation guarded by the previous check
|
||||
for i := range item.GlyphIdArray {
|
||||
item.GlyphIdArray[i] = binary.BigEndian.Uint16(src[20+i*2:])
|
||||
}
|
||||
n += arrayLengthGlyphIdArray * 2
|
||||
}
|
||||
return item, n, nil
|
||||
}
|
||||
|
||||
func ParseCmapSubtable12(src []byte) (CmapSubtable12, int, error) {
|
||||
var item CmapSubtable12
|
||||
n := 0
|
||||
if L := len(src); L < 16 {
|
||||
return item, 0, fmt.Errorf("reading CmapSubtable12: "+"EOF: expected length: 16, got %d", L)
|
||||
}
|
||||
_ = src[15] // early bound checking
|
||||
item.format = binary.BigEndian.Uint16(src[0:])
|
||||
item.reserved = binary.BigEndian.Uint16(src[2:])
|
||||
item.length = binary.BigEndian.Uint32(src[4:])
|
||||
item.language = binary.BigEndian.Uint32(src[8:])
|
||||
arrayLengthGroups := int(binary.BigEndian.Uint32(src[12:]))
|
||||
n += 16
|
||||
|
||||
{
|
||||
|
||||
if L := len(src); L < 16+arrayLengthGroups*12 {
|
||||
return item, 0, fmt.Errorf("reading CmapSubtable12: "+"EOF: expected length: %d, got %d", 16+arrayLengthGroups*12, L)
|
||||
}
|
||||
|
||||
item.Groups = make([]SequentialMapGroup, arrayLengthGroups) // allocation guarded by the previous check
|
||||
for i := range item.Groups {
|
||||
item.Groups[i].mustParse(src[16+i*12:])
|
||||
}
|
||||
n += arrayLengthGroups * 12
|
||||
}
|
||||
return item, n, nil
|
||||
}
|
||||
|
||||
func ParseCmapSubtable13(src []byte) (CmapSubtable13, int, error) {
|
||||
var item CmapSubtable13
|
||||
n := 0
|
||||
if L := len(src); L < 16 {
|
||||
return item, 0, fmt.Errorf("reading CmapSubtable13: "+"EOF: expected length: 16, got %d", L)
|
||||
}
|
||||
_ = src[15] // early bound checking
|
||||
item.format = binary.BigEndian.Uint16(src[0:])
|
||||
item.reserved = binary.BigEndian.Uint16(src[2:])
|
||||
item.length = binary.BigEndian.Uint32(src[4:])
|
||||
item.language = binary.BigEndian.Uint32(src[8:])
|
||||
arrayLengthGroups := int(binary.BigEndian.Uint32(src[12:]))
|
||||
n += 16
|
||||
|
||||
{
|
||||
|
||||
if L := len(src); L < 16+arrayLengthGroups*12 {
|
||||
return item, 0, fmt.Errorf("reading CmapSubtable13: "+"EOF: expected length: %d, got %d", 16+arrayLengthGroups*12, L)
|
||||
}
|
||||
|
||||
item.Groups = make([]SequentialMapGroup, arrayLengthGroups) // allocation guarded by the previous check
|
||||
for i := range item.Groups {
|
||||
item.Groups[i].mustParse(src[16+i*12:])
|
||||
}
|
||||
n += arrayLengthGroups * 12
|
||||
}
|
||||
return item, n, nil
|
||||
}
|
||||
|
||||
func ParseCmapSubtable14(src []byte) (CmapSubtable14, int, error) {
|
||||
var item CmapSubtable14
|
||||
n := 0
|
||||
if L := len(src); L < 10 {
|
||||
return item, 0, fmt.Errorf("reading CmapSubtable14: "+"EOF: expected length: 10, got %d", L)
|
||||
}
|
||||
_ = src[9] // early bound checking
|
||||
item.format = binary.BigEndian.Uint16(src[0:])
|
||||
item.length = binary.BigEndian.Uint32(src[2:])
|
||||
arrayLengthVarSelectors := int(binary.BigEndian.Uint32(src[6:]))
|
||||
n += 10
|
||||
|
||||
{
|
||||
|
||||
offset := 10
|
||||
for i := 0; i < arrayLengthVarSelectors; i++ {
|
||||
elem, read, err := ParseVariationSelector(src[offset:], src)
|
||||
if err != nil {
|
||||
return item, 0, fmt.Errorf("reading CmapSubtable14: %s", err)
|
||||
}
|
||||
item.VarSelectors = append(item.VarSelectors, elem)
|
||||
offset += read
|
||||
}
|
||||
n = offset
|
||||
}
|
||||
return item, n, nil
|
||||
}
|
||||
|
||||
func ParseCmapSubtable2(src []byte) (CmapSubtable2, int, error) {
|
||||
var item CmapSubtable2
|
||||
n := 0
|
||||
if L := len(src); L < 2 {
|
||||
return item, 0, fmt.Errorf("reading CmapSubtable2: "+"EOF: expected length: 2, got %d", L)
|
||||
}
|
||||
item.format = binary.BigEndian.Uint16(src[0:])
|
||||
n += 2
|
||||
|
||||
{
|
||||
|
||||
item.rawData = src[2:]
|
||||
n = len(src)
|
||||
}
|
||||
return item, n, nil
|
||||
}
|
||||
|
||||
func ParseCmapSubtable4(src []byte) (CmapSubtable4, int, error) {
|
||||
var item CmapSubtable4
|
||||
n := 0
|
||||
if L := len(src); L < 14 {
|
||||
return item, 0, fmt.Errorf("reading CmapSubtable4: "+"EOF: expected length: 14, got %d", L)
|
||||
}
|
||||
_ = src[13] // early bound checking
|
||||
item.format = binary.BigEndian.Uint16(src[0:])
|
||||
item.length = binary.BigEndian.Uint16(src[2:])
|
||||
item.language = binary.BigEndian.Uint16(src[4:])
|
||||
item.segCountX2 = binary.BigEndian.Uint16(src[6:])
|
||||
item.searchRange = binary.BigEndian.Uint16(src[8:])
|
||||
item.entrySelector = binary.BigEndian.Uint16(src[10:])
|
||||
item.rangeShift = binary.BigEndian.Uint16(src[12:])
|
||||
n += 14
|
||||
|
||||
{
|
||||
arrayLength := int(item.segCountX2 / 2)
|
||||
|
||||
if L := len(src); L < 14+arrayLength*2 {
|
||||
return item, 0, fmt.Errorf("reading CmapSubtable4: "+"EOF: expected length: %d, got %d", 14+arrayLength*2, L)
|
||||
}
|
||||
|
||||
item.EndCode = make([]uint16, arrayLength) // allocation guarded by the previous check
|
||||
for i := range item.EndCode {
|
||||
item.EndCode[i] = binary.BigEndian.Uint16(src[14+i*2:])
|
||||
}
|
||||
n += arrayLength * 2
|
||||
}
|
||||
if L := len(src); L < n+2 {
|
||||
return item, 0, fmt.Errorf("reading CmapSubtable4: "+"EOF: expected length: n + 2, got %d", L)
|
||||
}
|
||||
item.reservedPad = binary.BigEndian.Uint16(src[n:])
|
||||
n += 2
|
||||
|
||||
{
|
||||
arrayLength := int(item.segCountX2 / 2)
|
||||
|
||||
if L := len(src); L < n+arrayLength*2 {
|
||||
return item, 0, fmt.Errorf("reading CmapSubtable4: "+"EOF: expected length: %d, got %d", n+arrayLength*2, L)
|
||||
}
|
||||
|
||||
item.StartCode = make([]uint16, arrayLength) // allocation guarded by the previous check
|
||||
for i := range item.StartCode {
|
||||
item.StartCode[i] = binary.BigEndian.Uint16(src[n+i*2:])
|
||||
}
|
||||
n += arrayLength * 2
|
||||
}
|
||||
{
|
||||
arrayLength := int(item.segCountX2 / 2)
|
||||
|
||||
if L := len(src); L < n+arrayLength*2 {
|
||||
return item, 0, fmt.Errorf("reading CmapSubtable4: "+"EOF: expected length: %d, got %d", n+arrayLength*2, L)
|
||||
}
|
||||
|
||||
item.IdDelta = make([]uint16, arrayLength) // allocation guarded by the previous check
|
||||
for i := range item.IdDelta {
|
||||
item.IdDelta[i] = binary.BigEndian.Uint16(src[n+i*2:])
|
||||
}
|
||||
n += arrayLength * 2
|
||||
}
|
||||
{
|
||||
arrayLength := int(item.segCountX2 / 2)
|
||||
|
||||
if L := len(src); L < n+arrayLength*2 {
|
||||
return item, 0, fmt.Errorf("reading CmapSubtable4: "+"EOF: expected length: %d, got %d", n+arrayLength*2, L)
|
||||
}
|
||||
|
||||
item.IdRangeOffsets = make([]uint16, arrayLength) // allocation guarded by the previous check
|
||||
for i := range item.IdRangeOffsets {
|
||||
item.IdRangeOffsets[i] = binary.BigEndian.Uint16(src[n+i*2:])
|
||||
}
|
||||
n += arrayLength * 2
|
||||
}
|
||||
{
|
||||
|
||||
item.GlyphIDArray = src[n:]
|
||||
n = len(src)
|
||||
}
|
||||
return item, n, nil
|
||||
}
|
||||
|
||||
func ParseCmapSubtable6(src []byte) (CmapSubtable6, int, error) {
|
||||
var item CmapSubtable6
|
||||
n := 0
|
||||
if L := len(src); L < 10 {
|
||||
return item, 0, fmt.Errorf("reading CmapSubtable6: "+"EOF: expected length: 10, got %d", L)
|
||||
}
|
||||
_ = src[9] // early bound checking
|
||||
item.format = binary.BigEndian.Uint16(src[0:])
|
||||
item.length = binary.BigEndian.Uint16(src[2:])
|
||||
item.language = binary.BigEndian.Uint16(src[4:])
|
||||
item.FirstCode = binary.BigEndian.Uint16(src[6:])
|
||||
arrayLengthGlyphIdArray := int(binary.BigEndian.Uint16(src[8:]))
|
||||
n += 10
|
||||
|
||||
{
|
||||
|
||||
if L := len(src); L < 10+arrayLengthGlyphIdArray*2 {
|
||||
return item, 0, fmt.Errorf("reading CmapSubtable6: "+"EOF: expected length: %d, got %d", 10+arrayLengthGlyphIdArray*2, L)
|
||||
}
|
||||
|
||||
item.GlyphIdArray = make([]uint16, arrayLengthGlyphIdArray) // allocation guarded by the previous check
|
||||
for i := range item.GlyphIdArray {
|
||||
item.GlyphIdArray[i] = binary.BigEndian.Uint16(src[10+i*2:])
|
||||
}
|
||||
n += arrayLengthGlyphIdArray * 2
|
||||
}
|
||||
return item, n, nil
|
||||
}
|
||||
|
||||
func ParseDefaultUVSTable(src []byte) (DefaultUVSTable, int, error) {
|
||||
var item DefaultUVSTable
|
||||
n := 0
|
||||
if L := len(src); L < 4 {
|
||||
return item, 0, fmt.Errorf("reading DefaultUVSTable: "+"EOF: expected length: 4, got %d", L)
|
||||
}
|
||||
arrayLengthRanges := int(binary.BigEndian.Uint32(src[0:]))
|
||||
n += 4
|
||||
|
||||
{
|
||||
|
||||
if L := len(src); L < 4+arrayLengthRanges*4 {
|
||||
return item, 0, fmt.Errorf("reading DefaultUVSTable: "+"EOF: expected length: %d, got %d", 4+arrayLengthRanges*4, L)
|
||||
}
|
||||
|
||||
item.Ranges = make([]UnicodeRange, arrayLengthRanges) // allocation guarded by the previous check
|
||||
for i := range item.Ranges {
|
||||
item.Ranges[i].mustParse(src[4+i*4:])
|
||||
}
|
||||
n += arrayLengthRanges * 4
|
||||
}
|
||||
return item, n, nil
|
||||
}
|
||||
|
||||
func ParseEncodingRecord(src []byte, parentSrc []byte) (EncodingRecord, int, error) {
|
||||
var item EncodingRecord
|
||||
n := 0
|
||||
if L := len(src); L < 8 {
|
||||
return item, 0, fmt.Errorf("reading EncodingRecord: "+"EOF: expected length: 8, got %d", L)
|
||||
}
|
||||
_ = src[7] // early bound checking
|
||||
item.PlatformID = PlatformID(binary.BigEndian.Uint16(src[0:]))
|
||||
item.EncodingID = EncodingID(binary.BigEndian.Uint16(src[2:]))
|
||||
offsetSubtable := int(binary.BigEndian.Uint32(src[4:]))
|
||||
n += 8
|
||||
|
||||
{
|
||||
if offsetSubtable != 0 { // ignore null offset
|
||||
if L := len(parentSrc); L < offsetSubtable {
|
||||
return item, 0, fmt.Errorf("reading EncodingRecord: "+"EOF: expected length: %d, got %d", offsetSubtable, L)
|
||||
}
|
||||
|
||||
var (
|
||||
err error
|
||||
read int
|
||||
)
|
||||
item.Subtable, read, err = ParseCmapSubtable(parentSrc[offsetSubtable:])
|
||||
if err != nil {
|
||||
return item, 0, fmt.Errorf("reading EncodingRecord: %s", err)
|
||||
}
|
||||
offsetSubtable += read
|
||||
}
|
||||
}
|
||||
return item, n, nil
|
||||
}
|
||||
|
||||
func ParseUVSMappingTable(src []byte) (UVSMappingTable, int, error) {
|
||||
var item UVSMappingTable
|
||||
n := 0
|
||||
if L := len(src); L < 4 {
|
||||
return item, 0, fmt.Errorf("reading UVSMappingTable: "+"EOF: expected length: 4, got %d", L)
|
||||
}
|
||||
arrayLengthRanges := int(binary.BigEndian.Uint32(src[0:]))
|
||||
n += 4
|
||||
|
||||
{
|
||||
|
||||
if L := len(src); L < 4+arrayLengthRanges*5 {
|
||||
return item, 0, fmt.Errorf("reading UVSMappingTable: "+"EOF: expected length: %d, got %d", 4+arrayLengthRanges*5, L)
|
||||
}
|
||||
|
||||
item.Ranges = make([]UvsMappingRecord, arrayLengthRanges) // allocation guarded by the previous check
|
||||
for i := range item.Ranges {
|
||||
item.Ranges[i].mustParse(src[4+i*5:])
|
||||
}
|
||||
n += arrayLengthRanges * 5
|
||||
}
|
||||
return item, n, nil
|
||||
}
|
||||
|
||||
func ParseVariationSelector(src []byte, parentSrc []byte) (VariationSelector, int, error) {
|
||||
var item VariationSelector
|
||||
n := 0
|
||||
if L := len(src); L < 11 {
|
||||
return item, 0, fmt.Errorf("reading VariationSelector: "+"EOF: expected length: 11, got %d", L)
|
||||
}
|
||||
_ = src[10] // early bound checking
|
||||
item.VarSelector[0] = src[0]
|
||||
item.VarSelector[1] = src[1]
|
||||
item.VarSelector[2] = src[2]
|
||||
offsetDefaultUVS := int(binary.BigEndian.Uint32(src[3:]))
|
||||
offsetNonDefaultUVS := int(binary.BigEndian.Uint32(src[7:]))
|
||||
n += 11
|
||||
|
||||
{
|
||||
if offsetDefaultUVS != 0 { // ignore null offset
|
||||
if L := len(parentSrc); L < offsetDefaultUVS {
|
||||
return item, 0, fmt.Errorf("reading VariationSelector: "+"EOF: expected length: %d, got %d", offsetDefaultUVS, L)
|
||||
}
|
||||
|
||||
var err error
|
||||
item.DefaultUVS, _, err = ParseDefaultUVSTable(parentSrc[offsetDefaultUVS:])
|
||||
if err != nil {
|
||||
return item, 0, fmt.Errorf("reading VariationSelector: %s", err)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
{
|
||||
if offsetNonDefaultUVS != 0 { // ignore null offset
|
||||
if L := len(parentSrc); L < offsetNonDefaultUVS {
|
||||
return item, 0, fmt.Errorf("reading VariationSelector: "+"EOF: expected length: %d, got %d", offsetNonDefaultUVS, L)
|
||||
}
|
||||
|
||||
var err error
|
||||
item.NonDefaultUVS, _, err = ParseUVSMappingTable(parentSrc[offsetNonDefaultUVS:])
|
||||
if err != nil {
|
||||
return item, 0, fmt.Errorf("reading VariationSelector: %s", err)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
return item, n, nil
|
||||
}
|
||||
|
||||
func (item *SequentialMapGroup) mustParse(src []byte) {
|
||||
_ = src[11] // early bound checking
|
||||
item.StartCharCode = binary.BigEndian.Uint32(src[0:])
|
||||
item.EndCharCode = binary.BigEndian.Uint32(src[4:])
|
||||
item.StartGlyphID = binary.BigEndian.Uint32(src[8:])
|
||||
}
|
||||
|
||||
func (item *UnicodeRange) mustParse(src []byte) {
|
||||
_ = src[3] // early bound checking
|
||||
item.StartUnicodeValue[0] = src[0]
|
||||
item.StartUnicodeValue[1] = src[1]
|
||||
item.StartUnicodeValue[2] = src[2]
|
||||
item.AdditionalCount = src[3]
|
||||
}
|
||||
|
||||
func (item *UvsMappingRecord) mustParse(src []byte) {
|
||||
_ = src[4] // early bound checking
|
||||
item.UnicodeValue[0] = src[0]
|
||||
item.UnicodeValue[1] = src[1]
|
||||
item.UnicodeValue[2] = src[2]
|
||||
item.GlyphID = binary.BigEndian.Uint16(src[3:])
|
||||
}
|
||||
+132
@@ -0,0 +1,132 @@
|
||||
// SPDX-License-Identifier: Unlicense OR BSD-3-Clause
|
||||
|
||||
package tables
|
||||
|
||||
// Cmap is the Character to Glyph Index Mapping table
|
||||
// See https://learn.microsoft.com/en-us/typography/opentype/spec/cmap
|
||||
type Cmap struct {
|
||||
version uint16 // Table version number (0).
|
||||
numTables uint16 // Number of encoding tables that follow.
|
||||
Records []EncodingRecord `arrayCount:"ComputedField-numTables"`
|
||||
}
|
||||
|
||||
type EncodingRecord struct {
|
||||
PlatformID PlatformID // Platform ID.
|
||||
EncodingID EncodingID // Platform-specific encoding ID.
|
||||
Subtable CmapSubtable `offsetSize:"Offset32" offsetRelativeTo:"Parent"` // Byte offset from beginning of table to the subtable for this encoding.
|
||||
}
|
||||
|
||||
// CmapSubtable is the union type for the various cmap formats
|
||||
type CmapSubtable interface {
|
||||
isCmapSubtable()
|
||||
}
|
||||
|
||||
func (CmapSubtable0) isCmapSubtable() {}
|
||||
func (CmapSubtable2) isCmapSubtable() {}
|
||||
func (CmapSubtable4) isCmapSubtable() {}
|
||||
func (CmapSubtable6) isCmapSubtable() {}
|
||||
func (CmapSubtable10) isCmapSubtable() {}
|
||||
func (CmapSubtable12) isCmapSubtable() {}
|
||||
func (CmapSubtable13) isCmapSubtable() {}
|
||||
func (CmapSubtable14) isCmapSubtable() {}
|
||||
|
||||
type CmapSubtable0 struct {
|
||||
format uint16 `unionTag:"0"` // Format number is set to 0.
|
||||
length uint16 // This is the length in bytes of the subtable.
|
||||
language uint16
|
||||
GlyphIdArray [256]uint8 // An array that maps character codes to glyph index values.
|
||||
}
|
||||
|
||||
type CmapSubtable2 struct {
|
||||
format uint16 `unionTag:"2"` // Format number is set to 2.
|
||||
rawData []byte `arrayCount:"ToEnd"`
|
||||
}
|
||||
|
||||
type CmapSubtable4 struct {
|
||||
format uint16 `unionTag:"4"` // Format number is set to 4.
|
||||
length uint16 // This is the length in bytes of the subtable.
|
||||
language uint16
|
||||
segCountX2 uint16 // 2 × segCount.
|
||||
searchRange uint16 // Maximum power of 2 less than or equal to segCount, times 2 ((2**floor(log2(segCount))) * 2, where “**” is an exponentiation operator)
|
||||
entrySelector uint16 // Log2 of the maximum power of 2 less than or equal to numTables (log2(searchRange/2), which is equal to floor(log2(segCount)))
|
||||
rangeShift uint16 // segCount times 2, minus searchRange ((segCount * 2) - searchRange)
|
||||
EndCode []uint16 `arrayCount:"ComputedField-segCountX2 / 2"` // [segCount]uint16 End characterCode for each segment, last=0xFFFF.
|
||||
reservedPad uint16 // Set to 0.
|
||||
StartCode []uint16 `arrayCount:"ComputedField-segCountX2 / 2"` // [segCount]uint16 Start character code for each segment.
|
||||
IdDelta []uint16 `arrayCount:"ComputedField-segCountX2 / 2"` // [segCount]int16 Delta for all character codes in segment.
|
||||
IdRangeOffsets []uint16 `arrayCount:"ComputedField-segCountX2 / 2"` // [segCount]uint16 Offsets into glyphIdArray or 0
|
||||
GlyphIDArray []byte `arrayCount:"ToEnd"` // glyphIdArray : uint16[] glyph index array (arbitrary length)
|
||||
}
|
||||
|
||||
type CmapSubtable6 struct {
|
||||
format uint16 `unionTag:"6"` // Format number is set to 6.
|
||||
length uint16 // This is the length in bytes of the subtable.
|
||||
language uint16
|
||||
FirstCode uint16 // First character code of subrange.
|
||||
GlyphIdArray []GlyphID `arrayCount:"FirstUint16"` // Array of glyph index values for character codes in the range.
|
||||
}
|
||||
|
||||
type CmapSubtable10 struct {
|
||||
format uint16 `unionTag:"10"` // Subtable format; set to 10.
|
||||
reserved uint16 // Reserved; set to 0
|
||||
length uint32 // Byte length of this subtable (including the header)
|
||||
language uint32
|
||||
StartCharCode uint32 // First character code covered
|
||||
GlyphIdArray []GlyphID `arrayCount:"FirstUint32"` // Array of glyph indices for the character codes covered
|
||||
}
|
||||
|
||||
type CmapSubtable12 struct {
|
||||
format uint16 `unionTag:"12"` // Subtable format; set to 12.
|
||||
reserved uint16 // Reserved; set to 0
|
||||
length uint32 // Byte length of this subtable (including the header)
|
||||
language uint32 // For requirements on use of the language field, see “Use of the language field in 'cmap' subtables” in this document.
|
||||
Groups []SequentialMapGroup `arrayCount:"FirstUint32"` // Array of SequentialMapGroup records.
|
||||
}
|
||||
|
||||
type SequentialMapGroup struct {
|
||||
StartCharCode uint32 // First character code in this group
|
||||
EndCharCode uint32 // Last character code in this group
|
||||
StartGlyphID uint32 // Glyph index corresponding to the starting character code
|
||||
}
|
||||
|
||||
type CmapSubtable13 struct {
|
||||
format uint16 `unionTag:"13"` // Subtable format; set to 13.
|
||||
reserved uint16 // Reserved; set to 0
|
||||
length uint32 // Byte length of this subtable (including the header)
|
||||
language uint32 // For requirements on use of the language field, see “Use of the language field in 'cmap' subtables” in this document.
|
||||
Groups []SequentialMapGroup `arrayCount:"FirstUint32"` // Array of SequentialMapGroup records.
|
||||
}
|
||||
|
||||
type CmapSubtable14 struct {
|
||||
format uint16 `unionTag:"14"` // Subtable format. Set to 14.
|
||||
length uint32 // Byte length of this subtable (including this header)
|
||||
VarSelectors []VariationSelector `arrayCount:"FirstUint32"` // [numVarSelectorRecords] Array of VariationSelector records.
|
||||
}
|
||||
|
||||
type VariationSelector struct {
|
||||
VarSelector [3]byte // uint24 Variation selector
|
||||
DefaultUVS DefaultUVSTable `offsetSize:"Offset32" offsetRelativeTo:"Parent"` // Offset from the start of the format 14 subtable to Default UVS Table. May be 0.
|
||||
NonDefaultUVS UVSMappingTable `offsetSize:"Offset32" offsetRelativeTo:"Parent"` // Offset from the start of the format 14 subtable to Non-Default UVS Table. May be 0.
|
||||
}
|
||||
|
||||
// DefaultUVSTable is used in Cmap format 14
|
||||
// See https://learn.microsoft.com/en-us/typography/opentype/spec/cmap#default-uvs-table
|
||||
type DefaultUVSTable struct {
|
||||
Ranges []UnicodeRange `arrayCount:"FirstUint32"`
|
||||
}
|
||||
|
||||
type UnicodeRange struct {
|
||||
StartUnicodeValue [3]byte // uint24 First value in this range
|
||||
AdditionalCount uint8 // Number of additional values in this range
|
||||
}
|
||||
|
||||
// UVSMappingTable is used in Cmap format 14
|
||||
// See https://learn.microsoft.com/en-us/typography/opentype/spec/cmap#non-default-uvs-table
|
||||
type UVSMappingTable struct {
|
||||
Ranges []UvsMappingRecord `arrayCount:"FirstUint32"`
|
||||
}
|
||||
|
||||
type UvsMappingRecord struct {
|
||||
UnicodeValue [3]byte // uint24 Base Unicode value of the UVS
|
||||
GlyphID GlyphID // Glyph ID of the UVS
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
// SPDX-License-Identifier: Unlicense OR BSD-3-Clause
|
||||
|
||||
package tables
|
||||
|
||||
type BitmapSubtable struct {
|
||||
FirstGlyph GlyphID // First glyph ID of this range.
|
||||
LastGlyph GlyphID // Last glyph ID of this range (inclusive).
|
||||
IndexSubHeader
|
||||
}
|
||||
|
||||
// EBLC is the Embedded Bitmap Location Table
|
||||
// See - https://learn.microsoft.com/fr-fr/typography/opentype/spec/eblc
|
||||
type EBLC = CBLC
|
||||
|
||||
// Bloc is the bitmap location table
|
||||
// See - https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6bloc.html
|
||||
type Bloc = CBLC
|
||||
|
||||
// PaintColrLayersResolved is a simili PaintTable, build
|
||||
// from COLR version 0 table.
|
||||
type PaintColrLayersResolved []Layer
|
||||
|
||||
func (PaintColrLayersResolved) isPaintTable() {}
|
||||
Generated
Vendored
+363
@@ -0,0 +1,363 @@
|
||||
// SPDX-License-Identifier: Unlicense OR BSD-3-Clause
|
||||
|
||||
package tables
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// Code generated by binarygen from glyphs_bitmap_src.go. DO NOT EDIT
|
||||
|
||||
func (item *BigGlyphMetrics) mustParse(src []byte) {
|
||||
_ = src[7] // early bound checking
|
||||
item.SmallGlyphMetrics.mustParse(src[0:])
|
||||
item.vertBearingX = int8(src[5])
|
||||
item.vertBearingY = int8(src[6])
|
||||
item.vertAdvance = src[7]
|
||||
}
|
||||
|
||||
func (item *BitmapSize) mustParse(src []byte) {
|
||||
_ = src[47] // early bound checking
|
||||
item.indexSubTableArrayOffset = Offset32(binary.BigEndian.Uint32(src[0:]))
|
||||
item.indexTablesSize = binary.BigEndian.Uint32(src[4:])
|
||||
item.numberOfIndexSubTables = binary.BigEndian.Uint32(src[8:])
|
||||
item.colorRef = binary.BigEndian.Uint32(src[12:])
|
||||
item.Hori.mustParse(src[16:])
|
||||
item.Vert.mustParse(src[28:])
|
||||
item.startGlyphIndex = binary.BigEndian.Uint16(src[40:])
|
||||
item.endGlyphIndex = binary.BigEndian.Uint16(src[42:])
|
||||
item.PpemX = src[44]
|
||||
item.PpemY = src[45]
|
||||
item.bitDepth = src[46]
|
||||
item.flags = int8(src[47])
|
||||
}
|
||||
|
||||
func (item *GlyphIdOffsetPair) mustParse(src []byte) {
|
||||
_ = src[3] // early bound checking
|
||||
item.GlyphID = binary.BigEndian.Uint16(src[0:])
|
||||
item.SbitOffset = Offset16(binary.BigEndian.Uint16(src[2:]))
|
||||
}
|
||||
|
||||
func (item *IndexData2) mustParse(src []byte) {
|
||||
_ = src[11] // early bound checking
|
||||
item.ImageSize = binary.BigEndian.Uint32(src[0:])
|
||||
item.BigMetrics.mustParse(src[4:])
|
||||
}
|
||||
|
||||
func (item *IndexSubTableHeader) mustParse(src []byte) {
|
||||
_ = src[7] // early bound checking
|
||||
item.FirstGlyph = binary.BigEndian.Uint16(src[0:])
|
||||
item.LastGlyph = binary.BigEndian.Uint16(src[2:])
|
||||
item.additionalOffsetToIndexSubtable = Offset32(binary.BigEndian.Uint32(src[4:]))
|
||||
}
|
||||
|
||||
func ParseBitmapData17(src []byte) (BitmapData17, int, error) {
|
||||
var item BitmapData17
|
||||
n := 0
|
||||
if L := len(src); L < 9 {
|
||||
return item, 0, fmt.Errorf("reading BitmapData17: "+"EOF: expected length: 9, got %d", L)
|
||||
}
|
||||
_ = src[8] // early bound checking
|
||||
item.SmallGlyphMetrics.mustParse(src[0:])
|
||||
arrayLengthImage := int(binary.BigEndian.Uint32(src[5:]))
|
||||
n += 9
|
||||
|
||||
{
|
||||
|
||||
L := int(9 + arrayLengthImage)
|
||||
if len(src) < L {
|
||||
return item, 0, fmt.Errorf("reading BitmapData17: "+"EOF: expected length: %d, got %d", L, len(src))
|
||||
}
|
||||
item.Image = src[9:L]
|
||||
n = L
|
||||
}
|
||||
return item, n, nil
|
||||
}
|
||||
|
||||
func ParseBitmapData18(src []byte) (BitmapData18, int, error) {
|
||||
var item BitmapData18
|
||||
n := 0
|
||||
if L := len(src); L < 12 {
|
||||
return item, 0, fmt.Errorf("reading BitmapData18: "+"EOF: expected length: 12, got %d", L)
|
||||
}
|
||||
_ = src[11] // early bound checking
|
||||
item.BigGlyphMetrics.mustParse(src[0:])
|
||||
arrayLengthImage := int(binary.BigEndian.Uint32(src[8:]))
|
||||
n += 12
|
||||
|
||||
{
|
||||
|
||||
L := int(12 + arrayLengthImage)
|
||||
if len(src) < L {
|
||||
return item, 0, fmt.Errorf("reading BitmapData18: "+"EOF: expected length: %d, got %d", L, len(src))
|
||||
}
|
||||
item.Image = src[12:L]
|
||||
n = L
|
||||
}
|
||||
return item, n, nil
|
||||
}
|
||||
|
||||
func ParseBitmapData19(src []byte) (BitmapData19, int, error) {
|
||||
var item BitmapData19
|
||||
n := 0
|
||||
if L := len(src); L < 4 {
|
||||
return item, 0, fmt.Errorf("reading BitmapData19: "+"EOF: expected length: 4, got %d", L)
|
||||
}
|
||||
arrayLengthImage := int(binary.BigEndian.Uint32(src[0:]))
|
||||
n += 4
|
||||
|
||||
{
|
||||
|
||||
L := int(4 + arrayLengthImage)
|
||||
if len(src) < L {
|
||||
return item, 0, fmt.Errorf("reading BitmapData19: "+"EOF: expected length: %d, got %d", L, len(src))
|
||||
}
|
||||
item.Image = src[4:L]
|
||||
n = L
|
||||
}
|
||||
return item, n, nil
|
||||
}
|
||||
|
||||
func ParseBitmapData2(src []byte) (BitmapData2, int, error) {
|
||||
var item BitmapData2
|
||||
n := 0
|
||||
if L := len(src); L < 5 {
|
||||
return item, 0, fmt.Errorf("reading BitmapData2: "+"EOF: expected length: 5, got %d", L)
|
||||
}
|
||||
item.SmallGlyphMetrics.mustParse(src[0:])
|
||||
n += 5
|
||||
|
||||
{
|
||||
|
||||
item.Image = src[5:]
|
||||
n = len(src)
|
||||
}
|
||||
return item, n, nil
|
||||
}
|
||||
|
||||
func ParseBitmapData5(src []byte) (BitmapData5, int, error) {
|
||||
var item BitmapData5
|
||||
n := 0
|
||||
{
|
||||
|
||||
item.Image = src[0:]
|
||||
n = len(src)
|
||||
}
|
||||
return item, n, nil
|
||||
}
|
||||
|
||||
func ParseCBLC(src []byte) (CBLC, int, error) {
|
||||
var item CBLC
|
||||
n := 0
|
||||
if L := len(src); L < 8 {
|
||||
return item, 0, fmt.Errorf("reading CBLC: "+"EOF: expected length: 8, got %d", L)
|
||||
}
|
||||
_ = src[7] // early bound checking
|
||||
item.majorVersion = binary.BigEndian.Uint16(src[0:])
|
||||
item.minorVersion = binary.BigEndian.Uint16(src[2:])
|
||||
arrayLengthBitmapSizes := int(binary.BigEndian.Uint32(src[4:]))
|
||||
n += 8
|
||||
|
||||
{
|
||||
|
||||
if L := len(src); L < 8+arrayLengthBitmapSizes*48 {
|
||||
return item, 0, fmt.Errorf("reading CBLC: "+"EOF: expected length: %d, got %d", 8+arrayLengthBitmapSizes*48, L)
|
||||
}
|
||||
|
||||
item.BitmapSizes = make([]BitmapSize, arrayLengthBitmapSizes) // allocation guarded by the previous check
|
||||
for i := range item.BitmapSizes {
|
||||
item.BitmapSizes[i].mustParse(src[8+i*48:])
|
||||
}
|
||||
n += arrayLengthBitmapSizes * 48
|
||||
}
|
||||
{
|
||||
|
||||
err := item.parseIndexSubTables(src[:])
|
||||
if err != nil {
|
||||
return item, 0, fmt.Errorf("reading CBLC: %s", err)
|
||||
}
|
||||
}
|
||||
return item, n, nil
|
||||
}
|
||||
|
||||
func ParseIndexData1(src []byte, sbitOffsetsCount int) (IndexData1, int, error) {
|
||||
var item IndexData1
|
||||
n := 0
|
||||
{
|
||||
|
||||
if L := len(src); L < sbitOffsetsCount*4 {
|
||||
return item, 0, fmt.Errorf("reading IndexData1: "+"EOF: expected length: %d, got %d", sbitOffsetsCount*4, L)
|
||||
}
|
||||
|
||||
item.SbitOffsets = make([]Offset32, sbitOffsetsCount) // allocation guarded by the previous check
|
||||
for i := range item.SbitOffsets {
|
||||
item.SbitOffsets[i] = Offset32(binary.BigEndian.Uint32(src[i*4:]))
|
||||
}
|
||||
n += sbitOffsetsCount * 4
|
||||
}
|
||||
return item, n, nil
|
||||
}
|
||||
|
||||
func ParseIndexData2(src []byte) (IndexData2, int, error) {
|
||||
var item IndexData2
|
||||
n := 0
|
||||
if L := len(src); L < 12 {
|
||||
return item, 0, fmt.Errorf("reading IndexData2: "+"EOF: expected length: 12, got %d", L)
|
||||
}
|
||||
item.mustParse(src)
|
||||
n += 12
|
||||
return item, n, nil
|
||||
}
|
||||
|
||||
func ParseIndexData3(src []byte, sbitOffsetsCount int) (IndexData3, int, error) {
|
||||
var item IndexData3
|
||||
n := 0
|
||||
{
|
||||
|
||||
if L := len(src); L < sbitOffsetsCount*2 {
|
||||
return item, 0, fmt.Errorf("reading IndexData3: "+"EOF: expected length: %d, got %d", sbitOffsetsCount*2, L)
|
||||
}
|
||||
|
||||
item.SbitOffsets = make([]Offset16, sbitOffsetsCount) // allocation guarded by the previous check
|
||||
for i := range item.SbitOffsets {
|
||||
item.SbitOffsets[i] = Offset16(binary.BigEndian.Uint16(src[i*2:]))
|
||||
}
|
||||
n += sbitOffsetsCount * 2
|
||||
}
|
||||
return item, n, nil
|
||||
}
|
||||
|
||||
func ParseIndexData4(src []byte) (IndexData4, int, error) {
|
||||
var item IndexData4
|
||||
n := 0
|
||||
if L := len(src); L < 4 {
|
||||
return item, 0, fmt.Errorf("reading IndexData4: "+"EOF: expected length: 4, got %d", L)
|
||||
}
|
||||
item.numGlyphs = binary.BigEndian.Uint32(src[0:])
|
||||
n += 4
|
||||
|
||||
{
|
||||
arrayLength := int(item.numGlyphs + 1)
|
||||
|
||||
if L := len(src); L < 4+arrayLength*4 {
|
||||
return item, 0, fmt.Errorf("reading IndexData4: "+"EOF: expected length: %d, got %d", 4+arrayLength*4, L)
|
||||
}
|
||||
|
||||
item.GlyphArray = make([]GlyphIdOffsetPair, arrayLength) // allocation guarded by the previous check
|
||||
for i := range item.GlyphArray {
|
||||
item.GlyphArray[i].mustParse(src[4+i*4:])
|
||||
}
|
||||
n += arrayLength * 4
|
||||
}
|
||||
return item, n, nil
|
||||
}
|
||||
|
||||
func ParseIndexData5(src []byte) (IndexData5, int, error) {
|
||||
var item IndexData5
|
||||
n := 0
|
||||
if L := len(src); L < 16 {
|
||||
return item, 0, fmt.Errorf("reading IndexData5: "+"EOF: expected length: 16, got %d", L)
|
||||
}
|
||||
_ = src[15] // early bound checking
|
||||
item.ImageSize = binary.BigEndian.Uint32(src[0:])
|
||||
item.BigMetrics.mustParse(src[4:])
|
||||
arrayLengthGlyphIdArray := int(binary.BigEndian.Uint32(src[12:]))
|
||||
n += 16
|
||||
|
||||
{
|
||||
|
||||
if L := len(src); L < 16+arrayLengthGlyphIdArray*2 {
|
||||
return item, 0, fmt.Errorf("reading IndexData5: "+"EOF: expected length: %d, got %d", 16+arrayLengthGlyphIdArray*2, L)
|
||||
}
|
||||
|
||||
item.GlyphIdArray = make([]uint16, arrayLengthGlyphIdArray) // allocation guarded by the previous check
|
||||
for i := range item.GlyphIdArray {
|
||||
item.GlyphIdArray[i] = binary.BigEndian.Uint16(src[16+i*2:])
|
||||
}
|
||||
n += arrayLengthGlyphIdArray * 2
|
||||
}
|
||||
return item, n, nil
|
||||
}
|
||||
|
||||
func ParseIndexSubHeader(src []byte, sbitOffsetsCount int) (IndexSubHeader, int, error) {
|
||||
var item IndexSubHeader
|
||||
n := 0
|
||||
if L := len(src); L < 8 {
|
||||
return item, 0, fmt.Errorf("reading IndexSubHeader: "+"EOF: expected length: 8, got %d", L)
|
||||
}
|
||||
_ = src[7] // early bound checking
|
||||
item.indexFormat = indexVersion(binary.BigEndian.Uint16(src[0:]))
|
||||
item.ImageFormat = binary.BigEndian.Uint16(src[2:])
|
||||
item.ImageDataOffset = Offset32(binary.BigEndian.Uint32(src[4:]))
|
||||
n += 8
|
||||
|
||||
{
|
||||
var (
|
||||
read int
|
||||
err error
|
||||
)
|
||||
switch item.indexFormat {
|
||||
case indexVersion1:
|
||||
item.IndexData, read, err = ParseIndexData1(src[8:], sbitOffsetsCount)
|
||||
case indexVersion2:
|
||||
item.IndexData, read, err = ParseIndexData2(src[8:])
|
||||
case indexVersion3:
|
||||
item.IndexData, read, err = ParseIndexData3(src[8:], sbitOffsetsCount)
|
||||
case indexVersion4:
|
||||
item.IndexData, read, err = ParseIndexData4(src[8:])
|
||||
case indexVersion5:
|
||||
item.IndexData, read, err = ParseIndexData5(src[8:])
|
||||
default:
|
||||
err = fmt.Errorf("unsupported IndexDataVersion %d", item.indexFormat)
|
||||
}
|
||||
if err != nil {
|
||||
return item, 0, fmt.Errorf("reading IndexSubHeader: %s", err)
|
||||
}
|
||||
n += read
|
||||
}
|
||||
return item, n, nil
|
||||
}
|
||||
|
||||
func ParseIndexSubTableArray(src []byte, subtablesCount int) (IndexSubTableArray, int, error) {
|
||||
var item IndexSubTableArray
|
||||
n := 0
|
||||
{
|
||||
|
||||
if L := len(src); L < subtablesCount*8 {
|
||||
return item, 0, fmt.Errorf("reading IndexSubTableArray: "+"EOF: expected length: %d, got %d", subtablesCount*8, L)
|
||||
}
|
||||
|
||||
item.Subtables = make([]IndexSubTableHeader, subtablesCount) // allocation guarded by the previous check
|
||||
for i := range item.Subtables {
|
||||
item.Subtables[i].mustParse(src[i*8:])
|
||||
}
|
||||
n += subtablesCount * 8
|
||||
}
|
||||
return item, n, nil
|
||||
}
|
||||
|
||||
func (item *SbitLineMetrics) mustParse(src []byte) {
|
||||
_ = src[11] // early bound checking
|
||||
item.Ascender = int8(src[0])
|
||||
item.Descender = int8(src[1])
|
||||
item.widthMax = src[2]
|
||||
item.caretSlopeNumerator = int8(src[3])
|
||||
item.caretSlopeDenominator = int8(src[4])
|
||||
item.caretOffset = int8(src[5])
|
||||
item.minOriginSB = int8(src[6])
|
||||
item.minAdvanceSB = int8(src[7])
|
||||
item.MaxBeforeBL = int8(src[8])
|
||||
item.MinAfterBL = int8(src[9])
|
||||
item.pad1 = int8(src[10])
|
||||
item.pad2 = int8(src[11])
|
||||
}
|
||||
|
||||
func (item *SmallGlyphMetrics) mustParse(src []byte) {
|
||||
_ = src[4] // early bound checking
|
||||
item.Height = src[0]
|
||||
item.Width = src[1]
|
||||
item.BearingX = int8(src[2])
|
||||
item.BearingY = int8(src[3])
|
||||
item.Advance = src[4]
|
||||
}
|
||||
Generated
Vendored
+190
@@ -0,0 +1,190 @@
|
||||
// SPDX-License-Identifier: Unlicense OR BSD-3-Clause
|
||||
|
||||
package tables
|
||||
|
||||
import "fmt"
|
||||
|
||||
// CBLC is the Color Bitmap Location Table
|
||||
// See - https://learn.microsoft.com/fr-fr/typography/opentype/spec/cblc
|
||||
type CBLC struct {
|
||||
majorVersion uint16 // Major version of the CBLC table, = 3.
|
||||
minorVersion uint16 // Minor version of the CBLC table, = 0.
|
||||
BitmapSizes []BitmapSize `arrayCount:"FirstUint32"` // BitmapSize records array.
|
||||
IndexSubTables [][]BitmapSubtable `isOpaque:""` // with same length as [BitmapSizes]
|
||||
}
|
||||
|
||||
func (cb *CBLC) parseIndexSubTables(src []byte) error {
|
||||
cb.IndexSubTables = make([][]BitmapSubtable, len(cb.BitmapSizes))
|
||||
for i, size := range cb.BitmapSizes {
|
||||
start := int(size.indexSubTableArrayOffset)
|
||||
if L := len(src); L < start {
|
||||
return fmt.Errorf("EOF: expected length: %d, got %d", start, L)
|
||||
}
|
||||
subtables, _, err := ParseIndexSubTableArray(src[start:], int(size.numberOfIndexSubTables))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
sizeSubtables := make([]BitmapSubtable, len(subtables.Subtables))
|
||||
for j, subtable := range subtables.Subtables {
|
||||
numGlyphs := int(subtable.LastGlyph) - int(subtable.FirstGlyph) + 1
|
||||
subtableStart := start + int(subtable.additionalOffsetToIndexSubtable)
|
||||
|
||||
sizeSubtables[j].FirstGlyph = subtable.FirstGlyph
|
||||
sizeSubtables[j].LastGlyph = subtable.LastGlyph
|
||||
sizeSubtables[j].IndexSubHeader, _, err = ParseIndexSubHeader(src[subtableStart:], numGlyphs+1)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
cb.IndexSubTables[i] = sizeSubtables
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type BitmapSize struct {
|
||||
indexSubTableArrayOffset Offset32 // Offset to index subtable from beginning of CBLC.
|
||||
indexTablesSize uint32 // Number of bytes in corresponding index subtables and array.
|
||||
numberOfIndexSubTables uint32 // There is an index subtable for each range or format change.
|
||||
colorRef uint32 // Not used; set to 0.
|
||||
Hori SbitLineMetrics // Line metrics for text rendered horizontally.
|
||||
Vert SbitLineMetrics // Line metrics for text rendered vertically.
|
||||
startGlyphIndex uint16 // Lowest glyph index for this size.
|
||||
endGlyphIndex uint16 // Highest glyph index for this size.
|
||||
PpemX uint8 // Horizontal pixels per em.
|
||||
PpemY uint8 // Vertical pixels per em.
|
||||
bitDepth uint8 // In addtition to already defined bitDepth values 1, 2, 4, and 8 supported by existing implementations, the value of 32 is used to identify color bitmaps with 8 bit per pixel RGBA channels.
|
||||
flags int8 // Vertical or horizontal (see the Bitmap Flags section of the EBLC table chapter).
|
||||
}
|
||||
|
||||
type SbitLineMetrics struct {
|
||||
Ascender int8
|
||||
Descender int8
|
||||
widthMax uint8
|
||||
caretSlopeNumerator int8
|
||||
caretSlopeDenominator int8
|
||||
caretOffset int8
|
||||
minOriginSB int8
|
||||
minAdvanceSB int8
|
||||
MaxBeforeBL int8
|
||||
MinAfterBL int8
|
||||
pad1 int8
|
||||
pad2 int8
|
||||
}
|
||||
|
||||
type IndexSubTableArray struct {
|
||||
Subtables []IndexSubTableHeader
|
||||
}
|
||||
|
||||
type IndexSubTableHeader struct {
|
||||
FirstGlyph GlyphID // First glyph ID of this range.
|
||||
LastGlyph GlyphID // Last glyph ID of this range (inclusive).
|
||||
additionalOffsetToIndexSubtable Offset32 // Add to indexSubTableArrayOffset to get offset from beginning of EBLC.
|
||||
}
|
||||
|
||||
type IndexSubHeader struct {
|
||||
indexFormat indexVersion // Format of this IndexSubTable.
|
||||
ImageFormat uint16 // Format of EBDT image data.
|
||||
ImageDataOffset Offset32 // Offset to image data in EBDT table.
|
||||
IndexData IndexData `unionField:"indexFormat"`
|
||||
}
|
||||
|
||||
type indexVersion uint16
|
||||
|
||||
const (
|
||||
indexVersion1 indexVersion = iota + 1
|
||||
indexVersion2
|
||||
indexVersion3
|
||||
indexVersion4
|
||||
indexVersion5
|
||||
)
|
||||
|
||||
type IndexData interface {
|
||||
isIndexData()
|
||||
}
|
||||
|
||||
func (IndexData1) isIndexData() {}
|
||||
func (IndexData2) isIndexData() {}
|
||||
func (IndexData3) isIndexData() {}
|
||||
func (IndexData4) isIndexData() {}
|
||||
func (IndexData5) isIndexData() {}
|
||||
|
||||
type IndexData1 struct {
|
||||
// sizeOfArray = (lastGlyph - firstGlyph + 1) + 1 + 1 pad if needed
|
||||
// sbitOffsets[glyphIndex] + imageDataOffset = glyphData
|
||||
SbitOffsets []Offset32
|
||||
}
|
||||
|
||||
type IndexData2 struct {
|
||||
ImageSize uint32 // All the glyphs are of the same size.
|
||||
BigMetrics BigGlyphMetrics // All glyphs have the same metrics; glyph data may be compressed, byte-aligned, or bit-aligned.
|
||||
}
|
||||
|
||||
type IndexData3 struct {
|
||||
// sizeOfArray = (lastGlyph - firstGlyph + 1) + 1 + 1 pad if needed
|
||||
// sbitOffets[glyphIndex] + imageDataOffset = glyphData
|
||||
SbitOffsets []Offset16
|
||||
}
|
||||
|
||||
type IndexData4 struct {
|
||||
numGlyphs uint32 // Array length.
|
||||
GlyphArray []GlyphIdOffsetPair `arrayCount:"ComputedField-numGlyphs+1"` //[numGlyphs + 1] One per glyph.
|
||||
}
|
||||
|
||||
type GlyphIdOffsetPair struct {
|
||||
GlyphID GlyphID // Glyph ID of glyph present.
|
||||
SbitOffset Offset16 // Location in EBDT.
|
||||
}
|
||||
|
||||
type IndexData5 struct {
|
||||
ImageSize uint32 // All glyphs have the same data size.
|
||||
BigMetrics BigGlyphMetrics // All glyphs have the same metrics.
|
||||
GlyphIdArray []GlyphID `arrayCount:"FirstUint32"` // [numGlyphs] One per glyph, sorted by glyph ID.
|
||||
}
|
||||
|
||||
// ------------------------- actual data : shared by EBDT / CBDT / BDAT -------------------------
|
||||
// for now, we simplify the implementation to two cases:
|
||||
// - data, metrics (small)
|
||||
// - data only
|
||||
|
||||
type SmallGlyphMetrics struct {
|
||||
Height uint8 // Number of rows of data.
|
||||
Width uint8 // Number of columns of data.
|
||||
BearingX int8 // Distance in pixels from the horizontal origin to the left edge of the bitmap.
|
||||
BearingY int8 // Distance in pixels from the horizontal origin to the top edge of the bitmap.
|
||||
Advance uint8 // Horizontal advance width in pixels.
|
||||
}
|
||||
|
||||
type BigGlyphMetrics struct {
|
||||
SmallGlyphMetrics
|
||||
vertBearingX int8 // Distance in pixels from the vertical origin to the left edge of the bitmap.
|
||||
vertBearingY int8 // Distance in pixels from the vertical origin to the top edge of the bitmap.
|
||||
vertAdvance uint8 // Vertical advance width in pixels.
|
||||
}
|
||||
|
||||
// Format 2: small metrics, bit-aligned data
|
||||
type BitmapData2 struct {
|
||||
SmallGlyphMetrics
|
||||
Image []byte `arrayCount:"ToEnd"`
|
||||
}
|
||||
|
||||
// Format 5: metrics in CBLC table, bit-aligned image data only
|
||||
type BitmapData5 struct {
|
||||
Image []byte `arrayCount:"ToEnd"`
|
||||
}
|
||||
|
||||
// Format 17: small metrics, PNG image data
|
||||
type BitmapData17 struct {
|
||||
SmallGlyphMetrics
|
||||
Image []byte `arrayCount:"FirstUint32"`
|
||||
}
|
||||
|
||||
// Format 18: big metrics, PNG image data
|
||||
type BitmapData18 struct {
|
||||
BigGlyphMetrics
|
||||
Image []byte `arrayCount:"FirstUint32"`
|
||||
}
|
||||
|
||||
// Format 19: metrics in CBLC table, PNG image data
|
||||
type BitmapData19 struct {
|
||||
Image []byte `arrayCount:"FirstUint32"`
|
||||
}
|
||||
+1819
File diff suppressed because it is too large
Load Diff
+577
@@ -0,0 +1,577 @@
|
||||
package tables
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
)
|
||||
|
||||
func ParseCOLR(src []byte) (COLR1, error) {
|
||||
header, _, err := parseColr0(src)
|
||||
if err != nil {
|
||||
return COLR1{}, err
|
||||
}
|
||||
switch header.Version {
|
||||
case 0:
|
||||
return COLR1{colr0: header}, nil
|
||||
case 1:
|
||||
out, _, err := ParseCOLR1(src)
|
||||
return out, err
|
||||
default:
|
||||
return COLR1{}, fmt.Errorf("unsupported version for COLR: %d", header.Version)
|
||||
}
|
||||
}
|
||||
|
||||
// https://learn.microsoft.com/en-us/typography/opentype/spec/colr#colr-table-formats
|
||||
type colr0 struct {
|
||||
Version uint16 // Table version number
|
||||
numBaseGlyphRecords uint16 // Number of BaseGlyph records.
|
||||
baseGlyphRecords []baseGlyph `arrayCount:"ComputedField-numBaseGlyphRecords" offsetSize:"Offset32"` // Offset to baseGlyphRecords array, from beginning of COLR table.
|
||||
layerRecords []Layer `arrayCount:"ComputedField-numLayerRecords" offsetSize:"Offset32"` // Offset to layerRecords array, from beginning of COLR table.
|
||||
numLayerRecords uint16 // Number of Layer records.
|
||||
}
|
||||
|
||||
func (cl colr0) paintForGlyph(gi GlyphID) (PaintColrLayersResolved, bool) {
|
||||
num := len(cl.baseGlyphRecords)
|
||||
idx := sort.Search(num, func(i int) bool { return gi <= cl.baseGlyphRecords[i].GlyphID })
|
||||
if idx >= num {
|
||||
return nil, false
|
||||
}
|
||||
entry := cl.baseGlyphRecords[idx]
|
||||
if gi != entry.GlyphID {
|
||||
return nil, false
|
||||
}
|
||||
return cl.layerRecords[entry.FirstLayerIndex : entry.FirstLayerIndex+entry.NumLayers], true
|
||||
}
|
||||
|
||||
type COLR1 struct {
|
||||
colr0
|
||||
baseGlyphList baseGlyphList `offsetSize:"Offset32"` // Offset to BaseGlyphList table, from beginning of COLR table.
|
||||
LayerList LayerList `offsetSize:"Offset32"` // Offset to LayerList table, from beginning of COLR table (may be NULL).
|
||||
ClipList ClipList `offsetSize:"Offset32"` // Offset to ClipList table, from beginning of COLR table (may be NULL).
|
||||
VarIndexMap *DeltaSetMapping `offsetSize:"Offset32"` // Offset to DeltaSetIndexMap table, from beginning of COLR table (may be NULL).
|
||||
ItemVariationStore *ItemVarStore `offsetSize:"Offset32"` // Offset to ItemVariationStore, from beginning of COLR table (may be NULL).
|
||||
}
|
||||
|
||||
func (cl *COLR1) Search(gid GlyphID) (PaintTable, bool) {
|
||||
if cl == nil {
|
||||
return nil, false
|
||||
}
|
||||
// "Applications that support COLR version 1 should give preference to the version 1 color glyph.
|
||||
// For applications that support COLR version 1, the application should search for a base glyph ID first in the BaseGlyphList.
|
||||
// Then, if not found, search in the baseGlyphRecords array, if present."
|
||||
if paint, ok := cl.baseGlyphList.paintForGlyph(gid); ok {
|
||||
return paint, true
|
||||
}
|
||||
return cl.colr0.paintForGlyph(gid)
|
||||
}
|
||||
|
||||
type baseGlyph struct {
|
||||
GlyphID GlyphID // Glyph ID of the base glyph.
|
||||
FirstLayerIndex uint16 // Index (base 0) into the layerRecords array.
|
||||
NumLayers uint16 // Number of color layers associated with this glyph.
|
||||
}
|
||||
|
||||
type Layer struct {
|
||||
GlyphID GlyphID // Glyph ID of the glyph used for a given layer.
|
||||
PaletteIndex uint16 // Index (base 0) for a palette entry in the CPAL table.
|
||||
}
|
||||
|
||||
type baseGlyphList struct {
|
||||
paintRecords []baseGlyphPaintRecord `arrayCount:"FirstUint32"` // numBaseGlyphPaintRecords
|
||||
}
|
||||
|
||||
func (bl baseGlyphList) paintForGlyph(gi GlyphID) (PaintTable, bool) {
|
||||
num := len(bl.paintRecords)
|
||||
idx := sort.Search(num, func(i int) bool { return gi <= bl.paintRecords[i].GlyphID })
|
||||
if idx >= num {
|
||||
return nil, false
|
||||
}
|
||||
entry := bl.paintRecords[idx]
|
||||
if gi != entry.GlyphID {
|
||||
return nil, false
|
||||
}
|
||||
return entry.Paint, true
|
||||
}
|
||||
|
||||
type baseGlyphPaintRecord struct {
|
||||
GlyphID GlyphID // Glyph ID of the base glyph.
|
||||
Paint PaintTable `offsetSize:"Offset32" offsetRelativeTo:"Parent"` // Offset to a Paint table, from beginning of BaseGlyphList table.
|
||||
}
|
||||
|
||||
type LayerList struct {
|
||||
paintTables []PaintTable `arrayCount:"FirstUint32" offsetsArray:"Offset32"` // Offsets to Paint tables, from beginning of LayerList table.
|
||||
}
|
||||
|
||||
// Resolve returns an error for invalid (out of bounds) indices
|
||||
func (ll LayerList) Resolve(paint PaintColrLayers) ([]PaintTable, error) {
|
||||
last := paint.FirstLayerIndex + uint32(paint.NumLayers)
|
||||
if L := len(ll.paintTables); int(last) > L {
|
||||
return nil, fmt.Errorf("out of bounds PaintColrLayers: expected %d, got %d", last, L)
|
||||
}
|
||||
return ll.paintTables[paint.FirstLayerIndex:last], nil
|
||||
}
|
||||
|
||||
type ClipList struct {
|
||||
format uint8 // Set to 1.
|
||||
clips []Clip `arrayCount:"FirstUint32"` // Clip records. Sorted by startGlyphID.
|
||||
}
|
||||
|
||||
func (cl ClipList) Search(g GlyphID) (ClipBox, bool) {
|
||||
// binary search
|
||||
for i, j := 0, len(cl.clips); i < j; {
|
||||
h := i + (j-i)/2
|
||||
entry := cl.clips[h]
|
||||
if g < entry.StartGlyphID {
|
||||
j = h
|
||||
} else if entry.EndGlyphID < g {
|
||||
i = h + 1
|
||||
} else {
|
||||
return entry.ClipBox, true
|
||||
}
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
type Clip struct {
|
||||
StartGlyphID GlyphID // First glyph ID in the range.
|
||||
EndGlyphID GlyphID // Last glyph ID in the range.
|
||||
ClipBox ClipBox `offsetSize:"Offset24" offsetRelativeTo:"Parent"` // Offset to a ClipBox table, from beginning of ClipList table.
|
||||
}
|
||||
|
||||
type ClipBox interface {
|
||||
isClipBox()
|
||||
}
|
||||
|
||||
func (ClipBoxFormat1) isClipBox() {}
|
||||
func (ClipBoxFormat2) isClipBox() {}
|
||||
|
||||
// static clip box
|
||||
type ClipBoxFormat1 struct {
|
||||
format byte `unionTag:"1"`
|
||||
XMin int16 // Minimum x of clip box.
|
||||
YMin int16 // Minimum y of clip box.
|
||||
XMax int16 // Maximum x of clip box.
|
||||
YMax int16 // Maximum y of clip box.
|
||||
}
|
||||
|
||||
// variable clip box
|
||||
type ClipBoxFormat2 struct {
|
||||
format byte `unionTag:"2"`
|
||||
XMin int16 // Minimum x of clip box. For variation, use varIndexBase + 0.
|
||||
YMin int16 // Minimum y of clip box. For variation, use varIndexBase + 1.
|
||||
XMax int16 // Maximum x of clip box. For variation, use varIndexBase + 2.
|
||||
YMax int16 // Maximum y of clip box. For variation, use varIndexBase + 3.
|
||||
VarIndexBase uint32 // Base index into DeltaSetIndexMap.
|
||||
}
|
||||
|
||||
type ColorStop struct {
|
||||
StopOffset Fixed214 // Position on a color line.
|
||||
PaletteIndex uint16 // Index for a CPAL palette entry.
|
||||
Alpha Fixed214 // Alpha value.
|
||||
}
|
||||
|
||||
type VarColorStop struct {
|
||||
StopOffset Fixed214 // Position on a color line. For variation, use varIndexBase + 0.
|
||||
PaletteIndex uint16 // Index for a CPAL palette entry.
|
||||
Alpha Fixed214 // Alpha value. For variation, use varIndexBase + 1.
|
||||
VarIndexBase uint32 // Base index into DeltaSetIndexMap
|
||||
}
|
||||
|
||||
type Extend uint8
|
||||
|
||||
const (
|
||||
ExtendPad Extend = iota // Use nearest color stop.
|
||||
ExtendRepeat // Repeat from farthest color stop.
|
||||
ExtendReflect // Mirror color line from nearest end.
|
||||
)
|
||||
|
||||
type ColorLine struct {
|
||||
Extend Extend // An Extend enum value.
|
||||
ColorStops []ColorStop `arrayCount:"FirstUint16"` // [numStops]
|
||||
}
|
||||
|
||||
type VarColorLine struct {
|
||||
Extend Extend // An Extend enum value.
|
||||
ColorStops []VarColorStop `arrayCount:"FirstUint16"` // [numStops] Allows for variations.
|
||||
}
|
||||
|
||||
type Affine2x3 struct {
|
||||
Xx Float1616 // x-component of transformed x-basis vector.
|
||||
Yx Float1616 // y-component of transformed x-basis vector.
|
||||
Xy Float1616 // x-component of transformed y-basis vector.
|
||||
Yy Float1616 // y-component of transformed y-basis vector.
|
||||
Dx Float1616 // Translation in x direction.
|
||||
Dy Float1616 // Translation in y direction.
|
||||
}
|
||||
|
||||
type VarAffine2x3 struct {
|
||||
Xx Float1616 // x-component of transformed x-basis vector. For variation, use varIndexBase + 0.
|
||||
Yx Float1616 // y-component of transformed x-basis vector. For variation, use varIndexBase + 1.
|
||||
Xy Float1616 // x-component of transformed y-basis vector. For variation, use varIndexBase + 2.
|
||||
Yy Float1616 // y-component of transformed y-basis vector. For variation, use varIndexBase + 3.
|
||||
Dx Float1616 // Translation in x direction. For variation, use varIndexBase + 4.
|
||||
Dy Float1616 // Translation in y direction. For variation, use varIndexBase + 5.
|
||||
VarIndexBase uint32 // Base index into DeltaSetIndexMap.
|
||||
}
|
||||
|
||||
type PaintTable interface {
|
||||
isPaintTable()
|
||||
}
|
||||
|
||||
func (PaintColrLayers) isPaintTable() {}
|
||||
func (PaintSolid) isPaintTable() {}
|
||||
func (PaintVarSolid) isPaintTable() {}
|
||||
func (PaintLinearGradient) isPaintTable() {}
|
||||
func (PaintVarLinearGradient) isPaintTable() {}
|
||||
func (PaintRadialGradient) isPaintTable() {}
|
||||
func (PaintVarRadialGradient) isPaintTable() {}
|
||||
func (PaintSweepGradient) isPaintTable() {}
|
||||
func (PaintVarSweepGradient) isPaintTable() {}
|
||||
func (PaintGlyph) isPaintTable() {}
|
||||
func (PaintColrGlyph) isPaintTable() {}
|
||||
func (PaintTransform) isPaintTable() {}
|
||||
func (PaintVarTransform) isPaintTable() {}
|
||||
func (PaintTranslate) isPaintTable() {}
|
||||
func (PaintVarTranslate) isPaintTable() {}
|
||||
func (PaintScale) isPaintTable() {}
|
||||
func (PaintVarScale) isPaintTable() {}
|
||||
func (PaintScaleAroundCenter) isPaintTable() {}
|
||||
func (PaintVarScaleAroundCenter) isPaintTable() {}
|
||||
func (PaintScaleUniform) isPaintTable() {}
|
||||
func (PaintVarScaleUniform) isPaintTable() {}
|
||||
func (PaintScaleUniformAroundCenter) isPaintTable() {}
|
||||
func (PaintVarScaleUniformAroundCenter) isPaintTable() {}
|
||||
func (PaintRotate) isPaintTable() {}
|
||||
func (PaintVarRotate) isPaintTable() {}
|
||||
func (PaintRotateAroundCenter) isPaintTable() {}
|
||||
func (PaintVarRotateAroundCenter) isPaintTable() {}
|
||||
func (PaintSkew) isPaintTable() {}
|
||||
func (PaintVarSkew) isPaintTable() {}
|
||||
func (PaintSkewAroundCenter) isPaintTable() {}
|
||||
func (PaintVarSkewAroundCenter) isPaintTable() {}
|
||||
func (PaintComposite) isPaintTable() {}
|
||||
|
||||
// (format 1)
|
||||
type PaintColrLayers struct {
|
||||
format byte `unionTag:"1"`
|
||||
NumLayers uint8 // Number of offsets to paint tables to read from LayerList.
|
||||
FirstLayerIndex uint32 // Index (base 0) into the LayerList.
|
||||
}
|
||||
|
||||
// (format 2)
|
||||
type PaintSolid struct {
|
||||
format byte `unionTag:"2"`
|
||||
PaletteIndex uint16 // Index for a CPAL palette entry.
|
||||
Alpha Fixed214 // Alpha value.
|
||||
}
|
||||
|
||||
// (format 3)
|
||||
type PaintVarSolid struct {
|
||||
format byte `unionTag:"3"`
|
||||
PaletteIndex uint16 // Index for a CPAL palette entry.
|
||||
Alpha Fixed214 // Alpha value. For variation, use varIndexBase + 0.
|
||||
VarIndexBase uint32 // Base index into DeltaSetIndexMap.
|
||||
}
|
||||
|
||||
// (format 4)
|
||||
type PaintLinearGradient struct {
|
||||
format byte `unionTag:"4"`
|
||||
ColorLine ColorLine `offsetSize:"Offset24"` // Offset to ColorLine table, from beginning of PaintLinearGradient table.
|
||||
X0 int16 // Start point (p₀) x coordinate.
|
||||
Y0 int16 // Start point (p₀) y coordinate.
|
||||
X1 int16 // End point (p₁) x coordinate.
|
||||
Y1 int16 // End point (p₁) y coordinate.
|
||||
X2 int16 // Rotation point (p₂) x coordinate.
|
||||
Y2 int16 // Rotation point (p₂) y coordinate.
|
||||
}
|
||||
|
||||
// (format 5)
|
||||
type PaintVarLinearGradient struct {
|
||||
format byte `unionTag:"5"`
|
||||
ColorLine VarColorLine `offsetSize:"Offset24"` // Offset to VarColorLine table, from beginning of PaintVarLinearGradient table.
|
||||
X0 int16 // Start point (p₀) x coordinate. For variation, use varIndexBase + 0.
|
||||
Y0 int16 // Start point (p₀) y coordinate. For variation, use varIndexBase + 1.
|
||||
X1 int16 // End point (p₁) x coordinate. For variation, use varIndexBase + 2.
|
||||
Y1 int16 // End point (p₁) y coordinate. For variation, use varIndexBase + 3.
|
||||
X2 int16 // Rotation point (p₂) x coordinate. For variation, use varIndexBase + 4.
|
||||
Y2 int16 // Rotation point (p₂) y coordinate. For variation, use varIndexBase + 5.
|
||||
VarIndexBase uint32 // Base index into DeltaSetIndexMap.
|
||||
}
|
||||
|
||||
// (format 6)
|
||||
type PaintRadialGradient struct {
|
||||
format byte `unionTag:"6"`
|
||||
ColorLine ColorLine `offsetSize:"Offset24"` // Offset to ColorLine table, from beginning of PaintRadialGradient table.
|
||||
X0 int16 // Start circle center x coordinate.
|
||||
Y0 int16 // Start circle center y coordinate.
|
||||
Radius0 uint16 // Start circle radius.
|
||||
X1 int16 // End circle center x coordinate.
|
||||
Y1 int16 // End circle center y coordinate.
|
||||
Radius1 uint16 // End circle radius.
|
||||
}
|
||||
|
||||
// (format 7)
|
||||
type PaintVarRadialGradient struct {
|
||||
format byte `unionTag:"7"`
|
||||
ColorLine VarColorLine `offsetSize:"Offset24"` // Offset to VarColorLine table, from beginning of PaintVarRadialGradient table.
|
||||
X0 int16 // Start circle center x coordinate. For variation, use varIndexBase + 0.
|
||||
Y0 int16 // Start circle center y coordinate. For variation, use varIndexBase + 1.
|
||||
Radius0 uint16 // Start circle radius. For variation, use varIndexBase + 2.
|
||||
X1 int16 // End circle center x coordinate. For variation, use varIndexBase + 3.
|
||||
Y1 int16 // End circle center y coordinate. For variation, use varIndexBase + 4.
|
||||
Radius1 uint16 // End circle radius. For variation, use varIndexBase + 5.
|
||||
VarIndexBase uint32 // Base index into DeltaSetIndexMap.
|
||||
}
|
||||
|
||||
// (format 8)
|
||||
type PaintSweepGradient struct {
|
||||
format byte `unionTag:"8"`
|
||||
ColorLine ColorLine `offsetSize:"Offset24"` // Offset to ColorLine table, from beginning of PaintSweepGradient table.
|
||||
CenterX int16 // Center x coordinate.
|
||||
CenterY int16 // Center y coordinate.
|
||||
StartAngle Fixed214 // Start of the angular range of the gradient: add 1.0 and multiply by 180° to retrieve counter-clockwise degrees.
|
||||
EndAngle Fixed214 // End of the angular range of the gradient: add 1.0 and multiply by 180° to retrieve counter-clockwise degrees.
|
||||
}
|
||||
|
||||
// (format 9)
|
||||
type PaintVarSweepGradient struct {
|
||||
format byte `unionTag:"9"`
|
||||
ColorLine VarColorLine `offsetSize:"Offset24"` // Offset to VarColorLine table, from beginning of PaintVarSweepGradient table.
|
||||
CenterX int16 // Center x coordinate. For variation, use varIndexBase + 0.
|
||||
CenterY int16 // Center y coordinate. For variation, use varIndexBase + 1.
|
||||
StartAngle Fixed214 // Start of the angular range of the gradient: add 1.0 and multiply by 180° to retrieve counter-clockwise degrees. For variation, use varIndexBase + 2.
|
||||
EndAngle Fixed214 // End of the angular range of the gradient: add 1.0 and multiply by 180° to retrieve counter-clockwise degrees. For variation, use varIndexBase + 3.
|
||||
VarIndexBase uint32 // Base index into DeltaSetIndexMap.
|
||||
}
|
||||
|
||||
// (format 10)
|
||||
type PaintGlyph struct {
|
||||
format byte `unionTag:"10"`
|
||||
Paint PaintTable `offsetSize:"Offset24"` // Offset to a Paint table, from beginning of PaintGlyph table.
|
||||
GlyphID uint16 // Glyph ID for the source outline.
|
||||
}
|
||||
|
||||
// (format 11)
|
||||
type PaintColrGlyph struct {
|
||||
format byte `unionTag:"11"`
|
||||
GlyphID uint16 // Glyph ID for a BaseGlyphList base glyph.
|
||||
}
|
||||
|
||||
// (format 12)
|
||||
type PaintTransform struct {
|
||||
format byte `unionTag:"12"`
|
||||
Paint PaintTable `offsetSize:"Offset24"` // Offset to a Paint subtable, from beginning of PaintTransform table.
|
||||
Transform Affine2x3 `offsetSize:"Offset24"` // Offset to an Affine2x3 table, from beginning of PaintTransform table.
|
||||
}
|
||||
|
||||
// (format 13)
|
||||
type PaintVarTransform struct {
|
||||
format byte `unionTag:"13"`
|
||||
Paint PaintTable `offsetSize:"Offset24"` // Offset to a Paint subtable, from beginning of PaintVarTransform table.
|
||||
Transform VarAffine2x3 `offsetSize:"Offset24"` // Offset to a VarAffine2x3 table, from beginning of PaintVarTransform table.
|
||||
}
|
||||
|
||||
// (format 14)
|
||||
type PaintTranslate struct {
|
||||
format byte `unionTag:"14"`
|
||||
Paint PaintTable `offsetSize:"Offset24"` // Offset to a Paint subtable, from beginning of PaintTranslate table.
|
||||
Dx int16 // Translation in x direction.
|
||||
Dy int16 // Translation in y direction.
|
||||
}
|
||||
|
||||
// (format 15)
|
||||
type PaintVarTranslate struct {
|
||||
format byte `unionTag:"15"`
|
||||
Paint PaintTable `offsetSize:"Offset24"` // Offset to a Paint subtable, from beginning of PaintVarTranslate table.
|
||||
Dx int16 // Translation in x direction. For variation, use varIndexBase + 0.
|
||||
Dy int16 // Translation in y direction. For variation, use varIndexBase + 1.
|
||||
VarIndexBase uint32 // Base index into DeltaSetIndexMap.
|
||||
}
|
||||
|
||||
// (format 16)
|
||||
type PaintScale struct {
|
||||
format byte `unionTag:"16"`
|
||||
Paint PaintTable `offsetSize:"Offset24"` // Offset to a Paint subtable, from beginning of PaintScale table.
|
||||
ScaleX Fixed214 // Scale factor in x direction.
|
||||
ScaleY Fixed214 // Scale factor in y direction.
|
||||
}
|
||||
|
||||
// (format 17)
|
||||
type PaintVarScale struct {
|
||||
format byte `unionTag:"17"`
|
||||
Paint PaintTable `offsetSize:"Offset24"` // Offset to a Paint subtable, from beginning of PaintVarScale table.
|
||||
ScaleX Fixed214 // Scale factor in x direction. For variation, use varIndexBase + 0.
|
||||
ScaleY Fixed214 // Scale factor in y direction. For variation, use varIndexBase + 1.
|
||||
VarIndexBase uint32 // Base index into DeltaSetIndexMap.
|
||||
}
|
||||
|
||||
// (format 18)
|
||||
type PaintScaleAroundCenter struct {
|
||||
format byte `unionTag:"18"`
|
||||
Paint PaintTable `offsetSize:"Offset24"` // Offset to a Paint subtable, from beginning of PaintScaleAroundCenter table.
|
||||
ScaleX Fixed214 // Scale factor in x direction.
|
||||
ScaleY Fixed214 // Scale factor in y direction.
|
||||
CenterX int16 // x coordinate for the center of scaling.
|
||||
CenterY int16 // y coordinate for the center of scaling.
|
||||
}
|
||||
|
||||
// (format 19)
|
||||
type PaintVarScaleAroundCenter struct {
|
||||
format byte `unionTag:"19"`
|
||||
Paint PaintTable `offsetSize:"Offset24"` // Offset to a Paint subtable, from beginning of PaintVarScaleAroundCenter table.
|
||||
ScaleX Fixed214 // Scale factor in x direction. For variation, use varIndexBase + 0.
|
||||
ScaleY Fixed214 // Scale factor in y direction. For variation, use varIndexBase + 1.
|
||||
CenterX int16 // x coordinate for the center of scaling. For variation, use varIndexBase + 2.
|
||||
CenterY int16 // y coordinate for the center of scaling. For variation, use varIndexBase + 3.
|
||||
VarIndexBase uint32 // Base index into DeltaSetIndexMap.
|
||||
}
|
||||
|
||||
// (format 20)
|
||||
type PaintScaleUniform struct {
|
||||
format byte `unionTag:"20"`
|
||||
Paint PaintTable `offsetSize:"Offset24"` // Offset to a Paint subtable, from beginning of PaintScaleUniform table.
|
||||
Scale Fixed214 // Scale factor in x and y directions.
|
||||
}
|
||||
|
||||
// (format 21)
|
||||
type PaintVarScaleUniform struct {
|
||||
format byte `unionTag:"21"`
|
||||
Paint PaintTable `offsetSize:"Offset24"` // Offset to a Paint subtable, from beginning of PaintVarScaleUniform table.
|
||||
Scale Fixed214 // Scale factor in x and y directions. For variation, use varIndexBase + 0.
|
||||
VarIndexBase uint32 // Base index into DeltaSetIndexMap.
|
||||
}
|
||||
|
||||
// (format 22)
|
||||
type PaintScaleUniformAroundCenter struct {
|
||||
format byte `unionTag:"22"`
|
||||
Paint PaintTable `offsetSize:"Offset24"` // Offset to a Paint subtable, from beginning of PaintScaleUniformAroundCenter table.
|
||||
Scale Fixed214 // Scale factor in x and y directions.
|
||||
CenterX int16 // x coordinate for the center of scaling.
|
||||
CenterY int16 // y coordinate for the center of scaling.
|
||||
}
|
||||
|
||||
// (format 23)
|
||||
type PaintVarScaleUniformAroundCenter struct {
|
||||
format byte `unionTag:"23"`
|
||||
Paint PaintTable `offsetSize:"Offset24"` // Offset to a Paint subtable, from beginning of PaintVarScaleUniformAroundCenter table.
|
||||
Scale Fixed214 // Scale factor in x and y directions. For variation, use varIndexBase + 0.
|
||||
CenterX int16 // x coordinate for the center of scaling. For variation, use varIndexBase + 1.
|
||||
CenterY int16 // y coordinate for the center of scaling. For variation, use varIndexBase + 2.
|
||||
VarIndexBase uint32 // Base index into DeltaSetIndexMap.
|
||||
}
|
||||
|
||||
// (format 24)
|
||||
type PaintRotate struct {
|
||||
format byte `unionTag:"24"`
|
||||
Paint PaintTable `offsetSize:"Offset24"` // Offset to a Paint subtable, from beginning of PaintRotate table.
|
||||
Angle Fixed214 // Rotation angle, 180° in counter-clockwise degrees per 1.0 of value.
|
||||
}
|
||||
|
||||
// (format 25)
|
||||
type PaintVarRotate struct {
|
||||
format byte `unionTag:"25"`
|
||||
Paint PaintTable `offsetSize:"Offset24"` // Offset to a Paint subtable, from beginning of PaintVarRotate table.
|
||||
Angle Fixed214 // Rotation angle, 180° in counter-clockwise degrees per 1.0 of value. For variation, use varIndexBase + 0.
|
||||
VarIndexBase uint32 // Base index into DeltaSetIndexMap.
|
||||
}
|
||||
|
||||
// (format 26)
|
||||
type PaintRotateAroundCenter struct {
|
||||
format byte `unionTag:"26"`
|
||||
Paint PaintTable `offsetSize:"Offset24"` // Offset to a Paint subtable, from beginning of PaintRotateAroundCenter table.
|
||||
Angle Fixed214 // Rotation angle, 180° in counter-clockwise degrees per 1.0 of value.
|
||||
CenterX int16 // x coordinate for the center of rotation.
|
||||
CenterY int16 // y coordinate for the center of rotation.
|
||||
}
|
||||
|
||||
// (format 27)
|
||||
type PaintVarRotateAroundCenter struct {
|
||||
format byte `unionTag:"27"`
|
||||
Paint PaintTable `offsetSize:"Offset24"` // Offset to a Paint subtable, from beginning of PaintVarRotateAroundCenter table.
|
||||
Angle Fixed214 // Rotation angle, 180° in counter-clockwise degrees per 1.0 of value. For variation, use varIndexBase + 0.
|
||||
CenterX int16 // x coordinate for the center of rotation. For variation, use varIndexBase + 1.
|
||||
CenterY int16 // y coordinate for the center of rotation. For variation, use varIndexBase + 2.
|
||||
VarIndexBase uint32 // Base index into DeltaSetIndexMap.
|
||||
}
|
||||
|
||||
// (format 28)
|
||||
type PaintSkew struct {
|
||||
format byte `unionTag:"28"`
|
||||
Paint PaintTable `offsetSize:"Offset24"` // Offset to a Paint subtable, from beginning of PaintSkew table.
|
||||
XSkewAngle Fixed214 // Angle of skew in the direction of the x-axis, 180° in counter-clockwise degrees per 1.0 of value.
|
||||
YSkewAngle Fixed214 // Angle of skew in the direction of the y-axis, 180° in counter-clockwise degrees per 1.0 of value.
|
||||
}
|
||||
|
||||
// (format 29)
|
||||
type PaintVarSkew struct {
|
||||
format byte `unionTag:"29"`
|
||||
Paint PaintTable `offsetSize:"Offset24"` // Offset to a Paint subtable, from beginning of PaintVarSkew table.
|
||||
XSkewAngle Fixed214 // Angle of skew in the direction of the x-axis, 180° in counter-clockwise degrees per 1.0 of value. For variation, use varIndexBase + 0.
|
||||
YSkewAngle Fixed214 // Angle of skew in the direction of the y-axis, 180° in counter-clockwise degrees per 1.0 of value. For variation, use varIndexBase + 1.
|
||||
VarIndexBase uint32 // Base index into DeltaSetIndexMap.
|
||||
}
|
||||
|
||||
// (format 30)
|
||||
type PaintSkewAroundCenter struct {
|
||||
format byte `unionTag:"30"`
|
||||
Paint PaintTable `offsetSize:"Offset24"` // Offset to a Paint subtable, from beginning of PaintSkewAroundCenter table.
|
||||
XSkewAngle Fixed214 // Angle of skew in the direction of the x-axis, 180° in counter-clockwise degrees per 1.0 of value.
|
||||
YSkewAngle Fixed214 // Angle of skew in the direction of the y-axis, 180° in counter-clockwise degrees per 1.0 of value.
|
||||
CenterX int16 // x coordinate for the center of rotation.
|
||||
CenterY int16 // y coordinate for the center of rotation.
|
||||
}
|
||||
|
||||
// (format 31)
|
||||
type PaintVarSkewAroundCenter struct {
|
||||
format byte `unionTag:"31"`
|
||||
Paint PaintTable `offsetSize:"Offset24"` // Offset to a Paint subtable, from beginning of PaintVarSkewAroundCenter table.
|
||||
XSkewAngle Fixed214 // Angle of skew in the direction of the x-axis, 180° in counter-clockwise degrees per 1.0 of value. For variation, use varIndexBase + 0.
|
||||
YSkewAngle Fixed214 // Angle of skew in the direction of the y-axis, 180° in counter-clockwise degrees per 1.0 of value. For variation, use varIndexBase + 1.
|
||||
CenterX int16 // x coordinate for the center of rotation. For variation, use varIndexBase + 2.
|
||||
CenterY int16 // y coordinate for the center of rotation. For variation, use varIndexBase + 3.
|
||||
VarIndexBase uint32 // Base index into DeltaSetIndexMap.
|
||||
}
|
||||
|
||||
// (format 32)
|
||||
type PaintComposite struct {
|
||||
format byte `unionTag:"32"`
|
||||
SourcePaint PaintTable `offsetSize:"Offset24"` // Offset to a source Paint table, from beginning of PaintComposite table.
|
||||
CompositeMode CompositeMode // A CompositeMode enumeration value.
|
||||
BackdropPaint PaintTable `offsetSize:"Offset24"` // Offset to a backdrop Paint table, from beginning of PaintComposite table.
|
||||
}
|
||||
|
||||
type CompositeMode uint8
|
||||
|
||||
const (
|
||||
// Porter-Duff modes
|
||||
CompositeClear CompositeMode = iota // Clear
|
||||
CompositeSrc // Source (“Copy” in Composition & Blending Level 1)
|
||||
CompositeDest // Destination
|
||||
CompositeSrcOver // Source Over
|
||||
CompositeDestOver // Destination Over
|
||||
CompositeSrcIn // Source In
|
||||
CompositeDestIn // Destination In
|
||||
CompositeSrcOut // Source Out
|
||||
CompositeDestOut // Destination Out
|
||||
CompositeSrcAtop // Source Atop
|
||||
CompositeDestAtop // Destination Atop
|
||||
CompositeXor // XOR
|
||||
CompositePlus // Plus (“Lighter” in Composition & Blending Level 1)
|
||||
// Separable color blend modes:
|
||||
CompositeScreen // screen
|
||||
CompositeOverlay // overlay
|
||||
CompositeDarken // darken
|
||||
CompositeLighten // lighten
|
||||
CompositeColorDodge // color-dodge
|
||||
CompositeColorBurn // color-burn
|
||||
CompositeHardLight // hard-light
|
||||
CompositeSoftLight // soft-light
|
||||
CompositeDifference // difference
|
||||
CompositeExclusion // exclusion
|
||||
CompositeMultiply // multiply
|
||||
// Non-separable color blend modes:
|
||||
CompositeHslHue // hue
|
||||
CompositeHslSaturation // saturation
|
||||
CompositeHslColor // color
|
||||
CompositeHslLuminosity // luminosity
|
||||
)
|
||||
+67
@@ -0,0 +1,67 @@
|
||||
// SPDX-License-Identifier: Unlicense OR BSD-3-Clause
|
||||
|
||||
package tables
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// Code generated by binarygen from glyphs_cpal_src.go. DO NOT EDIT
|
||||
|
||||
func (item *ColorRecord) mustParse(src []byte) {
|
||||
_ = src[3] // early bound checking
|
||||
item.Blue = src[0]
|
||||
item.Green = src[1]
|
||||
item.Red = src[2]
|
||||
item.Alpha = src[3]
|
||||
}
|
||||
|
||||
func ParseCPAL(src []byte) (CPAL, int, error) {
|
||||
var item CPAL
|
||||
n := 0
|
||||
if L := len(src); L < 12 {
|
||||
return item, 0, fmt.Errorf("reading CPAL: "+"EOF: expected length: 12, got %d", L)
|
||||
}
|
||||
_ = src[11] // early bound checking
|
||||
item.Version = binary.BigEndian.Uint16(src[0:])
|
||||
item.NumPaletteEntries = binary.BigEndian.Uint16(src[2:])
|
||||
item.numPalettes = binary.BigEndian.Uint16(src[4:])
|
||||
item.numColorRecords = binary.BigEndian.Uint16(src[6:])
|
||||
offsetColorRecordsArray := int(binary.BigEndian.Uint32(src[8:]))
|
||||
n += 12
|
||||
|
||||
{
|
||||
if offsetColorRecordsArray != 0 { // ignore null offset
|
||||
if L := len(src); L < offsetColorRecordsArray {
|
||||
return item, 0, fmt.Errorf("reading CPAL: "+"EOF: expected length: %d, got %d", offsetColorRecordsArray, L)
|
||||
}
|
||||
|
||||
arrayLength := int(item.numColorRecords)
|
||||
|
||||
if L := len(src); L < offsetColorRecordsArray+arrayLength*4 {
|
||||
return item, 0, fmt.Errorf("reading CPAL: "+"EOF: expected length: %d, got %d", offsetColorRecordsArray+arrayLength*4, L)
|
||||
}
|
||||
|
||||
item.ColorRecordsArray = make([]ColorRecord, arrayLength) // allocation guarded by the previous check
|
||||
for i := range item.ColorRecordsArray {
|
||||
item.ColorRecordsArray[i].mustParse(src[offsetColorRecordsArray+i*4:])
|
||||
}
|
||||
offsetColorRecordsArray += arrayLength * 4
|
||||
}
|
||||
}
|
||||
{
|
||||
arrayLength := int(item.numPalettes)
|
||||
|
||||
if L := len(src); L < 12+arrayLength*2 {
|
||||
return item, 0, fmt.Errorf("reading CPAL: "+"EOF: expected length: %d, got %d", 12+arrayLength*2, L)
|
||||
}
|
||||
|
||||
item.ColorRecordIndices = make([]uint16, arrayLength) // allocation guarded by the previous check
|
||||
for i := range item.ColorRecordIndices {
|
||||
item.ColorRecordIndices[i] = binary.BigEndian.Uint16(src[12+i*2:])
|
||||
}
|
||||
n += arrayLength * 2
|
||||
}
|
||||
return item, n, nil
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
package tables
|
||||
|
||||
// https://learn.microsoft.com/en-us/typography/opentype/spec/cpal
|
||||
//
|
||||
// For now, only the CPAL version 0 is supported.
|
||||
type CPAL struct {
|
||||
Version uint16 // Table version number
|
||||
NumPaletteEntries uint16 // Number of palette entries in each palette.
|
||||
numPalettes uint16 // Number of palettes in the table.
|
||||
numColorRecords uint16 // Total number of color records, combined for all palettes.
|
||||
ColorRecordsArray []ColorRecord `arrayCount:"ComputedField-numColorRecords" offsetSize:"Offset32"` // Offset from the beginning of CPAL table to the first ColorRecord.
|
||||
ColorRecordIndices []uint16 `arrayCount:"ComputedField-numPalettes"` // [numPalettes] Index of each palette’s first color record in the combined color record array.
|
||||
}
|
||||
|
||||
type ColorRecord struct {
|
||||
Blue uint8 // Blue value (B0).
|
||||
Green uint8 // Green value (B1).
|
||||
Red uint8 // Red value (B2).
|
||||
Alpha uint8 // Alpha value (B3).
|
||||
}
|
||||
+135
@@ -0,0 +1,135 @@
|
||||
// SPDX-License-Identifier: Unlicense OR BSD-3-Clause
|
||||
|
||||
package tables
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// Code generated by binarygen from glyphs_glyf_src.go. DO NOT EDIT
|
||||
|
||||
func (item *CompositeGlyphPart) mustParse(src []byte) {
|
||||
_ = src[23] // early bound checking
|
||||
item.Flags = binary.BigEndian.Uint16(src[0:])
|
||||
item.GlyphIndex = binary.BigEndian.Uint16(src[2:])
|
||||
item.arg1 = binary.BigEndian.Uint16(src[4:])
|
||||
item.arg2 = binary.BigEndian.Uint16(src[6:])
|
||||
item.Scale[0] = float32(binary.BigEndian.Uint32(src[8:]))
|
||||
item.Scale[1] = float32(binary.BigEndian.Uint32(src[12:]))
|
||||
item.Scale[2] = float32(binary.BigEndian.Uint32(src[16:]))
|
||||
item.Scale[3] = float32(binary.BigEndian.Uint32(src[20:]))
|
||||
}
|
||||
|
||||
func (item *GlyphContourPoint) mustParse(src []byte) {
|
||||
_ = src[4] // early bound checking
|
||||
item.Flag = src[0]
|
||||
item.X = int16(binary.BigEndian.Uint16(src[1:]))
|
||||
item.Y = int16(binary.BigEndian.Uint16(src[3:]))
|
||||
}
|
||||
|
||||
func ParseCompositeGlyph(src []byte) (CompositeGlyph, int, error) {
|
||||
var item CompositeGlyph
|
||||
n := 0
|
||||
{
|
||||
|
||||
err := item.parseGlyphs(src[:])
|
||||
if err != nil {
|
||||
return item, 0, fmt.Errorf("reading CompositeGlyph: %s", err)
|
||||
}
|
||||
}
|
||||
{
|
||||
|
||||
err := item.parseInstructions(src[:])
|
||||
if err != nil {
|
||||
return item, 0, fmt.Errorf("reading CompositeGlyph: %s", err)
|
||||
}
|
||||
}
|
||||
return item, n, nil
|
||||
}
|
||||
|
||||
func ParseCompositeGlyphPart(src []byte) (CompositeGlyphPart, int, error) {
|
||||
var item CompositeGlyphPart
|
||||
n := 0
|
||||
if L := len(src); L < 24 {
|
||||
return item, 0, fmt.Errorf("reading CompositeGlyphPart: "+"EOF: expected length: 24, got %d", L)
|
||||
}
|
||||
item.mustParse(src)
|
||||
n += 24
|
||||
return item, n, nil
|
||||
}
|
||||
|
||||
func ParseGlyph(src []byte) (Glyph, int, error) {
|
||||
var item Glyph
|
||||
n := 0
|
||||
if L := len(src); L < 10 {
|
||||
return item, 0, fmt.Errorf("reading Glyph: "+"EOF: expected length: 10, got %d", L)
|
||||
}
|
||||
_ = src[9] // early bound checking
|
||||
item.numberOfContours = int16(binary.BigEndian.Uint16(src[0:]))
|
||||
item.XMin = int16(binary.BigEndian.Uint16(src[2:]))
|
||||
item.YMin = int16(binary.BigEndian.Uint16(src[4:]))
|
||||
item.XMax = int16(binary.BigEndian.Uint16(src[6:]))
|
||||
item.YMax = int16(binary.BigEndian.Uint16(src[8:]))
|
||||
n += 10
|
||||
|
||||
{
|
||||
|
||||
err := item.parseData(src[10:])
|
||||
if err != nil {
|
||||
return item, 0, fmt.Errorf("reading Glyph: %s", err)
|
||||
}
|
||||
}
|
||||
return item, n, nil
|
||||
}
|
||||
|
||||
func ParseGlyphContourPoint(src []byte) (GlyphContourPoint, int, error) {
|
||||
var item GlyphContourPoint
|
||||
n := 0
|
||||
if L := len(src); L < 5 {
|
||||
return item, 0, fmt.Errorf("reading GlyphContourPoint: "+"EOF: expected length: 5, got %d", L)
|
||||
}
|
||||
item.mustParse(src)
|
||||
n += 5
|
||||
return item, n, nil
|
||||
}
|
||||
|
||||
func ParseSimpleGlyph(src []byte, endPtsOfContoursCount int) (SimpleGlyph, int, error) {
|
||||
var item SimpleGlyph
|
||||
n := 0
|
||||
{
|
||||
|
||||
if L := len(src); L < endPtsOfContoursCount*2 {
|
||||
return item, 0, fmt.Errorf("reading SimpleGlyph: "+"EOF: expected length: %d, got %d", endPtsOfContoursCount*2, L)
|
||||
}
|
||||
|
||||
item.EndPtsOfContours = make([]uint16, endPtsOfContoursCount) // allocation guarded by the previous check
|
||||
for i := range item.EndPtsOfContours {
|
||||
item.EndPtsOfContours[i] = binary.BigEndian.Uint16(src[i*2:])
|
||||
}
|
||||
n += endPtsOfContoursCount * 2
|
||||
}
|
||||
if L := len(src); L < n+2 {
|
||||
return item, 0, fmt.Errorf("reading SimpleGlyph: "+"EOF: expected length: n + 2, got %d", L)
|
||||
}
|
||||
arrayLengthInstructions := int(binary.BigEndian.Uint16(src[n:]))
|
||||
n += 2
|
||||
|
||||
{
|
||||
|
||||
L := int(n + arrayLengthInstructions)
|
||||
if len(src) < L {
|
||||
return item, 0, fmt.Errorf("reading SimpleGlyph: "+"EOF: expected length: %d, got %d", L, len(src))
|
||||
}
|
||||
item.Instructions = src[n:L]
|
||||
n = L
|
||||
}
|
||||
{
|
||||
|
||||
err := item.parsePoints(src[n:], endPtsOfContoursCount)
|
||||
if err != nil {
|
||||
return item, 0, fmt.Errorf("reading SimpleGlyph: %s", err)
|
||||
}
|
||||
}
|
||||
return item, n, nil
|
||||
}
|
||||
+350
@@ -0,0 +1,350 @@
|
||||
// SPDX-License-Identifier: Unlicense OR BSD-3-Clause
|
||||
|
||||
package tables
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// shared with gvar, sbix, eblc
|
||||
// return an error only if data is not long enough
|
||||
func ParseLoca(src []byte, numGlyphs int, isLong bool) (out []uint32, err error) {
|
||||
var size int
|
||||
if isLong {
|
||||
size = (numGlyphs + 1) * 4
|
||||
} else {
|
||||
size = (numGlyphs + 1) * 2
|
||||
}
|
||||
if L := len(src); L < size {
|
||||
return nil, fmt.Errorf("reading Loca: EOF: expected length: %d, got %d", size, L)
|
||||
}
|
||||
out = make([]uint32, numGlyphs+1)
|
||||
if isLong {
|
||||
for i := range out {
|
||||
out[i] = binary.BigEndian.Uint32(src[4*i:])
|
||||
}
|
||||
} else {
|
||||
for i := range out {
|
||||
out[i] = 2 * uint32(binary.BigEndian.Uint16(src[2*i:])) // The actual local offset divided by 2 is stored.
|
||||
}
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// Glyph Data
|
||||
type Glyf []Glyph
|
||||
|
||||
// ParseGlyf parses the 'glyf' table.
|
||||
// locaOffsets has length numGlyphs + 1, and is returned by ParseLoca
|
||||
func ParseGlyf(src []byte, locaOffsets []uint32) (Glyf, error) {
|
||||
out := make(Glyf, len(locaOffsets)-1)
|
||||
var err error
|
||||
for i := range out {
|
||||
start, end := locaOffsets[i], locaOffsets[i+1]
|
||||
// If a glyph has no outline, then loca[n] = loca [n+1].
|
||||
if start == end {
|
||||
continue
|
||||
}
|
||||
out[i], _, err = ParseGlyph(src[start:end])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
type Glyph struct {
|
||||
numberOfContours int16 // If the number of contours is greater than or equal to zero, this is a simple glyph. If negative, this is a composite glyph — the value -1 should be used for composite glyphs.
|
||||
XMin int16 // Minimum x for coordinate data.
|
||||
YMin int16 // Minimum y for coordinate data.
|
||||
XMax int16 // Maximum x for coordinate data.
|
||||
YMax int16 // Maximum y for coordinate data.
|
||||
Data GlyphData `isOpaque:"" subsliceStart:"AtCurrent"`
|
||||
}
|
||||
|
||||
func (gl *Glyph) parseData(src []byte) (err error) {
|
||||
if gl.numberOfContours >= 0 { // simple glyph
|
||||
gl.Data, _, err = ParseSimpleGlyph(src, int(gl.numberOfContours))
|
||||
} else { // composite glyph
|
||||
gl.Data, _, err = ParseCompositeGlyph(src)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
type GlyphData interface {
|
||||
isGlyphData()
|
||||
}
|
||||
|
||||
func (SimpleGlyph) isGlyphData() {}
|
||||
func (CompositeGlyph) isGlyphData() {}
|
||||
|
||||
type SimpleGlyph struct {
|
||||
EndPtsOfContours []uint16 // [numberOfContours] Array of point indices for the last point of each contour, in increasing numeric order.
|
||||
Instructions []byte `arrayCount:"FirstUint16"` // [instructionLength] Array of instruction byte code for the glyph.
|
||||
Points []GlyphContourPoint `isOpaque:"" subsliceStart:"AtCurrent"`
|
||||
}
|
||||
|
||||
type GlyphContourPoint struct {
|
||||
Flag uint8
|
||||
X, Y int16
|
||||
}
|
||||
|
||||
const (
|
||||
xShortVector = 0x02
|
||||
xIsSameOrPositiveXShortVector = 0x10
|
||||
yShortVector = 0x04
|
||||
yIsSameOrPositiveYShortVector = 0x20
|
||||
)
|
||||
|
||||
func (sg *SimpleGlyph) parsePoints(src []byte, _ int) error {
|
||||
if len(sg.EndPtsOfContours) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
numPoints := int(sg.EndPtsOfContours[len(sg.EndPtsOfContours)-1]) + 1
|
||||
|
||||
const repeatFlag = 0x08
|
||||
|
||||
sg.Points = make([]GlyphContourPoint, numPoints)
|
||||
|
||||
// read flags
|
||||
// to avoid costly length check, we also precompute the expected data size for coordinates
|
||||
var (
|
||||
coordinatesLengthX, coordinatesLengthY int
|
||||
cursor int
|
||||
L = len(src)
|
||||
)
|
||||
for i := 0; i < numPoints; i++ {
|
||||
if L <= cursor {
|
||||
return errors.New("invalid simple glyph data flags (EOF)")
|
||||
}
|
||||
flag := src[cursor]
|
||||
sg.Points[i].Flag = flag
|
||||
cursor++
|
||||
|
||||
localLengthX, localLengthY := 0, 0
|
||||
if flag&xShortVector != 0 {
|
||||
localLengthX = 1
|
||||
} else if flag&xIsSameOrPositiveXShortVector == 0 {
|
||||
localLengthX = 2
|
||||
}
|
||||
if flag&yShortVector != 0 {
|
||||
localLengthY = 1
|
||||
} else if flag&yIsSameOrPositiveYShortVector == 0 {
|
||||
localLengthY = 2
|
||||
}
|
||||
|
||||
if flag&repeatFlag != 0 {
|
||||
if L <= cursor {
|
||||
return errors.New("invalid simple glyph data flags (EOF)")
|
||||
}
|
||||
repeatCount := int(src[cursor])
|
||||
cursor++
|
||||
if i+repeatCount+1 > numPoints { // gracefully handle out of bounds
|
||||
repeatCount = numPoints - i - 1
|
||||
}
|
||||
subSlice := sg.Points[i+1 : i+repeatCount+1]
|
||||
for j := range subSlice {
|
||||
subSlice[j].Flag = flag
|
||||
}
|
||||
i += repeatCount
|
||||
localLengthX += repeatCount * localLengthX
|
||||
localLengthY += repeatCount * localLengthY
|
||||
}
|
||||
|
||||
coordinatesLengthX += localLengthX
|
||||
coordinatesLengthY += localLengthY
|
||||
}
|
||||
|
||||
src = src[cursor:]
|
||||
if L, E := len(src), coordinatesLengthX+coordinatesLengthY; L < E {
|
||||
return fmt.Errorf("EOF: expected length: %d, got %d", E, L)
|
||||
}
|
||||
|
||||
dataX, dataY := src[:coordinatesLengthX], src[coordinatesLengthX:coordinatesLengthX+coordinatesLengthY]
|
||||
// read x and y coordinates
|
||||
parseGlyphContourPoints(dataX, dataY, sg.Points)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// returns the position after the read and the relative coordinate
|
||||
// the input slice has already been checked for length
|
||||
func readContourPoint(flag byte, data []byte, pos int, shortFlag, sameFlag uint8) (int, int16) {
|
||||
var v int16
|
||||
if flag&shortFlag != 0 {
|
||||
val := data[pos]
|
||||
pos++
|
||||
if flag&sameFlag != 0 {
|
||||
v += int16(val)
|
||||
} else {
|
||||
v -= int16(val)
|
||||
}
|
||||
} else if flag&sameFlag == 0 {
|
||||
val := binary.BigEndian.Uint16(data[pos:])
|
||||
pos += 2
|
||||
v += int16(val)
|
||||
}
|
||||
return pos, v
|
||||
}
|
||||
|
||||
// update the points in place
|
||||
func parseGlyphContourPoints(dataX, dataY []byte, points []GlyphContourPoint) {
|
||||
var (
|
||||
posX, posY int // position into data
|
||||
vX, offsetX, vY, offsetY int16 // coordinates are relative to the previous
|
||||
)
|
||||
for i, p := range points {
|
||||
posX, offsetX = readContourPoint(p.Flag, dataX, posX, xShortVector, xIsSameOrPositiveXShortVector)
|
||||
vX += offsetX
|
||||
points[i].X = vX
|
||||
|
||||
posY, offsetY = readContourPoint(p.Flag, dataY, posY, yShortVector, yIsSameOrPositiveYShortVector)
|
||||
vY += offsetY
|
||||
points[i].Y = vY
|
||||
}
|
||||
}
|
||||
|
||||
type CompositeGlyph struct {
|
||||
Glyphs []CompositeGlyphPart `isOpaque:""`
|
||||
Instructions []byte `isOpaque:""`
|
||||
}
|
||||
|
||||
const arg1And2AreWords = 1
|
||||
|
||||
func (cg *CompositeGlyph) parseGlyphs(src []byte) error {
|
||||
const (
|
||||
_ = 1 << iota
|
||||
_
|
||||
_
|
||||
weHaveAScale
|
||||
_
|
||||
moreComponents
|
||||
weHaveAnXAndYScale
|
||||
weHaveATwoByTwo
|
||||
weHaveInstructions
|
||||
)
|
||||
var flags uint16
|
||||
for do := true; do; do = flags&moreComponents != 0 {
|
||||
var part CompositeGlyphPart
|
||||
|
||||
if L := len(src); L < 4 {
|
||||
return fmt.Errorf("EOF: expected length: %d, got %d", 4, L)
|
||||
}
|
||||
flags = binary.BigEndian.Uint16(src)
|
||||
part.Flags = flags
|
||||
part.GlyphIndex = GlyphID(binary.BigEndian.Uint16(src[2:]))
|
||||
|
||||
if flags&arg1And2AreWords != 0 { // 16 bits
|
||||
if L, E := len(src), 4+4; L < E {
|
||||
return fmt.Errorf("EOF: expected length: %d, got %d", E, L)
|
||||
}
|
||||
part.arg1 = binary.BigEndian.Uint16(src[4:])
|
||||
part.arg2 = binary.BigEndian.Uint16(src[6:])
|
||||
src = src[8:]
|
||||
} else {
|
||||
if L, E := len(src), 4+2; L < E {
|
||||
return fmt.Errorf("EOF: expected length: %d, got %d", E, L)
|
||||
}
|
||||
part.arg1 = uint16(src[4])
|
||||
part.arg2 = uint16(src[5])
|
||||
src = src[6:]
|
||||
}
|
||||
|
||||
part.Scale[0], part.Scale[3] = 1, 1
|
||||
if flags&weHaveAScale != 0 {
|
||||
if L := len(src); L < 2 {
|
||||
return fmt.Errorf("EOF: expected length: %d, got %d", 2, L)
|
||||
}
|
||||
part.Scale[0] = Float214FromUint(binary.BigEndian.Uint16(src))
|
||||
part.Scale[3] = part.Scale[0]
|
||||
src = src[2:]
|
||||
} else if flags&weHaveAnXAndYScale != 0 {
|
||||
if L := len(src); L < 4 {
|
||||
return fmt.Errorf("EOF: expected length: %d, got %d", 4, L)
|
||||
}
|
||||
part.Scale[0] = Float214FromUint(binary.BigEndian.Uint16(src))
|
||||
part.Scale[3] = Float214FromUint(binary.BigEndian.Uint16(src[2:]))
|
||||
src = src[4:]
|
||||
} else if flags&weHaveATwoByTwo != 0 {
|
||||
if L := len(src); L < 8 {
|
||||
return fmt.Errorf("EOF: expected length: %d, got %d", 8, L)
|
||||
}
|
||||
part.Scale[0] = Float214FromUint(binary.BigEndian.Uint16(src))
|
||||
part.Scale[1] = Float214FromUint(binary.BigEndian.Uint16(src[2:]))
|
||||
part.Scale[2] = Float214FromUint(binary.BigEndian.Uint16(src[4:]))
|
||||
part.Scale[3] = Float214FromUint(binary.BigEndian.Uint16(src[6:]))
|
||||
src = src[8:]
|
||||
}
|
||||
|
||||
cg.Glyphs = append(cg.Glyphs, part)
|
||||
}
|
||||
|
||||
if flags&weHaveInstructions != 0 {
|
||||
if L := len(src); L < 2 {
|
||||
return fmt.Errorf("EOF: expected length: 2, got %d", L)
|
||||
}
|
||||
E := int(binary.BigEndian.Uint16(src))
|
||||
if L := len(src); L < E {
|
||||
return fmt.Errorf("EOF: expected length: %d, got %d", E, len(src))
|
||||
}
|
||||
cg.Instructions = src[0:E]
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// already handled in parseGlyphs
|
||||
func (cg *CompositeGlyph) parseInstructions(_ []byte) error { return nil }
|
||||
|
||||
type CompositeGlyphPart struct {
|
||||
Flags uint16
|
||||
GlyphIndex GlyphID
|
||||
|
||||
// raw value before interpretation:
|
||||
// arg1 and arg2 may be either :
|
||||
// - unsigned, when used as indices into the contour point list
|
||||
// (see ArgsAsIndices)
|
||||
// - signed, when used as translation in the transformation matrix
|
||||
// (see ArgsAsTranslation)
|
||||
arg1, arg2 uint16
|
||||
|
||||
// Scale is a matrix x, 01, 10, y ; default to identity
|
||||
Scale [4]float32
|
||||
}
|
||||
|
||||
func (c *CompositeGlyphPart) HasUseMyMetrics() bool {
|
||||
const useMyMetrics = 0x0200
|
||||
return c.Flags&useMyMetrics != 0
|
||||
}
|
||||
|
||||
// return true if arg1 and arg2 indicated an anchor point,
|
||||
// not offsets
|
||||
func (c *CompositeGlyphPart) IsAnchored() bool {
|
||||
const argsAreXyValues = 0x0002
|
||||
return c.Flags&argsAreXyValues == 0
|
||||
}
|
||||
|
||||
func (c *CompositeGlyphPart) IsScaledOffsets() bool {
|
||||
const (
|
||||
scaledComponentOffset = 0x0800
|
||||
unscaledComponentOffset = 0x1000
|
||||
)
|
||||
return c.Flags&(scaledComponentOffset|unscaledComponentOffset) == scaledComponentOffset
|
||||
}
|
||||
|
||||
func (c *CompositeGlyphPart) ArgsAsTranslation() (int16, int16) {
|
||||
// arg1 and arg2 are interpreted as signed integers here
|
||||
// the conversion depends on the original size (8 or 16 bits)
|
||||
if c.Flags&arg1And2AreWords != 0 {
|
||||
return int16(c.arg1), int16(c.arg2)
|
||||
}
|
||||
return int16(int8(uint8(c.arg1))), int16(int8(uint8(c.arg2)))
|
||||
}
|
||||
|
||||
func (c *CompositeGlyphPart) ArgsAsIndices() (int, int) {
|
||||
// arg1 and arg2 are interpreted as unsigned integers here
|
||||
return int(c.arg1), int(c.arg2)
|
||||
}
|
||||
+110
@@ -0,0 +1,110 @@
|
||||
// SPDX-License-Identifier: Unlicense OR BSD-3-Clause
|
||||
|
||||
package tables
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// Code generated by binarygen from glyphs_misc_src.go. DO NOT EDIT
|
||||
|
||||
func ParseSVG(src []byte) (SVG, int, error) {
|
||||
var item SVG
|
||||
n := 0
|
||||
if L := len(src); L < 10 {
|
||||
return item, 0, fmt.Errorf("reading SVG: "+"EOF: expected length: 10, got %d", L)
|
||||
}
|
||||
_ = src[9] // early bound checking
|
||||
item.version = binary.BigEndian.Uint16(src[0:])
|
||||
offsetSVGDocumentList := int(binary.BigEndian.Uint32(src[2:]))
|
||||
item.reserved = binary.BigEndian.Uint32(src[6:])
|
||||
n += 10
|
||||
|
||||
{
|
||||
if offsetSVGDocumentList != 0 { // ignore null offset
|
||||
if L := len(src); L < offsetSVGDocumentList {
|
||||
return item, 0, fmt.Errorf("reading SVG: "+"EOF: expected length: %d, got %d", offsetSVGDocumentList, L)
|
||||
}
|
||||
|
||||
var err error
|
||||
item.SVGDocumentList, _, err = ParseSVGDocumentList(src[offsetSVGDocumentList:])
|
||||
if err != nil {
|
||||
return item, 0, fmt.Errorf("reading SVG: %s", err)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
return item, n, nil
|
||||
}
|
||||
|
||||
func ParseSVGDocumentList(src []byte) (SVGDocumentList, int, error) {
|
||||
var item SVGDocumentList
|
||||
n := 0
|
||||
if L := len(src); L < 2 {
|
||||
return item, 0, fmt.Errorf("reading SVGDocumentList: "+"EOF: expected length: 2, got %d", L)
|
||||
}
|
||||
arrayLengthDocumentRecords := int(binary.BigEndian.Uint16(src[0:]))
|
||||
n += 2
|
||||
|
||||
{
|
||||
|
||||
if L := len(src); L < 2+arrayLengthDocumentRecords*12 {
|
||||
return item, 0, fmt.Errorf("reading SVGDocumentList: "+"EOF: expected length: %d, got %d", 2+arrayLengthDocumentRecords*12, L)
|
||||
}
|
||||
|
||||
item.DocumentRecords = make([]SVGDocumentRecord, arrayLengthDocumentRecords) // allocation guarded by the previous check
|
||||
for i := range item.DocumentRecords {
|
||||
item.DocumentRecords[i].mustParse(src[2+i*12:])
|
||||
}
|
||||
n += arrayLengthDocumentRecords * 12
|
||||
}
|
||||
{
|
||||
|
||||
item.SVGRawData = src[0:]
|
||||
n = len(src)
|
||||
}
|
||||
return item, n, nil
|
||||
}
|
||||
|
||||
func ParseVORG(src []byte) (VORG, int, error) {
|
||||
var item VORG
|
||||
n := 0
|
||||
if L := len(src); L < 8 {
|
||||
return item, 0, fmt.Errorf("reading VORG: "+"EOF: expected length: 8, got %d", L)
|
||||
}
|
||||
_ = src[7] // early bound checking
|
||||
item.majorVersion = binary.BigEndian.Uint16(src[0:])
|
||||
item.minorVersion = binary.BigEndian.Uint16(src[2:])
|
||||
item.DefaultVertOriginY = int16(binary.BigEndian.Uint16(src[4:]))
|
||||
arrayLengthVertOriginYMetrics := int(binary.BigEndian.Uint16(src[6:]))
|
||||
n += 8
|
||||
|
||||
{
|
||||
|
||||
if L := len(src); L < 8+arrayLengthVertOriginYMetrics*4 {
|
||||
return item, 0, fmt.Errorf("reading VORG: "+"EOF: expected length: %d, got %d", 8+arrayLengthVertOriginYMetrics*4, L)
|
||||
}
|
||||
|
||||
item.VertOriginYMetrics = make([]VertOriginYMetric, arrayLengthVertOriginYMetrics) // allocation guarded by the previous check
|
||||
for i := range item.VertOriginYMetrics {
|
||||
item.VertOriginYMetrics[i].mustParse(src[8+i*4:])
|
||||
}
|
||||
n += arrayLengthVertOriginYMetrics * 4
|
||||
}
|
||||
return item, n, nil
|
||||
}
|
||||
|
||||
func (item *SVGDocumentRecord) mustParse(src []byte) {
|
||||
_ = src[11] // early bound checking
|
||||
item.StartGlyphID = binary.BigEndian.Uint16(src[0:])
|
||||
item.EndGlyphID = binary.BigEndian.Uint16(src[2:])
|
||||
item.SvgDocOffset = Offset32(binary.BigEndian.Uint32(src[4:]))
|
||||
item.SvgDocLength = binary.BigEndian.Uint32(src[8:])
|
||||
}
|
||||
|
||||
func (item *VertOriginYMetric) mustParse(src []byte) {
|
||||
_ = src[3] // early bound checking
|
||||
item.GlyphIndex = binary.BigEndian.Uint16(src[0:])
|
||||
item.VertOriginY = int16(binary.BigEndian.Uint16(src[2:]))
|
||||
}
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
// SPDX-License-Identifier: Unlicense OR BSD-3-Clause
|
||||
|
||||
package tables
|
||||
|
||||
// SVG is the SVG (Scalable Vector Graphics) table.
|
||||
// See - https://learn.microsoft.com/fr-fr/typography/opentype/spec/svg
|
||||
type SVG struct {
|
||||
version uint16 // Table version (starting at 0). Set to 0.
|
||||
SVGDocumentList SVGDocumentList `offsetSize:"Offset32"` // Offset to the SVG Document List, from the start of the SVG table. Must be non-zero.
|
||||
reserved uint32 // Set to 0.
|
||||
}
|
||||
|
||||
type SVGDocumentList struct {
|
||||
DocumentRecords []SVGDocumentRecord `arrayCount:"FirstUint16"` // [numEntries] Array of SVG document records.
|
||||
SVGRawData []byte `subsliceStart:"AtStart" arrayCount:"ToEnd"`
|
||||
}
|
||||
|
||||
// Each SVG document record specifies a range of glyph IDs (from startGlyphID to endGlyphID, inclusive), and the location of its associated SVG document in the SVG table.
|
||||
type SVGDocumentRecord struct {
|
||||
StartGlyphID GlyphID // The first glyph ID for the range covered by this record.
|
||||
EndGlyphID GlyphID // The last glyph ID for the range covered by this record.
|
||||
SvgDocOffset Offset32 // Offset from the beginning of the SVGDocumentList to an SVG document. Must be non-zero.
|
||||
SvgDocLength uint32 // Length of the SVG document data. Must be non-zero.
|
||||
}
|
||||
|
||||
// CFF is the Compact Font Format Table.
|
||||
// Since it used its own format, quite different from the regular Opentype format,
|
||||
// its interpretation is handled externally (see font/cff).
|
||||
// See also https://learn.microsoft.com/fr-fr/typography/opentype/spec/cff
|
||||
type CFF = []byte
|
||||
|
||||
// VORG is the Vertical Origin Table
|
||||
// See - https://learn.microsoft.com/fr-fr/typography/opentype/spec/vorg
|
||||
type VORG struct {
|
||||
majorVersion uint16 // Major version (starting at 1). Set to 1.
|
||||
minorVersion uint16 // Minor version (starting at 0). Set to 0.
|
||||
DefaultVertOriginY int16 // The y coordinate of a glyph’s vertical origin, in the font’s design coordinate system, to be used if no entry is present for the glyph in the vertOriginYMetrics array.
|
||||
VertOriginYMetrics []VertOriginYMetric `arrayCount:"FirstUint16"`
|
||||
}
|
||||
|
||||
// YOrigin returns the vertical origin for [glyph].
|
||||
func (t *VORG) YOrigin(glyph GlyphID) int16 {
|
||||
// binary search
|
||||
for i, j := 0, len(t.VertOriginYMetrics); i < j; {
|
||||
h := i + (j-i)/2
|
||||
entry := t.VertOriginYMetrics[h]
|
||||
if glyph < entry.GlyphIndex {
|
||||
j = h
|
||||
} else if entry.GlyphIndex < glyph {
|
||||
i = h + 1
|
||||
} else {
|
||||
return entry.VertOriginY
|
||||
}
|
||||
}
|
||||
return t.DefaultVertOriginY
|
||||
}
|
||||
|
||||
type VertOriginYMetric struct {
|
||||
GlyphIndex GlyphID // Glyph index.
|
||||
VertOriginY int16 // Y coordinate, in the font’s design coordinate system, of the vertical origin of glyph with index glyphIndex.
|
||||
}
|
||||
+92
@@ -0,0 +1,92 @@
|
||||
// SPDX-License-Identifier: Unlicense OR BSD-3-Clause
|
||||
|
||||
package tables
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// Code generated by binarygen from glyphs_sbix_src.go. DO NOT EDIT
|
||||
|
||||
func ParseBitmapGlyphData(src []byte) (BitmapGlyphData, int, error) {
|
||||
var item BitmapGlyphData
|
||||
n := 0
|
||||
if L := len(src); L < 8 {
|
||||
return item, 0, fmt.Errorf("reading BitmapGlyphData: "+"EOF: expected length: 8, got %d", L)
|
||||
}
|
||||
_ = src[7] // early bound checking
|
||||
item.OriginOffsetX = int16(binary.BigEndian.Uint16(src[0:]))
|
||||
item.OriginOffsetY = int16(binary.BigEndian.Uint16(src[2:]))
|
||||
item.GraphicType = Tag(binary.BigEndian.Uint32(src[4:]))
|
||||
n += 8
|
||||
|
||||
{
|
||||
|
||||
item.Data = src[8:]
|
||||
n = len(src)
|
||||
}
|
||||
return item, n, nil
|
||||
}
|
||||
|
||||
func ParseSbix(src []byte, numGlyphs int) (Sbix, int, error) {
|
||||
var item Sbix
|
||||
n := 0
|
||||
if L := len(src); L < 8 {
|
||||
return item, 0, fmt.Errorf("reading Sbix: "+"EOF: expected length: 8, got %d", L)
|
||||
}
|
||||
_ = src[7] // early bound checking
|
||||
item.version = binary.BigEndian.Uint16(src[0:])
|
||||
item.Flags = binary.BigEndian.Uint16(src[2:])
|
||||
arrayLengthStrikes := int(binary.BigEndian.Uint32(src[4:]))
|
||||
n += 8
|
||||
|
||||
{
|
||||
|
||||
if L := len(src); L < 8+arrayLengthStrikes*4 {
|
||||
return item, 0, fmt.Errorf("reading Sbix: "+"EOF: expected length: %d, got %d", 8+arrayLengthStrikes*4, L)
|
||||
}
|
||||
|
||||
item.Strikes = make([]Strike, arrayLengthStrikes) // allocation guarded by the previous check
|
||||
for i := range item.Strikes {
|
||||
offset := int(binary.BigEndian.Uint32(src[8+i*4:]))
|
||||
// ignore null offsets
|
||||
if offset == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
if L := len(src); L < offset {
|
||||
return item, 0, fmt.Errorf("reading Sbix: "+"EOF: expected length: %d, got %d", offset, L)
|
||||
}
|
||||
|
||||
var err error
|
||||
item.Strikes[i], _, err = ParseStrike(src[offset:], numGlyphs)
|
||||
if err != nil {
|
||||
return item, 0, fmt.Errorf("reading Sbix: %s", err)
|
||||
}
|
||||
}
|
||||
n += arrayLengthStrikes * 4
|
||||
}
|
||||
return item, n, nil
|
||||
}
|
||||
|
||||
func ParseStrike(src []byte, numGlyphs int) (Strike, int, error) {
|
||||
var item Strike
|
||||
n := 0
|
||||
if L := len(src); L < 4 {
|
||||
return item, 0, fmt.Errorf("reading Strike: "+"EOF: expected length: 4, got %d", L)
|
||||
}
|
||||
_ = src[3] // early bound checking
|
||||
item.Ppem = binary.BigEndian.Uint16(src[0:])
|
||||
item.Ppi = binary.BigEndian.Uint16(src[2:])
|
||||
n += 4
|
||||
|
||||
{
|
||||
|
||||
err := item.parseGlyphDatas(src[:], numGlyphs)
|
||||
if err != nil {
|
||||
return item, 0, fmt.Errorf("reading Strike: %s", err)
|
||||
}
|
||||
}
|
||||
return item, n, nil
|
||||
}
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
// SPDX-License-Identifier: Unlicense OR BSD-3-Clause
|
||||
|
||||
package tables
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// Sbix is the Standard Bitmap Graphics Table
|
||||
// See - https://learn.microsoft.com/fr-fr/typography/opentype/spec/sbix
|
||||
type Sbix struct {
|
||||
version uint16 // Table version number — set to 1
|
||||
// Bit 0: Set to 1.
|
||||
// Bit 1: Draw outlines.
|
||||
// Bits 2 to 15: reserved (set to 0).
|
||||
Flags uint16
|
||||
Strikes []Strike `arrayCount:"FirstUint32" offsetsArray:"Offset32"` // [numStrikes] Offsets from the beginning of the 'sbix' table to data for each individual bitmap strike.
|
||||
}
|
||||
|
||||
// Strike stores one size of bitmap glyphs in the 'sbix' table.
|
||||
// binarygen: argument=numGlyphs int
|
||||
type Strike struct {
|
||||
Ppem uint16 // The PPEM size for which this strike was designed.
|
||||
Ppi uint16 // The device pixel density (in PPI) for which this strike was designed. (E.g., 96 PPI, 192 PPI.)
|
||||
GlyphDatas []BitmapGlyphData `isOpaque:""` //[numGlyphs+1] Offset from the beginning of the strike data header to bitmap data for an individual glyph ID.
|
||||
}
|
||||
|
||||
func (st *Strike) parseGlyphDatas(src []byte, numGlyphs int) error {
|
||||
const headerSize = 4
|
||||
offsets, err := ParseLoca(src[headerSize:], numGlyphs, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
st.GlyphDatas = make([]BitmapGlyphData, numGlyphs)
|
||||
for i := range st.GlyphDatas {
|
||||
start, end := offsets[i], offsets[i+1]
|
||||
if start == end { // no data
|
||||
continue
|
||||
}
|
||||
|
||||
if start > end {
|
||||
return fmt.Errorf("invalid strike offsets %d > %d", start, end)
|
||||
}
|
||||
|
||||
if L := len(src); L < int(end) {
|
||||
return fmt.Errorf("EOF: expected length: %d, got %d", end, L)
|
||||
}
|
||||
|
||||
st.GlyphDatas[i], _, err = ParseBitmapGlyphData(src[start:end])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type BitmapGlyphData struct {
|
||||
OriginOffsetX int16 // The horizontal (x-axis) position of the left edge of the bitmap graphic in relation to the glyph design space origin.
|
||||
OriginOffsetY int16 // The vertical (y-axis) position of the bottom edge of the bitmap graphic in relation to the glyph design space origin.
|
||||
GraphicType Tag // Indicates the format of the embedded graphic data: one of 'jpg ', 'png ' or 'tiff', or the special format 'dupe'.
|
||||
Data []byte `arrayCount:"ToEnd"` // The actual embedded graphic data. The total length is inferred from sequential entries in the glyphDataOffsets array and the fixed size (8 bytes) of the preceding fields.
|
||||
}
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
// SPDX-License-Identifier: Unlicense OR BSD-3-Clause
|
||||
|
||||
package tables
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// Code generated by binarygen from head_src.go. DO NOT EDIT
|
||||
|
||||
func (item *Head) mustParse(src []byte) {
|
||||
_ = src[53] // early bound checking
|
||||
item.majorVersion = binary.BigEndian.Uint16(src[0:])
|
||||
item.minorVersion = binary.BigEndian.Uint16(src[2:])
|
||||
item.fontRevision = binary.BigEndian.Uint32(src[4:])
|
||||
item.checksumAdjustment = binary.BigEndian.Uint32(src[8:])
|
||||
item.magicNumber = binary.BigEndian.Uint32(src[12:])
|
||||
item.flags = binary.BigEndian.Uint16(src[16:])
|
||||
item.UnitsPerEm = binary.BigEndian.Uint16(src[18:])
|
||||
item.created = binary.BigEndian.Uint64(src[20:])
|
||||
item.modified = binary.BigEndian.Uint64(src[28:])
|
||||
item.XMin = int16(binary.BigEndian.Uint16(src[36:]))
|
||||
item.YMin = int16(binary.BigEndian.Uint16(src[38:]))
|
||||
item.XMax = int16(binary.BigEndian.Uint16(src[40:]))
|
||||
item.YMax = int16(binary.BigEndian.Uint16(src[42:]))
|
||||
item.MacStyle = binary.BigEndian.Uint16(src[44:])
|
||||
item.lowestRecPPEM = binary.BigEndian.Uint16(src[46:])
|
||||
item.fontDirectionHint = int16(binary.BigEndian.Uint16(src[48:]))
|
||||
item.IndexToLocFormat = int16(binary.BigEndian.Uint16(src[50:]))
|
||||
item.glyphDataFormat = int16(binary.BigEndian.Uint16(src[52:]))
|
||||
}
|
||||
|
||||
func ParseHead(src []byte) (Head, int, error) {
|
||||
var item Head
|
||||
n := 0
|
||||
if L := len(src); L < 54 {
|
||||
return item, 0, fmt.Errorf("reading Head: "+"EOF: expected length: 54, got %d", L)
|
||||
}
|
||||
item.mustParse(src)
|
||||
n += 54
|
||||
return item, n, nil
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
// SPDX-License-Identifier: Unlicense OR BSD-3-Clause
|
||||
|
||||
package tables
|
||||
|
||||
// TableHead contains critical information about the rest of the font.
|
||||
// https://learn.microsoft.com/en-us/typography/opentype/spec/head
|
||||
// https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6head.html
|
||||
// https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6bhed.html
|
||||
type Head struct {
|
||||
majorVersion uint16
|
||||
minorVersion uint16
|
||||
fontRevision uint32
|
||||
checksumAdjustment uint32
|
||||
magicNumber uint32
|
||||
flags uint16
|
||||
UnitsPerEm uint16
|
||||
created longdatetime
|
||||
modified longdatetime
|
||||
XMin int16
|
||||
YMin int16
|
||||
XMax int16
|
||||
YMax int16
|
||||
MacStyle uint16
|
||||
lowestRecPPEM uint16
|
||||
fontDirectionHint int16
|
||||
IndexToLocFormat int16
|
||||
glyphDataFormat int16
|
||||
}
|
||||
|
||||
// Upem returns a sanitize version of the 'UnitsPerEm' field.
|
||||
func (head *Head) Upem() uint16 {
|
||||
if head.UnitsPerEm < 16 || head.UnitsPerEm > 16384 {
|
||||
return 1000
|
||||
}
|
||||
return head.UnitsPerEm
|
||||
}
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
// SPDX-License-Identifier: Unlicense OR BSD-3-Clause
|
||||
|
||||
package tables
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// Code generated by binarygen from hhea_vhea_src.go. DO NOT EDIT
|
||||
|
||||
func (item *Hhea) mustParse(src []byte) {
|
||||
_ = src[35] // early bound checking
|
||||
item.majorVersion = binary.BigEndian.Uint16(src[0:])
|
||||
item.minorVersion = binary.BigEndian.Uint16(src[2:])
|
||||
item.Ascender = int16(binary.BigEndian.Uint16(src[4:]))
|
||||
item.Descender = int16(binary.BigEndian.Uint16(src[6:]))
|
||||
item.LineGap = int16(binary.BigEndian.Uint16(src[8:]))
|
||||
item.AdvanceMax = binary.BigEndian.Uint16(src[10:])
|
||||
item.MinFirstSideBearing = int16(binary.BigEndian.Uint16(src[12:]))
|
||||
item.MinSecondSideBearing = int16(binary.BigEndian.Uint16(src[14:]))
|
||||
item.MaxExtent = int16(binary.BigEndian.Uint16(src[16:]))
|
||||
item.CaretSlopeRise = int16(binary.BigEndian.Uint16(src[18:]))
|
||||
item.CaretSlopeRun = int16(binary.BigEndian.Uint16(src[20:]))
|
||||
item.CaretOffset = int16(binary.BigEndian.Uint16(src[22:]))
|
||||
item.reserved[0] = binary.BigEndian.Uint16(src[24:])
|
||||
item.reserved[1] = binary.BigEndian.Uint16(src[26:])
|
||||
item.reserved[2] = binary.BigEndian.Uint16(src[28:])
|
||||
item.reserved[3] = binary.BigEndian.Uint16(src[30:])
|
||||
item.metricDataformat = int16(binary.BigEndian.Uint16(src[32:]))
|
||||
item.NumOfLongMetrics = binary.BigEndian.Uint16(src[34:])
|
||||
}
|
||||
|
||||
func ParseHhea(src []byte) (Hhea, int, error) {
|
||||
var item Hhea
|
||||
n := 0
|
||||
if L := len(src); L < 36 {
|
||||
return item, 0, fmt.Errorf("reading Hhea: "+"EOF: expected length: 36, got %d", L)
|
||||
}
|
||||
item.mustParse(src)
|
||||
n += 36
|
||||
return item, n, nil
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
// SPDX-License-Identifier: Unlicense OR BSD-3-Clause
|
||||
|
||||
package tables
|
||||
|
||||
// https://learn.microsoft.com/en-us/typography/opentype/spec/hhea
|
||||
type Hhea struct {
|
||||
majorVersion uint16
|
||||
minorVersion uint16
|
||||
Ascender int16
|
||||
Descender int16
|
||||
LineGap int16
|
||||
AdvanceMax uint16
|
||||
MinFirstSideBearing int16
|
||||
MinSecondSideBearing int16
|
||||
MaxExtent int16
|
||||
CaretSlopeRise int16
|
||||
CaretSlopeRun int16
|
||||
CaretOffset int16
|
||||
reserved [4]uint16
|
||||
metricDataformat int16
|
||||
NumOfLongMetrics uint16
|
||||
}
|
||||
|
||||
// https://learn.microsoft.com/en-us/typography/opentype/spec/vhea
|
||||
type Vhea = Hhea
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
// SPDX-License-Identifier: Unlicense OR BSD-3-Clause
|
||||
|
||||
package tables
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// Code generated by binarygen from hmtx_vmtx_src.go. DO NOT EDIT
|
||||
|
||||
func (item *LongHorMetric) mustParse(src []byte) {
|
||||
_ = src[3] // early bound checking
|
||||
item.AdvanceWidth = int16(binary.BigEndian.Uint16(src[0:]))
|
||||
item.LeftSideBearing = int16(binary.BigEndian.Uint16(src[2:]))
|
||||
}
|
||||
|
||||
func ParseHmtx(src []byte, metricsCount int, leftSideBearingsCount int) (Hmtx, int, error) {
|
||||
var item Hmtx
|
||||
n := 0
|
||||
{
|
||||
|
||||
if L := len(src); L < metricsCount*4 {
|
||||
return item, 0, fmt.Errorf("reading Hmtx: "+"EOF: expected length: %d, got %d", metricsCount*4, L)
|
||||
}
|
||||
|
||||
item.Metrics = make([]LongHorMetric, metricsCount) // allocation guarded by the previous check
|
||||
for i := range item.Metrics {
|
||||
item.Metrics[i].mustParse(src[i*4:])
|
||||
}
|
||||
n += metricsCount * 4
|
||||
}
|
||||
{
|
||||
|
||||
if L := len(src); L < n+leftSideBearingsCount*2 {
|
||||
return item, 0, fmt.Errorf("reading Hmtx: "+"EOF: expected length: %d, got %d", n+leftSideBearingsCount*2, L)
|
||||
}
|
||||
|
||||
item.LeftSideBearings = make([]int16, leftSideBearingsCount) // allocation guarded by the previous check
|
||||
for i := range item.LeftSideBearings {
|
||||
item.LeftSideBearings[i] = int16(binary.BigEndian.Uint16(src[n+i*2:]))
|
||||
}
|
||||
n += leftSideBearingsCount * 2
|
||||
}
|
||||
return item, n, nil
|
||||
}
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
// SPDX-License-Identifier: Unlicense OR BSD-3-Clause
|
||||
|
||||
package tables
|
||||
|
||||
// https://learn.microsoft.com/en-us/typography/opentype/spec/hmtx
|
||||
type Hmtx struct {
|
||||
Metrics []LongHorMetric `arrayCount:""`
|
||||
// avances are padded with the last value
|
||||
// and side bearings are given
|
||||
LeftSideBearings []int16 `arrayCount:""`
|
||||
}
|
||||
|
||||
func (table Hmtx) IsEmpty() bool {
|
||||
return len(table.Metrics)+len(table.LeftSideBearings) == 0
|
||||
}
|
||||
|
||||
// Advance returns the base side bearing, defaulting to 0 for invalid glyph index
|
||||
func (table Hmtx) Advance(gid GlyphID) int16 {
|
||||
LM, LS := len(table.Metrics), len(table.LeftSideBearings)
|
||||
index := int(gid)
|
||||
if index < LM {
|
||||
return table.Metrics[index].AdvanceWidth
|
||||
} else if index < LS+LM { // return the last value
|
||||
return table.Metrics[len(table.Metrics)-1].AdvanceWidth
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// SideBearing returns the base side bearing, defaulting to 0 for invalid glyph index
|
||||
func (table Hmtx) SideBearing(gid GlyphID) int16 {
|
||||
LM, LS := len(table.Metrics), len(table.LeftSideBearings)
|
||||
index := int(gid)
|
||||
if index < LM {
|
||||
return table.Metrics[index].LeftSideBearing
|
||||
} else if index < LS+LM {
|
||||
return table.LeftSideBearings[index-LM]
|
||||
} else {
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
type LongHorMetric struct {
|
||||
AdvanceWidth, LeftSideBearing int16
|
||||
}
|
||||
|
||||
// https://learn.microsoft.com/en-us/typography/opentype/spec/vmtx
|
||||
type Vmtx = Hmtx
|
||||
+101
@@ -0,0 +1,101 @@
|
||||
// SPDX-License-Identifier: Unlicense OR BSD-3-Clause
|
||||
|
||||
package tables
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// Kern is the kern table. It has multiple header format, defined in Apple AAT and Microsoft OT
|
||||
// specs, but the subtable data actually are the same.
|
||||
//
|
||||
// Microsoft (OT) format
|
||||
//
|
||||
// version uint16 : Table version number (0)
|
||||
// nTables uint16 : Number of subtables in the kerning table.
|
||||
//
|
||||
// Apple (AAT) old format
|
||||
//
|
||||
// version uint16 : The version number of the kerning table (0x0001 for the current version).
|
||||
// nTables uint16 : The number of subtables included in the kerning table.
|
||||
//
|
||||
// Apple (AAT) new format
|
||||
//
|
||||
// version uint32 : The version number of the kerning table (0x00010000 for the current version).
|
||||
// nTables uint32 : The number of subtables included in the kerning table.
|
||||
//
|
||||
// See - https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6kern.html
|
||||
// and - https://learn.microsoft.com/fr-fr/typography/opentype/spec/kern
|
||||
type Kern struct {
|
||||
version uint16
|
||||
Tables []KernSubtable
|
||||
}
|
||||
|
||||
// We apply the following logic:
|
||||
// - read the first uint16 -> it's always the major version
|
||||
// - if it's 0, we have a Miscrosoft table
|
||||
// - if it's 1, we have an Apple table. We read the next uint16,
|
||||
// to differentiate between the old and the new Apple format.
|
||||
func ParseKern(src []byte) (Kern, int, error) {
|
||||
if L := len(src); L < 4 {
|
||||
return Kern{}, 0, fmt.Errorf("reading Kern: "+"EOF: expected length: 4, got %d", L)
|
||||
}
|
||||
|
||||
var numTables uint32
|
||||
|
||||
major := binary.BigEndian.Uint16(src)
|
||||
switch major {
|
||||
case 0:
|
||||
numTables = uint32(binary.BigEndian.Uint16(src[2:]))
|
||||
src = src[4:]
|
||||
case 1:
|
||||
nextUint16 := binary.BigEndian.Uint16(src[2:])
|
||||
if nextUint16 == 0 {
|
||||
// either new format or old format with 0 subtables, the later being invalid (or at least useless)
|
||||
if len(src) < 8 {
|
||||
return Kern{}, 0, errors.New("invalid kern table version 1 (EOF)")
|
||||
}
|
||||
numTables = binary.BigEndian.Uint32(src[4:])
|
||||
src = src[8:]
|
||||
} else {
|
||||
// old format
|
||||
numTables = uint32(nextUint16)
|
||||
src = src[4:]
|
||||
}
|
||||
|
||||
default:
|
||||
return Kern{}, 0, fmt.Errorf("unsupported kern table version: %d", major)
|
||||
}
|
||||
|
||||
out := make([]KernSubtable, numTables)
|
||||
var (
|
||||
err error
|
||||
nbRead int
|
||||
isOT = major == 0
|
||||
)
|
||||
for i := range out {
|
||||
if L := len(src); L < nbRead {
|
||||
return Kern{}, 0, fmt.Errorf("reading Kern: "+"EOF: expected length: %d, got %d", nbRead, L)
|
||||
}
|
||||
src = src[nbRead:]
|
||||
if isOT {
|
||||
out[i], nbRead, err = ParseOTKernSubtableHeader(src)
|
||||
} else {
|
||||
out[i], nbRead, err = ParseAATKernSubtableHeader(src)
|
||||
}
|
||||
if err != nil {
|
||||
return Kern{}, 0, err
|
||||
}
|
||||
}
|
||||
|
||||
return Kern{
|
||||
version: major,
|
||||
Tables: out,
|
||||
}, 0, nil
|
||||
}
|
||||
|
||||
func (k AATKernSubtableHeader) Data() KernData { return k.data }
|
||||
|
||||
func (k OTKernSubtableHeader) Data() KernData { return k.data }
|
||||
+342
@@ -0,0 +1,342 @@
|
||||
// SPDX-License-Identifier: Unlicense OR BSD-3-Clause
|
||||
|
||||
package tables
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// Code generated by binarygen from kern_src.go. DO NOT EDIT
|
||||
|
||||
func ParseAATKernSubtableHeader(src []byte) (AATKernSubtableHeader, int, error) {
|
||||
var item AATKernSubtableHeader
|
||||
n := 0
|
||||
if L := len(src); L < 8 {
|
||||
return item, 0, fmt.Errorf("reading AATKernSubtableHeader: "+"EOF: expected length: 8, got %d", L)
|
||||
}
|
||||
_ = src[7] // early bound checking
|
||||
item.length = binary.BigEndian.Uint32(src[0:])
|
||||
item.Coverage = src[4]
|
||||
item.version = kernSTVersion(src[5])
|
||||
item.TupleCount = binary.BigEndian.Uint16(src[6:])
|
||||
n += 8
|
||||
|
||||
{
|
||||
var (
|
||||
read int
|
||||
err error
|
||||
)
|
||||
switch item.version {
|
||||
case kernSTVersion0:
|
||||
item.data, read, err = ParseKernData0(src[8:])
|
||||
case kernSTVersion1:
|
||||
item.data, read, err = ParseKernData1(src[8:])
|
||||
case kernSTVersion2:
|
||||
item.data, read, err = ParseKernData2(src[8:], src)
|
||||
case kernSTVersion3:
|
||||
item.data, read, err = ParseKernData3(src[8:])
|
||||
default:
|
||||
err = fmt.Errorf("unsupported KernDataVersion %d", item.version)
|
||||
}
|
||||
if err != nil {
|
||||
return item, 0, fmt.Errorf("reading AATKernSubtableHeader: %s", err)
|
||||
}
|
||||
n += read
|
||||
}
|
||||
var err error
|
||||
n, err = item.parseEnd(src)
|
||||
if err != nil {
|
||||
return item, 0, fmt.Errorf("reading AATKernSubtableHeader: %s", err)
|
||||
}
|
||||
|
||||
return item, n, nil
|
||||
}
|
||||
|
||||
func ParseAATStateTable(src []byte) (AATStateTable, int, error) {
|
||||
var item AATStateTable
|
||||
n := 0
|
||||
if L := len(src); L < 8 {
|
||||
return item, 0, fmt.Errorf("reading AATStateTable: "+"EOF: expected length: 8, got %d", L)
|
||||
}
|
||||
_ = src[7] // early bound checking
|
||||
item.StateSize = binary.BigEndian.Uint16(src[0:])
|
||||
offsetClassTable := int(binary.BigEndian.Uint16(src[2:]))
|
||||
item.stateArray = Offset16(binary.BigEndian.Uint16(src[4:]))
|
||||
item.entryTable = Offset16(binary.BigEndian.Uint16(src[6:]))
|
||||
n += 8
|
||||
|
||||
{
|
||||
if offsetClassTable != 0 { // ignore null offset
|
||||
if L := len(src); L < offsetClassTable {
|
||||
return item, 0, fmt.Errorf("reading AATStateTable: "+"EOF: expected length: %d, got %d", offsetClassTable, L)
|
||||
}
|
||||
|
||||
var err error
|
||||
item.ClassTable, _, err = ParseClassTable(src[offsetClassTable:])
|
||||
if err != nil {
|
||||
return item, 0, fmt.Errorf("reading AATStateTable: %s", err)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
{
|
||||
|
||||
err := item.parseStates(src[:])
|
||||
if err != nil {
|
||||
return item, 0, fmt.Errorf("reading AATStateTable: %s", err)
|
||||
}
|
||||
}
|
||||
{
|
||||
|
||||
read, err := item.parseEntries(src[:])
|
||||
if err != nil {
|
||||
return item, 0, fmt.Errorf("reading AATStateTable: %s", err)
|
||||
}
|
||||
n = read
|
||||
}
|
||||
return item, n, nil
|
||||
}
|
||||
|
||||
func ParseClassTable(src []byte) (ClassTable, int, error) {
|
||||
var item ClassTable
|
||||
n := 0
|
||||
if L := len(src); L < 4 {
|
||||
return item, 0, fmt.Errorf("reading ClassTable: "+"EOF: expected length: 4, got %d", L)
|
||||
}
|
||||
_ = src[3] // early bound checking
|
||||
item.StartGlyph = binary.BigEndian.Uint16(src[0:])
|
||||
arrayLengthValues := int(binary.BigEndian.Uint16(src[2:]))
|
||||
n += 4
|
||||
|
||||
{
|
||||
|
||||
L := int(4 + arrayLengthValues)
|
||||
if len(src) < L {
|
||||
return item, 0, fmt.Errorf("reading ClassTable: "+"EOF: expected length: %d, got %d", L, len(src))
|
||||
}
|
||||
item.Values = src[4:L]
|
||||
n = L
|
||||
}
|
||||
return item, n, nil
|
||||
}
|
||||
|
||||
func ParseKernData0(src []byte) (KernData0, int, error) {
|
||||
var item KernData0
|
||||
n := 0
|
||||
if L := len(src); L < 8 {
|
||||
return item, 0, fmt.Errorf("reading KernData0: "+"EOF: expected length: 8, got %d", L)
|
||||
}
|
||||
_ = src[7] // early bound checking
|
||||
item.nPairs = binary.BigEndian.Uint16(src[0:])
|
||||
item.searchRange = binary.BigEndian.Uint16(src[2:])
|
||||
item.entrySelector = binary.BigEndian.Uint16(src[4:])
|
||||
item.rangeShift = binary.BigEndian.Uint16(src[6:])
|
||||
n += 8
|
||||
|
||||
{
|
||||
arrayLength := int(item.nPairs)
|
||||
|
||||
if L := len(src); L < 8+arrayLength*6 {
|
||||
return item, 0, fmt.Errorf("reading KernData0: "+"EOF: expected length: %d, got %d", 8+arrayLength*6, L)
|
||||
}
|
||||
|
||||
item.Pairs = make([]Kernx0Record, arrayLength) // allocation guarded by the previous check
|
||||
for i := range item.Pairs {
|
||||
item.Pairs[i].mustParse(src[8+i*6:])
|
||||
}
|
||||
n += arrayLength * 6
|
||||
}
|
||||
return item, n, nil
|
||||
}
|
||||
|
||||
func ParseKernData1(src []byte) (KernData1, int, error) {
|
||||
var item KernData1
|
||||
n := 0
|
||||
{
|
||||
var (
|
||||
err error
|
||||
read int
|
||||
)
|
||||
item.AATStateTable, read, err = ParseAATStateTable(src[0:])
|
||||
if err != nil {
|
||||
return item, 0, fmt.Errorf("reading KernData1: %s", err)
|
||||
}
|
||||
n += read
|
||||
}
|
||||
if L := len(src); L < n+2 {
|
||||
return item, 0, fmt.Errorf("reading KernData1: "+"EOF: expected length: n + 2, got %d", L)
|
||||
}
|
||||
item.valueTable = binary.BigEndian.Uint16(src[n:])
|
||||
n += 2
|
||||
|
||||
{
|
||||
|
||||
err := item.parseValues(src[:])
|
||||
if err != nil {
|
||||
return item, 0, fmt.Errorf("reading KernData1: %s", err)
|
||||
}
|
||||
}
|
||||
return item, n, nil
|
||||
}
|
||||
|
||||
func ParseKernData2(src []byte, parentSrc []byte) (KernData2, int, error) {
|
||||
var item KernData2
|
||||
n := 0
|
||||
if L := len(src); L < 8 {
|
||||
return item, 0, fmt.Errorf("reading KernData2: "+"EOF: expected length: 8, got %d", L)
|
||||
}
|
||||
_ = src[7] // early bound checking
|
||||
item.rowWidth = binary.BigEndian.Uint16(src[0:])
|
||||
offsetLeft := int(binary.BigEndian.Uint16(src[2:]))
|
||||
offsetRight := int(binary.BigEndian.Uint16(src[4:]))
|
||||
item.KerningStart = Offset16(binary.BigEndian.Uint16(src[6:]))
|
||||
n += 8
|
||||
|
||||
{
|
||||
if offsetLeft != 0 { // ignore null offset
|
||||
if L := len(parentSrc); L < offsetLeft {
|
||||
return item, 0, fmt.Errorf("reading KernData2: "+"EOF: expected length: %d, got %d", offsetLeft, L)
|
||||
}
|
||||
|
||||
var err error
|
||||
item.Left, _, err = ParseAATLoopkup8Data(parentSrc[offsetLeft:])
|
||||
if err != nil {
|
||||
return item, 0, fmt.Errorf("reading KernData2: %s", err)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
{
|
||||
if offsetRight != 0 { // ignore null offset
|
||||
if L := len(parentSrc); L < offsetRight {
|
||||
return item, 0, fmt.Errorf("reading KernData2: "+"EOF: expected length: %d, got %d", offsetRight, L)
|
||||
}
|
||||
|
||||
var err error
|
||||
item.Right, _, err = ParseAATLoopkup8Data(parentSrc[offsetRight:])
|
||||
if err != nil {
|
||||
return item, 0, fmt.Errorf("reading KernData2: %s", err)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
{
|
||||
|
||||
err := item.parseKerningData(src[:], parentSrc)
|
||||
if err != nil {
|
||||
return item, 0, fmt.Errorf("reading KernData2: %s", err)
|
||||
}
|
||||
}
|
||||
return item, n, nil
|
||||
}
|
||||
|
||||
func ParseKernData3(src []byte) (KernData3, int, error) {
|
||||
var item KernData3
|
||||
n := 0
|
||||
if L := len(src); L < 6 {
|
||||
return item, 0, fmt.Errorf("reading KernData3: "+"EOF: expected length: 6, got %d", L)
|
||||
}
|
||||
_ = src[5] // early bound checking
|
||||
item.glyphCount = binary.BigEndian.Uint16(src[0:])
|
||||
item.kernValueCount = src[2]
|
||||
item.leftClassCount = src[3]
|
||||
item.RightClassCount = src[4]
|
||||
item.flags = src[5]
|
||||
n += 6
|
||||
|
||||
{
|
||||
arrayLength := int(item.kernValueCount)
|
||||
|
||||
if L := len(src); L < 6+arrayLength*2 {
|
||||
return item, 0, fmt.Errorf("reading KernData3: "+"EOF: expected length: %d, got %d", 6+arrayLength*2, L)
|
||||
}
|
||||
|
||||
item.Kernings = make([]int16, arrayLength) // allocation guarded by the previous check
|
||||
for i := range item.Kernings {
|
||||
item.Kernings[i] = int16(binary.BigEndian.Uint16(src[6+i*2:]))
|
||||
}
|
||||
n += arrayLength * 2
|
||||
}
|
||||
{
|
||||
arrayLength := int(item.glyphCount)
|
||||
|
||||
L := int(n + arrayLength)
|
||||
if len(src) < L {
|
||||
return item, 0, fmt.Errorf("reading KernData3: "+"EOF: expected length: %d, got %d", L, len(src))
|
||||
}
|
||||
item.LeftClass = src[n:L]
|
||||
n = L
|
||||
}
|
||||
{
|
||||
arrayLength := int(item.glyphCount)
|
||||
|
||||
L := int(n + arrayLength)
|
||||
if len(src) < L {
|
||||
return item, 0, fmt.Errorf("reading KernData3: "+"EOF: expected length: %d, got %d", L, len(src))
|
||||
}
|
||||
item.RightClass = src[n:L]
|
||||
n = L
|
||||
}
|
||||
{
|
||||
arrayLength := int(item.nKernIndex())
|
||||
|
||||
L := int(n + arrayLength)
|
||||
if len(src) < L {
|
||||
return item, 0, fmt.Errorf("reading KernData3: "+"EOF: expected length: %d, got %d", L, len(src))
|
||||
}
|
||||
item.KernIndex = src[n:L]
|
||||
n = L
|
||||
}
|
||||
var err error
|
||||
n, err = item.parseEnd(src)
|
||||
if err != nil {
|
||||
return item, 0, fmt.Errorf("reading KernData3: %s", err)
|
||||
}
|
||||
|
||||
return item, n, nil
|
||||
}
|
||||
|
||||
func ParseOTKernSubtableHeader(src []byte) (OTKernSubtableHeader, int, error) {
|
||||
var item OTKernSubtableHeader
|
||||
n := 0
|
||||
if L := len(src); L < 6 {
|
||||
return item, 0, fmt.Errorf("reading OTKernSubtableHeader: "+"EOF: expected length: 6, got %d", L)
|
||||
}
|
||||
_ = src[5] // early bound checking
|
||||
item.version = binary.BigEndian.Uint16(src[0:])
|
||||
item.length = binary.BigEndian.Uint16(src[2:])
|
||||
item.format = kernSTVersion(src[4])
|
||||
item.Coverage = src[5]
|
||||
n += 6
|
||||
|
||||
{
|
||||
var (
|
||||
read int
|
||||
err error
|
||||
)
|
||||
switch item.format {
|
||||
case kernSTVersion0:
|
||||
item.data, read, err = ParseKernData0(src[6:])
|
||||
case kernSTVersion1:
|
||||
item.data, read, err = ParseKernData1(src[6:])
|
||||
case kernSTVersion2:
|
||||
item.data, read, err = ParseKernData2(src[6:], src)
|
||||
case kernSTVersion3:
|
||||
item.data, read, err = ParseKernData3(src[6:])
|
||||
default:
|
||||
err = fmt.Errorf("unsupported KernDataVersion %d", item.format)
|
||||
}
|
||||
if err != nil {
|
||||
return item, 0, fmt.Errorf("reading OTKernSubtableHeader: %s", err)
|
||||
}
|
||||
n += read
|
||||
}
|
||||
var err error
|
||||
n, err = item.parseEnd(src)
|
||||
if err != nil {
|
||||
return item, 0, fmt.Errorf("reading OTKernSubtableHeader: %s", err)
|
||||
}
|
||||
|
||||
return item, n, nil
|
||||
}
|
||||
+143
@@ -0,0 +1,143 @@
|
||||
// SPDX-License-Identifier: Unlicense OR BSD-3-Clause
|
||||
|
||||
package tables
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type KernSubtable interface {
|
||||
// Data returns the actual kerning data
|
||||
Data() KernData
|
||||
}
|
||||
|
||||
type OTKernSubtableHeader struct {
|
||||
version uint16 // Kern subtable version number
|
||||
length uint16 // Length of the subtable, in bytes (including this header).
|
||||
format kernSTVersion // What type of information is contained in this table.
|
||||
Coverage byte // What type of information is contained in this table.
|
||||
data KernData `unionField:"format"`
|
||||
}
|
||||
|
||||
// check and return the length
|
||||
func (st *OTKernSubtableHeader) parseEnd(src []byte) (int, error) {
|
||||
if L, E := len(src), int(st.length); L < E {
|
||||
return 0, fmt.Errorf("EOF: expected length: %d, got %d", E, L)
|
||||
}
|
||||
return int(st.length), nil
|
||||
}
|
||||
|
||||
type AATKernSubtableHeader struct {
|
||||
length uint32 // The length of this subtable in bytes, including this header.
|
||||
Coverage byte // Circumstances under which this table is used.
|
||||
version kernSTVersion
|
||||
TupleCount uint16 // The tuple count. This value is only used with variation fonts and should be 0 for all other fonts. The subtable's tupleCount will be ignored if the 'kerx' table version is less than 4.
|
||||
data KernData `unionField:"version"`
|
||||
}
|
||||
|
||||
// check and return the length
|
||||
func (st *AATKernSubtableHeader) parseEnd(src []byte) (int, error) {
|
||||
if L, E := len(src), int(st.length); L < E {
|
||||
return 0, fmt.Errorf("EOF: expected length: %d, got %d", E, L)
|
||||
}
|
||||
return int(st.length), nil
|
||||
}
|
||||
|
||||
type KernData interface {
|
||||
isKernData()
|
||||
}
|
||||
|
||||
func (KernData0) isKernData() {}
|
||||
func (KernData1) isKernData() {}
|
||||
func (KernData2) isKernData() {}
|
||||
func (KernData3) isKernData() {}
|
||||
|
||||
type kernSTVersion byte
|
||||
|
||||
const (
|
||||
kernSTVersion0 kernSTVersion = iota
|
||||
kernSTVersion1
|
||||
kernSTVersion2
|
||||
kernSTVersion3
|
||||
)
|
||||
|
||||
type KernData0 struct {
|
||||
nPairs uint16 // The number of kerning pairs in this subtable.
|
||||
searchRange uint16 // The largest power of two less than or equal to the value of nPairs, multiplied by the size in bytes of an entry in the subtable.
|
||||
entrySelector uint16 // This is calculated as log2 of the largest power of two less than or equal to the value of nPairs. This value indicates how many iterations of the search loop have to be made. For example, in a list of eight items, there would be three iterations of the loop.
|
||||
rangeShift uint16 // The value of nPairs minus the largest power of two less than or equal to nPairs. This is multiplied b
|
||||
Pairs []Kernx0Record `arrayCount:"ComputedField-nPairs"`
|
||||
}
|
||||
|
||||
type KernData1 struct {
|
||||
AATStateTable
|
||||
valueTable uint16 // Offset in bytes from the beginning of the subtable to the beginning of the kerning table.
|
||||
Values []int16 `isOpaque:""`
|
||||
}
|
||||
|
||||
func (kd *KernData1) parseValues(src []byte) error {
|
||||
valuesOffset := int(kd.valueTable)
|
||||
// start by resolving offset -> index
|
||||
for i := range kd.Entries {
|
||||
entry := &kd.Entries[i]
|
||||
offset := int(entry.Flags & Kern1Offset)
|
||||
if offset == 0 || offset < valuesOffset {
|
||||
binary.BigEndian.PutUint16(entry.data[:], 0xFFFF)
|
||||
} else {
|
||||
index := uint16((offset - valuesOffset) / 2)
|
||||
binary.BigEndian.PutUint16(entry.data[:], index)
|
||||
}
|
||||
}
|
||||
var err error
|
||||
kd.Values, err = parseKernx1Values(src, kd.Entries, valuesOffset, 0)
|
||||
return err
|
||||
}
|
||||
|
||||
type KernData2 struct {
|
||||
rowWidth uint16 // The width, in bytes, of a row in the subtable.
|
||||
Left AATLoopkup8Data `offsetSize:"Offset16" offsetRelativeTo:"Parent"`
|
||||
Right AATLoopkup8Data `offsetSize:"Offset16" offsetRelativeTo:"Parent"`
|
||||
KerningStart Offset16 // Offset from beginning of this subtable to the start of the kerning array.
|
||||
KerningData []byte `isOpaque:"" offsetRelativeTo:"Parent"` // indexed by Left + Right
|
||||
}
|
||||
|
||||
func (kd *KernData2) parseKerningData(_ []byte, parentSrc []byte) error {
|
||||
kd.KerningData = parentSrc
|
||||
return nil
|
||||
}
|
||||
|
||||
type KernData3 struct {
|
||||
glyphCount uint16 // The number of glyphs in this font.
|
||||
kernValueCount uint8 // The number of kerning values.
|
||||
leftClassCount uint8 // The number of left-hand classes.
|
||||
RightClassCount uint8 // The number of right-hand classes.
|
||||
flags uint8 // Set to zero (reserved for future use).
|
||||
Kernings []int16 `arrayCount:"ComputedField-kernValueCount"`
|
||||
LeftClass []uint8 `arrayCount:"ComputedField-glyphCount"`
|
||||
RightClass []uint8 `arrayCount:"ComputedField-glyphCount"`
|
||||
KernIndex []uint8 `arrayCount:"ComputedField-nKernIndex()"`
|
||||
}
|
||||
|
||||
func (kd *KernData3) nKernIndex() int { return int(kd.leftClassCount) * int(kd.RightClassCount) }
|
||||
|
||||
// sanitize index and class values
|
||||
func (kd *KernData3) parseEnd(_ []byte) (int, error) {
|
||||
for _, index := range kd.KernIndex {
|
||||
if index >= kd.kernValueCount {
|
||||
return 0, errors.New("invalid kern subtable format 3 index value")
|
||||
}
|
||||
}
|
||||
|
||||
for i := range kd.LeftClass {
|
||||
if kd.LeftClass[i] >= kd.leftClassCount {
|
||||
return 0, errors.New("invalid kern subtable format 3 left class value")
|
||||
}
|
||||
if kd.RightClass[i] >= kd.RightClassCount {
|
||||
return 0, errors.New("invalid kern subtable format 3 right class value")
|
||||
}
|
||||
}
|
||||
|
||||
return 0, nil
|
||||
}
|
||||
+75
@@ -0,0 +1,75 @@
|
||||
// SPDX-License-Identifier: Unlicense OR BSD-3-Clause
|
||||
|
||||
package tables
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// Code generated by binarygen from maxp_src.go. DO NOT EDIT
|
||||
|
||||
func ParseMaxp(src []byte) (Maxp, int, error) {
|
||||
var item Maxp
|
||||
n := 0
|
||||
if L := len(src); L < 6 {
|
||||
return item, 0, fmt.Errorf("reading Maxp: "+"EOF: expected length: 6, got %d", L)
|
||||
}
|
||||
_ = src[5] // early bound checking
|
||||
item.version = maxpVersion(binary.BigEndian.Uint32(src[0:]))
|
||||
item.NumGlyphs = binary.BigEndian.Uint16(src[4:])
|
||||
n += 6
|
||||
|
||||
{
|
||||
var (
|
||||
read int
|
||||
err error
|
||||
)
|
||||
switch item.version {
|
||||
case maxpVersion05:
|
||||
item.data, read, err = parseMaxpData05(src[6:])
|
||||
case maxpVersion1:
|
||||
item.data, read, err = parseMaxpData1(src[6:])
|
||||
default:
|
||||
err = fmt.Errorf("unsupported maxpDataVersion %d", item.version)
|
||||
}
|
||||
if err != nil {
|
||||
return item, 0, fmt.Errorf("reading Maxp: %s", err)
|
||||
}
|
||||
n += read
|
||||
}
|
||||
return item, n, nil
|
||||
}
|
||||
|
||||
func (item *maxpData1) mustParse(src []byte) {
|
||||
item.rawData[0] = binary.BigEndian.Uint16(src[0:])
|
||||
item.rawData[1] = binary.BigEndian.Uint16(src[2:])
|
||||
item.rawData[2] = binary.BigEndian.Uint16(src[4:])
|
||||
item.rawData[3] = binary.BigEndian.Uint16(src[6:])
|
||||
item.rawData[4] = binary.BigEndian.Uint16(src[8:])
|
||||
item.rawData[5] = binary.BigEndian.Uint16(src[10:])
|
||||
item.rawData[6] = binary.BigEndian.Uint16(src[12:])
|
||||
item.rawData[7] = binary.BigEndian.Uint16(src[14:])
|
||||
item.rawData[8] = binary.BigEndian.Uint16(src[16:])
|
||||
item.rawData[9] = binary.BigEndian.Uint16(src[18:])
|
||||
item.rawData[10] = binary.BigEndian.Uint16(src[20:])
|
||||
item.rawData[11] = binary.BigEndian.Uint16(src[22:])
|
||||
item.rawData[12] = binary.BigEndian.Uint16(src[24:])
|
||||
}
|
||||
|
||||
func parseMaxpData05([]byte) (maxpData05, int, error) {
|
||||
var item maxpData05
|
||||
n := 0
|
||||
return item, n, nil
|
||||
}
|
||||
|
||||
func parseMaxpData1(src []byte) (maxpData1, int, error) {
|
||||
var item maxpData1
|
||||
n := 0
|
||||
if L := len(src); L < 26 {
|
||||
return item, 0, fmt.Errorf("reading maxpData1: "+"EOF: expected length: 26, got %d", L)
|
||||
}
|
||||
item.mustParse(src)
|
||||
n += 26
|
||||
return item, n, nil
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
// SPDX-License-Identifier: Unlicense OR BSD-3-Clause
|
||||
|
||||
package tables
|
||||
|
||||
// https://learn.microsoft.com/en-us/typography/opentype/spec/Maxp
|
||||
type Maxp struct {
|
||||
version maxpVersion
|
||||
NumGlyphs uint16
|
||||
data maxpData `unionField:"version"`
|
||||
}
|
||||
|
||||
type maxpVersion uint32
|
||||
|
||||
const (
|
||||
maxpVersion05 maxpVersion = 0x00005000
|
||||
maxpVersion1 maxpVersion = 0x00010000
|
||||
)
|
||||
|
||||
type maxpData interface {
|
||||
isMaxpVersion()
|
||||
}
|
||||
|
||||
func (maxpData05) isMaxpVersion() {}
|
||||
func (maxpData1) isMaxpVersion() {}
|
||||
|
||||
type maxpData05 struct{}
|
||||
|
||||
type maxpData1 struct {
|
||||
rawData [13]uint16
|
||||
}
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
// SPDX-License-Identifier: Unlicense OR BSD-3-Clause
|
||||
|
||||
package tables
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// Code generated by binarygen from name_src.go. DO NOT EDIT
|
||||
|
||||
func ParseName(src []byte) (Name, int, error) {
|
||||
var item Name
|
||||
n := 0
|
||||
if L := len(src); L < 6 {
|
||||
return item, 0, fmt.Errorf("reading Name: "+"EOF: expected length: 6, got %d", L)
|
||||
}
|
||||
_ = src[5] // early bound checking
|
||||
item.version = binary.BigEndian.Uint16(src[0:])
|
||||
item.count = binary.BigEndian.Uint16(src[2:])
|
||||
offsetStringData := int(binary.BigEndian.Uint16(src[4:]))
|
||||
n += 6
|
||||
|
||||
{
|
||||
if offsetStringData != 0 { // ignore null offset
|
||||
if L := len(src); L < offsetStringData {
|
||||
return item, 0, fmt.Errorf("reading Name: "+"EOF: expected length: %d, got %d", offsetStringData, L)
|
||||
}
|
||||
|
||||
item.stringData = src[offsetStringData:]
|
||||
}
|
||||
}
|
||||
{
|
||||
arrayLength := int(item.count)
|
||||
|
||||
if L := len(src); L < 6+arrayLength*12 {
|
||||
return item, 0, fmt.Errorf("reading Name: "+"EOF: expected length: %d, got %d", 6+arrayLength*12, L)
|
||||
}
|
||||
|
||||
item.nameRecords = make([]nameRecord, arrayLength) // allocation guarded by the previous check
|
||||
for i := range item.nameRecords {
|
||||
item.nameRecords[i].mustParse(src[6+i*12:])
|
||||
}
|
||||
n += arrayLength * 12
|
||||
}
|
||||
return item, n, nil
|
||||
}
|
||||
|
||||
func (item *nameRecord) mustParse(src []byte) {
|
||||
_ = src[11] // early bound checking
|
||||
item.platformID = PlatformID(binary.BigEndian.Uint16(src[0:]))
|
||||
item.encodingID = EncodingID(binary.BigEndian.Uint16(src[2:]))
|
||||
item.languageID = LanguageID(binary.BigEndian.Uint16(src[4:]))
|
||||
item.nameID = NameID(binary.BigEndian.Uint16(src[6:]))
|
||||
item.length = binary.BigEndian.Uint16(src[8:])
|
||||
item.stringOffset = binary.BigEndian.Uint16(src[10:])
|
||||
}
|
||||
+183
@@ -0,0 +1,183 @@
|
||||
// SPDX-License-Identifier: Unlicense OR BSD-3-Clause
|
||||
|
||||
package tables
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"unicode/utf16"
|
||||
)
|
||||
|
||||
const (
|
||||
PlatformUnicode PlatformID = iota
|
||||
PlatformMac
|
||||
PlatformIso // deprecated
|
||||
PlatformMicrosoft
|
||||
PlatformCustom
|
||||
_
|
||||
_
|
||||
PlatformAdobe // artificial
|
||||
)
|
||||
|
||||
const (
|
||||
PEUnicodeDefault = EncodingID(0)
|
||||
PEUnicodeBMP = EncodingID(3)
|
||||
PEUnicodeFull = EncodingID(4)
|
||||
PEUnicodeFull13 = EncodingID(6)
|
||||
PEMacRoman = PEUnicodeDefault
|
||||
PEMicrosoftSymbolCs = EncodingID(0)
|
||||
PEMicrosoftUnicodeCs = EncodingID(1)
|
||||
PEMicrosoftUcs4 = EncodingID(10)
|
||||
)
|
||||
|
||||
const (
|
||||
plMacEnglish = LanguageID(0)
|
||||
plUnicodeDefault = LanguageID(0)
|
||||
plMicrosoftEnglish = LanguageID(0x0409)
|
||||
)
|
||||
|
||||
// Naming table
|
||||
// See https://learn.microsoft.com/en-us/typography/opentype/spec/name
|
||||
type Name struct {
|
||||
version uint16
|
||||
count uint16
|
||||
stringData []byte `offsetSize:"Offset16" arrayCount:"ToEnd"`
|
||||
nameRecords []nameRecord `arrayCount:"ComputedField-count"`
|
||||
}
|
||||
|
||||
type nameRecord struct {
|
||||
platformID PlatformID
|
||||
encodingID EncodingID
|
||||
languageID LanguageID
|
||||
nameID NameID
|
||||
length uint16
|
||||
stringOffset uint16
|
||||
}
|
||||
|
||||
// selectRecord return the entry for `name` or nil if not found.
|
||||
func (names Name) selectRecord(name NameID) *nameRecord {
|
||||
var (
|
||||
foundAppleRoman = -1
|
||||
foundAppleEnglish = -1
|
||||
foundWin = -1
|
||||
foundUnicode = -1
|
||||
isEnglish = false
|
||||
)
|
||||
|
||||
for n, rec := range names.nameRecords {
|
||||
// According to the OpenType 1.3 specification, only Microsoft or
|
||||
// Apple platform IDs might be used in the `name' table. The
|
||||
// `Unicode' platform is reserved for the `cmap' table, and the
|
||||
// `ISO' one is deprecated.
|
||||
//
|
||||
// However, the Apple TrueType specification doesn't say the same
|
||||
// thing and goes to suggest that all Unicode `name' table entries
|
||||
// should be coded in UTF-16.
|
||||
if rec.nameID == name && rec.length > 0 {
|
||||
switch rec.platformID {
|
||||
case PlatformUnicode, PlatformIso:
|
||||
// there is `languageID' to check there. We should use this
|
||||
// field only as a last solution when nothing else is
|
||||
// available.
|
||||
foundUnicode = n
|
||||
case PlatformMac:
|
||||
// This is a bit special because some fonts will use either
|
||||
// an English language id, or a Roman encoding id, to indicate
|
||||
// the English version of its font name.
|
||||
if rec.languageID == plMacEnglish {
|
||||
foundAppleEnglish = n
|
||||
} else if rec.encodingID == PEMacRoman {
|
||||
foundAppleRoman = n
|
||||
}
|
||||
case PlatformMicrosoft:
|
||||
// we only take a non-English name when there is nothing
|
||||
// else available in the font
|
||||
if foundWin == -1 || (rec.languageID&0x3FF) == 0x009 {
|
||||
switch rec.encodingID {
|
||||
case PEMicrosoftSymbolCs, PEMicrosoftUnicodeCs, PEMicrosoftUcs4:
|
||||
isEnglish = (rec.languageID & 0x3FF) == 0x009
|
||||
foundWin = n
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foundApple := foundAppleRoman
|
||||
if foundAppleEnglish >= 0 {
|
||||
foundApple = foundAppleEnglish
|
||||
}
|
||||
|
||||
// some fonts contain invalid Unicode or Macintosh formatted entries;
|
||||
// we will thus favor names encoded in Windows formats if available
|
||||
// (provided it is an English name)
|
||||
if foundWin >= 0 && !(foundApple >= 0 && !isEnglish) {
|
||||
return &names.nameRecords[foundWin]
|
||||
} else if foundApple >= 0 {
|
||||
return &names.nameRecords[foundApple]
|
||||
} else if foundUnicode >= 0 {
|
||||
return &names.nameRecords[foundUnicode]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Name returns the entry at [name], encoded in UTF-8 when possible,
|
||||
// or an empty string if not found
|
||||
func (names Name) Name(name NameID) string {
|
||||
if record := names.selectRecord(name); record != nil {
|
||||
return names.decodeRecord(*record)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// decode is a best-effort attempt to get an UTF-8 encoded version of
|
||||
// Value. Only MicrosoftUnicode (3,1 ,X), MacRomain (1,0,X) and Unicode platform
|
||||
// strings are supported.
|
||||
func (names Name) decodeRecord(n nameRecord) string {
|
||||
end := int(n.stringOffset) + int(n.length)
|
||||
if end > len(names.stringData) {
|
||||
// invalid record
|
||||
return ""
|
||||
}
|
||||
value := names.stringData[n.stringOffset:end]
|
||||
|
||||
if n.platformID == PlatformUnicode ||
|
||||
(n.platformID == PlatformMicrosoft &&
|
||||
(n.encodingID == PEMicrosoftUnicodeCs || n.encodingID == PEMicrosoftUcs4 || n.encodingID == PEMicrosoftSymbolCs)) {
|
||||
return decodeUtf16(value)
|
||||
}
|
||||
|
||||
if n.platformID == PlatformMac && n.encodingID == PEMacRoman {
|
||||
return DecodeMacintosh(value)
|
||||
}
|
||||
|
||||
// no encoding detected, hope for utf8
|
||||
return string(value)
|
||||
}
|
||||
|
||||
// decode a big ending, no BOM utf16 string
|
||||
func decodeUtf16(b []byte) string {
|
||||
ints := make([]uint16, len(b)/2)
|
||||
for i := range ints {
|
||||
ints[i] = binary.BigEndian.Uint16(b[2*i:])
|
||||
}
|
||||
return string(utf16.Decode(ints))
|
||||
}
|
||||
|
||||
// Support for the old macintosh encoding
|
||||
|
||||
var macintoshEncoding = [256]rune{
|
||||
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 196, 197, 199, 201, 209, 214, 220, 225, 224, 226, 228, 227, 229, 231, 233, 232, 234, 235, 237, 236, 238, 239, 241, 243, 242, 244, 246, 245, 250, 249, 251, 252, 8224, 176, 162, 163, 167, 8226, 182, 223, 174, 169, 8482, 180, 168, 8800, 198, 216, 8734, 177, 8804, 8805, 165, 181, 8706, 8721, 8719, 960, 8747, 170, 186, 937, 230, 248, 191, 161, 172, 8730, 402, 8776, 8710, 171, 187, 8230, 160, 192, 195, 213, 338, 339, 8211, 8212, 8220, 8221, 8216, 8217, 247, 9674, 255, 376, 8260, 8364,
|
||||
8249, 8250, 64257, 64258, 8225, 183, 8218, 8222, 8240, 194, 202, 193, 203, 200, 205, 206, 207, 204, 211, 212, 63743, 210, 218, 219, 217, 305, 710, 732, 175, 728, 729, 730, 184, 733, 731, 711,
|
||||
}
|
||||
|
||||
// DecodeMacintoshByte returns the rune for the given byte
|
||||
func DecodeMacintoshByte(b byte) rune { return macintoshEncoding[b] }
|
||||
|
||||
// DecodeMacintosh decode a Macintosh encoded string
|
||||
func DecodeMacintosh(encoded []byte) string {
|
||||
out := make([]rune, len(encoded))
|
||||
for i, b := range encoded {
|
||||
out[i] = macintoshEncoding[b]
|
||||
}
|
||||
return string(out)
|
||||
}
|
||||
+66
@@ -0,0 +1,66 @@
|
||||
// SPDX-License-Identifier: Unlicense OR BSD-3-Clause
|
||||
|
||||
package tables
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// Code generated by binarygen from os2_src.go. DO NOT EDIT
|
||||
|
||||
func ParseOs2(src []byte) (Os2, int, error) {
|
||||
var item Os2
|
||||
n := 0
|
||||
if L := len(src); L < 78 {
|
||||
return item, 0, fmt.Errorf("reading Os2: "+"EOF: expected length: 78, got %d", L)
|
||||
}
|
||||
_ = src[77] // early bound checking
|
||||
item.Version = binary.BigEndian.Uint16(src[0:])
|
||||
item.XAvgCharWidth = binary.BigEndian.Uint16(src[2:])
|
||||
item.USWeightClass = binary.BigEndian.Uint16(src[4:])
|
||||
item.USWidthClass = binary.BigEndian.Uint16(src[6:])
|
||||
item.fSType = binary.BigEndian.Uint16(src[8:])
|
||||
item.YSubscriptXSize = int16(binary.BigEndian.Uint16(src[10:]))
|
||||
item.YSubscriptYSize = int16(binary.BigEndian.Uint16(src[12:]))
|
||||
item.YSubscriptXOffset = int16(binary.BigEndian.Uint16(src[14:]))
|
||||
item.YSubscriptYOffset = int16(binary.BigEndian.Uint16(src[16:]))
|
||||
item.YSuperscriptXSize = int16(binary.BigEndian.Uint16(src[18:]))
|
||||
item.YSuperscriptYSize = int16(binary.BigEndian.Uint16(src[20:]))
|
||||
item.YSuperscriptXOffset = int16(binary.BigEndian.Uint16(src[22:]))
|
||||
item.ySuperscriptYOffset = int16(binary.BigEndian.Uint16(src[24:]))
|
||||
item.YStrikeoutSize = int16(binary.BigEndian.Uint16(src[26:]))
|
||||
item.YStrikeoutPosition = int16(binary.BigEndian.Uint16(src[28:]))
|
||||
item.sFamilyClass = int16(binary.BigEndian.Uint16(src[30:]))
|
||||
item.panose[0] = src[32]
|
||||
item.panose[1] = src[33]
|
||||
item.panose[2] = src[34]
|
||||
item.panose[3] = src[35]
|
||||
item.panose[4] = src[36]
|
||||
item.panose[5] = src[37]
|
||||
item.panose[6] = src[38]
|
||||
item.panose[7] = src[39]
|
||||
item.panose[8] = src[40]
|
||||
item.panose[9] = src[41]
|
||||
item.ulCharRange[0] = binary.BigEndian.Uint32(src[42:])
|
||||
item.ulCharRange[1] = binary.BigEndian.Uint32(src[46:])
|
||||
item.ulCharRange[2] = binary.BigEndian.Uint32(src[50:])
|
||||
item.ulCharRange[3] = binary.BigEndian.Uint32(src[54:])
|
||||
item.achVendID = Tag(binary.BigEndian.Uint32(src[58:]))
|
||||
item.FsSelection = binary.BigEndian.Uint16(src[62:])
|
||||
item.USFirstCharIndex = binary.BigEndian.Uint16(src[64:])
|
||||
item.USLastCharIndex = binary.BigEndian.Uint16(src[66:])
|
||||
item.STypoAscender = int16(binary.BigEndian.Uint16(src[68:]))
|
||||
item.STypoDescender = int16(binary.BigEndian.Uint16(src[70:]))
|
||||
item.STypoLineGap = int16(binary.BigEndian.Uint16(src[72:]))
|
||||
item.usWinAscent = binary.BigEndian.Uint16(src[74:])
|
||||
item.usWinDescent = binary.BigEndian.Uint16(src[76:])
|
||||
n += 78
|
||||
|
||||
{
|
||||
|
||||
item.HigherVersionData = src[78:]
|
||||
n = len(src)
|
||||
}
|
||||
return item, n, nil
|
||||
}
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
// SPDX-License-Identifier: Unlicense OR BSD-3-Clause
|
||||
|
||||
package tables
|
||||
|
||||
// OS/2 and Windows Metrics Table
|
||||
// See https://learn.microsoft.com/en-us/typography/opentype/spec/os2
|
||||
type Os2 struct {
|
||||
Version uint16
|
||||
XAvgCharWidth uint16
|
||||
USWeightClass uint16
|
||||
USWidthClass uint16
|
||||
fSType uint16
|
||||
YSubscriptXSize int16
|
||||
YSubscriptYSize int16
|
||||
YSubscriptXOffset int16
|
||||
YSubscriptYOffset int16
|
||||
YSuperscriptXSize int16
|
||||
YSuperscriptYSize int16
|
||||
YSuperscriptXOffset int16
|
||||
ySuperscriptYOffset int16
|
||||
YStrikeoutSize int16
|
||||
YStrikeoutPosition int16
|
||||
sFamilyClass int16
|
||||
panose [10]byte
|
||||
ulCharRange [4]uint32
|
||||
achVendID Tag
|
||||
FsSelection uint16
|
||||
USFirstCharIndex uint16
|
||||
USLastCharIndex uint16
|
||||
STypoAscender int16
|
||||
STypoDescender int16
|
||||
STypoLineGap int16
|
||||
usWinAscent uint16
|
||||
usWinDescent uint16
|
||||
HigherVersionData []byte `arrayCount:"ToEnd"`
|
||||
}
|
||||
|
||||
func (os *Os2) FontPage() FontPage {
|
||||
if os.Version == 0 {
|
||||
return FontPage(os.FsSelection & 0xFF00)
|
||||
}
|
||||
return FPNone
|
||||
}
|
||||
|
||||
// See https://docs.microsoft.com/en-us/typography/legacy/legacy_arabic_fonts
|
||||
// https://github.com/Microsoft/Font-Validator/blob/520aaae/OTFontFileVal/val_OS2.cs#L644-L681
|
||||
type FontPage uint16
|
||||
|
||||
const (
|
||||
FPNone FontPage = 0
|
||||
FPHebrew FontPage = 0xB100 /* Hebrew Windows 3.1 font page */
|
||||
FPSimpArabic FontPage = 0xB200 /* Simplified Arabic Windows 3.1 font page */
|
||||
FPTradArabic FontPage = 0xB300 /* Traditional Arabic Windows 3.1 font page */
|
||||
FPOemArabic FontPage = 0xB400 /* OEM Arabic Windows 3.1 font page */
|
||||
FPSimpFarsi FontPage = 0xBA00 /* Simplified Farsi Windows 3.1 font page */
|
||||
FPTradFarsi FontPage = 0xBB00 /* Traditional Farsi Windows 3.1 font page */
|
||||
FPThai FontPage = 0xDE00 /* Thai Windows 3.1 font page */
|
||||
)
|
||||
+576
@@ -0,0 +1,576 @@
|
||||
// SPDX-License-Identifier: Unlicense OR BSD-3-Clause
|
||||
|
||||
package tables
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// Code generated by binarygen from ot_gdef_src.go. DO NOT EDIT
|
||||
|
||||
func (item *CaretValue1) mustParse(src []byte) {
|
||||
_ = src[3] // early bound checking
|
||||
item.caretValueFormat = binary.BigEndian.Uint16(src[0:])
|
||||
item.Coordinate = int16(binary.BigEndian.Uint16(src[2:]))
|
||||
}
|
||||
|
||||
func (item *CaretValue2) mustParse(src []byte) {
|
||||
_ = src[3] // early bound checking
|
||||
item.caretValueFormat = binary.BigEndian.Uint16(src[0:])
|
||||
item.CaretValuePointIndex = binary.BigEndian.Uint16(src[2:])
|
||||
}
|
||||
|
||||
func (item *ClassRangeRecord) mustParse(src []byte) {
|
||||
_ = src[5] // early bound checking
|
||||
item.StartGlyphID = binary.BigEndian.Uint16(src[0:])
|
||||
item.EndGlyphID = binary.BigEndian.Uint16(src[2:])
|
||||
item.Class = binary.BigEndian.Uint16(src[4:])
|
||||
}
|
||||
|
||||
func ParseAttachList(src []byte) (AttachList, int, error) {
|
||||
var item AttachList
|
||||
n := 0
|
||||
if L := len(src); L < 4 {
|
||||
return item, 0, fmt.Errorf("reading AttachList: "+"EOF: expected length: 4, got %d", L)
|
||||
}
|
||||
_ = src[3] // early bound checking
|
||||
offsetCoverage := int(binary.BigEndian.Uint16(src[0:]))
|
||||
arrayLengthAttachPoints := int(binary.BigEndian.Uint16(src[2:]))
|
||||
n += 4
|
||||
|
||||
{
|
||||
if offsetCoverage != 0 { // ignore null offset
|
||||
if L := len(src); L < offsetCoverage {
|
||||
return item, 0, fmt.Errorf("reading AttachList: "+"EOF: expected length: %d, got %d", offsetCoverage, L)
|
||||
}
|
||||
|
||||
var (
|
||||
err error
|
||||
read int
|
||||
)
|
||||
item.Coverage, read, err = ParseCoverage(src[offsetCoverage:])
|
||||
if err != nil {
|
||||
return item, 0, fmt.Errorf("reading AttachList: %s", err)
|
||||
}
|
||||
offsetCoverage += read
|
||||
}
|
||||
}
|
||||
{
|
||||
|
||||
if L := len(src); L < 4+arrayLengthAttachPoints*2 {
|
||||
return item, 0, fmt.Errorf("reading AttachList: "+"EOF: expected length: %d, got %d", 4+arrayLengthAttachPoints*2, L)
|
||||
}
|
||||
|
||||
item.AttachPoints = make([]AttachPoint, arrayLengthAttachPoints) // allocation guarded by the previous check
|
||||
for i := range item.AttachPoints {
|
||||
offset := int(binary.BigEndian.Uint16(src[4+i*2:]))
|
||||
// ignore null offsets
|
||||
if offset == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
if L := len(src); L < offset {
|
||||
return item, 0, fmt.Errorf("reading AttachList: "+"EOF: expected length: %d, got %d", offset, L)
|
||||
}
|
||||
|
||||
var err error
|
||||
item.AttachPoints[i], _, err = ParseAttachPoint(src[offset:])
|
||||
if err != nil {
|
||||
return item, 0, fmt.Errorf("reading AttachList: %s", err)
|
||||
}
|
||||
}
|
||||
n += arrayLengthAttachPoints * 2
|
||||
}
|
||||
return item, n, nil
|
||||
}
|
||||
|
||||
func ParseAttachPoint(src []byte) (AttachPoint, int, error) {
|
||||
var item AttachPoint
|
||||
n := 0
|
||||
if L := len(src); L < 2 {
|
||||
return item, 0, fmt.Errorf("reading AttachPoint: "+"EOF: expected length: 2, got %d", L)
|
||||
}
|
||||
arrayLengthPointIndices := int(binary.BigEndian.Uint16(src[0:]))
|
||||
n += 2
|
||||
|
||||
{
|
||||
|
||||
if L := len(src); L < 2+arrayLengthPointIndices*2 {
|
||||
return item, 0, fmt.Errorf("reading AttachPoint: "+"EOF: expected length: %d, got %d", 2+arrayLengthPointIndices*2, L)
|
||||
}
|
||||
|
||||
item.PointIndices = make([]uint16, arrayLengthPointIndices) // allocation guarded by the previous check
|
||||
for i := range item.PointIndices {
|
||||
item.PointIndices[i] = binary.BigEndian.Uint16(src[2+i*2:])
|
||||
}
|
||||
n += arrayLengthPointIndices * 2
|
||||
}
|
||||
return item, n, nil
|
||||
}
|
||||
|
||||
func ParseCaretValue(src []byte) (CaretValue, int, error) {
|
||||
var item CaretValue
|
||||
|
||||
if L := len(src); L < 2 {
|
||||
return item, 0, fmt.Errorf("reading CaretValue: "+"EOF: expected length: 2, got %d", L)
|
||||
}
|
||||
format := uint16(binary.BigEndian.Uint16(src[0:]))
|
||||
var (
|
||||
read int
|
||||
err error
|
||||
)
|
||||
switch format {
|
||||
case 1:
|
||||
item, read, err = ParseCaretValue1(src[0:])
|
||||
case 2:
|
||||
item, read, err = ParseCaretValue2(src[0:])
|
||||
case 3:
|
||||
item, read, err = ParseCaretValue3(src[0:])
|
||||
default:
|
||||
err = fmt.Errorf("unsupported CaretValue format %d", format)
|
||||
}
|
||||
if err != nil {
|
||||
return item, 0, fmt.Errorf("reading CaretValue: %s", err)
|
||||
}
|
||||
|
||||
return item, read, nil
|
||||
}
|
||||
|
||||
func ParseCaretValue1(src []byte) (CaretValue1, int, error) {
|
||||
var item CaretValue1
|
||||
n := 0
|
||||
if L := len(src); L < 4 {
|
||||
return item, 0, fmt.Errorf("reading CaretValue1: "+"EOF: expected length: 4, got %d", L)
|
||||
}
|
||||
item.mustParse(src)
|
||||
n += 4
|
||||
return item, n, nil
|
||||
}
|
||||
|
||||
func ParseCaretValue2(src []byte) (CaretValue2, int, error) {
|
||||
var item CaretValue2
|
||||
n := 0
|
||||
if L := len(src); L < 4 {
|
||||
return item, 0, fmt.Errorf("reading CaretValue2: "+"EOF: expected length: 4, got %d", L)
|
||||
}
|
||||
item.mustParse(src)
|
||||
n += 4
|
||||
return item, n, nil
|
||||
}
|
||||
|
||||
func ParseCaretValue3(src []byte) (CaretValue3, int, error) {
|
||||
var item CaretValue3
|
||||
n := 0
|
||||
if L := len(src); L < 6 {
|
||||
return item, 0, fmt.Errorf("reading CaretValue3: "+"EOF: expected length: 6, got %d", L)
|
||||
}
|
||||
_ = src[5] // early bound checking
|
||||
item.caretValueFormat = binary.BigEndian.Uint16(src[0:])
|
||||
item.Coordinate = int16(binary.BigEndian.Uint16(src[2:]))
|
||||
item.deviceOffset = Offset16(binary.BigEndian.Uint16(src[4:]))
|
||||
n += 6
|
||||
|
||||
{
|
||||
|
||||
err := item.parseDevice(src[:])
|
||||
if err != nil {
|
||||
return item, 0, fmt.Errorf("reading CaretValue3: %s", err)
|
||||
}
|
||||
}
|
||||
return item, n, nil
|
||||
}
|
||||
|
||||
func ParseClassDef(src []byte) (ClassDef, int, error) {
|
||||
var item ClassDef
|
||||
|
||||
if L := len(src); L < 2 {
|
||||
return item, 0, fmt.Errorf("reading ClassDef: "+"EOF: expected length: 2, got %d", L)
|
||||
}
|
||||
format := uint16(binary.BigEndian.Uint16(src[0:]))
|
||||
var (
|
||||
read int
|
||||
err error
|
||||
)
|
||||
switch format {
|
||||
case 1:
|
||||
item, read, err = ParseClassDef1(src[0:])
|
||||
case 2:
|
||||
item, read, err = ParseClassDef2(src[0:])
|
||||
default:
|
||||
err = fmt.Errorf("unsupported ClassDef format %d", format)
|
||||
}
|
||||
if err != nil {
|
||||
return item, 0, fmt.Errorf("reading ClassDef: %s", err)
|
||||
}
|
||||
|
||||
return item, read, nil
|
||||
}
|
||||
|
||||
func ParseClassDef1(src []byte) (ClassDef1, int, error) {
|
||||
var item ClassDef1
|
||||
n := 0
|
||||
if L := len(src); L < 6 {
|
||||
return item, 0, fmt.Errorf("reading ClassDef1: "+"EOF: expected length: 6, got %d", L)
|
||||
}
|
||||
_ = src[5] // early bound checking
|
||||
item.format = binary.BigEndian.Uint16(src[0:])
|
||||
item.StartGlyphID = binary.BigEndian.Uint16(src[2:])
|
||||
arrayLengthClassValueArray := int(binary.BigEndian.Uint16(src[4:]))
|
||||
n += 6
|
||||
|
||||
{
|
||||
|
||||
if L := len(src); L < 6+arrayLengthClassValueArray*2 {
|
||||
return item, 0, fmt.Errorf("reading ClassDef1: "+"EOF: expected length: %d, got %d", 6+arrayLengthClassValueArray*2, L)
|
||||
}
|
||||
|
||||
item.ClassValueArray = make([]uint16, arrayLengthClassValueArray) // allocation guarded by the previous check
|
||||
for i := range item.ClassValueArray {
|
||||
item.ClassValueArray[i] = binary.BigEndian.Uint16(src[6+i*2:])
|
||||
}
|
||||
n += arrayLengthClassValueArray * 2
|
||||
}
|
||||
return item, n, nil
|
||||
}
|
||||
|
||||
func ParseClassDef2(src []byte) (ClassDef2, int, error) {
|
||||
var item ClassDef2
|
||||
n := 0
|
||||
if L := len(src); L < 4 {
|
||||
return item, 0, fmt.Errorf("reading ClassDef2: "+"EOF: expected length: 4, got %d", L)
|
||||
}
|
||||
_ = src[3] // early bound checking
|
||||
item.format = binary.BigEndian.Uint16(src[0:])
|
||||
arrayLengthClassRangeRecords := int(binary.BigEndian.Uint16(src[2:]))
|
||||
n += 4
|
||||
|
||||
{
|
||||
|
||||
if L := len(src); L < 4+arrayLengthClassRangeRecords*6 {
|
||||
return item, 0, fmt.Errorf("reading ClassDef2: "+"EOF: expected length: %d, got %d", 4+arrayLengthClassRangeRecords*6, L)
|
||||
}
|
||||
|
||||
item.ClassRangeRecords = make([]ClassRangeRecord, arrayLengthClassRangeRecords) // allocation guarded by the previous check
|
||||
for i := range item.ClassRangeRecords {
|
||||
item.ClassRangeRecords[i].mustParse(src[4+i*6:])
|
||||
}
|
||||
n += arrayLengthClassRangeRecords * 6
|
||||
}
|
||||
return item, n, nil
|
||||
}
|
||||
|
||||
func ParseCoverage(src []byte) (Coverage, int, error) {
|
||||
var item Coverage
|
||||
|
||||
if L := len(src); L < 2 {
|
||||
return item, 0, fmt.Errorf("reading Coverage: "+"EOF: expected length: 2, got %d", L)
|
||||
}
|
||||
format := uint16(binary.BigEndian.Uint16(src[0:]))
|
||||
var (
|
||||
read int
|
||||
err error
|
||||
)
|
||||
switch format {
|
||||
case 1:
|
||||
item, read, err = ParseCoverage1(src[0:])
|
||||
case 2:
|
||||
item, read, err = ParseCoverage2(src[0:])
|
||||
default:
|
||||
err = fmt.Errorf("unsupported Coverage format %d", format)
|
||||
}
|
||||
if err != nil {
|
||||
return item, 0, fmt.Errorf("reading Coverage: %s", err)
|
||||
}
|
||||
|
||||
return item, read, nil
|
||||
}
|
||||
|
||||
func ParseCoverage1(src []byte) (Coverage1, int, error) {
|
||||
var item Coverage1
|
||||
n := 0
|
||||
if L := len(src); L < 4 {
|
||||
return item, 0, fmt.Errorf("reading Coverage1: "+"EOF: expected length: 4, got %d", L)
|
||||
}
|
||||
_ = src[3] // early bound checking
|
||||
item.format = binary.BigEndian.Uint16(src[0:])
|
||||
arrayLengthGlyphs := int(binary.BigEndian.Uint16(src[2:]))
|
||||
n += 4
|
||||
|
||||
{
|
||||
|
||||
if L := len(src); L < 4+arrayLengthGlyphs*2 {
|
||||
return item, 0, fmt.Errorf("reading Coverage1: "+"EOF: expected length: %d, got %d", 4+arrayLengthGlyphs*2, L)
|
||||
}
|
||||
|
||||
item.Glyphs = make([]uint16, arrayLengthGlyphs) // allocation guarded by the previous check
|
||||
for i := range item.Glyphs {
|
||||
item.Glyphs[i] = binary.BigEndian.Uint16(src[4+i*2:])
|
||||
}
|
||||
n += arrayLengthGlyphs * 2
|
||||
}
|
||||
return item, n, nil
|
||||
}
|
||||
|
||||
func ParseCoverage2(src []byte) (Coverage2, int, error) {
|
||||
var item Coverage2
|
||||
n := 0
|
||||
if L := len(src); L < 4 {
|
||||
return item, 0, fmt.Errorf("reading Coverage2: "+"EOF: expected length: 4, got %d", L)
|
||||
}
|
||||
_ = src[3] // early bound checking
|
||||
item.format = binary.BigEndian.Uint16(src[0:])
|
||||
arrayLengthRanges := int(binary.BigEndian.Uint16(src[2:]))
|
||||
n += 4
|
||||
|
||||
{
|
||||
|
||||
if L := len(src); L < 4+arrayLengthRanges*6 {
|
||||
return item, 0, fmt.Errorf("reading Coverage2: "+"EOF: expected length: %d, got %d", 4+arrayLengthRanges*6, L)
|
||||
}
|
||||
|
||||
item.Ranges = make([]RangeRecord, arrayLengthRanges) // allocation guarded by the previous check
|
||||
for i := range item.Ranges {
|
||||
item.Ranges[i].mustParse(src[4+i*6:])
|
||||
}
|
||||
n += arrayLengthRanges * 6
|
||||
}
|
||||
return item, n, nil
|
||||
}
|
||||
|
||||
func ParseGDEF(src []byte) (GDEF, int, error) {
|
||||
var item GDEF
|
||||
n := 0
|
||||
if L := len(src); L < 12 {
|
||||
return item, 0, fmt.Errorf("reading GDEF: "+"EOF: expected length: 12, got %d", L)
|
||||
}
|
||||
_ = src[11] // early bound checking
|
||||
item.majorVersion = binary.BigEndian.Uint16(src[0:])
|
||||
item.minorVersion = binary.BigEndian.Uint16(src[2:])
|
||||
offsetGlyphClassDef := int(binary.BigEndian.Uint16(src[4:]))
|
||||
offsetAttachList := int(binary.BigEndian.Uint16(src[6:]))
|
||||
offsetLigCaretList := int(binary.BigEndian.Uint16(src[8:]))
|
||||
offsetMarkAttachClass := int(binary.BigEndian.Uint16(src[10:]))
|
||||
n += 12
|
||||
|
||||
{
|
||||
if offsetGlyphClassDef != 0 { // ignore null offset
|
||||
if L := len(src); L < offsetGlyphClassDef {
|
||||
return item, 0, fmt.Errorf("reading GDEF: "+"EOF: expected length: %d, got %d", offsetGlyphClassDef, L)
|
||||
}
|
||||
|
||||
var (
|
||||
err error
|
||||
read int
|
||||
)
|
||||
item.GlyphClassDef, read, err = ParseClassDef(src[offsetGlyphClassDef:])
|
||||
if err != nil {
|
||||
return item, 0, fmt.Errorf("reading GDEF: %s", err)
|
||||
}
|
||||
offsetGlyphClassDef += read
|
||||
}
|
||||
}
|
||||
{
|
||||
if offsetAttachList != 0 { // ignore null offset
|
||||
if L := len(src); L < offsetAttachList {
|
||||
return item, 0, fmt.Errorf("reading GDEF: "+"EOF: expected length: %d, got %d", offsetAttachList, L)
|
||||
}
|
||||
|
||||
var err error
|
||||
item.AttachList, _, err = ParseAttachList(src[offsetAttachList:])
|
||||
if err != nil {
|
||||
return item, 0, fmt.Errorf("reading GDEF: %s", err)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
{
|
||||
if offsetLigCaretList != 0 { // ignore null offset
|
||||
if L := len(src); L < offsetLigCaretList {
|
||||
return item, 0, fmt.Errorf("reading GDEF: "+"EOF: expected length: %d, got %d", offsetLigCaretList, L)
|
||||
}
|
||||
|
||||
var err error
|
||||
item.LigCaretList, _, err = ParseLigCaretList(src[offsetLigCaretList:])
|
||||
if err != nil {
|
||||
return item, 0, fmt.Errorf("reading GDEF: %s", err)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
{
|
||||
if offsetMarkAttachClass != 0 { // ignore null offset
|
||||
if L := len(src); L < offsetMarkAttachClass {
|
||||
return item, 0, fmt.Errorf("reading GDEF: "+"EOF: expected length: %d, got %d", offsetMarkAttachClass, L)
|
||||
}
|
||||
|
||||
var (
|
||||
err error
|
||||
read int
|
||||
)
|
||||
item.MarkAttachClass, read, err = ParseClassDef(src[offsetMarkAttachClass:])
|
||||
if err != nil {
|
||||
return item, 0, fmt.Errorf("reading GDEF: %s", err)
|
||||
}
|
||||
offsetMarkAttachClass += read
|
||||
}
|
||||
}
|
||||
{
|
||||
|
||||
err := item.parseMarkGlyphSetsDef(src[:])
|
||||
if err != nil {
|
||||
return item, 0, fmt.Errorf("reading GDEF: %s", err)
|
||||
}
|
||||
}
|
||||
{
|
||||
|
||||
read, err := item.parseItemVarStore(src[:])
|
||||
if err != nil {
|
||||
return item, 0, fmt.Errorf("reading GDEF: %s", err)
|
||||
}
|
||||
n = read
|
||||
}
|
||||
return item, n, nil
|
||||
}
|
||||
|
||||
func ParseLigCaretList(src []byte) (LigCaretList, int, error) {
|
||||
var item LigCaretList
|
||||
n := 0
|
||||
if L := len(src); L < 4 {
|
||||
return item, 0, fmt.Errorf("reading LigCaretList: "+"EOF: expected length: 4, got %d", L)
|
||||
}
|
||||
_ = src[3] // early bound checking
|
||||
offsetCoverage := int(binary.BigEndian.Uint16(src[0:]))
|
||||
arrayLengthLigGlyphs := int(binary.BigEndian.Uint16(src[2:]))
|
||||
n += 4
|
||||
|
||||
{
|
||||
if offsetCoverage != 0 { // ignore null offset
|
||||
if L := len(src); L < offsetCoverage {
|
||||
return item, 0, fmt.Errorf("reading LigCaretList: "+"EOF: expected length: %d, got %d", offsetCoverage, L)
|
||||
}
|
||||
|
||||
var (
|
||||
err error
|
||||
read int
|
||||
)
|
||||
item.Coverage, read, err = ParseCoverage(src[offsetCoverage:])
|
||||
if err != nil {
|
||||
return item, 0, fmt.Errorf("reading LigCaretList: %s", err)
|
||||
}
|
||||
offsetCoverage += read
|
||||
}
|
||||
}
|
||||
{
|
||||
|
||||
if L := len(src); L < 4+arrayLengthLigGlyphs*2 {
|
||||
return item, 0, fmt.Errorf("reading LigCaretList: "+"EOF: expected length: %d, got %d", 4+arrayLengthLigGlyphs*2, L)
|
||||
}
|
||||
|
||||
item.LigGlyphs = make([]LigGlyph, arrayLengthLigGlyphs) // allocation guarded by the previous check
|
||||
for i := range item.LigGlyphs {
|
||||
offset := int(binary.BigEndian.Uint16(src[4+i*2:]))
|
||||
// ignore null offsets
|
||||
if offset == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
if L := len(src); L < offset {
|
||||
return item, 0, fmt.Errorf("reading LigCaretList: "+"EOF: expected length: %d, got %d", offset, L)
|
||||
}
|
||||
|
||||
var err error
|
||||
item.LigGlyphs[i], _, err = ParseLigGlyph(src[offset:])
|
||||
if err != nil {
|
||||
return item, 0, fmt.Errorf("reading LigCaretList: %s", err)
|
||||
}
|
||||
}
|
||||
n += arrayLengthLigGlyphs * 2
|
||||
}
|
||||
return item, n, nil
|
||||
}
|
||||
|
||||
func ParseLigGlyph(src []byte) (LigGlyph, int, error) {
|
||||
var item LigGlyph
|
||||
n := 0
|
||||
if L := len(src); L < 2 {
|
||||
return item, 0, fmt.Errorf("reading LigGlyph: "+"EOF: expected length: 2, got %d", L)
|
||||
}
|
||||
arrayLengthCaretValues := int(binary.BigEndian.Uint16(src[0:]))
|
||||
n += 2
|
||||
|
||||
{
|
||||
|
||||
if L := len(src); L < 2+arrayLengthCaretValues*2 {
|
||||
return item, 0, fmt.Errorf("reading LigGlyph: "+"EOF: expected length: %d, got %d", 2+arrayLengthCaretValues*2, L)
|
||||
}
|
||||
|
||||
item.CaretValues = make([]CaretValue, arrayLengthCaretValues) // allocation guarded by the previous check
|
||||
for i := range item.CaretValues {
|
||||
offset := int(binary.BigEndian.Uint16(src[2+i*2:]))
|
||||
// ignore null offsets
|
||||
if offset == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
if L := len(src); L < offset {
|
||||
return item, 0, fmt.Errorf("reading LigGlyph: "+"EOF: expected length: %d, got %d", offset, L)
|
||||
}
|
||||
|
||||
var err error
|
||||
item.CaretValues[i], _, err = ParseCaretValue(src[offset:])
|
||||
if err != nil {
|
||||
return item, 0, fmt.Errorf("reading LigGlyph: %s", err)
|
||||
}
|
||||
}
|
||||
n += arrayLengthCaretValues * 2
|
||||
}
|
||||
return item, n, nil
|
||||
}
|
||||
|
||||
func ParseMarkGlyphSets(src []byte) (MarkGlyphSets, int, error) {
|
||||
var item MarkGlyphSets
|
||||
n := 0
|
||||
if L := len(src); L < 4 {
|
||||
return item, 0, fmt.Errorf("reading MarkGlyphSets: "+"EOF: expected length: 4, got %d", L)
|
||||
}
|
||||
_ = src[3] // early bound checking
|
||||
item.format = binary.BigEndian.Uint16(src[0:])
|
||||
arrayLengthCoverages := int(binary.BigEndian.Uint16(src[2:]))
|
||||
n += 4
|
||||
|
||||
{
|
||||
|
||||
if L := len(src); L < 4+arrayLengthCoverages*4 {
|
||||
return item, 0, fmt.Errorf("reading MarkGlyphSets: "+"EOF: expected length: %d, got %d", 4+arrayLengthCoverages*4, L)
|
||||
}
|
||||
|
||||
item.Coverages = make([]Coverage, arrayLengthCoverages) // allocation guarded by the previous check
|
||||
for i := range item.Coverages {
|
||||
offset := int(binary.BigEndian.Uint32(src[4+i*4:]))
|
||||
// ignore null offsets
|
||||
if offset == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
if L := len(src); L < offset {
|
||||
return item, 0, fmt.Errorf("reading MarkGlyphSets: "+"EOF: expected length: %d, got %d", offset, L)
|
||||
}
|
||||
|
||||
var err error
|
||||
item.Coverages[i], _, err = ParseCoverage(src[offset:])
|
||||
if err != nil {
|
||||
return item, 0, fmt.Errorf("reading MarkGlyphSets: %s", err)
|
||||
}
|
||||
}
|
||||
n += arrayLengthCoverages * 4
|
||||
}
|
||||
return item, n, nil
|
||||
}
|
||||
|
||||
func (item *RangeRecord) mustParse(src []byte) {
|
||||
_ = src[5] // early bound checking
|
||||
item.StartGlyphID = binary.BigEndian.Uint16(src[0:])
|
||||
item.EndGlyphID = binary.BigEndian.Uint16(src[2:])
|
||||
item.StartCoverageIndex = binary.BigEndian.Uint16(src[4:])
|
||||
}
|
||||
+111
@@ -0,0 +1,111 @@
|
||||
// SPDX-License-Identifier: Unlicense OR BSD-3-Clause
|
||||
|
||||
package tables
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type GDEF struct {
|
||||
majorVersion uint16 // Major version of the GDEF table, = 1
|
||||
minorVersion uint16 // Minor version of the GDEF table, = 0, 2, 3
|
||||
GlyphClassDef ClassDef `offsetSize:"Offset16"` // Offset to class definition table for glyph type, from beginning of GDEF header (may be NULL)
|
||||
AttachList AttachList `offsetSize:"Offset16"` // Offset to attachment point list table, from beginning of GDEF header (may be NULL)
|
||||
LigCaretList LigCaretList `offsetSize:"Offset16"` // Offset to ligature caret list table, from beginning of GDEF header (may be NULL)
|
||||
MarkAttachClass ClassDef `offsetSize:"Offset16"` // Offset to class definition table for mark attachment type, from beginning of GDEF header (may be NULL)
|
||||
|
||||
MarkGlyphSetsDef MarkGlyphSets `isOpaque:""` // Offset to the table of mark glyph set definitions, from beginning of GDEF header (may be NULL)
|
||||
ItemVarStore ItemVarStore `isOpaque:""` // Offset to the Item Variation Store table, from beginning of GDEF header (may be NULL)
|
||||
}
|
||||
|
||||
func (gdef *GDEF) parseMarkGlyphSetsDef(src []byte) error {
|
||||
const headerSize = 12
|
||||
if gdef.minorVersion < 2 {
|
||||
return nil
|
||||
}
|
||||
if L := len(src); L < headerSize+2 {
|
||||
return fmt.Errorf("EOF: expected length: %d, got %d", headerSize+2, L)
|
||||
}
|
||||
offset := binary.BigEndian.Uint16(src[headerSize:])
|
||||
if offset != 0 {
|
||||
var err error
|
||||
gdef.MarkGlyphSetsDef, _, err = ParseMarkGlyphSets(src[offset:])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gdef *GDEF) parseItemVarStore(src []byte) (int, error) {
|
||||
const headerSize = 12 + 2
|
||||
if gdef.minorVersion < 3 {
|
||||
return 0, nil
|
||||
}
|
||||
if L := len(src); L < headerSize+4 {
|
||||
return 0, fmt.Errorf("EOF: expected length: %d, got %d", headerSize+4, L)
|
||||
}
|
||||
offset := binary.BigEndian.Uint32(src[headerSize:])
|
||||
if offset != 0 {
|
||||
var err error
|
||||
gdef.ItemVarStore, _, err = ParseItemVarStore(src[offset:])
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
return headerSize + 4, nil
|
||||
}
|
||||
|
||||
type AttachList struct {
|
||||
Coverage Coverage `offsetSize:"Offset16"` // Offset to Coverage table - from beginning of AttachList table
|
||||
AttachPoints []AttachPoint `arrayCount:"FirstUint16" offsetsArray:"Offset16"` // [glyphCount] Array of offsets to AttachPoint tables-from beginning of AttachList table-in Coverage Index order
|
||||
}
|
||||
|
||||
type AttachPoint struct {
|
||||
PointIndices []uint16 `arrayCount:"FirstUint16"` // [pointCount] Array of contour point indices -in increasing numerical order
|
||||
}
|
||||
|
||||
type LigCaretList struct {
|
||||
Coverage Coverage `offsetSize:"Offset16"` // Offset to Coverage table - from beginning of LigCaretList table
|
||||
LigGlyphs []LigGlyph `arrayCount:"FirstUint16" offsetsArray:"Offset16"` // [ligGlyphCount] Array of offsets to LigGlyph tables, from beginning of LigCaretList table —in Coverage Index order
|
||||
}
|
||||
|
||||
type LigGlyph struct {
|
||||
CaretValues []CaretValue `arrayCount:"FirstUint16" offsetsArray:"Offset16"` // [caretCount] Array of offsets to CaretValue tables, from beginning of LigGlyph table — in increasing coordinate order
|
||||
}
|
||||
|
||||
type CaretValue interface {
|
||||
isCaretValue()
|
||||
}
|
||||
|
||||
func (CaretValue1) isCaretValue() {}
|
||||
func (CaretValue2) isCaretValue() {}
|
||||
func (CaretValue3) isCaretValue() {}
|
||||
|
||||
type CaretValue1 struct {
|
||||
caretValueFormat uint16 `unionTag:"1"` // Format identifier: format = 1
|
||||
Coordinate int16 // X or Y value, in design units
|
||||
}
|
||||
|
||||
type CaretValue2 struct {
|
||||
caretValueFormat uint16 `unionTag:"2"` // Format identifier: format = 2
|
||||
CaretValuePointIndex uint16 // Contour point index on glyph
|
||||
}
|
||||
|
||||
type CaretValue3 struct {
|
||||
caretValueFormat uint16 `unionTag:"3"` // Format identifier: format = 3
|
||||
Coordinate int16 // X or Y value, in design units
|
||||
deviceOffset Offset16 // Offset to Device table (non-variable font) / Variation Index table (variable font) for X or Y value-from beginning of CaretValue table
|
||||
Device DeviceTable `isOpaque:""`
|
||||
}
|
||||
|
||||
func (cv *CaretValue3) parseDevice(src []byte) (err error) {
|
||||
cv.Device, err = parseDeviceTable(src, uint16(cv.deviceOffset))
|
||||
return err
|
||||
}
|
||||
|
||||
type MarkGlyphSets struct {
|
||||
format uint16 // Format identifier == 1
|
||||
Coverages []Coverage `arrayCount:"FirstUint16" offsetsArray:"Offset32"` // [markGlyphSetCount] Array of offsets to mark glyph set coverage tables, from the start of the MarkGlyphSets table.
|
||||
}
|
||||
+1744
File diff suppressed because it is too large
Load Diff
+276
@@ -0,0 +1,276 @@
|
||||
// SPDX-License-Identifier: Unlicense OR BSD-3-Clause
|
||||
|
||||
package tables
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type SinglePos struct {
|
||||
Data SinglePosData
|
||||
}
|
||||
|
||||
type SinglePosData interface {
|
||||
isSinglePosData()
|
||||
|
||||
Cov() Coverage
|
||||
}
|
||||
|
||||
func (SinglePosData1) isSinglePosData() {}
|
||||
func (SinglePosData2) isSinglePosData() {}
|
||||
|
||||
type SinglePosData1 struct {
|
||||
format uint16 `unionTag:"1"`
|
||||
coverage Coverage `offsetSize:"Offset16"` // Offset to Coverage table, from beginning of SinglePos subtable.
|
||||
ValueFormat ValueFormat // Defines the types of data in the ValueRecord.
|
||||
ValueRecord ValueRecord `isOpaque:""` // Defines positioning value(s) — applied to all glyphs in the Coverage table.
|
||||
}
|
||||
|
||||
func (sp *SinglePosData1) parseValueRecord(src []byte) (err error) {
|
||||
sp.ValueRecord, _, err = parseValueRecord(sp.ValueFormat, src, 6)
|
||||
return err
|
||||
}
|
||||
|
||||
type SinglePosData2 struct {
|
||||
format uint16 `unionTag:"2"`
|
||||
coverage Coverage `offsetSize:"Offset16"` // Offset to Coverage table, from beginning of SinglePos subtable.
|
||||
ValueFormat ValueFormat // Defines the types of data in the ValueRecords.
|
||||
valueCount uint16 // Number of ValueRecords — must equal glyphCount in the Coverage table.
|
||||
ValueRecords []ValueRecord `isOpaque:""` //[valueCount] Array of ValueRecords — positioning values applied to glyphs.
|
||||
}
|
||||
|
||||
func (sp *SinglePosData2) parseValueRecords(src []byte) (err error) {
|
||||
offset := 8
|
||||
sp.ValueRecords = make([]ValueRecord, sp.valueCount)
|
||||
for i := range sp.ValueRecords {
|
||||
sp.ValueRecords[i], offset, err = parseValueRecord(sp.ValueFormat, src, offset)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
type PairPos struct {
|
||||
Data PairPosData
|
||||
}
|
||||
|
||||
type PairPosData interface {
|
||||
isPairPosData()
|
||||
|
||||
Cov() Coverage
|
||||
}
|
||||
|
||||
func (PairPosData1) isPairPosData() {}
|
||||
func (PairPosData2) isPairPosData() {}
|
||||
|
||||
type PairPosData1 struct {
|
||||
format uint16 `unionTag:"1"`
|
||||
coverage Coverage `offsetSize:"Offset16"` // Offset to Coverage table, from beginning of PairPos subtable.
|
||||
|
||||
ValueFormat1 ValueFormat // Defines the types of data in valueRecord1 — for the first glyph in the pair (may be zero).
|
||||
ValueFormat2 ValueFormat // Defines the types of data in valueRecord2 — for the second glyph in the pair (may be zero).
|
||||
PairSets []PairSet `arrayCount:"FirstUint16" offsetsArray:"Offset16" arguments:"valueFormat1=.ValueFormat1, valueFormat2=.ValueFormat2"` //[pairSetCount] Array of offsets to PairSet tables. Offsets are from beginning of PairPos subtable, ordered by Coverage Index.
|
||||
}
|
||||
|
||||
// binarygen: argument=valueFormat1 ValueFormat
|
||||
// binarygen: argument=valueFormat2 ValueFormat
|
||||
type PairSet struct {
|
||||
pairValueCount uint16 // Number of PairValueRecords
|
||||
// we store the compressed form to avoid wasting to much memory
|
||||
data pairValueRecords `isOpaque:""`
|
||||
}
|
||||
|
||||
func (ps *PairSet) parseData(src []byte, fmt1, fmt2 ValueFormat) error {
|
||||
recNbUint16 := 1 + fmt1.size() + fmt2.size() // in uint16
|
||||
if exp := 2 + recNbUint16*2*int(ps.pairValueCount); len(src) < exp { //
|
||||
return fmt.Errorf("EOF: expected length: %d, got %d", exp, len(src))
|
||||
}
|
||||
ps.data = pairValueRecords{data: src, fmt1: fmt1, fmt2: fmt2}
|
||||
return nil
|
||||
}
|
||||
|
||||
type PairPosData2 struct {
|
||||
format uint16 `unionTag:"2"`
|
||||
coverage Coverage `offsetSize:"Offset16"` // Offset to Coverage table, from beginning of PairPos subtable.
|
||||
|
||||
ValueFormat1 ValueFormat // Defines the types of data in valueRecord1 — for the first glyph in the pair (may be zero).
|
||||
ValueFormat2 ValueFormat // Defines the types of data in valueRecord2 — for the second glyph in the pair (may be zero).
|
||||
|
||||
ClassDef1 ClassDef `offsetSize:"Offset16"` // Offset to ClassDef table, from beginning of PairPos subtable — for the first glyph of the pair.
|
||||
ClassDef2 ClassDef `offsetSize:"Offset16"` // Offset to ClassDef table, from beginning of PairPos subtable — for the second glyph of the pair.
|
||||
class1Count uint16 // Number of classes in classDef1 table — includes Class 0.
|
||||
class2Count uint16 // Number of classes in classDef2 table — includes Class 0.
|
||||
|
||||
classData []byte `subsliceStart:"AtStart" arrayCount:"ToEnd"`
|
||||
}
|
||||
|
||||
// Record returns the record for the given classes, which must come from ClassDef1
|
||||
// and ClassDef2
|
||||
func (pp *PairPosData2) Record(class1, class2 uint16) Class2Record {
|
||||
const headerSize = 16 // including posFormat and coverageOffset
|
||||
size2 := (pp.ValueFormat1.size() + pp.ValueFormat2.size()) * 2
|
||||
size1 := int(pp.class2Count) * size2
|
||||
offset := headerSize + size1*int(class1) + size2*int(class2)
|
||||
v1, newOffset, _ := parseValueRecord(pp.ValueFormat1, pp.classData, offset)
|
||||
v2, _, _ := parseValueRecord(pp.ValueFormat2, pp.classData, newOffset)
|
||||
return Class2Record{v1, v2}
|
||||
}
|
||||
|
||||
// DeviceTableHeader is the common header for DeviceTable
|
||||
// See https://learn.microsoft.com/fr-fr/typography/opentype/spec/chapter2#device-and-variationindex-tables
|
||||
type DeviceTableHeader struct {
|
||||
first uint16
|
||||
second uint16
|
||||
deltaFormat uint16 // Format of deltaValue array data
|
||||
}
|
||||
|
||||
type Anchor interface {
|
||||
isAnchor()
|
||||
}
|
||||
|
||||
type EntryExit struct {
|
||||
EntryAnchor Anchor
|
||||
ExitAnchor Anchor
|
||||
}
|
||||
|
||||
type CursivePos struct {
|
||||
posFormat uint16 // Format identifier: format = 1
|
||||
coverage Coverage `offsetSize:"Offset16"` // Offset to Coverage table, from beginning of CursivePos subtable.
|
||||
entryExitRecords []entryExitRecord `arrayCount:"FirstUint16"` //[entryExitCount] Array of EntryExit records, in Coverage index order.
|
||||
EntryExits []EntryExit `isOpaque:""`
|
||||
}
|
||||
|
||||
type entryExitRecord struct {
|
||||
entryAnchorOffset Offset16 // Offset to entryAnchor table, from beginning of CursivePos subtable (may be NULL).
|
||||
exitAnchorOffset Offset16 // Offset to exitAnchor table, from beginning of CursivePos subtable (may be NULL).
|
||||
}
|
||||
|
||||
func (cp *CursivePos) parseEntryExits(src []byte) error {
|
||||
cp.EntryExits = make([]EntryExit, len(cp.entryExitRecords))
|
||||
var err error
|
||||
for i, rec := range cp.entryExitRecords {
|
||||
if rec.entryAnchorOffset != 0 {
|
||||
if L := len(src); L < int(rec.entryAnchorOffset) {
|
||||
return fmt.Errorf("EOF: expected length: %d, got %d", rec.entryAnchorOffset, L)
|
||||
}
|
||||
cp.EntryExits[i].EntryAnchor, _, err = ParseAnchor(src[rec.entryAnchorOffset:])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if rec.exitAnchorOffset != 0 {
|
||||
if L := len(src); L < int(rec.exitAnchorOffset) {
|
||||
return fmt.Errorf("EOF: expected length: %d, got %d", rec.exitAnchorOffset, L)
|
||||
}
|
||||
cp.EntryExits[i].ExitAnchor, _, err = ParseAnchor(src[rec.exitAnchorOffset:])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type MarkBasePos struct {
|
||||
posFormat uint16 // Format identifier: format = 1
|
||||
markCoverage Coverage `offsetSize:"Offset16"` // Offset to markCoverage table, from beginning of MarkBasePos subtable.
|
||||
BaseCoverage Coverage `offsetSize:"Offset16"` // Offset to baseCoverage table, from beginning of MarkBasePos subtable.
|
||||
markClassCount uint16 // Number of classes defined for marks
|
||||
MarkArray MarkArray `offsetSize:"Offset16"` // Offset to MarkArray table, from beginning of MarkBasePos subtable.
|
||||
BaseArray BaseArray `offsetSize:"Offset16" arguments:"offsetsCount=.markClassCount"` // Offset to BaseArray table, from beginning of MarkBasePos subtable.
|
||||
}
|
||||
|
||||
type BaseArray struct {
|
||||
baseRecords []anchorOffsets `arrayCount:"FirstUint16"` // [markClassCount] Array of offsets (one per mark class) to Anchor tables. Offsets are from beginning of BaseArray table, ordered by class (offsets may be NULL).
|
||||
data []byte `arrayCount:"ToEnd" subsliceStart:"AtStart"`
|
||||
}
|
||||
|
||||
func (ba BaseArray) Anchors() AnchorMatrix { return AnchorMatrix{ba.baseRecords, ba.data} }
|
||||
|
||||
type anchorOffsets struct {
|
||||
offsets []Offset16 // Array of offsets to Anchor tables, with external length
|
||||
}
|
||||
|
||||
type MarkLigPos struct {
|
||||
posFormat uint16 // Format identifier: format = 1
|
||||
MarkCoverage Coverage `offsetSize:"Offset16"` // Offset to markCoverage table, from beginning of MarkLigPos subtable.
|
||||
LigatureCoverage Coverage `offsetSize:"Offset16"` // Offset to ligatureCoverage table, from beginning of MarkLigPos subtable.
|
||||
MarkClassCount uint16 // Number of defined mark classes
|
||||
MarkArray MarkArray `offsetSize:"Offset16"` // Offset to MarkArray table, from beginning of MarkLigPos subtable.
|
||||
LigatureArray LigatureArray `offsetSize:"Offset16" arguments:"offsetsCount=.MarkClassCount"` // Offset to LigatureArray table, from beginning of MarkLigPos subtable.
|
||||
}
|
||||
|
||||
type LigatureArray struct {
|
||||
LigatureAttachs []LigatureAttach `arrayCount:"FirstUint16" offsetsArray:"Offset16"` // [ligatureCount] Array of offsets to LigatureAttach tables. Offsets are from beginning of LigatureArray table, ordered by ligatureCoverage index.
|
||||
}
|
||||
|
||||
type LigatureAttach struct {
|
||||
// [componentCount] Array of Component records, ordered in writing direction.
|
||||
// Each element is an array of offsets (one per class, length = [markClassCount]) to Anchor tables. Offsets are from beginning of LigatureAttach table, ordered by class (offsets may be NULL).
|
||||
componentRecords []anchorOffsets `arrayCount:"FirstUint16"`
|
||||
data []byte `arrayCount:"ToEnd" subsliceStart:"AtStart"`
|
||||
}
|
||||
|
||||
func (la LigatureAttach) Anchors() AnchorMatrix { return AnchorMatrix{la.componentRecords, la.data} }
|
||||
|
||||
type MarkMarkPos struct {
|
||||
PosFormat uint16 // Format identifier: format = 1
|
||||
Mark1Coverage Coverage `offsetSize:"Offset16"` // Offset to Combining Mark Coverage table, from beginning of MarkMarkPos subtable.
|
||||
Mark2Coverage Coverage `offsetSize:"Offset16"` // Offset to Base Mark Coverage table, from beginning of MarkMarkPos subtable.
|
||||
MarkClassCount uint16 // Number of Combining Mark classes defined
|
||||
Mark1Array MarkArray `offsetSize:"Offset16"` // Offset to MarkArray table for mark1, from beginning of MarkMarkPos subtable.
|
||||
Mark2Array Mark2Array `offsetSize:"Offset16" arguments:"offsetsCount=.MarkClassCount"` // Offset to Mark2Array table for mark2, from beginning of MarkMarkPos subtable.
|
||||
}
|
||||
|
||||
type Mark2Array struct {
|
||||
// [mark2Count] Array of Mark2Records, in Coverage order.
|
||||
// Each element if an array of offsets (one per class, length = [markClassCount]) to Anchor tables. Offsets are from beginning of Mark2Array table, in class order (offsets may be NULL).
|
||||
mark2Records []anchorOffsets `arrayCount:"FirstUint16"`
|
||||
data []byte `arrayCount:"ToEnd" subsliceStart:"AtStart"`
|
||||
}
|
||||
|
||||
func (ma Mark2Array) Anchors() AnchorMatrix { return AnchorMatrix{ma.mark2Records, ma.data} }
|
||||
|
||||
type ContextualPos struct {
|
||||
Data ContextualPosITF
|
||||
}
|
||||
|
||||
type ContextualPosITF interface {
|
||||
isContextualPosITF()
|
||||
|
||||
Cov() Coverage
|
||||
}
|
||||
|
||||
type (
|
||||
ContextualPos1 SequenceContextFormat1
|
||||
ContextualPos2 SequenceContextFormat2
|
||||
ContextualPos3 SequenceContextFormat3
|
||||
)
|
||||
|
||||
func (ContextualPos1) isContextualPosITF() {}
|
||||
func (ContextualPos2) isContextualPosITF() {}
|
||||
func (ContextualPos3) isContextualPosITF() {}
|
||||
|
||||
type ChainedContextualPos struct {
|
||||
Data ChainedContextualPosITF
|
||||
}
|
||||
|
||||
type ChainedContextualPosITF interface {
|
||||
isChainedContextualPosITF()
|
||||
|
||||
Cov() Coverage
|
||||
}
|
||||
|
||||
type (
|
||||
ChainedContextualPos1 ChainedSequenceContextFormat1
|
||||
ChainedContextualPos2 ChainedSequenceContextFormat2
|
||||
ChainedContextualPos3 ChainedSequenceContextFormat3
|
||||
)
|
||||
|
||||
func (ChainedContextualPos1) isChainedContextualPosITF() {}
|
||||
func (ChainedContextualPos2) isChainedContextualPosITF() {}
|
||||
func (ChainedContextualPos3) isChainedContextualPosITF() {}
|
||||
|
||||
type ExtensionPos Extension
|
||||
+1115
File diff suppressed because it is too large
Load Diff
+117
@@ -0,0 +1,117 @@
|
||||
// SPDX-License-Identifier: Unlicense OR BSD-3-Clause
|
||||
|
||||
package tables
|
||||
|
||||
type SingleSubs struct {
|
||||
Data SingleSubstData
|
||||
}
|
||||
|
||||
type SingleSubstData interface {
|
||||
isSingleSubstData()
|
||||
|
||||
Cov() Coverage
|
||||
}
|
||||
|
||||
func (SingleSubstData1) isSingleSubstData() {}
|
||||
func (SingleSubstData2) isSingleSubstData() {}
|
||||
|
||||
type SingleSubstData1 struct {
|
||||
format uint16 `unionTag:"1"`
|
||||
Coverage Coverage `offsetSize:"Offset16"` // Offset to Coverage table, from beginning of substitution subtable
|
||||
DeltaGlyphID int16 // Add to original glyph ID to get substitute glyph ID
|
||||
}
|
||||
|
||||
type SingleSubstData2 struct {
|
||||
format uint16 `unionTag:"2"`
|
||||
Coverage Coverage `offsetSize:"Offset16"` // Offset to Coverage table, from beginning of substitution subtable
|
||||
SubstituteGlyphIDs []GlyphID `arrayCount:"FirstUint16"` //[glyphCount] Array of substitute glyph IDs — ordered by Coverage index
|
||||
}
|
||||
|
||||
type MultipleSubs struct {
|
||||
substFormat uint16 // Format identifier: format = 1
|
||||
Coverage Coverage `offsetSize:"Offset16"` // Offset to Coverage table, from beginning of substitution subtable
|
||||
Sequences []Sequence `arrayCount:"FirstUint16" offsetsArray:"Offset16"`
|
||||
//[sequenceCount] Array of offsets to Sequence tables. Offsets are from beginning of substitution subtable, ordered by Coverage index
|
||||
}
|
||||
|
||||
type Sequence struct {
|
||||
SubstituteGlyphIDs []GlyphID `arrayCount:"FirstUint16"` // [glyphCount] String of glyph IDs to substitute
|
||||
}
|
||||
|
||||
type AlternateSubs struct {
|
||||
substFormat uint16 // Format identifier: format = 1
|
||||
Coverage Coverage `offsetSize:"Offset16"` // Offset to Coverage table, from beginning of substitution subtable
|
||||
AlternateSets []AlternateSet `arrayCount:"FirstUint16" offsetsArray:"Offset16"`
|
||||
}
|
||||
|
||||
type AlternateSet struct {
|
||||
AlternateGlyphIDs []GlyphID `arrayCount:"FirstUint16"` // Array of alternate glyph IDs, in arbitrary order
|
||||
}
|
||||
|
||||
type LigatureSubs struct {
|
||||
substFormat uint16 // Format identifier: format = 1
|
||||
Coverage Coverage `offsetSize:"Offset16"` // Offset to Coverage table, from beginning of substitution subtable
|
||||
LigatureSets []LigatureSet `arrayCount:"FirstUint16" offsetsArray:"Offset16"` //[ligatureSetCount] Array of offsets to LigatureSet tables. Offsets are from beginning of substitution subtable, ordered by Coverage index
|
||||
}
|
||||
|
||||
// All ligatures beginning with the same glyph
|
||||
type LigatureSet struct {
|
||||
Ligatures []Ligature `arrayCount:"FirstUint16" offsetsArray:"Offset16"` // [LigatureCount] Array of offsets to Ligature tables. Offsets are from beginning of LigatureSet table, ordered by preference.
|
||||
}
|
||||
|
||||
// Glyph components for one ligature
|
||||
type Ligature struct {
|
||||
LigatureGlyph GlyphID // glyph ID of ligature to substitute
|
||||
componentCount uint16 // Number of components in the ligature
|
||||
ComponentGlyphIDs []GlyphID `arrayCount:"ComputedField-componentCount-1"` // [componentCount - 1] Array of component glyph IDs — start with the second component, ordered in writing direction
|
||||
}
|
||||
|
||||
type ContextualSubs struct {
|
||||
Data ContextualSubsITF
|
||||
}
|
||||
|
||||
type ContextualSubsITF interface {
|
||||
isContextualSubsITF()
|
||||
|
||||
Cov() Coverage
|
||||
}
|
||||
|
||||
type (
|
||||
ContextualSubs1 SequenceContextFormat1
|
||||
ContextualSubs2 SequenceContextFormat2
|
||||
ContextualSubs3 SequenceContextFormat3
|
||||
)
|
||||
|
||||
func (ContextualSubs1) isContextualSubsITF() {}
|
||||
func (ContextualSubs2) isContextualSubsITF() {}
|
||||
func (ContextualSubs3) isContextualSubsITF() {}
|
||||
|
||||
type ChainedContextualSubs struct {
|
||||
Data ChainedContextualSubsITF
|
||||
}
|
||||
|
||||
type ChainedContextualSubsITF interface {
|
||||
isChainedContextualSubsITF()
|
||||
|
||||
Cov() Coverage
|
||||
}
|
||||
|
||||
type (
|
||||
ChainedContextualSubs1 ChainedSequenceContextFormat1
|
||||
ChainedContextualSubs2 ChainedSequenceContextFormat2
|
||||
ChainedContextualSubs3 ChainedSequenceContextFormat3
|
||||
)
|
||||
|
||||
func (ChainedContextualSubs1) isChainedContextualSubsITF() {}
|
||||
func (ChainedContextualSubs2) isChainedContextualSubsITF() {}
|
||||
func (ChainedContextualSubs3) isChainedContextualSubsITF() {}
|
||||
|
||||
type ExtensionSubs Extension
|
||||
|
||||
type ReverseChainSingleSubs struct {
|
||||
substFormat uint16 // Format identifier: format = 1
|
||||
coverage Coverage `offsetSize:"Offset16"` // Offset to Coverage table, from beginning of substitution subtable.
|
||||
BacktrackCoverages []Coverage `arrayCount:"FirstUint16" offsetsArray:"Offset16"` //[backtrackGlyphCount] Array of offsets to coverage tables in backtrack sequence, in glyph sequence order.
|
||||
LookaheadCoverages []Coverage `arrayCount:"FirstUint16" offsetsArray:"Offset16"` //[lookaheadGlyphCount] Array of offsets to coverage tables in lookahead sequence, in glyph sequence order.
|
||||
SubstituteGlyphIDs []GlyphID `arrayCount:"FirstUint16"` //[glyphCount] Array of substitute glyph IDs — ordered by Coverage index.
|
||||
}
|
||||
+814
@@ -0,0 +1,814 @@
|
||||
// SPDX-License-Identifier: Unlicense OR BSD-3-Clause
|
||||
|
||||
package tables
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/bits"
|
||||
)
|
||||
|
||||
// The following are types shared by GSUB and GPOS tables
|
||||
|
||||
// Coverage specifies all the glyphs affected by a substitution or
|
||||
// positioning operation described in a subtable.
|
||||
// Conceptually is it a []GlyphIndex, with an Index method,
|
||||
// but it may be implemented for efficiently.
|
||||
// See https://learn.microsoft.com/typography/opentype/spec/chapter2#lookup-table
|
||||
type Coverage interface {
|
||||
isCov()
|
||||
|
||||
// Index returns the index of the provided glyph, or
|
||||
// `false` if the glyph is not covered by this lookup.
|
||||
// Note: this method is injective: two distincts, covered glyphs are mapped
|
||||
// to distincts indices.
|
||||
Index(GlyphID) (int, bool)
|
||||
|
||||
// Len return the number of glyphs covered.
|
||||
// It is 0 for empty coverages.
|
||||
// For non empty Coverages, it is also 1 + (maximum index returned)
|
||||
Len() int
|
||||
}
|
||||
|
||||
func (Coverage1) isCov() {}
|
||||
func (Coverage2) isCov() {}
|
||||
|
||||
type Coverage1 struct {
|
||||
format uint16 `unionTag:"1"`
|
||||
Glyphs []GlyphID `arrayCount:"FirstUint16"`
|
||||
}
|
||||
|
||||
type Coverage2 struct {
|
||||
format uint16 `unionTag:"2"`
|
||||
Ranges []RangeRecord `arrayCount:"FirstUint16"`
|
||||
}
|
||||
|
||||
type RangeRecord struct {
|
||||
StartGlyphID GlyphID // First glyph ID in the range
|
||||
EndGlyphID GlyphID // Last glyph ID in the range
|
||||
StartCoverageIndex uint16 // Coverage Index of first glyph ID in range
|
||||
}
|
||||
|
||||
// ClassDef stores a value for a set of GlyphIDs.
|
||||
// Conceptually it is a map[GlyphID]uint16, but it may
|
||||
// be implemented more efficiently.
|
||||
type ClassDef interface {
|
||||
isClassDef()
|
||||
Class(gi GlyphID) (uint16, bool)
|
||||
|
||||
// Extent returns the maximum class ID + 1. This is the length
|
||||
// required for an array to be indexed by the class values.
|
||||
Extent() int
|
||||
}
|
||||
|
||||
func (ClassDef1) isClassDef() {}
|
||||
func (ClassDef2) isClassDef() {}
|
||||
|
||||
type ClassDef1 struct {
|
||||
format uint16 `unionTag:"1"`
|
||||
StartGlyphID GlyphID // First glyph ID of the classValueArray
|
||||
ClassValueArray []uint16 `arrayCount:"FirstUint16"` //[glyphCount] Array of Class Values — one per glyph ID
|
||||
}
|
||||
|
||||
type ClassDef2 struct {
|
||||
format uint16 `unionTag:"2"`
|
||||
ClassRangeRecords []ClassRangeRecord `arrayCount:"FirstUint16"` //[glyphCount] Array of Class Values — one per glyph ID
|
||||
}
|
||||
|
||||
type ClassRangeRecord struct {
|
||||
StartGlyphID GlyphID // First glyph ID in the range
|
||||
EndGlyphID GlyphID // Last glyph ID in the range
|
||||
Class uint16 // Applied to all glyphs in the range
|
||||
}
|
||||
|
||||
// Lookups
|
||||
|
||||
type SequenceLookupRecord struct {
|
||||
SequenceIndex uint16 // Index (zero-based) into the input glyph sequence
|
||||
LookupListIndex uint16 // Index (zero-based) into the LookupList
|
||||
}
|
||||
|
||||
type SequenceContextFormat1 struct {
|
||||
format uint16 `unionTag:"1"`
|
||||
coverage Coverage `offsetSize:"Offset16"` // Offset to Coverage table, from beginning of SequenceContextFormat1 table
|
||||
SeqRuleSet []SequenceRuleSet `arrayCount:"FirstUint16" offsetsArray:"Offset16"` //[seqRuleSetCount] Array of offsets to SequenceRuleSet tables, from beginning of SequenceContextFormat1 table (offsets may be NULL)
|
||||
}
|
||||
|
||||
func (sc *SequenceContextFormat1) sanitize(lookupCount uint16) error {
|
||||
for _, set := range sc.SeqRuleSet {
|
||||
for _, rule := range set.SeqRule {
|
||||
if err := rule.sanitize(lookupCount); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type SequenceRuleSet struct {
|
||||
SeqRule []SequenceRule `arrayCount:"FirstUint16" offsetsArray:"Offset16"` // Array of offsets to SequenceRule tables, from beginning of the SequenceRuleSet table
|
||||
}
|
||||
|
||||
type SequenceRule struct {
|
||||
glyphCount uint16 // Number of glyphs in the input glyph sequence
|
||||
seqLookupCount uint16 // Number of SequenceLookupRecords
|
||||
InputSequence []GlyphID `arrayCount:"ComputedField-glyphCount-1"` //[glyphCount - 1] Array of input glyph IDs—starting with the second glyph
|
||||
SeqLookupRecords []SequenceLookupRecord `arrayCount:"ComputedField-seqLookupCount"` //[seqLookupCount] Array of Sequence lookup records
|
||||
}
|
||||
|
||||
func (sr *SequenceRule) sanitize(lookupCount uint16) error {
|
||||
for _, rec := range sr.SeqLookupRecords {
|
||||
if rec.SequenceIndex >= sr.glyphCount {
|
||||
return fmt.Errorf("invalid sequence lookup table (input index %d >= %d)", rec.SequenceIndex, sr.glyphCount)
|
||||
}
|
||||
if rec.LookupListIndex >= lookupCount {
|
||||
return fmt.Errorf("invalid sequence lookup table (lookup index %d >= %d)", rec.LookupListIndex, lookupCount)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type SequenceContextFormat2 struct {
|
||||
format uint16 `unionTag:"2"`
|
||||
coverage Coverage `offsetSize:"Offset16"` // Offset to Coverage table, from beginning of SequenceContextFormat2 table
|
||||
ClassDef ClassDef `offsetSize:"Offset16"` // Offset to ClassDef table, from beginning of SequenceContextFormat2 table
|
||||
ClassSeqRuleSet []ClassSequenceRuleSet `arrayCount:"FirstUint16" offsetsArray:"Offset16"` //[classSeqRuleSetCount] Array of offsets to ClassSequenceRuleSet tables, from beginning of SequenceContextFormat2 table (may be NULL)
|
||||
}
|
||||
|
||||
// ClassSequenceRuleSet has the same binary format as SequenceRuleSet,
|
||||
// and using the same type simplifies later processing.
|
||||
type ClassSequenceRuleSet = SequenceRuleSet
|
||||
|
||||
type SequenceContextFormat3 struct {
|
||||
format uint16 `unionTag:"3"`
|
||||
glyphCount uint16 // Number of glyphs in the input sequence
|
||||
seqLookupCount uint16 // Number of SequenceLookupRecords
|
||||
Coverages []Coverage `arrayCount:"ComputedField-glyphCount" offsetsArray:"Offset16"` //[glyphCount] Array of offsets to Coverage tables, from beginning of SequenceContextFormat3 subtable
|
||||
SeqLookupRecords []SequenceLookupRecord `arrayCount:"ComputedField-seqLookupCount"` //[seqLookupCount] Array of SequenceLookupRecords
|
||||
}
|
||||
|
||||
type ChainedSequenceContextFormat1 struct {
|
||||
format uint16 `unionTag:"1"`
|
||||
coverage Coverage `offsetSize:"Offset16"` // Offset to Coverage table, from beginning of ChainSequenceContextFormat1 table
|
||||
ChainedSeqRuleSet []ChainedSequenceRuleSet `arrayCount:"FirstUint16" offsetsArray:"Offset16"` //[chainedSeqRuleSetCount] Array of offsets to ChainedSeqRuleSet tables, from beginning of ChainedSequenceContextFormat1 table (may be NULL)
|
||||
}
|
||||
|
||||
type ChainedSequenceRuleSet struct {
|
||||
ChainedSeqRules []ChainedSequenceRule `arrayCount:"FirstUint16" offsetsArray:"Offset16"` // Array of offsets to SequenceRule tables, from beginning of the SequenceRuleSet table
|
||||
}
|
||||
|
||||
type ChainedSequenceRule struct {
|
||||
BacktrackSequence []GlyphID `arrayCount:"FirstUint16"` //[backtrackGlyphCount] Array of backtrack glyph IDs
|
||||
inputGlyphCount uint16 // Number of glyphs in the input sequence
|
||||
InputSequence []GlyphID `arrayCount:"ComputedField-inputGlyphCount-1"` //[inputGlyphCount - 1] Array of input glyph IDs—start with second glyph
|
||||
LookaheadSequence []GlyphID `arrayCount:"FirstUint16"` //[lookaheadGlyphCount] Array of lookahead glyph IDs
|
||||
SeqLookupRecords []SequenceLookupRecord `arrayCount:"FirstUint16"` //[seqLookupCount] Array of SequenceLookupRecords
|
||||
}
|
||||
|
||||
type ChainedSequenceContextFormat2 struct {
|
||||
format uint16 `unionTag:"2"`
|
||||
coverage Coverage `offsetSize:"Offset16"` // Offset to Coverage table, from beginning of ChainedSequenceContextFormat2 table
|
||||
BacktrackClassDef ClassDef `offsetSize:"Offset16"` // Offset to ClassDef table containing backtrack sequence context, from beginning of ChainedSequenceContextFormat2 table
|
||||
InputClassDef ClassDef `offsetSize:"Offset16"` // Offset to ClassDef table containing input sequence context, from beginning of ChainedSequenceContextFormat2 table
|
||||
LookaheadClassDef ClassDef `offsetSize:"Offset16"` // Offset to ClassDef table containing lookahead sequence context, from beginning of ChainedSequenceContextFormat2 table
|
||||
ChainedClassSeqRuleSet []ChainedClassSequenceRuleSet `arrayCount:"FirstUint16" offsetsArray:"Offset16"` //[chainedClassSeqRuleSetCount] Array of offsets to ChainedClassSequenceRuleSet tables, from beginning of ChainedSequenceContextFormat2 table (may be NULL)
|
||||
}
|
||||
|
||||
// ChainedClassSequenceRuleSet has the same binary format as ChainedSequenceRuleSet,
|
||||
// and using the same type simplifies later processing.
|
||||
type ChainedClassSequenceRuleSet = ChainedSequenceRuleSet
|
||||
|
||||
type ChainedSequenceContextFormat3 struct {
|
||||
format uint16 `unionTag:"3"`
|
||||
BacktrackCoverages []Coverage `arrayCount:"FirstUint16" offsetsArray:"Offset16"` //[backtrackGlyphCount] Array of offsets to coverage tables for the backtrack sequence
|
||||
InputCoverages []Coverage `arrayCount:"FirstUint16" offsetsArray:"Offset16"` //[inputGlyphCount] Array of offsets to coverage tables for the input sequence
|
||||
LookaheadCoverages []Coverage `arrayCount:"FirstUint16" offsetsArray:"Offset16"` //[lookaheadGlyphCount] Array of offsets to coverage tables for the lookahead sequence
|
||||
SeqLookupRecords []SequenceLookupRecord `arrayCount:"FirstUint16"` //[seqLookupCount] Array of SequenceLookupRecords
|
||||
}
|
||||
|
||||
type Extension struct {
|
||||
substFormat uint16 // Format identifier. Set to 1.
|
||||
ExtensionLookupType uint16 // Lookup type of subtable referenced by extensionOffset (that is, the extension subtable).
|
||||
ExtensionOffset Offset32 // Offset to the extension subtable, of lookup type extensionLookupType, relative to the start of the ExtensionSubstFormat1 subtable.
|
||||
RawData []byte `subsliceStart:"AtStart" arrayCount:"ToEnd"`
|
||||
}
|
||||
|
||||
// GSUB is the Glyph Substitution (GSUB) table.
|
||||
// It provides data for substition of glyphs for appropriate rendering of scripts,
|
||||
// such as cursively-connecting forms in Arabic script,
|
||||
// or for advanced typographic effects, such as ligatures.
|
||||
// See https://learn.microsoft.com/fr-fr/typography/opentype/spec/gsub
|
||||
type GSUB Layout
|
||||
|
||||
// GSUBLookup is one lookup subtable data
|
||||
type GSUBLookup interface {
|
||||
isGSUBLookup()
|
||||
|
||||
// Coverage returns the coverage of the lookup subtable.
|
||||
// For ContextualSubs3 and ChainedContextualSubs3, its the coverage of the first input.
|
||||
Cov() Coverage
|
||||
}
|
||||
|
||||
func (SingleSubs) isGSUBLookup() {}
|
||||
func (MultipleSubs) isGSUBLookup() {}
|
||||
func (AlternateSubs) isGSUBLookup() {}
|
||||
func (LigatureSubs) isGSUBLookup() {}
|
||||
func (ContextualSubs) isGSUBLookup() {}
|
||||
func (ChainedContextualSubs) isGSUBLookup() {}
|
||||
func (ExtensionSubs) isGSUBLookup() {}
|
||||
func (ReverseChainSingleSubs) isGSUBLookup() {}
|
||||
|
||||
func (ms MultipleSubs) Sanitize() error {
|
||||
if exp, got := ms.Coverage.Len(), len(ms.Sequences); exp != got {
|
||||
return fmt.Errorf("GSUB: invalid MultipleSubs sequences count (%d != %d)", exp, got)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ls LigatureSubs) Sanitize() error {
|
||||
if exp, got := ls.Coverage.Len(), len(ls.LigatureSets); exp != got {
|
||||
return fmt.Errorf("GSUB: invalid LigatureSubs sets count (%d != %d)", exp, got)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cs ContextualSubs) Sanitize(lookupCount uint16) error {
|
||||
if f1, isFormat1 := cs.Data.(ContextualSubs1); isFormat1 {
|
||||
return (*SequenceContextFormat1)(&f1).sanitize(lookupCount)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (rs ReverseChainSingleSubs) Sanitize() error {
|
||||
if exp, got := rs.coverage.Len(), len(rs.SubstituteGlyphIDs); exp != got {
|
||||
return fmt.Errorf("GSUB: invalid ReverseChainSingleSubs glyphs count (%d != %d)", exp, got)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ext ExtensionSubs) Resolve() (GSUBLookup, error) {
|
||||
if L, E := len(ext.RawData), int(ext.ExtensionOffset); L < E {
|
||||
return nil, fmt.Errorf("EOF: expected length: %d, got %d", E, L)
|
||||
}
|
||||
lk, err := parseGSUBLookup(ext.RawData[ext.ExtensionOffset:], ext.ExtensionLookupType)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if _, isExt := lk.(ExtensionSubs); isExt {
|
||||
return nil, errors.New("invalid extension substitution table")
|
||||
}
|
||||
return lk, nil
|
||||
}
|
||||
|
||||
func parseGSUBLookup(src []byte, lookupType uint16) (out GSUBLookup, err error) {
|
||||
switch lookupType {
|
||||
case 1: // Single (format 1.1 1.2) Replace one glyph with one glyph
|
||||
out, _, err = ParseSingleSubs(src)
|
||||
case 2: // Multiple (format 2.1) Replace one glyph with more than one glyph
|
||||
out, _, err = ParseMultipleSubs(src)
|
||||
case 3: // Alternate (format 3.1) Replace one glyph with one of many glyphs
|
||||
out, _, err = ParseAlternateSubs(src)
|
||||
case 4: // Ligature (format 4.1) Replace multiple glyphs with one glyph
|
||||
out, _, err = ParseLigatureSubs(src)
|
||||
case 5: // Context (format 5.1 5.2 5.3) Replace one or more glyphs in context
|
||||
out, _, err = ParseContextualSubs(src)
|
||||
case 6: // Chaining Context (format 6.1 6.2 6.3) Replace one or more glyphs in chained context
|
||||
out, _, err = ParseChainedContextualSubs(src)
|
||||
case 7: // Extension Substitution (format 7.1) Extension mechanism for other substitutions
|
||||
out, _, err = ParseExtensionSubs(src)
|
||||
case 8: // Reverse chaining context single (format 8.1)
|
||||
out, _, err = ParseReverseChainSingleSubs(src)
|
||||
default:
|
||||
err = fmt.Errorf("invalid GSUB Loopkup type %d", lookupType)
|
||||
}
|
||||
return out, err
|
||||
}
|
||||
|
||||
// AsGSUBLookups returns the GSUB lookup subtables.
|
||||
func (lk Lookup) AsGSUBLookups() ([]GSUBLookup, error) {
|
||||
var err error
|
||||
out := make([]GSUBLookup, len(lk.subtableOffsets))
|
||||
for i, offset := range lk.subtableOffsets {
|
||||
if L := len(lk.rawData); L < int(offset) {
|
||||
return nil, fmt.Errorf("EOF: expected length: %d, got %d", offset, L)
|
||||
}
|
||||
out[i], err = parseGSUBLookup(lk.rawData[offset:], lk.lookupType)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// ------------------------ GPOS common data structures ------------------------
|
||||
|
||||
// GPOS is the Glyph Positioning (GPOS) table.
|
||||
// It provides precise control over glyph placement
|
||||
// for sophisticated text layout and rendering in each script
|
||||
// and language system that a font supports.
|
||||
// See https://learn.microsoft.com/fr-fr/typography/opentype/spec/gpos
|
||||
type GPOS Layout
|
||||
|
||||
type GPOSLookup interface {
|
||||
isGPOSLookup()
|
||||
|
||||
// Coverage returns the coverage of the lookup subtable.
|
||||
// For ContextualPos3 and ChainedContextualPos3, its the coverage of the first input.
|
||||
Cov() Coverage
|
||||
}
|
||||
|
||||
func (SinglePos) isGPOSLookup() {}
|
||||
func (PairPos) isGPOSLookup() {}
|
||||
func (CursivePos) isGPOSLookup() {}
|
||||
func (MarkBasePos) isGPOSLookup() {}
|
||||
func (MarkLigPos) isGPOSLookup() {}
|
||||
func (MarkMarkPos) isGPOSLookup() {}
|
||||
func (ContextualPos) isGPOSLookup() {}
|
||||
func (ChainedContextualPos) isGPOSLookup() {}
|
||||
func (ExtensionPos) isGPOSLookup() {}
|
||||
|
||||
func (sp *SinglePos) Sanitize() error {
|
||||
if f2, isFormat2 := sp.Data.(SinglePosData2); isFormat2 {
|
||||
if exp, got := f2.coverage.Len(), len(f2.ValueRecords); exp != got {
|
||||
return fmt.Errorf("GPOS: invalid SinglePos values count (%d != %d)", exp, got)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (pp *PairPos) Sanitize() error {
|
||||
if f1, isFormat1 := pp.Data.(PairPosData1); isFormat1 {
|
||||
// there are fonts with to much PairSets : accept it
|
||||
if exp, got := f1.coverage.Len(), len(f1.PairSets); exp > got {
|
||||
return fmt.Errorf("GPOS: invalid PairPos1 sets count (%d > %d)", exp, got)
|
||||
}
|
||||
} else if f2, isFormat2 := pp.Data.(PairPosData2); isFormat2 {
|
||||
if exp, got := f2.ClassDef1.Extent(), int(f2.class1Count); exp != got {
|
||||
return fmt.Errorf("GPOS: invalid PairPos2 class1 count (%d != %d)", exp, got)
|
||||
}
|
||||
if exp, got := f2.ClassDef2.Extent(), int(f2.class2Count); exp != got {
|
||||
return fmt.Errorf("GPOS: invalid PairPos2 class2 count (%d != %d)", exp, got)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (mp *MarkBasePos) Sanitize() error {
|
||||
if exp, got := mp.markCoverage.Len(), len(mp.MarkArray.MarkRecords); exp != got {
|
||||
return fmt.Errorf("GPOS: invalid MarkBasePos marks count (%d != %d)", exp, got)
|
||||
}
|
||||
if exp, got := mp.BaseCoverage.Len(), len(mp.BaseArray.baseRecords); exp != got {
|
||||
return fmt.Errorf("GPOS: invalid MarkBasePos marks count (%d != %d)", exp, got)
|
||||
}
|
||||
if err := mp.BaseArray.Anchors().sanitizeOffsets(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (mp *MarkLigPos) Sanitize() error {
|
||||
if exp, got := mp.MarkCoverage.Len(), len(mp.MarkArray.MarkAnchors); exp != got {
|
||||
return fmt.Errorf("GPOS: invalid MarkBasePos marks count (%d != %d)", exp, got)
|
||||
}
|
||||
if exp, got := mp.LigatureCoverage.Len(), len(mp.LigatureArray.LigatureAttachs); exp != got {
|
||||
return fmt.Errorf("GPOS: invalid MarkBasePos marks count (%d != %d)", exp, got)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cs *ContextualPos) Sanitize(lookupCount uint16) error {
|
||||
if f1, isFormat1 := cs.Data.(ContextualPos1); isFormat1 {
|
||||
return (*SequenceContextFormat1)(&f1).sanitize(lookupCount)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ext ExtensionPos) Resolve() (GPOSLookup, error) {
|
||||
if L, E := len(ext.RawData), int(ext.ExtensionOffset); L < E {
|
||||
return nil, fmt.Errorf("EOF: expected length: %d, got %d", E, L)
|
||||
}
|
||||
lk, err := parseGPOSLookup(ext.RawData[ext.ExtensionOffset:], ext.ExtensionLookupType)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if _, isExt := lk.(ExtensionPos); isExt {
|
||||
return nil, errors.New("invalid extension positioning table")
|
||||
}
|
||||
return lk, nil
|
||||
}
|
||||
|
||||
func parseGPOSLookup(src []byte, lookupType uint16) (out GPOSLookup, err error) {
|
||||
switch lookupType {
|
||||
case 1: // Single adjustment Adjust position of a single glyph
|
||||
out, _, err = ParseSinglePos(src)
|
||||
case 2: // Pair adjustment Adjust position of a pair of glyphs
|
||||
out, _, err = ParsePairPos(src)
|
||||
case 3: // Cursive attachment Attach cursive glyphs
|
||||
out, _, err = ParseCursivePos(src)
|
||||
case 4: // MarkToBase attachment Attach a combining mark to a base glyph
|
||||
out, _, err = ParseMarkBasePos(src)
|
||||
case 5: // MarkToLigature attachment Attach a combining mark to a ligature
|
||||
out, _, err = ParseMarkLigPos(src)
|
||||
case 6: // MarkToMark attachment Attach a combining mark to another mark
|
||||
out, _, err = ParseMarkMarkPos(src)
|
||||
case 7: // Context positioning Position one or more glyphs in context
|
||||
out, _, err = ParseContextualPos(src)
|
||||
case 8: // Chained Context positioning Position one or more glyphs in chained context
|
||||
out, _, err = ParseChainedContextualPos(src)
|
||||
case 9: // Extension positioning Extension mechanism for other positionings
|
||||
out, _, err = ParseExtensionPos(src)
|
||||
default:
|
||||
err = fmt.Errorf("invalid GPOS Loopkup type %d", lookupType)
|
||||
}
|
||||
return out, err
|
||||
}
|
||||
|
||||
// AsGPOSLookups returns the GPOS lookup subtables
|
||||
func (lk Lookup) AsGPOSLookups() ([]GPOSLookup, error) {
|
||||
var err error
|
||||
out := make([]GPOSLookup, len(lk.subtableOffsets))
|
||||
for i, offset := range lk.subtableOffsets {
|
||||
if L := len(lk.rawData); L < int(offset) {
|
||||
return nil, fmt.Errorf("EOF: expected length: %d, got %d", offset, L)
|
||||
}
|
||||
out[i], err = parseGPOSLookup(lk.rawData[offset:], lk.lookupType)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// ValueFormat is a mask indicating which field
|
||||
// are set in a GPOS [ValueRecord].
|
||||
// It is often shared between many records.
|
||||
type ValueFormat uint16
|
||||
|
||||
// number of fields present
|
||||
func (f ValueFormat) size() int { return bits.OnesCount16(uint16(f)) }
|
||||
|
||||
const (
|
||||
XPlacement ValueFormat = 1 << iota // Includes horizontal adjustment for placement
|
||||
YPlacement // Includes vertical adjustment for placement
|
||||
XAdvance // Includes horizontal adjustment for advance
|
||||
YAdvance // Includes vertical adjustment for advance
|
||||
XPlaDevice // Includes horizontal Device table for placement
|
||||
YPlaDevice // Includes vertical Device table for placement
|
||||
XAdvDevice // Includes horizontal Device table for advance
|
||||
YAdvDevice // Includes vertical Device table for advance
|
||||
|
||||
// Mask for having any Device table
|
||||
Devices = XPlaDevice | YPlaDevice | XAdvDevice | YAdvDevice
|
||||
)
|
||||
|
||||
// ValueRecord has optional fields
|
||||
type ValueRecord struct {
|
||||
XPlacement int16 // Horizontal adjustment for placement, in design units.
|
||||
YPlacement int16 // Vertical adjustment for placement, in design units.
|
||||
XAdvance int16 // Horizontal adjustment for advance, in design units — only used for horizontal layout.
|
||||
YAdvance int16 // Vertical adjustment for advance, in design units — only used for vertical layout.
|
||||
XPlaDevice DeviceTable // Offset to Device table (non-variable font) / VariationIndex table (variable font) for horizontal placement, from beginning of the immediate parent table (SinglePos or PairPosFormat2 lookup subtable, PairSet table within a PairPosFormat1 lookup subtable) — may be NULL.
|
||||
YPlaDevice DeviceTable // Offset to Device table (non-variable font) / VariationIndex table (variable font) for vertical placement, from beginning of the immediate parent table (SinglePos or PairPosFormat2 lookup subtable, PairSet table within a PairPosFormat1 lookup subtable) — may be NULL.
|
||||
XAdvDevice DeviceTable // Offset to Device table (non-variable font) / VariationIndex table (variable font) for horizontal advance, from beginning of the immediate parent table (SinglePos or PairPosFormat2 lookup subtable, PairSet table within a PairPosFormat1 lookup subtable) — may be NULL.
|
||||
YAdvDevice DeviceTable // Offset to Device table (non-variable font) / VariationIndex table (variable font) for vertical advance, from beginning of the immediate parent table (SinglePos or PairPosFormat2 lookup subtable, PairSet table within a PairPosFormat1 lookup su
|
||||
}
|
||||
|
||||
// [data] must start at the immediate parent table, [offset] indicating
|
||||
// the start of the record in it.
|
||||
// Returns [offset] + the number of bytes read from [offset]
|
||||
// Note that a [format] with value 0, is supported, resulting in a no-op
|
||||
func parseValueRecord(format ValueFormat, data []byte, offset int) (out ValueRecord, _ int, err error) {
|
||||
if L := len(data); L < offset {
|
||||
return out, 0, fmt.Errorf("EOF: expected length: %d, got %d", offset, L)
|
||||
}
|
||||
|
||||
size := format.size() // number of fields present
|
||||
if size == 0 { // return early
|
||||
return out, offset, nil
|
||||
}
|
||||
// start by parsing the list of values
|
||||
values, err := ParseUint16s(data[offset:], size)
|
||||
if err != nil {
|
||||
return out, 0, fmt.Errorf("invalid value record: %s", err)
|
||||
}
|
||||
// follow the order
|
||||
cursor := 0
|
||||
if format&XPlacement != 0 {
|
||||
out.XPlacement = int16(values[cursor])
|
||||
cursor++
|
||||
}
|
||||
if format&YPlacement != 0 {
|
||||
out.YPlacement = int16(values[cursor])
|
||||
cursor++
|
||||
}
|
||||
if format&XAdvance != 0 {
|
||||
out.XAdvance = int16(values[cursor])
|
||||
cursor++
|
||||
}
|
||||
if format&YAdvance != 0 {
|
||||
out.YAdvance = int16(values[cursor])
|
||||
cursor++
|
||||
}
|
||||
if format&XPlaDevice != 0 {
|
||||
if devOffset := values[cursor]; devOffset != 0 {
|
||||
out.XPlaDevice, err = parseDeviceTable(data, devOffset)
|
||||
if err != nil {
|
||||
return out, 0, err
|
||||
}
|
||||
}
|
||||
cursor++
|
||||
}
|
||||
if format&YPlaDevice != 0 {
|
||||
if devOffset := values[cursor]; devOffset != 0 {
|
||||
out.YPlaDevice, err = parseDeviceTable(data, devOffset)
|
||||
if err != nil {
|
||||
return out, 0, err
|
||||
}
|
||||
}
|
||||
cursor++
|
||||
}
|
||||
if format&XAdvDevice != 0 {
|
||||
if devOffset := values[cursor]; devOffset != 0 {
|
||||
out.XAdvDevice, err = parseDeviceTable(data, devOffset)
|
||||
if err != nil {
|
||||
return out, 0, err
|
||||
}
|
||||
}
|
||||
cursor++
|
||||
}
|
||||
if format&YAdvDevice != 0 {
|
||||
if devOffset := values[cursor]; devOffset != 0 {
|
||||
out.YAdvDevice, err = parseDeviceTable(data, devOffset)
|
||||
if err != nil {
|
||||
return out, 0, err
|
||||
}
|
||||
}
|
||||
cursor++ // useless actually
|
||||
}
|
||||
return out, offset + 2*size, err
|
||||
}
|
||||
|
||||
type pairValueRecords struct {
|
||||
data []byte // start with the item count
|
||||
fmt1, fmt2 ValueFormat
|
||||
}
|
||||
|
||||
// panic if index is out of range
|
||||
func (ps pairValueRecords) get(index int) (out PairValueRecord, err error) {
|
||||
recLen := 1 + ps.fmt1.size() + ps.fmt2.size()
|
||||
offset := 2 + 2*index*recLen
|
||||
|
||||
out.SecondGlyph = GlyphID(binary.BigEndian.Uint16(ps.data[offset:]))
|
||||
v1, newOffset, err := parseValueRecord(ps.fmt1, ps.data, offset+2)
|
||||
if err != nil {
|
||||
return out, fmt.Errorf("invalid pair set table: %s", err)
|
||||
}
|
||||
v2, _, err := parseValueRecord(ps.fmt2, ps.data, newOffset)
|
||||
if err != nil {
|
||||
return out, fmt.Errorf("invalid pair set table: %s", err)
|
||||
}
|
||||
out.ValueRecord1 = v1
|
||||
out.ValueRecord2 = v2
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// DeviceTable is either an DeviceHinting for standard fonts,
|
||||
// or a DeviceVariation for variable fonts.
|
||||
type DeviceTable interface {
|
||||
isDevice()
|
||||
}
|
||||
|
||||
func (DeviceHinting) isDevice() {}
|
||||
func (DeviceVariation) isDevice() {}
|
||||
|
||||
type DeviceHinting struct {
|
||||
// with length endSize - startSize + 1
|
||||
Values []int8
|
||||
// correction range, in ppem
|
||||
StartSize, EndSize uint16
|
||||
}
|
||||
|
||||
type DeviceVariation VariationStoreIndex
|
||||
|
||||
func parseDeviceTable(src []byte, offset uint16) (DeviceTable, error) {
|
||||
if L := len(src); L < int(offset)+6 {
|
||||
return nil, fmt.Errorf("EOF: expected length: %d, got %d", offset+6, L)
|
||||
}
|
||||
var header DeviceTableHeader
|
||||
header.mustParse(src[offset:])
|
||||
|
||||
switch format := header.deltaFormat; format {
|
||||
case 1, 2, 3:
|
||||
var out DeviceHinting
|
||||
|
||||
out.StartSize, out.EndSize = header.first, header.second
|
||||
if out.EndSize < out.StartSize {
|
||||
return nil, errors.New("invalid positionning device subtable")
|
||||
}
|
||||
|
||||
nbPerUint16 := 16 / (1 << format) // 8, 4 or 2
|
||||
outLength := int(out.EndSize - out.StartSize + 1)
|
||||
var count int
|
||||
if outLength%nbPerUint16 == 0 {
|
||||
count = outLength / nbPerUint16
|
||||
} else {
|
||||
// add padding
|
||||
count = outLength/nbPerUint16 + 1
|
||||
}
|
||||
uint16s, err := ParseUint16s(src[offset+6:], count)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out.Values = make([]int8, count*nbPerUint16) // handle rounding error by reslicing after
|
||||
switch format {
|
||||
case 1:
|
||||
for i, u := range uint16s {
|
||||
uint16As2Bits(out.Values[i*8:], u)
|
||||
}
|
||||
case 2:
|
||||
for i, u := range uint16s {
|
||||
uint16As4Bits(out.Values[i*4:], u)
|
||||
}
|
||||
case 3:
|
||||
for i, u := range uint16s {
|
||||
uint16As8Bits(out.Values[i*2:], u)
|
||||
}
|
||||
}
|
||||
out.Values = out.Values[:outLength]
|
||||
return out, nil
|
||||
case 0x8000:
|
||||
return DeviceVariation{DeltaSetOuter: header.first, DeltaSetInner: header.second}, nil
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported positionning device subtable: %d", format)
|
||||
}
|
||||
}
|
||||
|
||||
type PairValueRecord struct {
|
||||
SecondGlyph GlyphID // Glyph ID of second glyph in the pair (first glyph is listed in the Coverage table).
|
||||
ValueRecord1 ValueRecord // Positioning data for the first glyph in the pair.
|
||||
ValueRecord2 ValueRecord // Positioning data for the second glyph in the pair.
|
||||
}
|
||||
|
||||
type Class1Record []Class2Record //[class2Count] Array of Class2 records, ordered by classes in classDef2.
|
||||
|
||||
type Class2Record struct {
|
||||
ValueRecord1 ValueRecord // Positioning for first glyph — empty if valueFormat1 = 0.
|
||||
ValueRecord2 ValueRecord // Positioning for second glyph — empty if valueFormat2 = 0.
|
||||
}
|
||||
|
||||
func (AnchorFormat1) isAnchor() {}
|
||||
func (AnchorFormat2) isAnchor() {}
|
||||
func (AnchorFormat3) isAnchor() {}
|
||||
|
||||
type AnchorFormat1 struct {
|
||||
anchorFormat uint16 `unionTag:"1"`
|
||||
XCoordinate int16 // Horizontal value, in design units
|
||||
YCoordinate int16 // Vertical value, in design units
|
||||
}
|
||||
|
||||
type AnchorFormat2 struct {
|
||||
anchorFormat uint16 `unionTag:"2"`
|
||||
XCoordinate int16 // Horizontal value, in design units
|
||||
YCoordinate int16 // Vertical value, in design units
|
||||
AnchorPoint uint16 // Index to glyph contour point
|
||||
}
|
||||
|
||||
type AnchorFormat3 struct {
|
||||
anchorFormat uint16 `unionTag:"3"`
|
||||
XCoordinate int16 // Horizontal value, in design units
|
||||
YCoordinate int16 // Vertical value, in design units
|
||||
xDeviceOffset Offset16 // Offset to Device table (non-variable font) / VariationIndex table (variable font) for X coordinate, from beginning of Anchor table (may be NULL)
|
||||
yDeviceOffset Offset16 // Offset to Device table (non-variable font) / VariationIndex table (variable font) for Y coordinate, from beginning of Anchor table (may be NULL)
|
||||
XDevice DeviceTable `isOpaque:""` // Offset to Device table (non-variable font) / VariationIndex table (variable font) for X coordinate, from beginning of Anchor table (may be NULL)
|
||||
YDevice DeviceTable `isOpaque:""` // Offset to Device table (non-variable font) / VariationIndex table (variable font) for Y coordinate, from beginning of Anchor table (may be NULL)
|
||||
}
|
||||
|
||||
func (af *AnchorFormat3) parseXDevice(src []byte) error {
|
||||
if af.xDeviceOffset == 0 {
|
||||
return nil
|
||||
}
|
||||
var err error
|
||||
af.XDevice, err = parseDeviceTable(src, uint16(af.xDeviceOffset))
|
||||
return err
|
||||
}
|
||||
|
||||
func (af *AnchorFormat3) parseYDevice(src []byte) error {
|
||||
if af.yDeviceOffset == 0 {
|
||||
return nil
|
||||
}
|
||||
var err error
|
||||
af.YDevice, err = parseDeviceTable(src, uint16(af.yDeviceOffset))
|
||||
return err
|
||||
}
|
||||
|
||||
// AnchorMatrix is a compact representation of a [][]Anchor
|
||||
type AnchorMatrix struct {
|
||||
records []anchorOffsets
|
||||
data []byte
|
||||
}
|
||||
|
||||
func (am AnchorMatrix) Len() int { return len(am.records) }
|
||||
|
||||
func (am AnchorMatrix) sanitizeOffsets() error {
|
||||
for _, list := range am.records {
|
||||
for _, offset := range list.offsets {
|
||||
if offset == 0 {
|
||||
continue
|
||||
}
|
||||
if L := len(am.data); L < int(offset) {
|
||||
return fmt.Errorf("EOF: expected length: %d, got %d", offset, L)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (am AnchorMatrix) Anchor(index, class int) Anchor {
|
||||
if len(am.records) < index {
|
||||
return nil
|
||||
}
|
||||
offsets := am.records[index].offsets
|
||||
if len(offsets) < class {
|
||||
return nil
|
||||
}
|
||||
offset := offsets[class]
|
||||
if offset == 0 {
|
||||
return nil
|
||||
}
|
||||
anchor, _, _ := ParseAnchor(am.data[offset:]) // offset is sanitized
|
||||
return anchor
|
||||
}
|
||||
|
||||
type MarkArray struct {
|
||||
MarkRecords []MarkRecord `arrayCount:"FirstUint16"` //[markCount] Array of MarkRecords, ordered by corresponding glyphs in the associated mark Coverage table.
|
||||
MarkAnchors []Anchor `isOpaque:""` // with same length as MarkRecords
|
||||
}
|
||||
|
||||
func (ma *MarkArray) parseMarkAnchors(src []byte) error {
|
||||
ma.MarkAnchors = make([]Anchor, len(ma.MarkRecords))
|
||||
var err error
|
||||
for i, rec := range ma.MarkRecords {
|
||||
if L := len(src); L < int(rec.markAnchorOffset) {
|
||||
return fmt.Errorf("EOF: expected length: %d, got %d", rec.markAnchorOffset, L)
|
||||
}
|
||||
ma.MarkAnchors[i], _, err = ParseAnchor(src[rec.markAnchorOffset:])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type MarkRecord struct {
|
||||
MarkClass uint16 // Class defined for the associated mark.
|
||||
markAnchorOffset Offset16 // Offset to Anchor table, from beginning of MarkArray table.
|
||||
}
|
||||
|
||||
// ------------------------------ parsing helpers ------------------------------
|
||||
|
||||
// write 8 elements
|
||||
func uint16As2Bits(dst []int8, u uint16) {
|
||||
const mask = 0xFE // 11111110
|
||||
dst[0] = int8((0-uint8(u>>15&1))&mask | uint8(u>>14&1))
|
||||
dst[1] = int8((0-uint8(u>>13&1))&mask | uint8(u>>12&1))
|
||||
dst[2] = int8((0-uint8(u>>11&1))&mask | uint8(u>>10&1))
|
||||
dst[3] = int8((0-uint8(u>>9&1))&mask | uint8(u>>8&1))
|
||||
dst[4] = int8((0-uint8(u>>7&1))&mask | uint8(u>>6&1))
|
||||
dst[5] = int8((0-uint8(u>>5&1))&mask | uint8(u>>4&1))
|
||||
dst[6] = int8((0-uint8(u>>3&1))&mask | uint8(u>>2&1))
|
||||
dst[7] = int8((0-uint8(u>>1&1))&mask | uint8(u>>0&1))
|
||||
}
|
||||
|
||||
// write 4 elements
|
||||
func uint16As4Bits(dst []int8, u uint16) {
|
||||
const mask = 0xF8 // 11111000
|
||||
|
||||
dst[0] = int8((0-uint8(u>>15&1))&mask | uint8(u>>12&0x07))
|
||||
dst[1] = int8((0-uint8(u>>11&1))&mask | uint8(u>>8&0x07))
|
||||
dst[2] = int8((0-uint8(u>>7&1))&mask | uint8(u>>4&0x07))
|
||||
dst[3] = int8((0-uint8(u>>3&1))&mask | uint8(u>>0&0x07))
|
||||
}
|
||||
|
||||
// write 2 elements
|
||||
func uint16As8Bits(dst []int8, u uint16) {
|
||||
dst[0] = int8(u >> 8)
|
||||
dst[1] = int8(u)
|
||||
}
|
||||
|
||||
// ParseUint16s interprets data as a (big endian) uint16 slice.
|
||||
// It returns an error if [data] is not long enough for the given [count].
|
||||
func ParseUint16s(src []byte, count int) ([]uint16, error) {
|
||||
if L := len(src); L < 2*count {
|
||||
return nil, fmt.Errorf("EOF: expected length: %d, got %d", 2*count, L)
|
||||
}
|
||||
out := make([]uint16, count)
|
||||
for i := range out {
|
||||
out[i] = binary.BigEndian.Uint16(src[2*i:])
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
+506
@@ -0,0 +1,506 @@
|
||||
// SPDX-License-Identifier: Unlicense OR BSD-3-Clause
|
||||
|
||||
package tables
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// Code generated by binarygen from ot_layout_src.go. DO NOT EDIT
|
||||
|
||||
func (item *ConditionFormat1) mustParse(src []byte) {
|
||||
_ = src[7] // early bound checking
|
||||
item.format = binary.BigEndian.Uint16(src[0:])
|
||||
item.AxisIndex = binary.BigEndian.Uint16(src[2:])
|
||||
item.FilterRangeMinValue = Coord(binary.BigEndian.Uint16(src[4:]))
|
||||
item.FilterRangeMaxValue = Coord(binary.BigEndian.Uint16(src[6:]))
|
||||
}
|
||||
|
||||
func ParseConditionFormat1(src []byte) (ConditionFormat1, int, error) {
|
||||
var item ConditionFormat1
|
||||
n := 0
|
||||
if L := len(src); L < 8 {
|
||||
return item, 0, fmt.Errorf("reading ConditionFormat1: "+"EOF: expected length: 8, got %d", L)
|
||||
}
|
||||
item.mustParse(src)
|
||||
n += 8
|
||||
return item, n, nil
|
||||
}
|
||||
|
||||
func ParseConditionSet(src []byte) (ConditionSet, int, error) {
|
||||
var item ConditionSet
|
||||
n := 0
|
||||
if L := len(src); L < 2 {
|
||||
return item, 0, fmt.Errorf("reading ConditionSet: "+"EOF: expected length: 2, got %d", L)
|
||||
}
|
||||
arrayLengthConditions := int(binary.BigEndian.Uint16(src[0:]))
|
||||
n += 2
|
||||
|
||||
{
|
||||
|
||||
if L := len(src); L < 2+arrayLengthConditions*4 {
|
||||
return item, 0, fmt.Errorf("reading ConditionSet: "+"EOF: expected length: %d, got %d", 2+arrayLengthConditions*4, L)
|
||||
}
|
||||
|
||||
item.Conditions = make([]ConditionFormat1, arrayLengthConditions) // allocation guarded by the previous check
|
||||
for i := range item.Conditions {
|
||||
offset := int(binary.BigEndian.Uint32(src[2+i*4:]))
|
||||
// ignore null offsets
|
||||
if offset == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
if L := len(src); L < offset {
|
||||
return item, 0, fmt.Errorf("reading ConditionSet: "+"EOF: expected length: %d, got %d", offset, L)
|
||||
}
|
||||
|
||||
var err error
|
||||
item.Conditions[i], _, err = ParseConditionFormat1(src[offset:])
|
||||
if err != nil {
|
||||
return item, 0, fmt.Errorf("reading ConditionSet: %s", err)
|
||||
}
|
||||
}
|
||||
n += arrayLengthConditions * 4
|
||||
}
|
||||
return item, n, nil
|
||||
}
|
||||
|
||||
func ParseFeature(src []byte) (Feature, int, error) {
|
||||
var item Feature
|
||||
n := 0
|
||||
if L := len(src); L < 4 {
|
||||
return item, 0, fmt.Errorf("reading Feature: "+"EOF: expected length: 4, got %d", L)
|
||||
}
|
||||
_ = src[3] // early bound checking
|
||||
item.featureParamsOffset = binary.BigEndian.Uint16(src[0:])
|
||||
arrayLengthLookupListIndices := int(binary.BigEndian.Uint16(src[2:]))
|
||||
n += 4
|
||||
|
||||
{
|
||||
|
||||
if L := len(src); L < 4+arrayLengthLookupListIndices*2 {
|
||||
return item, 0, fmt.Errorf("reading Feature: "+"EOF: expected length: %d, got %d", 4+arrayLengthLookupListIndices*2, L)
|
||||
}
|
||||
|
||||
item.LookupListIndices = make([]uint16, arrayLengthLookupListIndices) // allocation guarded by the previous check
|
||||
for i := range item.LookupListIndices {
|
||||
item.LookupListIndices[i] = binary.BigEndian.Uint16(src[4+i*2:])
|
||||
}
|
||||
n += arrayLengthLookupListIndices * 2
|
||||
}
|
||||
return item, n, nil
|
||||
}
|
||||
|
||||
func ParseFeatureList(src []byte) (FeatureList, int, error) {
|
||||
var item FeatureList
|
||||
n := 0
|
||||
if L := len(src); L < 2 {
|
||||
return item, 0, fmt.Errorf("reading FeatureList: "+"EOF: expected length: 2, got %d", L)
|
||||
}
|
||||
arrayLengthRecords := int(binary.BigEndian.Uint16(src[0:]))
|
||||
n += 2
|
||||
|
||||
{
|
||||
|
||||
if L := len(src); L < 2+arrayLengthRecords*6 {
|
||||
return item, 0, fmt.Errorf("reading FeatureList: "+"EOF: expected length: %d, got %d", 2+arrayLengthRecords*6, L)
|
||||
}
|
||||
|
||||
item.Records = make([]TagOffsetRecord, arrayLengthRecords) // allocation guarded by the previous check
|
||||
for i := range item.Records {
|
||||
item.Records[i].mustParse(src[2+i*6:])
|
||||
}
|
||||
n += arrayLengthRecords * 6
|
||||
}
|
||||
{
|
||||
|
||||
err := item.parseFeatures(src[:])
|
||||
if err != nil {
|
||||
return item, 0, fmt.Errorf("reading FeatureList: %s", err)
|
||||
}
|
||||
}
|
||||
return item, n, nil
|
||||
}
|
||||
|
||||
func ParseFeatureTableSubstitution(src []byte) (FeatureTableSubstitution, int, error) {
|
||||
var item FeatureTableSubstitution
|
||||
n := 0
|
||||
if L := len(src); L < 6 {
|
||||
return item, 0, fmt.Errorf("reading FeatureTableSubstitution: "+"EOF: expected length: 6, got %d", L)
|
||||
}
|
||||
_ = src[5] // early bound checking
|
||||
item.majorVersion = binary.BigEndian.Uint16(src[0:])
|
||||
item.minorVersion = binary.BigEndian.Uint16(src[2:])
|
||||
arrayLengthSubstitutions := int(binary.BigEndian.Uint16(src[4:]))
|
||||
n += 6
|
||||
|
||||
{
|
||||
|
||||
offset := 6
|
||||
for i := 0; i < arrayLengthSubstitutions; i++ {
|
||||
elem, read, err := ParseFeatureTableSubstitutionRecord(src[offset:], src)
|
||||
if err != nil {
|
||||
return item, 0, fmt.Errorf("reading FeatureTableSubstitution: %s", err)
|
||||
}
|
||||
item.Substitutions = append(item.Substitutions, elem)
|
||||
offset += read
|
||||
}
|
||||
n = offset
|
||||
}
|
||||
return item, n, nil
|
||||
}
|
||||
|
||||
func ParseFeatureTableSubstitutionRecord(src []byte, parentSrc []byte) (FeatureTableSubstitutionRecord, int, error) {
|
||||
var item FeatureTableSubstitutionRecord
|
||||
n := 0
|
||||
if L := len(src); L < 6 {
|
||||
return item, 0, fmt.Errorf("reading FeatureTableSubstitutionRecord: "+"EOF: expected length: 6, got %d", L)
|
||||
}
|
||||
_ = src[5] // early bound checking
|
||||
item.FeatureIndex = binary.BigEndian.Uint16(src[0:])
|
||||
offsetAlternateFeature := int(binary.BigEndian.Uint32(src[2:]))
|
||||
n += 6
|
||||
|
||||
{
|
||||
if offsetAlternateFeature != 0 { // ignore null offset
|
||||
if L := len(parentSrc); L < offsetAlternateFeature {
|
||||
return item, 0, fmt.Errorf("reading FeatureTableSubstitutionRecord: "+"EOF: expected length: %d, got %d", offsetAlternateFeature, L)
|
||||
}
|
||||
|
||||
var err error
|
||||
item.AlternateFeature, _, err = ParseFeature(parentSrc[offsetAlternateFeature:])
|
||||
if err != nil {
|
||||
return item, 0, fmt.Errorf("reading FeatureTableSubstitutionRecord: %s", err)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
return item, n, nil
|
||||
}
|
||||
|
||||
func ParseFeatureVariation(src []byte) (FeatureVariation, int, error) {
|
||||
var item FeatureVariation
|
||||
n := 0
|
||||
if L := len(src); L < 8 {
|
||||
return item, 0, fmt.Errorf("reading FeatureVariation: "+"EOF: expected length: 8, got %d", L)
|
||||
}
|
||||
_ = src[7] // early bound checking
|
||||
item.majorVersion = binary.BigEndian.Uint16(src[0:])
|
||||
item.minorVersion = binary.BigEndian.Uint16(src[2:])
|
||||
arrayLengthFeatureVariationRecords := int(binary.BigEndian.Uint32(src[4:]))
|
||||
n += 8
|
||||
|
||||
{
|
||||
|
||||
offset := 8
|
||||
for i := 0; i < arrayLengthFeatureVariationRecords; i++ {
|
||||
elem, read, err := ParseFeatureVariationRecord(src[offset:], src)
|
||||
if err != nil {
|
||||
return item, 0, fmt.Errorf("reading FeatureVariation: %s", err)
|
||||
}
|
||||
item.FeatureVariationRecords = append(item.FeatureVariationRecords, elem)
|
||||
offset += read
|
||||
}
|
||||
n = offset
|
||||
}
|
||||
return item, n, nil
|
||||
}
|
||||
|
||||
func ParseFeatureVariationRecord(src []byte, parentSrc []byte) (FeatureVariationRecord, int, error) {
|
||||
var item FeatureVariationRecord
|
||||
n := 0
|
||||
if L := len(src); L < 8 {
|
||||
return item, 0, fmt.Errorf("reading FeatureVariationRecord: "+"EOF: expected length: 8, got %d", L)
|
||||
}
|
||||
_ = src[7] // early bound checking
|
||||
offsetConditionSet := int(binary.BigEndian.Uint32(src[0:]))
|
||||
offsetSubstitutions := int(binary.BigEndian.Uint32(src[4:]))
|
||||
n += 8
|
||||
|
||||
{
|
||||
if offsetConditionSet != 0 { // ignore null offset
|
||||
if L := len(parentSrc); L < offsetConditionSet {
|
||||
return item, 0, fmt.Errorf("reading FeatureVariationRecord: "+"EOF: expected length: %d, got %d", offsetConditionSet, L)
|
||||
}
|
||||
|
||||
var err error
|
||||
item.ConditionSet, _, err = ParseConditionSet(parentSrc[offsetConditionSet:])
|
||||
if err != nil {
|
||||
return item, 0, fmt.Errorf("reading FeatureVariationRecord: %s", err)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
{
|
||||
if offsetSubstitutions != 0 { // ignore null offset
|
||||
if L := len(parentSrc); L < offsetSubstitutions {
|
||||
return item, 0, fmt.Errorf("reading FeatureVariationRecord: "+"EOF: expected length: %d, got %d", offsetSubstitutions, L)
|
||||
}
|
||||
|
||||
var err error
|
||||
item.Substitutions, _, err = ParseFeatureTableSubstitution(parentSrc[offsetSubstitutions:])
|
||||
if err != nil {
|
||||
return item, 0, fmt.Errorf("reading FeatureVariationRecord: %s", err)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
return item, n, nil
|
||||
}
|
||||
|
||||
func ParseLangSys(src []byte) (LangSys, int, error) {
|
||||
var item LangSys
|
||||
n := 0
|
||||
if L := len(src); L < 6 {
|
||||
return item, 0, fmt.Errorf("reading LangSys: "+"EOF: expected length: 6, got %d", L)
|
||||
}
|
||||
_ = src[5] // early bound checking
|
||||
item.lookupOrderOffset = binary.BigEndian.Uint16(src[0:])
|
||||
item.RequiredFeatureIndex = binary.BigEndian.Uint16(src[2:])
|
||||
arrayLengthFeatureIndices := int(binary.BigEndian.Uint16(src[4:]))
|
||||
n += 6
|
||||
|
||||
{
|
||||
|
||||
if L := len(src); L < 6+arrayLengthFeatureIndices*2 {
|
||||
return item, 0, fmt.Errorf("reading LangSys: "+"EOF: expected length: %d, got %d", 6+arrayLengthFeatureIndices*2, L)
|
||||
}
|
||||
|
||||
item.FeatureIndices = make([]uint16, arrayLengthFeatureIndices) // allocation guarded by the previous check
|
||||
for i := range item.FeatureIndices {
|
||||
item.FeatureIndices[i] = binary.BigEndian.Uint16(src[6+i*2:])
|
||||
}
|
||||
n += arrayLengthFeatureIndices * 2
|
||||
}
|
||||
return item, n, nil
|
||||
}
|
||||
|
||||
func ParseLayout(src []byte) (Layout, int, error) {
|
||||
var item Layout
|
||||
n := 0
|
||||
if L := len(src); L < 10 {
|
||||
return item, 0, fmt.Errorf("reading Layout: "+"EOF: expected length: 10, got %d", L)
|
||||
}
|
||||
_ = src[9] // early bound checking
|
||||
item.majorVersion = binary.BigEndian.Uint16(src[0:])
|
||||
item.minorVersion = binary.BigEndian.Uint16(src[2:])
|
||||
offsetScriptList := int(binary.BigEndian.Uint16(src[4:]))
|
||||
offsetFeatureList := int(binary.BigEndian.Uint16(src[6:]))
|
||||
offsetLookupList := int(binary.BigEndian.Uint16(src[8:]))
|
||||
n += 10
|
||||
|
||||
{
|
||||
if offsetScriptList != 0 { // ignore null offset
|
||||
if L := len(src); L < offsetScriptList {
|
||||
return item, 0, fmt.Errorf("reading Layout: "+"EOF: expected length: %d, got %d", offsetScriptList, L)
|
||||
}
|
||||
|
||||
var err error
|
||||
item.ScriptList, _, err = ParseScriptList(src[offsetScriptList:])
|
||||
if err != nil {
|
||||
return item, 0, fmt.Errorf("reading Layout: %s", err)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
{
|
||||
if offsetFeatureList != 0 { // ignore null offset
|
||||
if L := len(src); L < offsetFeatureList {
|
||||
return item, 0, fmt.Errorf("reading Layout: "+"EOF: expected length: %d, got %d", offsetFeatureList, L)
|
||||
}
|
||||
|
||||
var err error
|
||||
item.FeatureList, _, err = ParseFeatureList(src[offsetFeatureList:])
|
||||
if err != nil {
|
||||
return item, 0, fmt.Errorf("reading Layout: %s", err)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
{
|
||||
if offsetLookupList != 0 { // ignore null offset
|
||||
if L := len(src); L < offsetLookupList {
|
||||
return item, 0, fmt.Errorf("reading Layout: "+"EOF: expected length: %d, got %d", offsetLookupList, L)
|
||||
}
|
||||
|
||||
var err error
|
||||
item.LookupList, _, err = parseLookupList(src[offsetLookupList:])
|
||||
if err != nil {
|
||||
return item, 0, fmt.Errorf("reading Layout: %s", err)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
{
|
||||
|
||||
read, err := item.parseFeatureVariations(src[:])
|
||||
if err != nil {
|
||||
return item, 0, fmt.Errorf("reading Layout: %s", err)
|
||||
}
|
||||
n = read
|
||||
}
|
||||
return item, n, nil
|
||||
}
|
||||
|
||||
func ParseLookup(src []byte) (Lookup, int, error) {
|
||||
var item Lookup
|
||||
n := 0
|
||||
if L := len(src); L < 6 {
|
||||
return item, 0, fmt.Errorf("reading Lookup: "+"EOF: expected length: 6, got %d", L)
|
||||
}
|
||||
_ = src[5] // early bound checking
|
||||
item.lookupType = binary.BigEndian.Uint16(src[0:])
|
||||
item.LookupFlag = binary.BigEndian.Uint16(src[2:])
|
||||
arrayLengthSubtableOffsets := int(binary.BigEndian.Uint16(src[4:]))
|
||||
n += 6
|
||||
|
||||
{
|
||||
|
||||
if L := len(src); L < 6+arrayLengthSubtableOffsets*2 {
|
||||
return item, 0, fmt.Errorf("reading Lookup: "+"EOF: expected length: %d, got %d", 6+arrayLengthSubtableOffsets*2, L)
|
||||
}
|
||||
|
||||
item.subtableOffsets = make([]Offset16, arrayLengthSubtableOffsets) // allocation guarded by the previous check
|
||||
for i := range item.subtableOffsets {
|
||||
item.subtableOffsets[i] = Offset16(binary.BigEndian.Uint16(src[6+i*2:]))
|
||||
}
|
||||
n += arrayLengthSubtableOffsets * 2
|
||||
}
|
||||
if L := len(src); L < n+2 {
|
||||
return item, 0, fmt.Errorf("reading Lookup: "+"EOF: expected length: n + 2, got %d", L)
|
||||
}
|
||||
item.MarkFilteringSet = binary.BigEndian.Uint16(src[n:])
|
||||
n += 2
|
||||
|
||||
{
|
||||
|
||||
item.rawData = src[0:]
|
||||
n = len(src)
|
||||
}
|
||||
return item, n, nil
|
||||
}
|
||||
|
||||
func ParseScript(src []byte) (Script, int, error) {
|
||||
var item Script
|
||||
n := 0
|
||||
if L := len(src); L < 4 {
|
||||
return item, 0, fmt.Errorf("reading Script: "+"EOF: expected length: 4, got %d", L)
|
||||
}
|
||||
_ = src[3] // early bound checking
|
||||
offsetDefaultLangSys := int(binary.BigEndian.Uint16(src[0:]))
|
||||
arrayLengthLangSysRecords := int(binary.BigEndian.Uint16(src[2:]))
|
||||
n += 4
|
||||
|
||||
{
|
||||
if offsetDefaultLangSys != 0 { // ignore null offset
|
||||
if L := len(src); L < offsetDefaultLangSys {
|
||||
return item, 0, fmt.Errorf("reading Script: "+"EOF: expected length: %d, got %d", offsetDefaultLangSys, L)
|
||||
}
|
||||
|
||||
var tmpDefaultLangSys LangSys
|
||||
var err error
|
||||
tmpDefaultLangSys, _, err = ParseLangSys(src[offsetDefaultLangSys:])
|
||||
if err != nil {
|
||||
return item, 0, fmt.Errorf("reading Script: %s", err)
|
||||
}
|
||||
|
||||
item.DefaultLangSys = &tmpDefaultLangSys
|
||||
}
|
||||
}
|
||||
{
|
||||
|
||||
if L := len(src); L < 4+arrayLengthLangSysRecords*6 {
|
||||
return item, 0, fmt.Errorf("reading Script: "+"EOF: expected length: %d, got %d", 4+arrayLengthLangSysRecords*6, L)
|
||||
}
|
||||
|
||||
item.LangSysRecords = make([]TagOffsetRecord, arrayLengthLangSysRecords) // allocation guarded by the previous check
|
||||
for i := range item.LangSysRecords {
|
||||
item.LangSysRecords[i].mustParse(src[4+i*6:])
|
||||
}
|
||||
n += arrayLengthLangSysRecords * 6
|
||||
}
|
||||
{
|
||||
|
||||
err := item.parseLangSys(src[:])
|
||||
if err != nil {
|
||||
return item, 0, fmt.Errorf("reading Script: %s", err)
|
||||
}
|
||||
}
|
||||
return item, n, nil
|
||||
}
|
||||
|
||||
func ParseScriptList(src []byte) (ScriptList, int, error) {
|
||||
var item ScriptList
|
||||
n := 0
|
||||
if L := len(src); L < 2 {
|
||||
return item, 0, fmt.Errorf("reading ScriptList: "+"EOF: expected length: 2, got %d", L)
|
||||
}
|
||||
arrayLengthRecords := int(binary.BigEndian.Uint16(src[0:]))
|
||||
n += 2
|
||||
|
||||
{
|
||||
|
||||
if L := len(src); L < 2+arrayLengthRecords*6 {
|
||||
return item, 0, fmt.Errorf("reading ScriptList: "+"EOF: expected length: %d, got %d", 2+arrayLengthRecords*6, L)
|
||||
}
|
||||
|
||||
item.Records = make([]TagOffsetRecord, arrayLengthRecords) // allocation guarded by the previous check
|
||||
for i := range item.Records {
|
||||
item.Records[i].mustParse(src[2+i*6:])
|
||||
}
|
||||
n += arrayLengthRecords * 6
|
||||
}
|
||||
{
|
||||
|
||||
err := item.parseScripts(src[:])
|
||||
if err != nil {
|
||||
return item, 0, fmt.Errorf("reading ScriptList: %s", err)
|
||||
}
|
||||
}
|
||||
return item, n, nil
|
||||
}
|
||||
|
||||
func (item *TagOffsetRecord) mustParse(src []byte) {
|
||||
_ = src[5] // early bound checking
|
||||
item.Tag = Tag(binary.BigEndian.Uint32(src[0:]))
|
||||
item.Offset = binary.BigEndian.Uint16(src[4:])
|
||||
}
|
||||
|
||||
func parseLookupList(src []byte) (lookupList, int, error) {
|
||||
var item lookupList
|
||||
n := 0
|
||||
if L := len(src); L < 2 {
|
||||
return item, 0, fmt.Errorf("reading lookupList: "+"EOF: expected length: 2, got %d", L)
|
||||
}
|
||||
arrayLengthLookups := int(binary.BigEndian.Uint16(src[0:]))
|
||||
n += 2
|
||||
|
||||
{
|
||||
|
||||
if L := len(src); L < 2+arrayLengthLookups*2 {
|
||||
return item, 0, fmt.Errorf("reading lookupList: "+"EOF: expected length: %d, got %d", 2+arrayLengthLookups*2, L)
|
||||
}
|
||||
|
||||
item.Lookups = make([]Lookup, arrayLengthLookups) // allocation guarded by the previous check
|
||||
for i := range item.Lookups {
|
||||
offset := int(binary.BigEndian.Uint16(src[2+i*2:]))
|
||||
// ignore null offsets
|
||||
if offset == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
if L := len(src); L < offset {
|
||||
return item, 0, fmt.Errorf("reading lookupList: "+"EOF: expected length: %d, got %d", offset, L)
|
||||
}
|
||||
|
||||
var err error
|
||||
item.Lookups[i], _, err = ParseLookup(src[offset:])
|
||||
if err != nil {
|
||||
return item, 0, fmt.Errorf("reading lookupList: %s", err)
|
||||
}
|
||||
}
|
||||
n += arrayLengthLookups * 2
|
||||
}
|
||||
return item, n, nil
|
||||
}
|
||||
+172
@@ -0,0 +1,172 @@
|
||||
// SPDX-License-Identifier: Unlicense OR BSD-3-Clause
|
||||
|
||||
package tables
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// Layout represents the common layout table used by GPOS and GSUB.
|
||||
// The Features field contains all the features for this layout. However,
|
||||
// the script and language determines which feature is used.
|
||||
//
|
||||
// See https://learn.microsoft.com/typography/opentype/spec/chapter2#organization
|
||||
// See https://learn.microsoft.com/typography/opentype/spec/gpos
|
||||
// See https://www.microsoft.com/typography/otspec/GSUB.htm
|
||||
type Layout struct {
|
||||
majorVersion uint16 // Major version of the GPOS table, = 1
|
||||
minorVersion uint16 // Minor version of the GPOS table, = 0 or 1
|
||||
ScriptList ScriptList `offsetSize:"Offset16"` // Offset to ScriptList table, from beginning of GPOS table
|
||||
FeatureList FeatureList `offsetSize:"Offset16"` // Offset to FeatureList table, from beginning of GPOS table
|
||||
LookupList lookupList `offsetSize:"Offset16"` // Offset to LookupList table, from beginning of GPOS table
|
||||
FeatureVariations *FeatureVariation `isOpaque:""` // Offset to FeatureVariations table, from beginning of GPOS table (may be NULL)
|
||||
}
|
||||
|
||||
func (lt *Layout) parseFeatureVariations(src []byte) (int, error) {
|
||||
const layoutHeaderSize = 2 + 2 + 2 + 2 + 2
|
||||
if lt.minorVersion != 1 {
|
||||
return 0, nil
|
||||
}
|
||||
if L := len(src); L < layoutHeaderSize+4 {
|
||||
return 0, fmt.Errorf("reading Layout: EOF: expected length: 4, got %d", L)
|
||||
}
|
||||
offset := binary.BigEndian.Uint32(src[layoutHeaderSize:])
|
||||
if offset == 0 {
|
||||
return 4, nil
|
||||
}
|
||||
if L := len(src); L < int(offset) {
|
||||
return 0, fmt.Errorf("reading Layout: EOF: expected length: %d, got %d", offset, L)
|
||||
}
|
||||
fv, _, err := ParseFeatureVariation(src[offset:])
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
lt.FeatureVariations = &fv
|
||||
|
||||
return 4, nil
|
||||
}
|
||||
|
||||
type TagOffsetRecord struct {
|
||||
Tag Tag // 4-byte script tag identifier
|
||||
Offset uint16 // Offset to object from beginning of list
|
||||
}
|
||||
|
||||
type ScriptList struct {
|
||||
Records []TagOffsetRecord `arrayCount:"FirstUint16"` // Array of ScriptRecords, listed alphabetically by script tag
|
||||
Scripts []Script `isOpaque:""`
|
||||
}
|
||||
|
||||
func (sl *ScriptList) parseScripts(src []byte) error {
|
||||
sl.Scripts = make([]Script, len(sl.Records))
|
||||
for i, rec := range sl.Records {
|
||||
var err error
|
||||
if L := len(src); L < int(rec.Offset) {
|
||||
return fmt.Errorf("EOF: expected length: %d, got %d", rec.Offset, L)
|
||||
}
|
||||
sl.Scripts[i], _, err = ParseScript(src[rec.Offset:])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type Script struct {
|
||||
DefaultLangSys *LangSys `offsetSize:"Offset16"` // Offset to default LangSys table, from beginning of Script table — may be NULL
|
||||
LangSysRecords []TagOffsetRecord `arrayCount:"FirstUint16"` // [langSysCount] Array of LangSysRecords, listed alphabetically by LangSys tag
|
||||
LangSys []LangSys `isOpaque:""` // same length as langSysRecords
|
||||
}
|
||||
|
||||
func (sc *Script) parseLangSys(src []byte) error {
|
||||
sc.LangSys = make([]LangSys, len(sc.LangSysRecords))
|
||||
for i, rec := range sc.LangSysRecords {
|
||||
var err error
|
||||
if L := len(src); L < int(rec.Offset) {
|
||||
return fmt.Errorf("EOF: expected length: %d, got %d", rec.Offset, L)
|
||||
}
|
||||
sc.LangSys[i], _, err = ParseLangSys(src[rec.Offset:])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type LangSys struct {
|
||||
lookupOrderOffset uint16 // = NULL (reserved for an offset to a reordering table)
|
||||
RequiredFeatureIndex uint16 // Index of a feature required for this language system; if no required features = 0xFFFF
|
||||
FeatureIndices []uint16 `arrayCount:"FirstUint16"` // [featureIndexCount] Array of indices into the FeatureList, in arbitrary order
|
||||
}
|
||||
|
||||
type FeatureList struct {
|
||||
Records []TagOffsetRecord `arrayCount:"FirstUint16"` // Array of FeatureRecords — zero-based (first feature has FeatureIndex = 0), listed alphabetically by feature tag
|
||||
Features []Feature `isOpaque:""`
|
||||
}
|
||||
|
||||
func (fl *FeatureList) parseFeatures(src []byte) error {
|
||||
fl.Features = make([]Feature, len(fl.Records))
|
||||
for i, rec := range fl.Records {
|
||||
var err error
|
||||
if L := len(src); L < int(rec.Offset) {
|
||||
return fmt.Errorf("EOF: expected length: %d, got %d", rec.Offset, L)
|
||||
}
|
||||
fl.Features[i], _, err = ParseFeature(src[rec.Offset:])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type Feature struct {
|
||||
featureParamsOffset uint16 // Offset from start of Feature table to FeatureParams table, if defined for the feature and present, else NULL
|
||||
LookupListIndices []uint16 `arrayCount:"FirstUint16"` // [lookupIndexCount] Array of indices into the LookupList — zero-based (first lookup is LookupListIndex = 0)
|
||||
}
|
||||
|
||||
type lookupList struct {
|
||||
Lookups []Lookup `arrayCount:"FirstUint16" offsetsArray:"Offset16"` // Array of offsets to Lookup tables, from beginning of LookupList — zero based (first lookup is Lookup index = 0)
|
||||
}
|
||||
|
||||
// Lookup is the common format for GSUB and GPOS lookups
|
||||
type Lookup struct {
|
||||
lookupType uint16 // Different enumerations for GSUB and GPOS
|
||||
LookupFlag uint16 // Lookup qualifiers
|
||||
subtableOffsets []Offset16 `arrayCount:"FirstUint16"` // [subTableCount] Array of offsets to lookup subtables, from beginning of Lookup table
|
||||
MarkFilteringSet uint16 // Index (base 0) into GDEF mark glyph sets structure. This field is only present if the USE_MARK_FILTERING_SET lookup flag is set.
|
||||
rawData []byte `subsliceStart:"AtStart" arrayCount:"ToEnd"`
|
||||
}
|
||||
|
||||
type FeatureVariation struct {
|
||||
majorVersion uint16 // Major version of the FeatureVariations table — set to 1.
|
||||
minorVersion uint16 // Minor version of the FeatureVariations table — set to 0.
|
||||
FeatureVariationRecords []FeatureVariationRecord `arrayCount:"FirstUint32"` //[featureVariationRecordCount] Array of feature variation records.
|
||||
}
|
||||
|
||||
type FeatureVariationRecord struct {
|
||||
ConditionSet ConditionSet `offsetSize:"Offset32" offsetRelativeTo:"Parent"` // Offset to a condition set table, from beginning of FeatureVariations table.
|
||||
Substitutions FeatureTableSubstitution `offsetSize:"Offset32" offsetRelativeTo:"Parent"` // Offset to a feature table substitution table, from beginning of the FeatureVariations table.
|
||||
}
|
||||
|
||||
type ConditionSet struct {
|
||||
// uint16 conditionCount Number of Conditions for this condition set.
|
||||
Conditions []ConditionFormat1 `arrayCount:"FirstUint16" offsetsArray:"Offset32"` // [conditionCount] Array of offsets to condition tables, from beginning of the ConditionSet table.
|
||||
}
|
||||
|
||||
type ConditionFormat1 struct {
|
||||
format uint16 // Format, = 1
|
||||
AxisIndex uint16 // Index (zero-based) for the variation axis within the 'fvar' table.
|
||||
FilterRangeMinValue Coord // Minimum value of the font variation instances that satisfy this condition.
|
||||
FilterRangeMaxValue Coord // Maximum value of the font variation instances that satisfy this condition.
|
||||
}
|
||||
|
||||
type FeatureTableSubstitution struct {
|
||||
majorVersion uint16 // Major version of the feature table substitution table — set to 1
|
||||
minorVersion uint16 // Minor version of the feature table substitution table — set to 0.
|
||||
Substitutions []FeatureTableSubstitutionRecord `arrayCount:"FirstUint16"` // [substitutionCount] Array of feature table substitution records.
|
||||
}
|
||||
|
||||
type FeatureTableSubstitutionRecord struct {
|
||||
FeatureIndex uint16 // The feature table index to match.
|
||||
AlternateFeature Feature `offsetSize:"Offset32" offsetRelativeTo:"Parent"` // Offset to an alternate feature table, from start of the FeatureTableSubstitution table.
|
||||
}
|
||||
+283
@@ -0,0 +1,283 @@
|
||||
// SPDX-License-Identifier: Unlicense OR BSD-3-Clause
|
||||
|
||||
package tables
|
||||
|
||||
import (
|
||||
"sort"
|
||||
)
|
||||
|
||||
func (c Coverage1) Index(gi GlyphID) (int, bool) {
|
||||
num := len(c.Glyphs)
|
||||
idx := sort.Search(num, func(i int) bool { return gi <= c.Glyphs[i] })
|
||||
if idx < num && c.Glyphs[idx] == gi {
|
||||
return idx, true
|
||||
}
|
||||
return 0, false
|
||||
}
|
||||
|
||||
func (cl Coverage1) Len() int { return len(cl.Glyphs) }
|
||||
|
||||
func (c Coverage2) Index(gi GlyphID) (int, bool) {
|
||||
num := len(c.Ranges)
|
||||
if num == 0 {
|
||||
return 0, false
|
||||
}
|
||||
|
||||
idx := sort.Search(num, func(i int) bool { return gi <= c.Ranges[i].StartGlyphID })
|
||||
// idx either points to a matching start, or to the next range (or idx==num)
|
||||
// e.g. with the range example from above: 130 points to 130-135 range, 133 points to 137-137 range
|
||||
|
||||
// check if gi is the start of a range, but only if sort.Search returned a valid result
|
||||
if idx < num {
|
||||
if rang := c.Ranges[idx]; gi == rang.StartGlyphID {
|
||||
return int(rang.StartCoverageIndex), true
|
||||
}
|
||||
}
|
||||
// check if gi is in previous range
|
||||
if idx > 0 {
|
||||
idx--
|
||||
if rang := c.Ranges[idx]; gi >= rang.StartGlyphID && gi <= rang.EndGlyphID {
|
||||
return int(rang.StartCoverageIndex) + int(gi-rang.StartGlyphID), true
|
||||
}
|
||||
}
|
||||
|
||||
return 0, false
|
||||
}
|
||||
|
||||
func (cr Coverage2) Len() int {
|
||||
size := 0
|
||||
for _, r := range cr.Ranges {
|
||||
size += int(r.EndGlyphID - r.StartGlyphID + 1)
|
||||
}
|
||||
return size
|
||||
}
|
||||
|
||||
func (cl ClassDef1) Class(gi GlyphID) (uint16, bool) {
|
||||
if gi < cl.StartGlyphID || gi >= cl.StartGlyphID+GlyphID(len(cl.ClassValueArray)) {
|
||||
return 0, false
|
||||
}
|
||||
return cl.ClassValueArray[gi-cl.StartGlyphID], true
|
||||
}
|
||||
|
||||
func (cl ClassDef1) Extent() int {
|
||||
max := uint16(0)
|
||||
for _, cid := range cl.ClassValueArray {
|
||||
if cid >= max {
|
||||
max = cid
|
||||
}
|
||||
}
|
||||
return int(max) + 1
|
||||
}
|
||||
|
||||
func (cl ClassDef2) Class(g GlyphID) (uint16, bool) {
|
||||
// 'adapted' from golang/x/image/font/sfnt
|
||||
c := cl.ClassRangeRecords
|
||||
num := len(c)
|
||||
if num == 0 {
|
||||
return 0, false
|
||||
}
|
||||
|
||||
// classRange is an array of startGlyphID, endGlyphID and target class ID.
|
||||
// Ranges are non-overlapping.
|
||||
// E.g. 130, 135, 1 137, 137, 5 etc
|
||||
|
||||
idx := sort.Search(num, func(i int) bool { return g <= c[i].StartGlyphID })
|
||||
// idx either points to a matching start, or to the next range (or idx==num)
|
||||
// e.g. with the range example from above: 130 points to 130-135 range, 133 points to 137-137 range
|
||||
|
||||
// check if gi is the start of a range, but only if sort.Search returned a valid result
|
||||
if idx < num {
|
||||
if class := c[idx]; g == c[idx].StartGlyphID {
|
||||
return class.Class, true
|
||||
}
|
||||
}
|
||||
// check if gi is in previous range
|
||||
if idx > 0 {
|
||||
idx--
|
||||
if class := c[idx]; g >= class.StartGlyphID && g <= class.EndGlyphID {
|
||||
return class.Class, true
|
||||
}
|
||||
}
|
||||
|
||||
return 0, false
|
||||
}
|
||||
|
||||
func (cl ClassDef2) Extent() int {
|
||||
max := uint16(0)
|
||||
for _, r := range cl.ClassRangeRecords {
|
||||
if r.Class >= max {
|
||||
max = r.Class
|
||||
}
|
||||
}
|
||||
return int(max) + 1
|
||||
}
|
||||
|
||||
// ------------------------------------ layout getters ------------------------------------
|
||||
|
||||
// FindLanguage looks for [language] and return its index into the [LangSys] slice,
|
||||
// or -1 if the tag is not found.
|
||||
func (sc Script) FindLanguage(language Tag) int {
|
||||
// LangSys is sorted: binary search
|
||||
low, high := 0, len(sc.LangSysRecords)
|
||||
for low < high {
|
||||
mid := low + (high-low)/2 // avoid overflow when computing mid
|
||||
p := sc.LangSysRecords[mid].Tag
|
||||
if language < p {
|
||||
high = mid
|
||||
} else if language > p {
|
||||
low = mid + 1
|
||||
} else {
|
||||
return mid
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
// GetLangSys return the language at [index]. It [index] is out of range (for example with 0xFFFF),
|
||||
// it returns [DefaultLangSys] (which may be empty)
|
||||
func (sc Script) GetLangSys(index uint16) LangSys {
|
||||
if int(index) >= len(sc.LangSys) {
|
||||
if sc.DefaultLangSys != nil {
|
||||
return *sc.DefaultLangSys
|
||||
}
|
||||
return LangSys{RequiredFeatureIndex: 0xFFFF}
|
||||
}
|
||||
return sc.LangSys[index]
|
||||
}
|
||||
|
||||
// --------------------------------------- gsub ---------------------------------------
|
||||
|
||||
func (d SingleSubstData1) Cov() Coverage { return d.Coverage }
|
||||
func (d SingleSubstData2) Cov() Coverage { return d.Coverage }
|
||||
|
||||
func (cs ContextualSubs1) Cov() Coverage { return cs.coverage }
|
||||
func (cs ContextualSubs2) Cov() Coverage { return cs.coverage }
|
||||
func (cs ContextualSubs3) Cov() Coverage {
|
||||
if len(cs.Coverages) == 0 { // return an empty, valid Coverage
|
||||
return Coverage1{}
|
||||
}
|
||||
return cs.Coverages[0]
|
||||
}
|
||||
|
||||
func (cc ChainedContextualSubs1) Cov() Coverage { return cc.coverage }
|
||||
func (cc ChainedContextualSubs2) Cov() Coverage { return cc.coverage }
|
||||
func (cc ChainedContextualSubs3) Cov() Coverage {
|
||||
if len(cc.InputCoverages) == 0 { // return an empty, valid Coverage
|
||||
return Coverage1{}
|
||||
}
|
||||
return cc.InputCoverages[0]
|
||||
}
|
||||
|
||||
func (lk SingleSubs) Cov() Coverage { return lk.Data.Cov() }
|
||||
func (lk MultipleSubs) Cov() Coverage { return lk.Coverage }
|
||||
func (lk AlternateSubs) Cov() Coverage { return lk.Coverage }
|
||||
func (lk LigatureSubs) Cov() Coverage { return lk.Coverage }
|
||||
func (lk ContextualSubs) Cov() Coverage { return lk.Data.Cov() }
|
||||
func (lk ChainedContextualSubs) Cov() Coverage { return lk.Data.Cov() }
|
||||
func (lk ExtensionSubs) Cov() Coverage { return nil } // not used anyway
|
||||
func (lk ReverseChainSingleSubs) Cov() Coverage { return lk.coverage }
|
||||
|
||||
// --------------------------------------- gpos ---------------------------------------
|
||||
|
||||
func (d SinglePosData1) Cov() Coverage { return d.coverage }
|
||||
func (d SinglePosData2) Cov() Coverage { return d.coverage }
|
||||
|
||||
func (d PairPosData1) Cov() Coverage { return d.coverage }
|
||||
func (d PairPosData2) Cov() Coverage { return d.coverage }
|
||||
|
||||
func (cs ContextualPos1) Cov() Coverage { return cs.coverage }
|
||||
func (cs ContextualPos2) Cov() Coverage { return cs.coverage }
|
||||
func (cs ContextualPos3) Cov() Coverage {
|
||||
if len(cs.Coverages) == 0 { // return an empty, valid Coverage
|
||||
return Coverage1{}
|
||||
}
|
||||
return cs.Coverages[0]
|
||||
}
|
||||
|
||||
func (cc ChainedContextualPos1) Cov() Coverage { return cc.coverage }
|
||||
func (cc ChainedContextualPos2) Cov() Coverage { return cc.coverage }
|
||||
func (cc ChainedContextualPos3) Cov() Coverage {
|
||||
if len(cc.InputCoverages) == 0 { // return an empty, valid Coverage
|
||||
return Coverage1{}
|
||||
}
|
||||
return cc.InputCoverages[0]
|
||||
}
|
||||
|
||||
func (lk SinglePos) Cov() Coverage { return lk.Data.Cov() }
|
||||
func (lk PairPos) Cov() Coverage { return lk.Data.Cov() }
|
||||
func (lk CursivePos) Cov() Coverage { return lk.coverage }
|
||||
func (lk MarkBasePos) Cov() Coverage { return lk.markCoverage }
|
||||
func (lk MarkLigPos) Cov() Coverage { return lk.MarkCoverage }
|
||||
func (lk MarkMarkPos) Cov() Coverage { return lk.Mark1Coverage }
|
||||
func (lk ContextualPos) Cov() Coverage { return lk.Data.Cov() }
|
||||
func (lk ChainedContextualPos) Cov() Coverage { return lk.Data.Cov() }
|
||||
func (lk ExtensionPos) Cov() Coverage { return nil } // not used anyway
|
||||
|
||||
// FindGlyph performs a binary search in the list, returning the record for `secondGlyph`,
|
||||
// or `nil` if not found.
|
||||
func (ps PairSet) FindGlyph(secondGlyph GlyphID) (PairValueRecord, bool) {
|
||||
low, high := 0, int(ps.pairValueCount)
|
||||
for low < high {
|
||||
mid := low + (high-low)/2 // avoid overflow when computing mid
|
||||
rec, err := ps.data.get(mid)
|
||||
if err != nil { // argh...
|
||||
return PairValueRecord{}, false
|
||||
}
|
||||
p := rec.SecondGlyph
|
||||
if secondGlyph < p {
|
||||
high = mid
|
||||
} else if secondGlyph > p {
|
||||
low = mid + 1
|
||||
} else {
|
||||
return rec, true
|
||||
}
|
||||
}
|
||||
return PairValueRecord{}, false
|
||||
}
|
||||
|
||||
// GetDelta returns the hint for the given `ppem`, scaled by `scale`.
|
||||
// It returns 0 for out of range `ppem` values.
|
||||
func (dev DeviceHinting) GetDelta(ppem uint16, scale int32) int32 {
|
||||
if ppem == 0 {
|
||||
return 0
|
||||
}
|
||||
|
||||
if ppem < dev.StartSize || ppem > dev.EndSize {
|
||||
return 0
|
||||
}
|
||||
|
||||
pixels := dev.Values[ppem-dev.StartSize]
|
||||
|
||||
return int32(pixels) * (scale / int32(ppem))
|
||||
}
|
||||
|
||||
// -------------------------------------- gdef --------------------------------------
|
||||
|
||||
// GlyphProps is a 16-bit integer where the lower 8-bit have bits representing
|
||||
// glyph class, and high 8-bit the mark attachment type (if any).
|
||||
type GlyphProps = uint16
|
||||
|
||||
const (
|
||||
GPBaseGlyph GlyphProps = 1 << (iota + 1)
|
||||
GPLigature
|
||||
GPMark
|
||||
)
|
||||
|
||||
// GlyphProps return a summary of the glyph properties.
|
||||
func (gd *GDEF) GlyphProps(glyph GlyphID) GlyphProps {
|
||||
klass, _ := gd.GlyphClassDef.Class(glyph)
|
||||
switch klass {
|
||||
case 1:
|
||||
return GPBaseGlyph
|
||||
case 2:
|
||||
return GPLigature
|
||||
case 3:
|
||||
var klass uint16 // it is actually a byte
|
||||
if gd.MarkAttachClass != nil {
|
||||
klass, _ = gd.MarkAttachClass.Class(glyph)
|
||||
}
|
||||
return GlyphProps(klass)<<8 | GPMark
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
}
|
||||
+94
@@ -0,0 +1,94 @@
|
||||
// SPDX-License-Identifier: Unlicense OR BSD-3-Clause
|
||||
|
||||
package tables
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// Code generated by binarygen from post_src.go. DO NOT EDIT
|
||||
|
||||
func ParsePost(src []byte) (Post, int, error) {
|
||||
var item Post
|
||||
n := 0
|
||||
if L := len(src); L < 32 {
|
||||
return item, 0, fmt.Errorf("reading Post: "+"EOF: expected length: 32, got %d", L)
|
||||
}
|
||||
_ = src[31] // early bound checking
|
||||
item.version = postVersion(binary.BigEndian.Uint32(src[0:]))
|
||||
item.italicAngle = binary.BigEndian.Uint32(src[4:])
|
||||
item.UnderlinePosition = int16(binary.BigEndian.Uint16(src[8:]))
|
||||
item.UnderlineThickness = int16(binary.BigEndian.Uint16(src[10:]))
|
||||
item.IsFixedPitch = binary.BigEndian.Uint32(src[12:])
|
||||
item.memoryUsage[0] = binary.BigEndian.Uint32(src[16:])
|
||||
item.memoryUsage[1] = binary.BigEndian.Uint32(src[20:])
|
||||
item.memoryUsage[2] = binary.BigEndian.Uint32(src[24:])
|
||||
item.memoryUsage[3] = binary.BigEndian.Uint32(src[28:])
|
||||
n += 32
|
||||
|
||||
{
|
||||
var (
|
||||
read int
|
||||
err error
|
||||
)
|
||||
switch item.version {
|
||||
case postVersion10:
|
||||
item.Names, read, err = ParsePostNames10(src[32:])
|
||||
case postVersion20:
|
||||
item.Names, read, err = ParsePostNames20(src[32:])
|
||||
case postVersion30:
|
||||
item.Names, read, err = ParsePostNames30(src[32:])
|
||||
default:
|
||||
err = fmt.Errorf("unsupported PostNamesVersion %d", item.version)
|
||||
}
|
||||
if err != nil {
|
||||
return item, 0, fmt.Errorf("reading Post: %s", err)
|
||||
}
|
||||
n += read
|
||||
}
|
||||
return item, n, nil
|
||||
}
|
||||
|
||||
func ParsePostNames10([]byte) (PostNames10, int, error) {
|
||||
var item PostNames10
|
||||
n := 0
|
||||
return item, n, nil
|
||||
}
|
||||
|
||||
func ParsePostNames20(src []byte) (PostNames20, int, error) {
|
||||
var item PostNames20
|
||||
n := 0
|
||||
if L := len(src); L < 2 {
|
||||
return item, 0, fmt.Errorf("reading PostNames20: "+"EOF: expected length: 2, got %d", L)
|
||||
}
|
||||
arrayLengthGlyphNameIndexes := int(binary.BigEndian.Uint16(src[0:]))
|
||||
n += 2
|
||||
|
||||
{
|
||||
|
||||
if L := len(src); L < 2+arrayLengthGlyphNameIndexes*2 {
|
||||
return item, 0, fmt.Errorf("reading PostNames20: "+"EOF: expected length: %d, got %d", 2+arrayLengthGlyphNameIndexes*2, L)
|
||||
}
|
||||
|
||||
item.GlyphNameIndexes = make([]uint16, arrayLengthGlyphNameIndexes) // allocation guarded by the previous check
|
||||
for i := range item.GlyphNameIndexes {
|
||||
item.GlyphNameIndexes[i] = binary.BigEndian.Uint16(src[2+i*2:])
|
||||
}
|
||||
n += arrayLengthGlyphNameIndexes * 2
|
||||
}
|
||||
{
|
||||
|
||||
err := item.parseStrings(src[n:])
|
||||
if err != nil {
|
||||
return item, 0, fmt.Errorf("reading PostNames20: %s", err)
|
||||
}
|
||||
}
|
||||
return item, n, nil
|
||||
}
|
||||
|
||||
func ParsePostNames30([]byte) (PostNames30, int, error) {
|
||||
var item PostNames30
|
||||
n := 0
|
||||
return item, n, nil
|
||||
}
|
||||
+65
@@ -0,0 +1,65 @@
|
||||
// SPDX-License-Identifier: Unlicense OR BSD-3-Clause
|
||||
|
||||
package tables
|
||||
|
||||
import "fmt"
|
||||
|
||||
// PostScript table
|
||||
// See https://learn.microsoft.com/en-us/typography/opentype/spec/post
|
||||
type Post struct {
|
||||
version postVersion
|
||||
italicAngle uint32
|
||||
// UnderlinePosition is the suggested distance of the top of the
|
||||
// underline from the baseline (negative values indicate below baseline).
|
||||
UnderlinePosition int16
|
||||
// Suggested values for the underline thickness.
|
||||
UnderlineThickness int16
|
||||
// IsFixedPitch indicates that the font is not proportionally spaced
|
||||
// (i.e. monospaced).
|
||||
IsFixedPitch uint32
|
||||
memoryUsage [4]uint32
|
||||
Names PostNames `unionField:"version"`
|
||||
}
|
||||
|
||||
type PostNames interface {
|
||||
isPostNames()
|
||||
}
|
||||
|
||||
func (PostNames10) isPostNames() {}
|
||||
func (PostNames20) isPostNames() {}
|
||||
func (PostNames30) isPostNames() {}
|
||||
|
||||
type postVersion uint32
|
||||
|
||||
const (
|
||||
postVersion10 postVersion = 0x00010000
|
||||
postVersion20 postVersion = 0x00020000
|
||||
postVersion30 postVersion = 0x00030000
|
||||
)
|
||||
|
||||
type PostNames10 struct{}
|
||||
|
||||
type PostNames20 struct {
|
||||
GlyphNameIndexes []uint16 `arrayCount:"FirstUint16"` // size numGlyph
|
||||
Strings []string `isOpaque:"" subsliceStart:"AtCurrent"`
|
||||
}
|
||||
|
||||
// see https://learn.microsoft.com/en-us/typography/opentype/spec/post#version-20
|
||||
func (ps *PostNames20) parseStrings(src []byte) error {
|
||||
// "Strings are in Pascal string format, meaning that the first byte of
|
||||
// a given string is a length: the number of characters in that string.
|
||||
// The length byte is not included; for example, a length byte of 8 indicates
|
||||
// that the 8 bytes following the length byte comprise the string character data."
|
||||
for i := 0; i < len(src); {
|
||||
length := int(src[i]) // read the length
|
||||
end := i + 1 + length
|
||||
if L := len(src); L < end {
|
||||
return fmt.Errorf("invalid Postscript names tables format 20: EOF: expected %d, got %d", end, L)
|
||||
}
|
||||
ps.Strings = append(ps.Strings, string(src[i+1:end]))
|
||||
i = end
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type PostNames30 PostNames10
|
||||
+73
@@ -0,0 +1,73 @@
|
||||
// SPDX-License-Identifier: Unlicense OR BSD-3-Clause
|
||||
|
||||
package tables
|
||||
|
||||
import "github.com/go-text/typesetting/font/opentype"
|
||||
|
||||
//go:generate ../../../../typesetting-utils/generators/binarygen/cmd/generator . _src.go
|
||||
|
||||
type GlyphID = uint16
|
||||
|
||||
// NameID is the ID for entries in the font table.
|
||||
type NameID uint16
|
||||
|
||||
type Tag = opentype.Tag
|
||||
|
||||
// Float1616 is a float32, represented in
|
||||
// fixed 16.16 format in font files.
|
||||
type Float1616 = float32
|
||||
|
||||
func Float1616FromUint(v uint32) Float1616 {
|
||||
// value are actually signed integers
|
||||
return Float1616(int32(v)) / (1 << 16)
|
||||
}
|
||||
|
||||
func Float1616ToUint(f Float1616) uint32 {
|
||||
return uint32(int32(f * (1 << 16)))
|
||||
}
|
||||
|
||||
// Fixed214 is a number stored as a fixed 2.14 integer
|
||||
type Fixed214 = Coord
|
||||
|
||||
func Float214FromUint(v uint16) float32 {
|
||||
// value are actually signed integers
|
||||
return float32(int16(v)) / (1 << 14)
|
||||
}
|
||||
|
||||
// Coord is a real number in [-1;1], stored as a fixed 2.14 integer
|
||||
type Coord int16
|
||||
|
||||
func NewCoord(c float64) Coord {
|
||||
return Coord(c * (1 << 14))
|
||||
}
|
||||
|
||||
func abs(c Coord) Coord {
|
||||
if c < 0 {
|
||||
return -c
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
func readUint24(b []byte) uint32 {
|
||||
_ = b[2] // bounds check hint to compiler; see golang.org/issue/14808
|
||||
return uint32(b[2]) | uint32(b[1])<<8 | uint32(b[0])<<16
|
||||
}
|
||||
|
||||
// Number of seconds since 12:00 midnight that started January 1st 1904 in GMT/UTC time zone.
|
||||
type longdatetime = uint64
|
||||
|
||||
// PlatformID represents the platform id for entries in the name table.
|
||||
type PlatformID uint16
|
||||
|
||||
// EncodingID represents the platform specific id for entries in the name table.
|
||||
// The most common values are provided as constants.
|
||||
type EncodingID uint16
|
||||
|
||||
// LanguageID represents the language used by an entry in the name table
|
||||
type LanguageID uint16
|
||||
|
||||
// Offset16 is an offset into the input byte slice
|
||||
type Offset16 uint16
|
||||
|
||||
// Offset32 is an offset into the input byte slice
|
||||
type Offset32 uint32
|
||||
+716
@@ -0,0 +1,716 @@
|
||||
// SPDX-License-Identifier: Unlicense OR BSD-3-Clause
|
||||
|
||||
package tables
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// Code generated by binarygen from xvar_src.go. DO NOT EDIT
|
||||
|
||||
func (item *AxisRecord) mustParse(src []byte) {
|
||||
_ = src[7] // early bound checking
|
||||
item.Tag = Tag(binary.BigEndian.Uint32(src[0:]))
|
||||
item.NameID = NameID(binary.BigEndian.Uint16(src[4:]))
|
||||
item.Ordering = binary.BigEndian.Uint16(src[6:])
|
||||
}
|
||||
|
||||
func (item *AxisValue1) mustParse(src []byte) {
|
||||
_ = src[11] // early bound checking
|
||||
item.format = binary.BigEndian.Uint16(src[0:])
|
||||
item.axisIndex = binary.BigEndian.Uint16(src[2:])
|
||||
item.flags = binary.BigEndian.Uint16(src[4:])
|
||||
item.valueNameID = NameID(binary.BigEndian.Uint16(src[6:]))
|
||||
item.value = Float1616FromUint(binary.BigEndian.Uint32(src[8:]))
|
||||
}
|
||||
|
||||
func (item *AxisValue2) mustParse(src []byte) {
|
||||
_ = src[19] // early bound checking
|
||||
item.format = binary.BigEndian.Uint16(src[0:])
|
||||
item.axisIndex = binary.BigEndian.Uint16(src[2:])
|
||||
item.flags = binary.BigEndian.Uint16(src[4:])
|
||||
item.valueNameID = NameID(binary.BigEndian.Uint16(src[6:]))
|
||||
item.nominalValue = Float1616FromUint(binary.BigEndian.Uint32(src[8:]))
|
||||
item.rangeMinValue = Float1616FromUint(binary.BigEndian.Uint32(src[12:]))
|
||||
item.rangeMaxValue = Float1616FromUint(binary.BigEndian.Uint32(src[16:]))
|
||||
}
|
||||
|
||||
func (item *AxisValue3) mustParse(src []byte) {
|
||||
_ = src[15] // early bound checking
|
||||
item.format = binary.BigEndian.Uint16(src[0:])
|
||||
item.axisIndex = binary.BigEndian.Uint16(src[2:])
|
||||
item.flags = binary.BigEndian.Uint16(src[4:])
|
||||
item.valueNameID = NameID(binary.BigEndian.Uint16(src[6:]))
|
||||
item.value = Float1616FromUint(binary.BigEndian.Uint32(src[8:]))
|
||||
item.linkedValue = Float1616FromUint(binary.BigEndian.Uint32(src[12:]))
|
||||
}
|
||||
|
||||
func (item *AxisValueMap) mustParse(src []byte) {
|
||||
_ = src[3] // early bound checking
|
||||
item.FromCoordinate = Coord(binary.BigEndian.Uint16(src[0:]))
|
||||
item.ToCoordinate = Coord(binary.BigEndian.Uint16(src[2:]))
|
||||
}
|
||||
|
||||
func (item *AxisValueRecord) mustParse(src []byte) {
|
||||
_ = src[5] // early bound checking
|
||||
item.axisIndex = binary.BigEndian.Uint16(src[0:])
|
||||
item.value = Float1616FromUint(binary.BigEndian.Uint32(src[2:]))
|
||||
}
|
||||
|
||||
func ParseAvar(src []byte) (Avar, int, error) {
|
||||
var item Avar
|
||||
n := 0
|
||||
if L := len(src); L < 8 {
|
||||
return item, 0, fmt.Errorf("reading Avar: "+"EOF: expected length: 8, got %d", L)
|
||||
}
|
||||
_ = src[7] // early bound checking
|
||||
item.majorVersion = binary.BigEndian.Uint16(src[0:])
|
||||
item.minorVersion = binary.BigEndian.Uint16(src[2:])
|
||||
item.reserved = binary.BigEndian.Uint16(src[4:])
|
||||
arrayLengthAxisSegmentMaps := int(binary.BigEndian.Uint16(src[6:]))
|
||||
n += 8
|
||||
|
||||
{
|
||||
|
||||
offset := 8
|
||||
for i := 0; i < arrayLengthAxisSegmentMaps; i++ {
|
||||
elem, read, err := ParseSegmentMaps(src[offset:])
|
||||
if err != nil {
|
||||
return item, 0, fmt.Errorf("reading Avar: %s", err)
|
||||
}
|
||||
item.AxisSegmentMaps = append(item.AxisSegmentMaps, elem)
|
||||
offset += read
|
||||
}
|
||||
n = offset
|
||||
}
|
||||
return item, n, nil
|
||||
}
|
||||
|
||||
func ParseAxisValue(src []byte) (AxisValue, int, error) {
|
||||
var item AxisValue
|
||||
|
||||
if L := len(src); L < 2 {
|
||||
return item, 0, fmt.Errorf("reading AxisValue: "+"EOF: expected length: 2, got %d", L)
|
||||
}
|
||||
format := uint16(binary.BigEndian.Uint16(src[0:]))
|
||||
var (
|
||||
read int
|
||||
err error
|
||||
)
|
||||
switch format {
|
||||
case 1:
|
||||
item, read, err = ParseAxisValue1(src[0:])
|
||||
case 2:
|
||||
item, read, err = ParseAxisValue2(src[0:])
|
||||
case 3:
|
||||
item, read, err = ParseAxisValue3(src[0:])
|
||||
case 4:
|
||||
item, read, err = ParseAxisValue4(src[0:])
|
||||
default:
|
||||
err = fmt.Errorf("unsupported AxisValue format %d", format)
|
||||
}
|
||||
if err != nil {
|
||||
return item, 0, fmt.Errorf("reading AxisValue: %s", err)
|
||||
}
|
||||
|
||||
return item, read, nil
|
||||
}
|
||||
|
||||
func ParseAxisValue1(src []byte) (AxisValue1, int, error) {
|
||||
var item AxisValue1
|
||||
n := 0
|
||||
if L := len(src); L < 12 {
|
||||
return item, 0, fmt.Errorf("reading AxisValue1: "+"EOF: expected length: 12, got %d", L)
|
||||
}
|
||||
item.mustParse(src)
|
||||
n += 12
|
||||
return item, n, nil
|
||||
}
|
||||
|
||||
func ParseAxisValue2(src []byte) (AxisValue2, int, error) {
|
||||
var item AxisValue2
|
||||
n := 0
|
||||
if L := len(src); L < 20 {
|
||||
return item, 0, fmt.Errorf("reading AxisValue2: "+"EOF: expected length: 20, got %d", L)
|
||||
}
|
||||
item.mustParse(src)
|
||||
n += 20
|
||||
return item, n, nil
|
||||
}
|
||||
|
||||
func ParseAxisValue3(src []byte) (AxisValue3, int, error) {
|
||||
var item AxisValue3
|
||||
n := 0
|
||||
if L := len(src); L < 16 {
|
||||
return item, 0, fmt.Errorf("reading AxisValue3: "+"EOF: expected length: 16, got %d", L)
|
||||
}
|
||||
item.mustParse(src)
|
||||
n += 16
|
||||
return item, n, nil
|
||||
}
|
||||
|
||||
func ParseAxisValue4(src []byte) (AxisValue4, int, error) {
|
||||
var item AxisValue4
|
||||
n := 0
|
||||
if L := len(src); L < 8 {
|
||||
return item, 0, fmt.Errorf("reading AxisValue4: "+"EOF: expected length: 8, got %d", L)
|
||||
}
|
||||
_ = src[7] // early bound checking
|
||||
item.format = binary.BigEndian.Uint16(src[0:])
|
||||
item.axisCount = binary.BigEndian.Uint16(src[2:])
|
||||
item.flags = binary.BigEndian.Uint16(src[4:])
|
||||
item.valueNameID = NameID(binary.BigEndian.Uint16(src[6:]))
|
||||
n += 8
|
||||
|
||||
{
|
||||
arrayLength := int(item.axisCount)
|
||||
|
||||
if L := len(src); L < 8+arrayLength*6 {
|
||||
return item, 0, fmt.Errorf("reading AxisValue4: "+"EOF: expected length: %d, got %d", 8+arrayLength*6, L)
|
||||
}
|
||||
|
||||
item.axisValues = make([]AxisValueRecord, arrayLength) // allocation guarded by the previous check
|
||||
for i := range item.axisValues {
|
||||
item.axisValues[i].mustParse(src[8+i*6:])
|
||||
}
|
||||
n += arrayLength * 6
|
||||
}
|
||||
return item, n, nil
|
||||
}
|
||||
|
||||
func ParseAxisValueArray(src []byte, valuesCount int) (AxisValueArray, int, error) {
|
||||
var item AxisValueArray
|
||||
n := 0
|
||||
{
|
||||
|
||||
if L := len(src); L < valuesCount*2 {
|
||||
return item, 0, fmt.Errorf("reading AxisValueArray: "+"EOF: expected length: %d, got %d", valuesCount*2, L)
|
||||
}
|
||||
|
||||
item.Values = make([]AxisValue, valuesCount) // allocation guarded by the previous check
|
||||
for i := range item.Values {
|
||||
offset := int(binary.BigEndian.Uint16(src[i*2:]))
|
||||
// ignore null offsets
|
||||
if offset == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
if L := len(src); L < offset {
|
||||
return item, 0, fmt.Errorf("reading AxisValueArray: "+"EOF: expected length: %d, got %d", offset, L)
|
||||
}
|
||||
|
||||
var err error
|
||||
item.Values[i], _, err = ParseAxisValue(src[offset:])
|
||||
if err != nil {
|
||||
return item, 0, fmt.Errorf("reading AxisValueArray: %s", err)
|
||||
}
|
||||
}
|
||||
n += valuesCount * 2
|
||||
}
|
||||
return item, n, nil
|
||||
}
|
||||
|
||||
func ParseFvar(src []byte) (Fvar, int, error) {
|
||||
var item Fvar
|
||||
n := 0
|
||||
if L := len(src); L < 16 {
|
||||
return item, 0, fmt.Errorf("reading Fvar: "+"EOF: expected length: 16, got %d", L)
|
||||
}
|
||||
_ = src[15] // early bound checking
|
||||
item.majorVersion = binary.BigEndian.Uint16(src[0:])
|
||||
item.minorVersion = binary.BigEndian.Uint16(src[2:])
|
||||
item.axesArrayOffset = Offset16(binary.BigEndian.Uint16(src[4:]))
|
||||
item.reserved = binary.BigEndian.Uint16(src[6:])
|
||||
item.axisCount = binary.BigEndian.Uint16(src[8:])
|
||||
item.axisSize = binary.BigEndian.Uint16(src[10:])
|
||||
item.instanceCount = binary.BigEndian.Uint16(src[12:])
|
||||
item.instanceSize = binary.BigEndian.Uint16(src[14:])
|
||||
n += 16
|
||||
|
||||
{
|
||||
|
||||
err := item.parseFvarRecords(src[:])
|
||||
if err != nil {
|
||||
return item, 0, fmt.Errorf("reading Fvar: %s", err)
|
||||
}
|
||||
}
|
||||
return item, n, nil
|
||||
}
|
||||
|
||||
func ParseFvarRecords(src []byte, axisCount int, instanceCount int, instanceSize int) (FvarRecords, int, error) {
|
||||
var item FvarRecords
|
||||
n := 0
|
||||
{
|
||||
|
||||
if L := len(src); L < axisCount*20 {
|
||||
return item, 0, fmt.Errorf("reading FvarRecords: "+"EOF: expected length: %d, got %d", axisCount*20, L)
|
||||
}
|
||||
|
||||
item.Axis = make([]VariationAxisRecord, axisCount) // allocation guarded by the previous check
|
||||
for i := range item.Axis {
|
||||
item.Axis[i].mustParse(src[i*20:])
|
||||
}
|
||||
n += axisCount * 20
|
||||
}
|
||||
{
|
||||
|
||||
err := item.parseInstances(src[n:], axisCount, instanceCount, instanceSize)
|
||||
if err != nil {
|
||||
return item, 0, fmt.Errorf("reading FvarRecords: %s", err)
|
||||
}
|
||||
}
|
||||
return item, n, nil
|
||||
}
|
||||
|
||||
func ParseGlyphVariationData(src []byte, axisCount int) (GlyphVariationData, int, error) {
|
||||
var item GlyphVariationData
|
||||
n := 0
|
||||
if L := len(src); L < 4 {
|
||||
return item, 0, fmt.Errorf("reading GlyphVariationData: "+"EOF: expected length: 4, got %d", L)
|
||||
}
|
||||
_ = src[3] // early bound checking
|
||||
item.tupleVariationCount = binary.BigEndian.Uint16(src[0:])
|
||||
offsetSerializedData := int(binary.BigEndian.Uint16(src[2:]))
|
||||
n += 4
|
||||
|
||||
{
|
||||
if offsetSerializedData != 0 { // ignore null offset
|
||||
if L := len(src); L < offsetSerializedData {
|
||||
return item, 0, fmt.Errorf("reading GlyphVariationData: "+"EOF: expected length: %d, got %d", offsetSerializedData, L)
|
||||
}
|
||||
|
||||
item.SerializedData = src[offsetSerializedData:]
|
||||
}
|
||||
}
|
||||
{
|
||||
arrayLength := int(item.tupleVariationCount & 0x0FFF)
|
||||
|
||||
offset := 4
|
||||
for i := 0; i < arrayLength; i++ {
|
||||
elem, read, err := ParseTupleVariationHeader(src[offset:], axisCount)
|
||||
if err != nil {
|
||||
return item, 0, fmt.Errorf("reading GlyphVariationData: %s", err)
|
||||
}
|
||||
item.TupleVariationHeaders = append(item.TupleVariationHeaders, elem)
|
||||
offset += read
|
||||
}
|
||||
n = offset
|
||||
}
|
||||
return item, n, nil
|
||||
}
|
||||
|
||||
func ParseGvar(src []byte) (Gvar, int, error) {
|
||||
var item Gvar
|
||||
n := 0
|
||||
if L := len(src); L < 20 {
|
||||
return item, 0, fmt.Errorf("reading Gvar: "+"EOF: expected length: 20, got %d", L)
|
||||
}
|
||||
_ = src[19] // early bound checking
|
||||
item.majorVersion = binary.BigEndian.Uint16(src[0:])
|
||||
item.minorVersion = binary.BigEndian.Uint16(src[2:])
|
||||
item.axisCount = binary.BigEndian.Uint16(src[4:])
|
||||
item.sharedTupleCount = binary.BigEndian.Uint16(src[6:])
|
||||
offsetSharedTuples := int(binary.BigEndian.Uint32(src[8:]))
|
||||
item.glyphCount = binary.BigEndian.Uint16(src[12:])
|
||||
item.flags = binary.BigEndian.Uint16(src[14:])
|
||||
item.glyphVariationDataArrayOffset = Offset32(binary.BigEndian.Uint32(src[16:]))
|
||||
n += 20
|
||||
|
||||
{
|
||||
if offsetSharedTuples != 0 { // ignore null offset
|
||||
if L := len(src); L < offsetSharedTuples {
|
||||
return item, 0, fmt.Errorf("reading Gvar: "+"EOF: expected length: %d, got %d", offsetSharedTuples, L)
|
||||
}
|
||||
|
||||
var err error
|
||||
item.SharedTuples, _, err = ParseSharedTuples(src[offsetSharedTuples:], int(item.sharedTupleCount), int(item.axisCount))
|
||||
if err != nil {
|
||||
return item, 0, fmt.Errorf("reading Gvar: %s", err)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
{
|
||||
|
||||
err := item.parseGlyphVariationDataOffsets(src[20:])
|
||||
if err != nil {
|
||||
return item, 0, fmt.Errorf("reading Gvar: %s", err)
|
||||
}
|
||||
}
|
||||
{
|
||||
|
||||
err := item.parseGlyphVariationDatas(src[:])
|
||||
if err != nil {
|
||||
return item, 0, fmt.Errorf("reading Gvar: %s", err)
|
||||
}
|
||||
}
|
||||
return item, n, nil
|
||||
}
|
||||
|
||||
func ParseHVAR(src []byte) (HVAR, int, error) {
|
||||
var item HVAR
|
||||
n := 0
|
||||
if L := len(src); L < 20 {
|
||||
return item, 0, fmt.Errorf("reading HVAR: "+"EOF: expected length: 20, got %d", L)
|
||||
}
|
||||
_ = src[19] // early bound checking
|
||||
item.majorVersion = binary.BigEndian.Uint16(src[0:])
|
||||
item.minorVersion = binary.BigEndian.Uint16(src[2:])
|
||||
offsetItemVariationStore := int(binary.BigEndian.Uint32(src[4:]))
|
||||
offsetAdvanceWidthMapping := int(binary.BigEndian.Uint32(src[8:]))
|
||||
offsetLsbMapping := int(binary.BigEndian.Uint32(src[12:]))
|
||||
offsetRsbMapping := int(binary.BigEndian.Uint32(src[16:]))
|
||||
n += 20
|
||||
|
||||
{
|
||||
if offsetItemVariationStore != 0 { // ignore null offset
|
||||
if L := len(src); L < offsetItemVariationStore {
|
||||
return item, 0, fmt.Errorf("reading HVAR: "+"EOF: expected length: %d, got %d", offsetItemVariationStore, L)
|
||||
}
|
||||
|
||||
var err error
|
||||
item.ItemVariationStore, _, err = ParseItemVarStore(src[offsetItemVariationStore:])
|
||||
if err != nil {
|
||||
return item, 0, fmt.Errorf("reading HVAR: %s", err)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
{
|
||||
if offsetAdvanceWidthMapping != 0 { // ignore null offset
|
||||
if L := len(src); L < offsetAdvanceWidthMapping {
|
||||
return item, 0, fmt.Errorf("reading HVAR: "+"EOF: expected length: %d, got %d", offsetAdvanceWidthMapping, L)
|
||||
}
|
||||
|
||||
var err error
|
||||
item.AdvanceWidthMapping, _, err = ParseDeltaSetMapping(src[offsetAdvanceWidthMapping:])
|
||||
if err != nil {
|
||||
return item, 0, fmt.Errorf("reading HVAR: %s", err)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
{
|
||||
if offsetLsbMapping != 0 { // ignore null offset
|
||||
if L := len(src); L < offsetLsbMapping {
|
||||
return item, 0, fmt.Errorf("reading HVAR: "+"EOF: expected length: %d, got %d", offsetLsbMapping, L)
|
||||
}
|
||||
|
||||
var tmpLsbMapping DeltaSetMapping
|
||||
var err error
|
||||
tmpLsbMapping, _, err = ParseDeltaSetMapping(src[offsetLsbMapping:])
|
||||
if err != nil {
|
||||
return item, 0, fmt.Errorf("reading HVAR: %s", err)
|
||||
}
|
||||
|
||||
item.LsbMapping = &tmpLsbMapping
|
||||
}
|
||||
}
|
||||
{
|
||||
if offsetRsbMapping != 0 { // ignore null offset
|
||||
if L := len(src); L < offsetRsbMapping {
|
||||
return item, 0, fmt.Errorf("reading HVAR: "+"EOF: expected length: %d, got %d", offsetRsbMapping, L)
|
||||
}
|
||||
|
||||
var tmpRsbMapping DeltaSetMapping
|
||||
var err error
|
||||
tmpRsbMapping, _, err = ParseDeltaSetMapping(src[offsetRsbMapping:])
|
||||
if err != nil {
|
||||
return item, 0, fmt.Errorf("reading HVAR: %s", err)
|
||||
}
|
||||
|
||||
item.RsbMapping = &tmpRsbMapping
|
||||
}
|
||||
}
|
||||
return item, n, nil
|
||||
}
|
||||
|
||||
func ParseInstanceRecord(src []byte, coordinatesCount int) (InstanceRecord, int, error) {
|
||||
var item InstanceRecord
|
||||
n := 0
|
||||
if L := len(src); L < 4 {
|
||||
return item, 0, fmt.Errorf("reading InstanceRecord: "+"EOF: expected length: 4, got %d", L)
|
||||
}
|
||||
_ = src[3] // early bound checking
|
||||
item.SubfamilyNameID = binary.BigEndian.Uint16(src[0:])
|
||||
item.flags = binary.BigEndian.Uint16(src[2:])
|
||||
n += 4
|
||||
|
||||
{
|
||||
|
||||
if L := len(src); L < 4+coordinatesCount*4 {
|
||||
return item, 0, fmt.Errorf("reading InstanceRecord: "+"EOF: expected length: %d, got %d", 4+coordinatesCount*4, L)
|
||||
}
|
||||
|
||||
item.Coordinates = make([]float32, coordinatesCount) // allocation guarded by the previous check
|
||||
for i := range item.Coordinates {
|
||||
item.Coordinates[i] = Float1616FromUint(binary.BigEndian.Uint32(src[4+i*4:]))
|
||||
}
|
||||
n += coordinatesCount * 4
|
||||
}
|
||||
{
|
||||
|
||||
read, err := item.parsePostScriptNameID(src[n:], coordinatesCount)
|
||||
if err != nil {
|
||||
return item, 0, fmt.Errorf("reading InstanceRecord: %s", err)
|
||||
}
|
||||
n += read
|
||||
}
|
||||
return item, n, nil
|
||||
}
|
||||
|
||||
func ParseMVAR(src []byte) (MVAR, int, error) {
|
||||
var item MVAR
|
||||
n := 0
|
||||
if L := len(src); L < 12 {
|
||||
return item, 0, fmt.Errorf("reading MVAR: "+"EOF: expected length: 12, got %d", L)
|
||||
}
|
||||
_ = src[11] // early bound checking
|
||||
item.majorVersion = binary.BigEndian.Uint16(src[0:])
|
||||
item.minorVersion = binary.BigEndian.Uint16(src[2:])
|
||||
item.reserved = binary.BigEndian.Uint16(src[4:])
|
||||
item.valueRecordSize = binary.BigEndian.Uint16(src[6:])
|
||||
item.valueRecordCount = binary.BigEndian.Uint16(src[8:])
|
||||
offsetItemVariationStore := int(binary.BigEndian.Uint16(src[10:]))
|
||||
n += 12
|
||||
|
||||
{
|
||||
if offsetItemVariationStore != 0 { // ignore null offset
|
||||
if L := len(src); L < offsetItemVariationStore {
|
||||
return item, 0, fmt.Errorf("reading MVAR: "+"EOF: expected length: %d, got %d", offsetItemVariationStore, L)
|
||||
}
|
||||
|
||||
var err error
|
||||
item.ItemVariationStore, _, err = ParseItemVarStore(src[offsetItemVariationStore:])
|
||||
if err != nil {
|
||||
return item, 0, fmt.Errorf("reading MVAR: %s", err)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
{
|
||||
|
||||
err := item.parseValueRecords(src[12:])
|
||||
if err != nil {
|
||||
return item, 0, fmt.Errorf("reading MVAR: %s", err)
|
||||
}
|
||||
}
|
||||
return item, n, nil
|
||||
}
|
||||
|
||||
func ParseSTAT(src []byte) (STAT, int, error) {
|
||||
var item STAT
|
||||
n := 0
|
||||
if L := len(src); L < 20 {
|
||||
return item, 0, fmt.Errorf("reading STAT: "+"EOF: expected length: 20, got %d", L)
|
||||
}
|
||||
_ = src[19] // early bound checking
|
||||
item.majorVersion = binary.BigEndian.Uint16(src[0:])
|
||||
item.minorVersion = binary.BigEndian.Uint16(src[2:])
|
||||
item.designAxisSize = binary.BigEndian.Uint16(src[4:])
|
||||
item.designAxisCount = binary.BigEndian.Uint16(src[6:])
|
||||
offsetDesignAxes := int(binary.BigEndian.Uint32(src[8:]))
|
||||
item.axisValueCount = binary.BigEndian.Uint16(src[12:])
|
||||
offsetAxisValues := int(binary.BigEndian.Uint32(src[14:]))
|
||||
item.elidedFallbackNameID = binary.BigEndian.Uint16(src[18:])
|
||||
n += 20
|
||||
|
||||
{
|
||||
if offsetDesignAxes != 0 { // ignore null offset
|
||||
if L := len(src); L < offsetDesignAxes {
|
||||
return item, 0, fmt.Errorf("reading STAT: "+"EOF: expected length: %d, got %d", offsetDesignAxes, L)
|
||||
}
|
||||
|
||||
arrayLength := int(item.designAxisCount)
|
||||
|
||||
if L := len(src); L < offsetDesignAxes+arrayLength*8 {
|
||||
return item, 0, fmt.Errorf("reading STAT: "+"EOF: expected length: %d, got %d", offsetDesignAxes+arrayLength*8, L)
|
||||
}
|
||||
|
||||
item.designAxes = make([]AxisRecord, arrayLength) // allocation guarded by the previous check
|
||||
for i := range item.designAxes {
|
||||
item.designAxes[i].mustParse(src[offsetDesignAxes+i*8:])
|
||||
}
|
||||
offsetDesignAxes += arrayLength * 8
|
||||
}
|
||||
}
|
||||
{
|
||||
if offsetAxisValues != 0 { // ignore null offset
|
||||
if L := len(src); L < offsetAxisValues {
|
||||
return item, 0, fmt.Errorf("reading STAT: "+"EOF: expected length: %d, got %d", offsetAxisValues, L)
|
||||
}
|
||||
|
||||
var err error
|
||||
item.axisValues, _, err = ParseAxisValueArray(src[offsetAxisValues:], int(item.axisValueCount))
|
||||
if err != nil {
|
||||
return item, 0, fmt.Errorf("reading STAT: %s", err)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
return item, n, nil
|
||||
}
|
||||
|
||||
func ParseSegmentMaps(src []byte) (SegmentMaps, int, error) {
|
||||
var item SegmentMaps
|
||||
n := 0
|
||||
if L := len(src); L < 2 {
|
||||
return item, 0, fmt.Errorf("reading SegmentMaps: "+"EOF: expected length: 2, got %d", L)
|
||||
}
|
||||
arrayLengthAxisValueMaps := int(binary.BigEndian.Uint16(src[0:]))
|
||||
n += 2
|
||||
|
||||
{
|
||||
|
||||
if L := len(src); L < 2+arrayLengthAxisValueMaps*4 {
|
||||
return item, 0, fmt.Errorf("reading SegmentMaps: "+"EOF: expected length: %d, got %d", 2+arrayLengthAxisValueMaps*4, L)
|
||||
}
|
||||
|
||||
item.AxisValueMaps = make([]AxisValueMap, arrayLengthAxisValueMaps) // allocation guarded by the previous check
|
||||
for i := range item.AxisValueMaps {
|
||||
item.AxisValueMaps[i].mustParse(src[2+i*4:])
|
||||
}
|
||||
n += arrayLengthAxisValueMaps * 4
|
||||
}
|
||||
return item, n, nil
|
||||
}
|
||||
|
||||
func ParseSharedTuples(src []byte, sharedTuplesCount int, valuesCount int) (SharedTuples, int, error) {
|
||||
var item SharedTuples
|
||||
n := 0
|
||||
{
|
||||
|
||||
offset := 0
|
||||
for i := 0; i < sharedTuplesCount; i++ {
|
||||
elem, read, err := ParseTuple(src[offset:], valuesCount)
|
||||
if err != nil {
|
||||
return item, 0, fmt.Errorf("reading SharedTuples: %s", err)
|
||||
}
|
||||
item.SharedTuples = append(item.SharedTuples, elem)
|
||||
offset += read
|
||||
}
|
||||
n = offset
|
||||
}
|
||||
return item, n, nil
|
||||
}
|
||||
|
||||
func ParseTuple(src []byte, valuesCount int) (Tuple, int, error) {
|
||||
var item Tuple
|
||||
n := 0
|
||||
{
|
||||
|
||||
if L := len(src); L < valuesCount*2 {
|
||||
return item, 0, fmt.Errorf("reading Tuple: "+"EOF: expected length: %d, got %d", valuesCount*2, L)
|
||||
}
|
||||
|
||||
item.Values = make([]Coord, valuesCount) // allocation guarded by the previous check
|
||||
for i := range item.Values {
|
||||
item.Values[i] = Coord(binary.BigEndian.Uint16(src[i*2:]))
|
||||
}
|
||||
n += valuesCount * 2
|
||||
}
|
||||
return item, n, nil
|
||||
}
|
||||
|
||||
func ParseTupleVariationHeader(src []byte, axisCount int) (TupleVariationHeader, int, error) {
|
||||
var item TupleVariationHeader
|
||||
n := 0
|
||||
if L := len(src); L < 4 {
|
||||
return item, 0, fmt.Errorf("reading TupleVariationHeader: "+"EOF: expected length: 4, got %d", L)
|
||||
}
|
||||
_ = src[3] // early bound checking
|
||||
item.VariationDataSize = binary.BigEndian.Uint16(src[0:])
|
||||
item.tupleIndex = binary.BigEndian.Uint16(src[2:])
|
||||
n += 4
|
||||
|
||||
{
|
||||
|
||||
read, err := item.parsePeakTuple(src[4:], axisCount)
|
||||
if err != nil {
|
||||
return item, 0, fmt.Errorf("reading TupleVariationHeader: %s", err)
|
||||
}
|
||||
n += read
|
||||
}
|
||||
{
|
||||
|
||||
read, err := item.parseIntermediateTuples(src[n:], axisCount)
|
||||
if err != nil {
|
||||
return item, 0, fmt.Errorf("reading TupleVariationHeader: %s", err)
|
||||
}
|
||||
n += read
|
||||
}
|
||||
return item, n, nil
|
||||
}
|
||||
|
||||
func ParseVVAR(src []byte) (VVAR, int, error) {
|
||||
var item VVAR
|
||||
n := 0
|
||||
{
|
||||
var (
|
||||
err error
|
||||
read int
|
||||
)
|
||||
item.HVAR, read, err = ParseHVAR(src[0:])
|
||||
if err != nil {
|
||||
return item, 0, fmt.Errorf("reading VVAR: %s", err)
|
||||
}
|
||||
n += read
|
||||
}
|
||||
if L := len(src); L < n+4 {
|
||||
return item, 0, fmt.Errorf("reading VVAR: "+"EOF: expected length: n + 4, got %d", L)
|
||||
}
|
||||
offsetVOrgMapping := int(binary.BigEndian.Uint32(src[n:]))
|
||||
n += 4
|
||||
|
||||
{
|
||||
if offsetVOrgMapping != 0 { // ignore null offset
|
||||
if L := len(src); L < offsetVOrgMapping {
|
||||
return item, 0, fmt.Errorf("reading VVAR: "+"EOF: expected length: %d, got %d", offsetVOrgMapping, L)
|
||||
}
|
||||
|
||||
var tmpVOrgMapping DeltaSetMapping
|
||||
var err error
|
||||
tmpVOrgMapping, _, err = ParseDeltaSetMapping(src[offsetVOrgMapping:])
|
||||
if err != nil {
|
||||
return item, 0, fmt.Errorf("reading VVAR: %s", err)
|
||||
}
|
||||
|
||||
item.VOrgMapping = &tmpVOrgMapping
|
||||
}
|
||||
}
|
||||
return item, n, nil
|
||||
}
|
||||
|
||||
func ParseVarValueRecord(src []byte) (VarValueRecord, int, error) {
|
||||
var item VarValueRecord
|
||||
n := 0
|
||||
if L := len(src); L < 8 {
|
||||
return item, 0, fmt.Errorf("reading VarValueRecord: "+"EOF: expected length: 8, got %d", L)
|
||||
}
|
||||
item.mustParse(src)
|
||||
n += 8
|
||||
return item, n, nil
|
||||
}
|
||||
|
||||
func (item *VarValueRecord) mustParse(src []byte) {
|
||||
_ = src[7] // early bound checking
|
||||
item.ValueTag = Tag(binary.BigEndian.Uint32(src[0:]))
|
||||
item.Index.mustParse(src[4:])
|
||||
}
|
||||
|
||||
func (item *VariationAxisRecord) mustParse(src []byte) {
|
||||
_ = src[19] // early bound checking
|
||||
item.Tag = Tag(binary.BigEndian.Uint32(src[0:]))
|
||||
item.Minimum = Float1616FromUint(binary.BigEndian.Uint32(src[4:]))
|
||||
item.Default = Float1616FromUint(binary.BigEndian.Uint32(src[8:]))
|
||||
item.Maximum = Float1616FromUint(binary.BigEndian.Uint32(src[12:]))
|
||||
item.flags = binary.BigEndian.Uint16(src[16:])
|
||||
item.strid = NameID(binary.BigEndian.Uint16(src[18:]))
|
||||
}
|
||||
|
||||
func (item *VariationStoreIndex) mustParse(src []byte) {
|
||||
_ = src[3] // early bound checking
|
||||
item.DeltaSetOuter = binary.BigEndian.Uint16(src[0:])
|
||||
item.DeltaSetInner = binary.BigEndian.Uint16(src[2:])
|
||||
}
|
||||
+689
@@ -0,0 +1,689 @@
|
||||
// SPDX-License-Identifier: Unlicense OR BSD-3-Clause
|
||||
|
||||
package tables
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
)
|
||||
|
||||
// ------------------------------------ fvar ------------------------------------
|
||||
|
||||
// Fvar is the Font Variations Table.
|
||||
// See - https://learn.microsoft.com/fr-fr/typography/opentype/spec/fvar
|
||||
type Fvar struct {
|
||||
majorVersion uint16 // Major version number of the font variations table — set to 1.
|
||||
minorVersion uint16 // Minor version number of the font variations table — set to 0.
|
||||
axesArrayOffset Offset16 // Offset in bytes from the beginning of the table to the start of the VariationAxisRecord array.
|
||||
reserved uint16 // This field is permanently reserved. Set to 2.
|
||||
axisCount uint16 // The number of variation axes in the font (the number of records in the axes array).
|
||||
axisSize uint16 // The size in bytes of each VariationAxisRecord — set to 20 (0x0014) for this version.
|
||||
instanceCount uint16 // The number of named instances defined in the font (the number of records in the instances array).
|
||||
instanceSize uint16 // The size in bytes of each InstanceRecord — set to either axisCount * sizeof(Fixed) + 4, or to axisCount * sizeof(Fixed) + 6.
|
||||
FvarRecords `isOpaque:""`
|
||||
}
|
||||
|
||||
func (fv *Fvar) parseFvarRecords(src []byte) (err error) {
|
||||
if L := len(src); L < int(fv.axesArrayOffset) {
|
||||
return fmt.Errorf("EOF: expected length: %d, got %d", fv.axesArrayOffset, L)
|
||||
}
|
||||
fv.FvarRecords, _, err = ParseFvarRecords(src[fv.axesArrayOffset:], int(fv.axisCount), int(fv.instanceCount), int(fv.axisCount))
|
||||
return
|
||||
}
|
||||
|
||||
// binarygen: argument=instanceCount int
|
||||
// binarygen: argument=instanceSize int
|
||||
type FvarRecords struct {
|
||||
Axis []VariationAxisRecord
|
||||
Instances []InstanceRecord `isOpaque:"" subsliceStart:"AtCurrent"`
|
||||
}
|
||||
|
||||
func (fvr *FvarRecords) parseInstances(src []byte, axisCount, instanceCount, instanceSize int) error {
|
||||
if L := len(src); L < instanceCount*instanceSize {
|
||||
return fmt.Errorf("EOF: expected length: %d, got %d", instanceCount*instanceSize, L)
|
||||
}
|
||||
fvr.Instances = make([]InstanceRecord, instanceCount)
|
||||
for i := range fvr.Instances {
|
||||
var err error
|
||||
fvr.Instances[i], _, err = ParseInstanceRecord(src[instanceSize*i:], axisCount)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type VariationAxisRecord struct {
|
||||
Tag Tag // Tag identifying the design variation for the axis.
|
||||
Minimum Float1616 // mininum value on the variation axis that the font covers
|
||||
Default Float1616 // default position on the axis
|
||||
Maximum Float1616 // maximum value on the variation axis that the font covers
|
||||
flags uint16 // Axis qualifiers — see details below.
|
||||
strid NameID // name entry in the font's ‘name’ table
|
||||
}
|
||||
|
||||
type InstanceRecord struct {
|
||||
SubfamilyNameID uint16 // The name ID for entries in the 'name' table that provide subfamily names for this instance.
|
||||
flags uint16 // Reserved for future use — set to 0.
|
||||
Coordinates []Float1616 // [axisCount] The coordinates array for this instance.
|
||||
PostScriptNameID uint16 `isOpaque:"" subsliceStart:"AtCurrent"` // Optional. The name ID for entries in the 'name' table that provide PostScript names for this instance.
|
||||
}
|
||||
|
||||
func (ir *InstanceRecord) parsePostScriptNameID(src []byte, _ int) (int, error) {
|
||||
if len(src) >= 2 {
|
||||
ir.PostScriptNameID = binary.BigEndian.Uint16(src)
|
||||
return 2, nil
|
||||
}
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
type ItemVarStore struct {
|
||||
format uint16 // Format — set to 1
|
||||
VariationRegionList VariationRegionList `offsetSize:"Offset32"` // Offset in bytes from the start of the item variation store to the variation region list.
|
||||
ItemVariationDatas []ItemVariationData `arrayCount:"FirstUint16" offsetsArray:"Offset32"` // [itemVariationDataCount] Offsets in bytes from the start of the item variation store to each item variation data subtable.
|
||||
}
|
||||
|
||||
// GetDelta uses the variation [store] and the selected instance coordinates [coords]
|
||||
// to compute the value at [index].
|
||||
func (store ItemVarStore) GetDelta(index VariationStoreIndex, coords []Coord) float32 {
|
||||
if int(index.DeltaSetOuter) >= len(store.ItemVariationDatas) {
|
||||
return 0
|
||||
}
|
||||
varData := store.ItemVariationDatas[index.DeltaSetOuter]
|
||||
if int(index.DeltaSetInner) >= len(varData.DeltaSets) {
|
||||
return 0
|
||||
}
|
||||
deltaSet := varData.DeltaSets[index.DeltaSetInner]
|
||||
var delta float32
|
||||
for i, regionIndex := range varData.RegionIndexes {
|
||||
region := store.VariationRegionList.VariationRegions[regionIndex]
|
||||
v := region.Evaluate(coords)
|
||||
delta += float32(deltaSet[i]) * v
|
||||
}
|
||||
return delta
|
||||
}
|
||||
|
||||
// AxisCount returns the number of axis found in the
|
||||
// var store, which must be the same as the one in the 'fvar' table.
|
||||
// It returns -1 if the store is empty
|
||||
func (vs *ItemVarStore) AxisCount() int {
|
||||
if vs.format == 0 {
|
||||
return -1
|
||||
}
|
||||
return int(vs.VariationRegionList.axisCount)
|
||||
}
|
||||
|
||||
type VariationRegionList struct {
|
||||
axisCount uint16 // The number of variation axes for this font. This must be the same number as axisCount in the 'fvar' table.
|
||||
VariationRegions []VariationRegion `arrayCount:"FirstUint16" arguments:"regionAxesCount=.axisCount"` // [regionCount] Array of variation regions.
|
||||
}
|
||||
|
||||
type VariationRegion struct {
|
||||
// Array of region axis coordinates records, in the order of axes given in the 'fvar' table.
|
||||
// Each RegionAxisCoordinates record provides coordinate values for a region along a single axis:
|
||||
RegionAxes []RegionAxisCoordinates // [axisCount]
|
||||
}
|
||||
|
||||
// Evaluate returns the scalar factor of the region
|
||||
func (vr VariationRegion) Evaluate(coords []Coord) float32 {
|
||||
v := float32(1)
|
||||
for axis, coord := range coords {
|
||||
factor := vr.RegionAxes[axis].evaluate(coord)
|
||||
v *= factor
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
type RegionAxisCoordinates struct {
|
||||
StartCoord Coord // The region start coordinate value for the current axis.
|
||||
PeakCoord Coord // The region peak coordinate value for the current axis.
|
||||
EndCoord Coord // The region end coordinate value for the current axis.
|
||||
}
|
||||
|
||||
// evaluate returns the factor corresponding to the given [coord],
|
||||
// interpolating between start and end.
|
||||
func (reg RegionAxisCoordinates) evaluate(coord Coord) float32 {
|
||||
start, peak, end := reg.StartCoord, reg.PeakCoord, reg.EndCoord
|
||||
if peak == 0 || coord == peak {
|
||||
return 1
|
||||
} else if coord == 0 { // Faster
|
||||
return 0
|
||||
}
|
||||
|
||||
if coord <= start || end <= coord {
|
||||
return 0
|
||||
}
|
||||
|
||||
// Interpolate
|
||||
if coord < peak {
|
||||
return float32(coord-start) / float32(peak-start)
|
||||
}
|
||||
return float32(end-coord) / float32(end-peak)
|
||||
}
|
||||
|
||||
type ItemVariationData struct {
|
||||
itemCount uint16 // The number of delta sets for distinct items.
|
||||
wordDeltaCount uint16 // A packed field: the high bit is a flag—see details below.
|
||||
regionIndexCount uint16 // The number of variation regions referenced.
|
||||
RegionIndexes []uint16 `arrayCount:"ComputedField-regionIndexCount"` //[regionIndexCount] Array of indices into the variation region list for the regions referenced by this item variation data table.
|
||||
DeltaSets [][]int16 `isOpaque:"" subsliceStart:"AtCurrent"` //[itemCount] Delta-set rows.
|
||||
}
|
||||
|
||||
func (ivd *ItemVariationData) parseDeltaSets(src []byte) error {
|
||||
const (
|
||||
LONG_WORDS = 0x8000 // Flag indicating that “word” deltas are long (int32)
|
||||
WORD_DELTA_COUNT_MASK = 0x7FFF // Count of “word” delt
|
||||
)
|
||||
if ivd.wordDeltaCount&LONG_WORDS != 0 {
|
||||
return errors.New("LONG_WORDS not implemented in DeltaSets")
|
||||
}
|
||||
itemCount := int(ivd.itemCount)
|
||||
shortDeltaCount := int(WORD_DELTA_COUNT_MASK & ivd.wordDeltaCount)
|
||||
regionIndexCount := int(ivd.regionIndexCount)
|
||||
|
||||
rowLength := shortDeltaCount + regionIndexCount
|
||||
if L := len(src); L < itemCount*rowLength {
|
||||
return fmt.Errorf("EOF: expected length: %d, got %d", itemCount*rowLength, L)
|
||||
}
|
||||
if shortDeltaCount > regionIndexCount {
|
||||
return errors.New("invalid item variation data subtable")
|
||||
}
|
||||
ivd.DeltaSets = make([][]int16, itemCount)
|
||||
for i := range ivd.DeltaSets {
|
||||
vi := make([]int16, regionIndexCount)
|
||||
j := 0
|
||||
for ; j < shortDeltaCount; j++ {
|
||||
vi[j] = int16(binary.BigEndian.Uint16(src[2*j:]))
|
||||
}
|
||||
for ; j < regionIndexCount; j++ {
|
||||
vi[j] = int16(int8(src[shortDeltaCount+j]))
|
||||
}
|
||||
ivd.DeltaSets[i] = vi
|
||||
src = src[rowLength:]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ------------------------------------ GVAR ------------------------------------
|
||||
|
||||
// See - https://learn.microsoft.com/fr-fr/typography/opentype/spec/gvar
|
||||
type Gvar struct {
|
||||
majorVersion uint16 // Major version number of the glyph variations table — set to 1.
|
||||
minorVersion uint16 // Minor version number of the glyph variations table — set to 0.
|
||||
axisCount uint16 // The number of variation axes for this font. This must be the same number as axisCount in the 'fvar' table.
|
||||
sharedTupleCount uint16 // The number of shared tuple records. Shared tuple records can be referenced within glyph variation data tables for multiple glyphs, as opposed to other tuple records stored directly within a glyph variation data table.
|
||||
SharedTuples `offsetSize:"Offset32" arguments:"sharedTuplesCount=.sharedTupleCount,valuesCount=.axisCount"` // Offset from the start of this table to the shared tuple records.
|
||||
glyphCount uint16 // The number of glyphs in this font. This must match the number of glyphs stored elsewhere in the font.
|
||||
flags uint16 // Bit-field that gives the format of the offset array that follows. If bit 0 is clear, the offsets are uint16; if bit 0 is set, the offsets are uint32.
|
||||
glyphVariationDataArrayOffset Offset32 // Offset from the start of this table to the array of GlyphVariationData tables.
|
||||
glyphVariationDataOffsets []uint32 `isOpaque:"" subsliceStart:"AtCurrent"` // [glyphCount + 1]Offset16 or Offset32 Offsets from the start of the GlyphVariationData array to each GlyphVariationData table.
|
||||
GlyphVariationDatas []GlyphVariationData `isOpaque:""`
|
||||
}
|
||||
|
||||
func (gv *Gvar) parseGlyphVariationDataOffsets(src []byte) error {
|
||||
var err error
|
||||
gv.glyphVariationDataOffsets, err = ParseLoca(src, int(gv.glyphCount), gv.flags&1 != 0)
|
||||
return err
|
||||
}
|
||||
|
||||
func (gv *Gvar) parseGlyphVariationDatas(src []byte) error {
|
||||
gv.GlyphVariationDatas = make([]GlyphVariationData, gv.glyphCount)
|
||||
startArray := uint32(gv.glyphVariationDataArrayOffset)
|
||||
for i := range gv.GlyphVariationDatas {
|
||||
start, end := int(startArray+gv.glyphVariationDataOffsets[i]), int(startArray+gv.glyphVariationDataOffsets[i+1])
|
||||
if start == end {
|
||||
continue
|
||||
}
|
||||
|
||||
if start > end {
|
||||
return fmt.Errorf("invalid offsets %d > %d", start, end)
|
||||
}
|
||||
|
||||
if L := len(src); L < end {
|
||||
return fmt.Errorf("EOF: expected length: %d, got %d", end, L)
|
||||
}
|
||||
|
||||
var err error
|
||||
gv.GlyphVariationDatas[i], _, err = ParseGlyphVariationData(src[start:end], int(gv.axisCount))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type SharedTuples struct {
|
||||
SharedTuples []Tuple // [sharedTupleCount] Array of tuple records shared across all glyph variation data tables.
|
||||
}
|
||||
|
||||
type Tuple struct {
|
||||
Values []Coord // [axisCount] Coordinate array specifying a position within the font’s variation space. The number of elements must match the axisCount specified in the 'fvar' table.
|
||||
}
|
||||
|
||||
type GlyphVariationData struct {
|
||||
tupleVariationCount uint16 // A packed field. The high 4 bits are flags, and the low 12 bits are the number of tuple variation tables for this glyph. The number of tuple variation tables can be any number between 1 and 4095.
|
||||
SerializedData []byte `offsetSize:"Offset16" arrayCount:"ToEnd"` // Offset from the start of the GlyphVariationData table to the serialized data
|
||||
TupleVariationHeaders []TupleVariationHeader `arrayCount:"ComputedField-tupleVariationCount&0x0FFF"` //[tupleCount] Array of tuple variation headers.
|
||||
}
|
||||
|
||||
// HasSharedPointNumbers returns true if the 'sharedPointNumbers' is on.
|
||||
func (gv *GlyphVariationData) HasSharedPointNumbers() bool {
|
||||
const sharedPointNumbers = 0x8000
|
||||
return gv.tupleVariationCount&sharedPointNumbers != 0
|
||||
}
|
||||
|
||||
// binarygen: argument=axisCount int
|
||||
type TupleVariationHeader struct {
|
||||
VariationDataSize uint16 // The size in bytes of the serialized data for this tuple variation table.
|
||||
tupleIndex uint16 // A packed field. The high 4 bits are flags (see below). The low 12 bits are an index into a shared tuple records array.
|
||||
// Peak tuple record for this tuple variation table — optional, determined by flags in the tupleIndex value.
|
||||
// Note that this must always be included in the 'cvar' table.
|
||||
PeakTuple Tuple `isOpaque:"" subsliceStart:"AtCurrent"`
|
||||
IntermediateTuples [2]Tuple `isOpaque:"" subsliceStart:"AtCurrent"` // Intermediate start/end tuple record for this tuple variation table — optional, determined by flags in the tupleIndex value.
|
||||
}
|
||||
|
||||
func (tv *TupleVariationHeader) parsePeakTuple(src []byte, axisCount int) (read int, err error) {
|
||||
const embeddedPeakTuple = 0x8000
|
||||
if hasPeak := tv.tupleIndex&embeddedPeakTuple != 0; hasPeak {
|
||||
tv.PeakTuple, read, err = ParseTuple(src, axisCount)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (tv *TupleVariationHeader) parseIntermediateTuples(src []byte, axisCount int) (read int, err error) {
|
||||
const intermediateRegion = 0x4000
|
||||
if hasRegions := tv.tupleIndex&intermediateRegion != 0; hasRegions {
|
||||
tv.IntermediateTuples[0], read, err = ParseTuple(src, axisCount)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
tv.IntermediateTuples[1], _, err = ParseTuple(src[read:], axisCount)
|
||||
read *= 2
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// HasPrivatePointNumbers returns true if the flag 'privatePointNumbers' is on
|
||||
func (t *TupleVariationHeader) HasPrivatePointNumbers() bool {
|
||||
const privatePointNumbers = 0x2000
|
||||
return t.tupleIndex&privatePointNumbers != 0
|
||||
}
|
||||
|
||||
// Index returns the tuple index, after masking
|
||||
func (t *TupleVariationHeader) Index() uint16 {
|
||||
const TupleIndexMask = 0x0FFF
|
||||
return t.tupleIndex & TupleIndexMask
|
||||
}
|
||||
|
||||
// ---------------------------------- HVAR/VVAR ----------------------------------
|
||||
|
||||
// See - https://learn.microsoft.com/fr-fr/typography/opentype/spec/hvar
|
||||
type HVAR struct {
|
||||
majorVersion uint16 // Major version number of the horizontal metrics variations table — set to 1.
|
||||
minorVersion uint16 // Minor version number of the horizontal metrics variations table — set to 0.
|
||||
ItemVariationStore ItemVarStore `offsetSize:"Offset32"` // Offset in bytes from the start of this table to the item variation store table.
|
||||
AdvanceWidthMapping DeltaSetMapping `offsetSize:"Offset32"` // Offset in bytes from the start of this table to the delta-set index mapping for advance widths (may be NULL).
|
||||
LsbMapping *DeltaSetMapping `offsetSize:"Offset32"` // Offset in bytes from the start of this table to the delta-set index mapping for left side bearings (may be NULL).
|
||||
RsbMapping *DeltaSetMapping `offsetSize:"Offset32"` // Offset in bytes from the start of this table to the delta-set index mapping for right side bearings (may be NULL).
|
||||
}
|
||||
|
||||
func (t *HVAR) AdvanceDelta(glyph GlyphID, coords []Coord) float32 {
|
||||
index := t.AdvanceWidthMapping.Index(glyph)
|
||||
return t.ItemVariationStore.GetDelta(index, coords)
|
||||
}
|
||||
|
||||
// VariationStoreIndex reference an item in the variation store
|
||||
type VariationStoreIndex struct {
|
||||
DeltaSetOuter, DeltaSetInner uint16
|
||||
}
|
||||
|
||||
type DeltaSetMapping struct {
|
||||
format uint8 // DeltaSetIndexMap format: 0 or 1
|
||||
entryFormat uint8 // A packed field that describes the compressed representation of delta-set indices. See details below.
|
||||
// uint16 or uint32 mapCount : The number of mapping entries.
|
||||
Map []VariationStoreIndex `isOpaque:"" subsliceStart:"AtCurrent"`
|
||||
}
|
||||
|
||||
// Index returns the [VariationStoreIndex] for the given index.
|
||||
func (m DeltaSetMapping) Index(glyph GlyphID) VariationStoreIndex {
|
||||
// If a mapping table is not provided, glyph indices are used as implicit delta-set indices.
|
||||
// [...] the delta-set outer-level index is zero, and the glyph ID is used as the inner-level index.
|
||||
if len(m.Map) == 0 {
|
||||
return VariationStoreIndex{DeltaSetInner: uint16(glyph)}
|
||||
}
|
||||
|
||||
// If a given glyph ID is greater than mapCount - 1, then the last entry is used.
|
||||
if int(glyph) >= len(m.Map) {
|
||||
glyph = GlyphID(len(m.Map) - 1)
|
||||
}
|
||||
|
||||
return m.Map[glyph]
|
||||
}
|
||||
|
||||
func (ds *DeltaSetMapping) parseMap(src []byte) error {
|
||||
var mapCount int
|
||||
switch ds.format {
|
||||
case 0:
|
||||
if L := len(src); L < 2 {
|
||||
return fmt.Errorf("EOF: expected length: %d, got %d", 2, L)
|
||||
}
|
||||
mapCount = int(binary.BigEndian.Uint16(src))
|
||||
src = src[2:]
|
||||
case 1:
|
||||
if L := len(src); L < 4 {
|
||||
return fmt.Errorf("EOF: expected length: %d, got %d", 4, L)
|
||||
}
|
||||
mapCount = int(binary.BigEndian.Uint32(src))
|
||||
src = src[4:]
|
||||
default:
|
||||
return fmt.Errorf("unsupported DeltaSetMapping format %d", ds.format)
|
||||
}
|
||||
|
||||
const (
|
||||
INNER_INDEX_BIT_COUNT_MASK = 0x0F // Mask for the low 4 bits, which give the count of bits minus one that are used in each entry for the inner-level index.
|
||||
MAP_ENTRY_SIZE_MASK = 0x30 // Mask for bits that indicate the size in bytes minus one of each entry.
|
||||
)
|
||||
innerBitSize := ds.entryFormat&INNER_INDEX_BIT_COUNT_MASK + 1
|
||||
entrySize := int((ds.entryFormat&MAP_ENTRY_SIZE_MASK)>>4 + 1)
|
||||
if entrySize > 4 || len(src) < entrySize*mapCount {
|
||||
return fmt.Errorf("invalid delta-set mapping (length %d, entrySize %d, mapCount %d)", len(src), entrySize, mapCount)
|
||||
}
|
||||
ds.Map = make([]VariationStoreIndex, mapCount)
|
||||
for i := range ds.Map {
|
||||
var v uint32
|
||||
for _, b := range src[entrySize*i : entrySize*(i+1)] { // 1 to 4 bytes
|
||||
v = v<<8 + uint32(b)
|
||||
}
|
||||
ds.Map[i].DeltaSetOuter = uint16(v >> innerBitSize)
|
||||
ds.Map[i].DeltaSetInner = uint16(v & (1<<innerBitSize - 1))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// See - https://learn.microsoft.com/fr-fr/typography/opentype/spec/vvar
|
||||
type VVAR struct {
|
||||
HVAR
|
||||
VOrgMapping *DeltaSetMapping `offsetSize:"Offset32"` // Offset in bytes from the start of this table to the delta-set index mapping for Y coordinates of vertical origins (may be NULL).
|
||||
}
|
||||
|
||||
func (vv *VVAR) VorgDelta(glyph GlyphID, coords []Coord) float32 {
|
||||
if vv.VOrgMapping == nil {
|
||||
return 0
|
||||
}
|
||||
varidx := vv.VOrgMapping.Index(glyph)
|
||||
return vv.ItemVariationStore.GetDelta(varidx, coords)
|
||||
}
|
||||
|
||||
// ------------------------------------ avar ------------------------------------
|
||||
|
||||
// avar — Axis Variations Table
|
||||
type Avar struct {
|
||||
majorVersion uint16 // Major version number of the axis variations table — set to 1.
|
||||
minorVersion uint16 // Minor version number of the axis variations table — set to 0.
|
||||
reserved uint16 // Permanently reserved; set to zero.
|
||||
AxisSegmentMaps []SegmentMaps `arrayCount:"FirstUint16"` //[axisCount] The segment maps array — one segment map for each axis, in the order of axes specified in the 'fvar' table.
|
||||
}
|
||||
|
||||
type SegmentMaps struct {
|
||||
// [positionMapCount] The array of axis value map records for this axis.
|
||||
// Each axis value map record provides a single axis-value mapping correspondence.
|
||||
AxisValueMaps []AxisValueMap `arrayCount:"FirstUint16"`
|
||||
}
|
||||
|
||||
func (sm SegmentMaps) Map(value Coord) Coord {
|
||||
// copied from harfbuzz/src/hb-ot-var-avar-table.hh
|
||||
|
||||
l := sm.AxisValueMaps
|
||||
|
||||
// The following special-cases are not part of OpenType, which requires
|
||||
// that at least -1, 0, and +1 must be mapped. But we include these as
|
||||
// part of a better error recovery scheme.
|
||||
if len(l) == 0 {
|
||||
return value
|
||||
} else if len(l) == 1 {
|
||||
return value - l[0].FromCoordinate + l[0].ToCoordinate
|
||||
}
|
||||
|
||||
// At least two mappings now.
|
||||
|
||||
// CoreText is wild...
|
||||
// PingFangUI avar needs all this special-casing...
|
||||
// So we implement an extended version of the spec here,
|
||||
// which is more robust and more likely to be compatible with
|
||||
// the wild.
|
||||
|
||||
const p1 = Coord(1 << 14)
|
||||
const m1 = -p1
|
||||
|
||||
start := 0
|
||||
end := len(l)
|
||||
if l[start].FromCoordinate == m1 && l[start].ToCoordinate == m1 && l[start+1].FromCoordinate == m1 {
|
||||
start++
|
||||
}
|
||||
if l[end-1].FromCoordinate == p1 && l[end-1].ToCoordinate == p1 && l[end-2].FromCoordinate == p1 {
|
||||
end--
|
||||
}
|
||||
|
||||
// Look for exact match first, and do lots of special-casing.
|
||||
var i int
|
||||
for i = start; i < end; i++ {
|
||||
if value == l[i].FromCoordinate {
|
||||
break
|
||||
}
|
||||
}
|
||||
if i < end {
|
||||
// There's at least one exact match. See if there are more.
|
||||
j := i
|
||||
for ; j+1 < end; j++ {
|
||||
if value != l[j+1].FromCoordinate {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// [i,j] inclusive are all exact matches:
|
||||
|
||||
// If there's only one, return it. This is the only spec-compliant case.
|
||||
if i == j {
|
||||
return l[i].ToCoordinate
|
||||
}
|
||||
// If there's exactly three, return the middle one.
|
||||
if i+2 == j {
|
||||
return l[i+1].ToCoordinate
|
||||
}
|
||||
|
||||
// Ignore the middle ones. Return the one mapping closer to 0.
|
||||
if value < 0 {
|
||||
return l[j].ToCoordinate
|
||||
}
|
||||
if value > 0 {
|
||||
return l[i].ToCoordinate
|
||||
}
|
||||
|
||||
// Mapping 0 ? CoreText seems confused. It seems to prefer 0 here...
|
||||
// So we'll just return the smallest one. lol
|
||||
if abs(l[i].ToCoordinate) < abs(l[j].ToCoordinate) {
|
||||
return l[i].ToCoordinate
|
||||
}
|
||||
return l[j].ToCoordinate
|
||||
}
|
||||
|
||||
// There's at least two and we're not an exact match. Prepare to lerp.
|
||||
|
||||
// Find the segment we're in.
|
||||
for i = start; i < end; i++ {
|
||||
if value < l[i].FromCoordinate {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if i == 0 {
|
||||
// Value before all segments; Shift.
|
||||
return value - l[0].FromCoordinate + l[0].ToCoordinate
|
||||
}
|
||||
if i == end {
|
||||
// Value after all segments; Shift.
|
||||
return value - l[end-1].FromCoordinate + l[end-1].ToCoordinate
|
||||
}
|
||||
|
||||
// Actually interpolate.
|
||||
before := l[i-1]
|
||||
after := l[i]
|
||||
denom := float64(after.FromCoordinate - before.FromCoordinate) // Can't be zero by now.
|
||||
return before.ToCoordinate + Coord(math.Round(float64(after.ToCoordinate-before.ToCoordinate)*float64(value-before.FromCoordinate))/denom)
|
||||
}
|
||||
|
||||
type AxisValueMap struct {
|
||||
FromCoordinate Coord // A normalized coordinate value obtained using default normalization.
|
||||
ToCoordinate Coord // The modified, normalized coordinate value.
|
||||
}
|
||||
|
||||
// ----------------------------------------- MVAR -----------------------------------------
|
||||
|
||||
type MVAR struct {
|
||||
majorVersion uint16 // Major version number of the metrics variations table — set to 1.
|
||||
minorVersion uint16 // Minor version number of the metrics variations table — set to 0.
|
||||
reserved uint16 // Not used; set to 0.
|
||||
valueRecordSize uint16 // The size in bytes of each value record — must be greater than zero.
|
||||
valueRecordCount uint16 // The number of value records — may be zero.
|
||||
ItemVariationStore ItemVarStore `offsetSize:"Offset16"` // Offset in bytes from the start of this table to the item variation store table. If valueRecordCount is zero, set to zero; if valueRecordCount is greater than zero, must be greater than zero.
|
||||
ValueRecords []VarValueRecord `isOpaque:"" subsliceStart:"AtCurrent"` // [valueRecordCount] Array of value records that identify target items and the associated delta-set index for each. The valueTag records must be in binary order of their valueTag field.
|
||||
}
|
||||
|
||||
// Quoting the spec:
|
||||
// "The valueRecordSize field indicates the size of each value record.
|
||||
// Future, minor version updates of the MVAR table may define compatible
|
||||
// extensions to the value record format with additional fields.
|
||||
// Implementations must use the valueRecordSize field to determine the start of each record."
|
||||
func (mv *MVAR) parseValueRecords(src []byte) error {
|
||||
expectedL := int(mv.valueRecordSize) * int(mv.valueRecordCount)
|
||||
if L := len(src); L < expectedL {
|
||||
return fmt.Errorf("EOF: expected length: %d, got %d", expectedL, L)
|
||||
}
|
||||
mv.ValueRecords = make([]VarValueRecord, mv.valueRecordCount)
|
||||
for i := range mv.ValueRecords {
|
||||
mv.ValueRecords[i].mustParse(src[int(mv.valueRecordSize)*i:])
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type VarValueRecord struct {
|
||||
ValueTag Tag // Four-byte tag identifying a font-wide measure.
|
||||
Index VariationStoreIndex // A delta-set index — used to select an item variation data subtable within the item variation store.
|
||||
}
|
||||
|
||||
// ------------------------------------------------- STAT -------------------------------------------------
|
||||
|
||||
// STAT is the Style Attributes Table
|
||||
// See https://learn.microsoft.com/en-us/typography/opentype/spec/stat
|
||||
type STAT struct {
|
||||
majorVersion uint16 // Major version number of the style attributes table — set to 1.
|
||||
minorVersion uint16 // Minor version number of the style attributes table — set to 2.
|
||||
designAxisSize uint16 // The size in bytes of each axis record.
|
||||
designAxisCount uint16 // The number of axis records. In a font with an 'fvar' table, this value must be greater than or equal to the axisCount value in the 'fvar' table. In all fonts, must be greater than zero if axisValueCount is greater than zero.
|
||||
designAxes []AxisRecord `offsetSize:"Offset32" arrayCount:"ComputedField-designAxisCount"` // Offset in bytes from the beginning of the STAT table to the start of the design axes array. If designAxisCount is zero, set to zero; if designAxisCount is greater than zero, must be greater than zero.
|
||||
axisValueCount uint16 // The number of axis value tables.
|
||||
axisValues AxisValueArray `offsetSize:"Offset32" arguments:"valuesCount=.axisValueCount"` // Offset in bytes from the beginning of the STAT table to the start of the design axes value offsets array. If axisValueCount is zero, set to zero; if axisValueCount is greater than zero, must be greater than zero.
|
||||
elidedFallbackNameID uint16 // Name ID used as fallback when projection of names into a particular font model produces a subfamily name containing only elidable elements.
|
||||
}
|
||||
|
||||
func (st *STAT) getAxisIndex(tag Tag) (uint16, bool) {
|
||||
for index, record := range st.designAxes {
|
||||
if record.Tag == tag {
|
||||
return uint16(index), true
|
||||
}
|
||||
}
|
||||
return 0, false
|
||||
}
|
||||
|
||||
func (st STAT) Value(tag Tag) (float32, bool) {
|
||||
axisIndex, ok := st.getAxisIndex(tag)
|
||||
if !ok {
|
||||
return 0, false
|
||||
}
|
||||
|
||||
for _, axisValue := range st.axisValues.Values {
|
||||
if axisValue.index() == axisIndex {
|
||||
return axisValue.valueFor(axisIndex), true
|
||||
}
|
||||
}
|
||||
|
||||
return 0, false
|
||||
}
|
||||
|
||||
type AxisRecord struct {
|
||||
Tag Tag // A tag identifying the axis of design variation.
|
||||
NameID NameID // The name ID for entries in the 'name' table that provide a display string for this axis.
|
||||
Ordering uint16 // A value that applications can use to determine primary sorting of face names, or for ordering of labels when composing family or face names.
|
||||
}
|
||||
|
||||
type AxisValueArray struct {
|
||||
Values []AxisValue `offsetsArray:"Offset16"` // Offset in bytes from the beginning of the STAT table to the start of the design axes value offsets array. If axisValueCount is zero, set to zero; if axisValueCount is greater than zero, must be greater than zero.
|
||||
}
|
||||
|
||||
type AxisValue interface {
|
||||
name() NameID
|
||||
index() uint16
|
||||
valueFor(index uint16) float32
|
||||
}
|
||||
|
||||
func (av AxisValue1) name() NameID { return av.valueNameID }
|
||||
func (av AxisValue2) name() NameID { return av.valueNameID }
|
||||
func (av AxisValue3) name() NameID { return av.valueNameID }
|
||||
func (av AxisValue4) name() NameID { return av.valueNameID }
|
||||
|
||||
func (av AxisValue1) index() uint16 { return av.axisIndex }
|
||||
func (av AxisValue2) index() uint16 { return av.axisIndex }
|
||||
func (av AxisValue3) index() uint16 { return av.axisIndex }
|
||||
func (av AxisValue4) index() uint16 { return 0xFFFF }
|
||||
|
||||
func (av AxisValue1) valueFor(index uint16) float32 { return av.value }
|
||||
func (av AxisValue2) valueFor(index uint16) float32 { return av.nominalValue }
|
||||
func (av AxisValue3) valueFor(index uint16) float32 { return av.value }
|
||||
func (av AxisValue4) valueFor(index uint16) float32 {
|
||||
return av.axisValues[index].value
|
||||
}
|
||||
|
||||
type AxisValue1 struct {
|
||||
format uint16 `unionTag:"1"` // Format identifier — set to 1.
|
||||
axisIndex uint16 // Zero-base index into the axis record array identifying the axis of design variation to which the axis value table applies. Must be less than designAxisCount.
|
||||
flags uint16 // Flags — see below for details.
|
||||
valueNameID NameID // The name ID for entries in the 'name' table that provide a display string for this attribute value.
|
||||
value Float1616 // A numeric value for this attribute value.
|
||||
}
|
||||
|
||||
type AxisValue2 struct {
|
||||
format uint16 `unionTag:"2"` // Format identifier — set to 2.
|
||||
axisIndex uint16 // Zero-base index into the axis record array identifying the axis of design variation to which the axis value table applies. Must be less than designAxisCount.
|
||||
flags uint16 // Flags — see below for details.
|
||||
valueNameID NameID // The name ID for entries in the 'name' table that provide a display string for this attribute value.
|
||||
nominalValue Float1616 // A nominal numeric value for this attribute value.
|
||||
rangeMinValue Float1616 // The minimum value for a range associated with the specified name ID.
|
||||
rangeMaxValue Float1616 // The maximum value for a range associated with the specified name ID
|
||||
}
|
||||
|
||||
type AxisValue3 struct {
|
||||
format uint16 `unionTag:"3"` // Format identifier — set to 3.
|
||||
axisIndex uint16 // Zero-base index into the axis record array identifying the axis of design variation to which the axis value table applies. Must be less than designAxisCount.
|
||||
flags uint16 // Flags — see below for details.
|
||||
valueNameID NameID // The name ID for entries in the 'name' table that provide a display string for this attribute value.
|
||||
value Float1616 // A numeric value for this attribute value.
|
||||
linkedValue Float1616 // The numeric value for a style-linked mapping from this value.
|
||||
}
|
||||
|
||||
type AxisValue4 struct {
|
||||
format uint16 `unionTag:"4"` // Format identifier — set to 4.
|
||||
axisCount uint16 // The total number of axes contributing to this axis-values combination.
|
||||
flags uint16 // Flags — see below for details.
|
||||
valueNameID NameID // The name ID for entries in the 'name' table that provide a display string for this combination of axis values.
|
||||
axisValues []AxisValueRecord `arrayCount:"ComputedField-axisCount"` //[axisCount] Array of AxisValue records that provide the combination of axis values, one for each contributing axis.
|
||||
}
|
||||
|
||||
type AxisValueRecord struct {
|
||||
axisIndex uint16 // Zero-base index into the axis record array identifying the axis to which this value applies. Must be less than designAxisCount.
|
||||
value Float1616 // A numeric value for this attribute value.
|
||||
}
|
||||
+80
@@ -0,0 +1,80 @@
|
||||
package opentype
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"math"
|
||||
)
|
||||
|
||||
// Table is one opentype binary table and its tag.
|
||||
type Table struct {
|
||||
Content []byte
|
||||
Tag Tag
|
||||
}
|
||||
|
||||
// WriteTTF creates a single Truetype font file (.ttf) from the given [tables] slice,
|
||||
// which must be sorted by Tag
|
||||
func WriteTTF(tables []Table) []byte {
|
||||
introLength := uint32(otfHeaderSize + len(tables)*otfEntrySize)
|
||||
buffer := make([]byte, introLength)
|
||||
|
||||
writeTTFHeader(len(tables), buffer)
|
||||
|
||||
tableOffset := introLength // the actual content will start after the header + table directory
|
||||
for i, table := range tables {
|
||||
cs := checksum(table.Content)
|
||||
tableLength := uint32(len(table.Content))
|
||||
|
||||
slice := buffer[otfHeaderSize+i*otfEntrySize:]
|
||||
binary.BigEndian.PutUint32(slice, uint32(table.Tag))
|
||||
binary.BigEndian.PutUint32(slice[4:], cs)
|
||||
binary.BigEndian.PutUint32(slice[8:], tableOffset)
|
||||
binary.BigEndian.PutUint32(slice[12:], tableLength)
|
||||
|
||||
// update the offset
|
||||
tableOffset = tableOffset + tableLength
|
||||
}
|
||||
|
||||
// append the actual table content :
|
||||
// allocate only once
|
||||
buffer = append(buffer, make([]byte, tableOffset-introLength)...)
|
||||
tableOffset = introLength
|
||||
for _, table := range tables {
|
||||
copy(buffer[tableOffset:], table.Content)
|
||||
tableOffset = tableOffset + uint32(len(table.Content))
|
||||
}
|
||||
|
||||
return buffer
|
||||
}
|
||||
|
||||
// out is assumed to have a length >= ttfHeaderSize
|
||||
func writeTTFHeader(nTables int, out []byte) {
|
||||
log2 := math.Floor(math.Log2(float64(nTables)))
|
||||
// Maximum power of 2 less than or equal to numTables, times 16 ((2**floor(log2(numTables))) * 16, where “**” is an exponentiation operator).
|
||||
searchRange := math.Pow(2, log2) * 16
|
||||
// Log2 of the maximum power of 2 less than or equal to numTables (log2(searchRange/16), which is equal to floor(log2(numTables))).
|
||||
entrySelector := log2
|
||||
// numTables times 16, minus searchRange ((numTables * 16) - searchRange).
|
||||
rangeShift := nTables*16 - int(searchRange)
|
||||
|
||||
binary.BigEndian.PutUint32(out[:], uint32(TrueType))
|
||||
binary.BigEndian.PutUint16(out[4:], uint16(nTables))
|
||||
binary.BigEndian.PutUint16(out[6:], uint16(searchRange))
|
||||
binary.BigEndian.PutUint16(out[8:], uint16(entrySelector))
|
||||
binary.BigEndian.PutUint16(out[10:], uint16(rangeShift))
|
||||
}
|
||||
|
||||
func checksum(table []byte) uint32 {
|
||||
// "To accommodate data with a length that is not a multiple of four,
|
||||
// the above algorithm must be modified to treat the data as though
|
||||
// it contains zero padding to a length that is a multiple of four."
|
||||
if r := len(table) % 4; r != 0 {
|
||||
table = append(table, make([]byte, r)...)
|
||||
}
|
||||
|
||||
var sum uint32
|
||||
for i := 0; i < len(table)/4; i++ {
|
||||
sum += binary.BigEndian.Uint32(table[i*4:])
|
||||
}
|
||||
|
||||
return sum
|
||||
}
|
||||
+69
@@ -0,0 +1,69 @@
|
||||
// SPDX-License-Identifier: Unlicense OR BSD-3-Clause
|
||||
|
||||
package font
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
|
||||
"github.com/go-text/typesetting/font/opentype/tables"
|
||||
)
|
||||
|
||||
type os2 struct {
|
||||
version uint16
|
||||
xAvgCharWidth uint16
|
||||
|
||||
*os2Desc
|
||||
|
||||
useTypoMetrics bool // true if the field sTypoAscender, sTypoDescender and sTypoLineGap are valid.
|
||||
|
||||
ySubscriptXSize float32
|
||||
ySubscriptYSize float32
|
||||
ySubscriptXOffset float32
|
||||
ySubscriptYOffset float32
|
||||
ySuperscriptXSize float32
|
||||
ySuperscriptYSize float32
|
||||
ySuperscriptXOffset float32
|
||||
yStrikeoutSize float32
|
||||
yStrikeoutPosition float32
|
||||
sTypoAscender float32
|
||||
sTypoDescender float32
|
||||
sTypoLineGap float32
|
||||
sxHeigh float32
|
||||
sCapHeight float32
|
||||
}
|
||||
|
||||
func newOs2(os tables.Os2) (os2, error) {
|
||||
out := os2{
|
||||
version: os.Version,
|
||||
xAvgCharWidth: os.XAvgCharWidth,
|
||||
os2Desc: newOS2Desc(os),
|
||||
ySubscriptXSize: float32(os.YSubscriptXSize),
|
||||
ySubscriptYSize: float32(os.YSubscriptYSize),
|
||||
ySubscriptXOffset: float32(os.YSubscriptXOffset),
|
||||
ySubscriptYOffset: float32(os.YSubscriptYOffset),
|
||||
ySuperscriptXSize: float32(os.YSuperscriptXSize),
|
||||
ySuperscriptYSize: float32(os.YSuperscriptYSize),
|
||||
ySuperscriptXOffset: float32(os.YSuperscriptXOffset),
|
||||
yStrikeoutSize: float32(os.YStrikeoutSize),
|
||||
yStrikeoutPosition: float32(os.YStrikeoutPosition),
|
||||
sTypoAscender: float32(os.STypoAscender),
|
||||
sTypoDescender: float32(os.STypoDescender),
|
||||
sTypoLineGap: float32(os.STypoLineGap),
|
||||
}
|
||||
// add addition info for version >= 2
|
||||
if os.Version >= 2 {
|
||||
if len(os.HigherVersionData) < 12 {
|
||||
return os2{}, errors.New("invalid table os2")
|
||||
}
|
||||
out.sxHeigh = float32(binary.BigEndian.Uint16(os.HigherVersionData[8:]))
|
||||
out.sCapHeight = float32(binary.BigEndian.Uint16(os.HigherVersionData[10:]))
|
||||
}
|
||||
|
||||
const useTypoMetrics = 1 << 7
|
||||
use := os.FsSelection&useTypoMetrics != 0
|
||||
hasData := os.USWeightClass != 0 || os.USWidthClass != 0 || os.USFirstCharIndex != 0 || os.USLastCharIndex != 0
|
||||
out.useTypoMetrics = use && hasData
|
||||
|
||||
return out, nil
|
||||
}
|
||||
+255
@@ -0,0 +1,255 @@
|
||||
// SPDX-License-Identifier: Unlicense OR BSD-3-Clause
|
||||
|
||||
package font
|
||||
|
||||
import "github.com/go-text/typesetting/font/opentype/tables"
|
||||
|
||||
// shared between GSUB and GPOS
|
||||
type Layout struct {
|
||||
Scripts []Script
|
||||
Features []Feature
|
||||
FeatureVariations []tables.FeatureVariationRecord
|
||||
}
|
||||
|
||||
func newLayout(table tables.Layout) Layout {
|
||||
fCount := len(table.FeatureList.Features)
|
||||
out := Layout{
|
||||
Scripts: make([]Script, len(table.ScriptList.Scripts)),
|
||||
Features: make([]Feature, fCount),
|
||||
}
|
||||
for i, s := range table.ScriptList.Scripts {
|
||||
if langSys := s.DefaultLangSys; langSys != nil {
|
||||
sanitizeLangSys(langSys, fCount)
|
||||
}
|
||||
for i := range s.LangSys {
|
||||
sanitizeLangSys(&s.LangSys[i], fCount)
|
||||
}
|
||||
out.Scripts[i] = Script{
|
||||
Script: s,
|
||||
Tag: table.ScriptList.Records[i].Tag,
|
||||
}
|
||||
}
|
||||
for i, f := range table.FeatureList.Features {
|
||||
out.Features[i] = Feature{
|
||||
Feature: f,
|
||||
Tag: table.FeatureList.Records[i].Tag,
|
||||
}
|
||||
}
|
||||
if table.FeatureVariations != nil {
|
||||
out.FeatureVariations = table.FeatureVariations.FeatureVariationRecords
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func sanitizeLangSys(langSys *tables.LangSys, featuresCount int) {
|
||||
if int(langSys.RequiredFeatureIndex) >= featuresCount {
|
||||
// invalid index : replace it by the sentinel value
|
||||
langSys.RequiredFeatureIndex = 0xFFFF
|
||||
}
|
||||
}
|
||||
|
||||
type Script struct {
|
||||
tables.Script
|
||||
Tag Tag
|
||||
}
|
||||
|
||||
type Feature struct {
|
||||
tables.Feature
|
||||
Tag Tag
|
||||
}
|
||||
|
||||
// FindScript looks for [script] and return its index into the Scripts slice,
|
||||
// or -1 if the tag is not found.
|
||||
func (la *Layout) FindScript(script Tag) int {
|
||||
// Scripts is sorted: binary search
|
||||
low, high := 0, len(la.Scripts)
|
||||
for low < high {
|
||||
mid := low + (high-low)/2 // avoid overflow when computing mid
|
||||
p := la.Scripts[mid].Tag
|
||||
if script < p {
|
||||
high = mid
|
||||
} else if script > p {
|
||||
low = mid + 1
|
||||
} else {
|
||||
return mid
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
// FindVariationIndex returns the first feature variation matching
|
||||
// the specified variation coordinates, as an index in the
|
||||
// `FeatureVariations` field.
|
||||
// It returns `-1` if not found.
|
||||
func (la *Layout) FindVariationIndex(coords []VarCoord) int {
|
||||
for i, record := range la.FeatureVariations {
|
||||
if evaluateVarRec(record, coords) {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
// returns `true` if the feature is concerned by the `coords`
|
||||
func evaluateVarRec(fv tables.FeatureVariationRecord, coords []VarCoord) bool {
|
||||
for _, c := range fv.ConditionSet.Conditions {
|
||||
if !evaluateCondition(c, coords) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// returns `true` if `coords` match the condition `c`
|
||||
func evaluateCondition(c tables.ConditionFormat1, coords []VarCoord) bool {
|
||||
var coord VarCoord
|
||||
if int(c.AxisIndex) < len(coords) {
|
||||
coord = coords[c.AxisIndex]
|
||||
}
|
||||
return c.FilterRangeMinValue <= coord && coord <= c.FilterRangeMaxValue
|
||||
}
|
||||
|
||||
// FindFeatureIndex fetches the index for a given feature tag in the GSUB or GPOS table.
|
||||
// Returns false if not found
|
||||
func (la *Layout) FindFeatureIndex(featureTag Tag) (uint16, bool) {
|
||||
for i, feat := range la.Features { // i fits in uint16
|
||||
if featureTag == feat.Tag {
|
||||
return uint16(i), true
|
||||
}
|
||||
}
|
||||
return 0, false
|
||||
}
|
||||
|
||||
// ---------------------------------- GSUB ----------------------------------
|
||||
|
||||
type GSUB struct {
|
||||
Layout
|
||||
Lookups []GSUBLookup
|
||||
}
|
||||
|
||||
type LookupOptions struct {
|
||||
// Lookup qualifiers.
|
||||
Flag uint16
|
||||
// Index (base 0) into GDEF mark glyph sets structure,
|
||||
// meaningfull only if UseMarkFilteringSet is set.
|
||||
MarkFilteringSet uint16
|
||||
}
|
||||
|
||||
const UseMarkFilteringSet = 1 << 4
|
||||
|
||||
// Props returns a 32-bit integer where the lower 16-bit is `Flag` and
|
||||
// the higher 16-bit is `MarkFilteringSet` if the lookup uses one.
|
||||
func (lo LookupOptions) Props() uint32 {
|
||||
flag := uint32(lo.Flag)
|
||||
if lo.Flag&UseMarkFilteringSet != 0 {
|
||||
flag |= uint32(lo.MarkFilteringSet) << 16
|
||||
}
|
||||
return flag
|
||||
}
|
||||
|
||||
type GSUBLookup struct {
|
||||
LookupOptions
|
||||
Subtables []tables.GSUBLookup
|
||||
}
|
||||
|
||||
func newGSUB(table tables.Layout) (GSUB, error) {
|
||||
out := GSUB{
|
||||
Layout: newLayout(table),
|
||||
Lookups: make([]GSUBLookup, len(table.LookupList.Lookups)),
|
||||
}
|
||||
for i, lk := range table.LookupList.Lookups {
|
||||
subtables, err := lk.AsGSUBLookups()
|
||||
if err != nil {
|
||||
return GSUB{}, err
|
||||
}
|
||||
for j, subtable := range subtables {
|
||||
// start by resolving extension
|
||||
if ext, isExt := subtable.(tables.ExtensionSubs); isExt {
|
||||
subtables[j], err = ext.Resolve()
|
||||
if err != nil {
|
||||
return GSUB{}, err
|
||||
}
|
||||
}
|
||||
|
||||
// sanitize each lookup
|
||||
switch subtable := subtable.(type) {
|
||||
case tables.MultipleSubs:
|
||||
err = subtable.Sanitize()
|
||||
case tables.LigatureSubs:
|
||||
err = subtable.Sanitize()
|
||||
case tables.ContextualSubs:
|
||||
err = subtable.Sanitize(uint16(len(out.Lookups)))
|
||||
case tables.ReverseChainSingleSubs:
|
||||
err = subtable.Sanitize()
|
||||
}
|
||||
if err != nil {
|
||||
return GSUB{}, err
|
||||
}
|
||||
}
|
||||
out.Lookups[i] = GSUBLookup{
|
||||
LookupOptions: LookupOptions{
|
||||
Flag: lk.LookupFlag,
|
||||
MarkFilteringSet: lk.MarkFilteringSet,
|
||||
},
|
||||
Subtables: subtables,
|
||||
}
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
type GPOS struct {
|
||||
Layout
|
||||
Lookups []GPOSLookup
|
||||
}
|
||||
|
||||
type GPOSLookup struct {
|
||||
LookupOptions
|
||||
Subtables []tables.GPOSLookup
|
||||
}
|
||||
|
||||
func newGPOS(table tables.Layout) (GPOS, error) {
|
||||
out := GPOS{
|
||||
Layout: newLayout(table),
|
||||
Lookups: make([]GPOSLookup, len(table.LookupList.Lookups)),
|
||||
}
|
||||
for i, lk := range table.LookupList.Lookups {
|
||||
subtables, err := lk.AsGPOSLookups()
|
||||
if err != nil {
|
||||
return GPOS{}, err
|
||||
}
|
||||
for j, subtable := range subtables {
|
||||
// start by resolving extension
|
||||
if ext, isExt := subtable.(tables.ExtensionPos); isExt {
|
||||
subtables[j], err = ext.Resolve()
|
||||
if err != nil {
|
||||
return GPOS{}, err
|
||||
}
|
||||
}
|
||||
|
||||
// sanitize each lookup
|
||||
switch subtable := subtable.(type) {
|
||||
case tables.SinglePos:
|
||||
err = subtable.Sanitize()
|
||||
case tables.PairPos:
|
||||
err = subtable.Sanitize()
|
||||
case tables.MarkBasePos:
|
||||
err = subtable.Sanitize()
|
||||
case tables.MarkLigPos:
|
||||
err = subtable.Sanitize()
|
||||
case tables.ContextualPos:
|
||||
err = subtable.Sanitize(uint16(len(out.Lookups)))
|
||||
}
|
||||
if err != nil {
|
||||
return GPOS{}, err
|
||||
}
|
||||
}
|
||||
out.Lookups[i] = GPOSLookup{
|
||||
LookupOptions: LookupOptions{
|
||||
Flag: lk.LookupFlag,
|
||||
MarkFilteringSet: lk.MarkFilteringSet,
|
||||
},
|
||||
Subtables: subtables,
|
||||
}
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
+359
@@ -0,0 +1,359 @@
|
||||
// SPDX-License-Identifier: Unlicense OR BSD-3-Clause
|
||||
|
||||
package font
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/go-text/typesetting/font/opentype/tables"
|
||||
)
|
||||
|
||||
const numBuiltInPostNames = len(builtInPostNames)
|
||||
|
||||
// names is the built-in post table names listed at
|
||||
// https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6post.html
|
||||
var builtInPostNames = [...]string{
|
||||
".notdef",
|
||||
".null",
|
||||
"nonmarkingreturn",
|
||||
"space",
|
||||
"exclam",
|
||||
"quotedbl",
|
||||
"numbersign",
|
||||
"dollar",
|
||||
"percent",
|
||||
"ampersand",
|
||||
"quotesingle",
|
||||
"parenleft",
|
||||
"parenright",
|
||||
"asterisk",
|
||||
"plus",
|
||||
"comma",
|
||||
"hyphen",
|
||||
"period",
|
||||
"slash",
|
||||
"zero",
|
||||
"one",
|
||||
"two",
|
||||
"three",
|
||||
"four",
|
||||
"five",
|
||||
"six",
|
||||
"seven",
|
||||
"eight",
|
||||
"nine",
|
||||
"colon",
|
||||
"semicolon",
|
||||
"less",
|
||||
"equal",
|
||||
"greater",
|
||||
"question",
|
||||
"at",
|
||||
"A",
|
||||
"B",
|
||||
"C",
|
||||
"D",
|
||||
"E",
|
||||
"F",
|
||||
"G",
|
||||
"H",
|
||||
"I",
|
||||
"J",
|
||||
"K",
|
||||
"L",
|
||||
"M",
|
||||
"N",
|
||||
"O",
|
||||
"P",
|
||||
"Q",
|
||||
"R",
|
||||
"S",
|
||||
"T",
|
||||
"U",
|
||||
"V",
|
||||
"W",
|
||||
"X",
|
||||
"Y",
|
||||
"Z",
|
||||
"bracketleft",
|
||||
"backslash",
|
||||
"bracketright",
|
||||
"asciicircum",
|
||||
"underscore",
|
||||
"grave",
|
||||
"a",
|
||||
"b",
|
||||
"c",
|
||||
"d",
|
||||
"e",
|
||||
"f",
|
||||
"g",
|
||||
"h",
|
||||
"i",
|
||||
"j",
|
||||
"k",
|
||||
"l",
|
||||
"m",
|
||||
"n",
|
||||
"o",
|
||||
"p",
|
||||
"q",
|
||||
"r",
|
||||
"s",
|
||||
"t",
|
||||
"u",
|
||||
"v",
|
||||
"w",
|
||||
"x",
|
||||
"y",
|
||||
"z",
|
||||
"braceleft",
|
||||
"bar",
|
||||
"braceright",
|
||||
"asciitilde",
|
||||
"Adieresis",
|
||||
"Aring",
|
||||
"Ccedilla",
|
||||
"Eacute",
|
||||
"Ntilde",
|
||||
"Odieresis",
|
||||
"Udieresis",
|
||||
"aacute",
|
||||
"agrave",
|
||||
"acircumflex",
|
||||
"adieresis",
|
||||
"atilde",
|
||||
"aring",
|
||||
"ccedilla",
|
||||
"eacute",
|
||||
"egrave",
|
||||
"ecircumflex",
|
||||
"edieresis",
|
||||
"iacute",
|
||||
"igrave",
|
||||
"icircumflex",
|
||||
"idieresis",
|
||||
"ntilde",
|
||||
"oacute",
|
||||
"ograve",
|
||||
"ocircumflex",
|
||||
"odieresis",
|
||||
"otilde",
|
||||
"uacute",
|
||||
"ugrave",
|
||||
"ucircumflex",
|
||||
"udieresis",
|
||||
"dagger",
|
||||
"degree",
|
||||
"cent",
|
||||
"sterling",
|
||||
"section",
|
||||
"bullet",
|
||||
"paragraph",
|
||||
"germandbls",
|
||||
"registered",
|
||||
"copyright",
|
||||
"trademark",
|
||||
"acute",
|
||||
"dieresis",
|
||||
"notequal",
|
||||
"AE",
|
||||
"Oslash",
|
||||
"infinity",
|
||||
"plusminus",
|
||||
"lessequal",
|
||||
"greaterequal",
|
||||
"yen",
|
||||
"mu",
|
||||
"partialdiff",
|
||||
"summation",
|
||||
"product",
|
||||
"pi",
|
||||
"integral",
|
||||
"ordfeminine",
|
||||
"ordmasculine",
|
||||
"Omega",
|
||||
"ae",
|
||||
"oslash",
|
||||
"questiondown",
|
||||
"exclamdown",
|
||||
"logicalnot",
|
||||
"radical",
|
||||
"florin",
|
||||
"approxequal",
|
||||
"Delta",
|
||||
"guillemotleft",
|
||||
"guillemotright",
|
||||
"ellipsis",
|
||||
"nonbreakingspace",
|
||||
"Agrave",
|
||||
"Atilde",
|
||||
"Otilde",
|
||||
"OE",
|
||||
"oe",
|
||||
"endash",
|
||||
"emdash",
|
||||
"quotedblleft",
|
||||
"quotedblright",
|
||||
"quoteleft",
|
||||
"quoteright",
|
||||
"divide",
|
||||
"lozenge",
|
||||
"ydieresis",
|
||||
"Ydieresis",
|
||||
"fraction",
|
||||
"currency",
|
||||
"guilsinglleft",
|
||||
"guilsinglright",
|
||||
"fi",
|
||||
"fl",
|
||||
"daggerdbl",
|
||||
"periodcentered",
|
||||
"quotesinglbase",
|
||||
"quotedblbase",
|
||||
"perthousand",
|
||||
"Acircumflex",
|
||||
"Ecircumflex",
|
||||
"Aacute",
|
||||
"Edieresis",
|
||||
"Egrave",
|
||||
"Iacute",
|
||||
"Icircumflex",
|
||||
"Idieresis",
|
||||
"Igrave",
|
||||
"Oacute",
|
||||
"Ocircumflex",
|
||||
"apple",
|
||||
"Ograve",
|
||||
"Uacute",
|
||||
"Ucircumflex",
|
||||
"Ugrave",
|
||||
"dotlessi",
|
||||
"circumflex",
|
||||
"tilde",
|
||||
"macron",
|
||||
"breve",
|
||||
"dotaccent",
|
||||
"ring",
|
||||
"cedilla",
|
||||
"hungarumlaut",
|
||||
"ogonek",
|
||||
"caron",
|
||||
"Lslash",
|
||||
"lslash",
|
||||
"Scaron",
|
||||
"scaron",
|
||||
"Zcaron",
|
||||
"zcaron",
|
||||
"brokenbar",
|
||||
"Eth",
|
||||
"eth",
|
||||
"Yacute",
|
||||
"yacute",
|
||||
"Thorn",
|
||||
"thorn",
|
||||
"minus",
|
||||
"multiply",
|
||||
"onesuperior",
|
||||
"twosuperior",
|
||||
"threesuperior",
|
||||
"onehalf",
|
||||
"onequarter",
|
||||
"threequarters",
|
||||
"franc",
|
||||
"Gbreve",
|
||||
"gbreve",
|
||||
"Idotaccent",
|
||||
"Scedilla",
|
||||
"scedilla",
|
||||
"Cacute",
|
||||
"cacute",
|
||||
"Ccaron",
|
||||
"ccaron",
|
||||
"dcroat",
|
||||
}
|
||||
|
||||
type post struct {
|
||||
// suggested distance of the top of the
|
||||
// underline from the baseline (negative values indicate below baseline).
|
||||
underlinePosition float32
|
||||
// suggested values for the underline thickness.
|
||||
underlineThickness float32
|
||||
|
||||
names postGlyphNames
|
||||
|
||||
isFixedPitch bool
|
||||
}
|
||||
|
||||
func newPost(pst tables.Post) (post, error) {
|
||||
out := post{
|
||||
underlinePosition: float32(pst.UnderlinePosition),
|
||||
underlineThickness: float32(pst.UnderlineThickness),
|
||||
isFixedPitch: pst.IsFixedPitch != 0,
|
||||
}
|
||||
switch names := pst.Names.(type) {
|
||||
case tables.PostNames10:
|
||||
out.names = postNames10or30{}
|
||||
case tables.PostNames20:
|
||||
n := postNames20(names)
|
||||
if err := n.sanitize(); err != nil {
|
||||
return out, err
|
||||
}
|
||||
out.names = n
|
||||
case tables.PostNames30:
|
||||
// no-op, do not use the post name tables
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// postGlyphNames stores the names of a 'post' table.
|
||||
type postGlyphNames interface {
|
||||
// GlyphName return the postscript name of a
|
||||
// glyph, or an empty string if it not found
|
||||
glyphName(x GID) string
|
||||
}
|
||||
|
||||
type postNames10or30 struct{}
|
||||
|
||||
func (p postNames10or30) glyphName(x GID) string {
|
||||
if int(x) >= numBuiltInPostNames {
|
||||
return ""
|
||||
}
|
||||
// https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6post.html
|
||||
return builtInPostNames[x]
|
||||
}
|
||||
|
||||
type postNames20 tables.PostNames20
|
||||
|
||||
func (p postNames20) glyphName(x GID) string {
|
||||
if int(x) >= len(p.GlyphNameIndexes) {
|
||||
return ""
|
||||
}
|
||||
u := int(p.GlyphNameIndexes[x])
|
||||
if u < numBuiltInPostNames {
|
||||
return builtInPostNames[u]
|
||||
}
|
||||
u -= numBuiltInPostNames
|
||||
return p.Strings[u]
|
||||
}
|
||||
|
||||
// check that all the indexes are valid
|
||||
func (p postNames20) sanitize() error {
|
||||
var maxIndex uint16
|
||||
// find the maximum
|
||||
for _, u := range p.GlyphNameIndexes {
|
||||
// https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6post.html
|
||||
// says that "32768 through 65535 are reserved for future use".
|
||||
if u > 32767 {
|
||||
return errors.New("invalid index in Postscript names table format 20")
|
||||
}
|
||||
if u > maxIndex {
|
||||
maxIndex = u
|
||||
}
|
||||
}
|
||||
|
||||
if int(maxIndex) >= numBuiltInPostNames && len(p.Strings) < (int(maxIndex)-numBuiltInPostNames) {
|
||||
return errors.New("invalid index in Postscript names table format 20")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
+479
@@ -0,0 +1,479 @@
|
||||
// SPDX-License-Identifier: Unlicense OR BSD-3-Clause
|
||||
|
||||
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
|
||||
)
|
||||
|
||||
// GlyphData describe how to draw a glyph.
|
||||
// It is either an GlyphOutline, GlyphSVG or GlyphBitmap.
|
||||
type GlyphData interface {
|
||||
isGlyphData()
|
||||
}
|
||||
|
||||
func (GlyphOutline) isGlyphData() {}
|
||||
func (GlyphSVG) isGlyphData() {}
|
||||
func (GlyphBitmap) isGlyphData() {}
|
||||
func (GlyphColor) isGlyphData() {}
|
||||
|
||||
// GlyphOutline exposes the path to draw for
|
||||
// vector glyph.
|
||||
// Coordinates are expressed in fonts units.
|
||||
type GlyphOutline struct {
|
||||
Segments []Segment
|
||||
}
|
||||
|
||||
// Sideways updates the coordinates of the outline by applying
|
||||
// a 90° clockwise rotation, and adding [yOffset] afterwards.
|
||||
//
|
||||
// When used for vertical text, pass
|
||||
// -Glyph.YOffset, converted in font units, as [yOffset]
|
||||
// (a positive value to lift the glyph up).
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
// GlyphSVG is an SVG description for the glyph,
|
||||
// as found in Opentype SVG table.
|
||||
type GlyphSVG struct {
|
||||
// The SVG image content, decompressed if needed.
|
||||
// The actual glyph description is an SVG element
|
||||
// with id="glyph<GID>" (as in id="glyph12"),
|
||||
// and several glyphs may share the same Source
|
||||
Source []byte
|
||||
|
||||
// According to the specification, a fallback outline
|
||||
// should be specified for each SVG glyphs
|
||||
Outline GlyphOutline
|
||||
}
|
||||
|
||||
type GlyphBitmap struct {
|
||||
// The actual image content, whose interpretation depends
|
||||
// on the Format field.
|
||||
Data []byte
|
||||
Format BitmapFormat
|
||||
Width, Height int // number of columns and rows
|
||||
|
||||
// Outline may be specified to be drawn with bitmap
|
||||
Outline *GlyphOutline
|
||||
}
|
||||
|
||||
// BitmapFormat identifies the format on the glyph
|
||||
// raw data. Across the various font files, many formats
|
||||
// may be encountered : black and white bitmaps, PNG, TIFF, JPG.
|
||||
type BitmapFormat uint8
|
||||
|
||||
const (
|
||||
_ BitmapFormat = iota
|
||||
// The [GlyphBitmap.Data] slice stores a black or white (0/1)
|
||||
// bit image, whose length L satisfies
|
||||
// L * 8 >= [GlyphBitmap.Width] * [GlyphBitmap.Height]
|
||||
BlackAndWhite
|
||||
// The [GlyphBitmap.Data] slice stores a PNG encoded image
|
||||
PNG
|
||||
// The [GlyphBitmap.Data] slice stores a JPG encoded image
|
||||
JPG
|
||||
// The [GlyphBitmap.Data] slice stores a TIFF encoded image
|
||||
TIFF
|
||||
)
|
||||
|
||||
// BitmapSize expose the size of bitmap glyphs.
|
||||
// One font may contain several sizes.
|
||||
type BitmapSize struct {
|
||||
Height, Width uint16
|
||||
XPpem, YPpem uint16
|
||||
}
|
||||
|
||||
// GlyphColor describe a colored glyph, as found in
|
||||
// COLR tables
|
||||
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: // PNG
|
||||
out.Format = PNG
|
||||
case 2, 5:
|
||||
out.Format = BlackAndWhite
|
||||
// ensure data length
|
||||
L := out.Width * out.Height // in bits
|
||||
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
|
||||
}
|
||||
|
||||
// un-compress if needed
|
||||
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
|
||||
}
|
||||
|
||||
// this file converts from font format for glyph outlines to
|
||||
// segments that rasterizer will consume
|
||||
//
|
||||
// adapted from snft/truetype.go
|
||||
|
||||
func midPoint(p, q SegmentPoint) SegmentPoint {
|
||||
return SegmentPoint{
|
||||
X: (p.X + q.X) / 2,
|
||||
Y: (p.Y + q.Y) / 2,
|
||||
}
|
||||
}
|
||||
|
||||
// build the segments from the resolved contour points
|
||||
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 {
|
||||
// closing the contour
|
||||
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)
|
||||
}
|
||||
|
||||
// apply variation when needed
|
||||
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
|
||||
}
|
||||
|
||||
// -------------------------- Public API --------------------------
|
||||
|
||||
// BitmapSizes returns the size of bitmap glyphs present in the font.
|
||||
func (font *Font) BitmapSizes() []BitmapSize {
|
||||
upem := font.head.UnitsPerEm
|
||||
|
||||
avgWidth := font.os2.xAvgCharWidth
|
||||
|
||||
// handle invalid head/os2 tables
|
||||
if upem == 0 || font.os2.version == 0xFFFF {
|
||||
avgWidth = 1
|
||||
upem = 1
|
||||
}
|
||||
|
||||
// adapted from freetype tt_face_load_sbit
|
||||
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
|
||||
}
|
||||
|
||||
// GlyphData returns the glyph content for [gid], or nil if
|
||||
// not found.
|
||||
//
|
||||
// See also the various GlyphDataXXX methods, for more control
|
||||
// on the types of glyphs loaded.
|
||||
func (f *Face) GlyphData(gid GID) GlyphData {
|
||||
g := gID(gid)
|
||||
|
||||
// since outline may be specified for SVG and bitmaps, check it at the end
|
||||
// check color first over SVG, since it is more optimized format
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// GlyphDataOutline looks for glyph data in 'glyf', 'CFF ' and 'CFF2' tables.
|
||||
//
|
||||
// It is a bit faster than calling [Face.GlyphData] and may be used for instance
|
||||
// when rendering colored glyphs (from the 'COLR' table).
|
||||
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
|
||||
}
|
||||
|
||||
// GlyphDataSVG looks for glyph data in the 'SVG ' table.
|
||||
func (f *Face) GlyphDataSVG(gid gID) (GlyphSVG, bool) {
|
||||
outS, ok := f.svg.glyphData(gid)
|
||||
if !ok {
|
||||
return GlyphSVG{}, false
|
||||
}
|
||||
// Spec :
|
||||
// For every SVG glyph description, there must be a corresponding TrueType,
|
||||
// CFF or CFF2 glyph description in the font.
|
||||
outS.Outline, _ = f.GlyphDataOutline(gid)
|
||||
return outS, true
|
||||
}
|
||||
|
||||
// GlyphDataColor looks for glyph data in the 'COLR' table.
|
||||
func (f *Face) GlyphDataColor(gid gID) (GlyphColor, bool) {
|
||||
v, ok := f.COLR.Search(gid)
|
||||
return GlyphColor{v}, ok
|
||||
}
|
||||
|
||||
// GlyphDataBitmap looks for glyph data in the 'sbix', 'CBDT', 'EBDT' and 'BDAT' tables.
|
||||
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
|
||||
}
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
// SPDX-License-Identifier: Unlicense OR BSD-3-Clause
|
||||
|
||||
package font
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/go-text/typesetting/font/opentype/tables"
|
||||
)
|
||||
|
||||
type svg []svgDocument
|
||||
|
||||
func newSvg(table tables.SVG) (svg, error) {
|
||||
rawData := table.SVGDocumentList.SVGRawData
|
||||
out := make(svg, len(table.SVGDocumentList.DocumentRecords))
|
||||
for i, rec := range table.SVGDocumentList.DocumentRecords {
|
||||
start, end := rec.SvgDocOffset, rec.SvgDocOffset+tables.Offset32(rec.SvgDocLength)
|
||||
if len(rawData) < int(end) {
|
||||
return nil, fmt.Errorf("invalid svg table (EOF: expected %d, got %d)", end, len(rawData))
|
||||
}
|
||||
out[i] = svgDocument{
|
||||
first: rec.StartGlyphID,
|
||||
last: rec.EndGlyphID,
|
||||
svg: rawData[start:end],
|
||||
}
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
type svgDocument struct {
|
||||
// svg document
|
||||
// each glyph description must be written
|
||||
// in an element with id=glyphXXX
|
||||
svg []byte
|
||||
first gID // The first glyph ID in the range described by this index entry.
|
||||
last gID // The last glyph ID in the range described by this index entry. Must be >= startGlyphID.
|
||||
}
|
||||
|
||||
// rawGlyphData returns the SVG document for [gid], or false.
|
||||
func (s svg) rawGlyphData(gid gID) ([]byte, bool) {
|
||||
// binary search
|
||||
for i, j := 0, len(s); i < j; {
|
||||
h := i + (j-i)/2
|
||||
entry := s[h]
|
||||
if gid < entry.first {
|
||||
j = h
|
||||
} else if entry.last < gid {
|
||||
i = h + 1
|
||||
} else {
|
||||
return entry.svg, true
|
||||
}
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
+596
@@ -0,0 +1,596 @@
|
||||
// SPDX-License-Identifier: Unlicense OR BSD-3-Clause
|
||||
|
||||
package font
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
|
||||
"github.com/go-text/typesetting/font/opentype/tables"
|
||||
)
|
||||
|
||||
// axis records
|
||||
type fvar []tables.VariationAxisRecord
|
||||
|
||||
func newFvar(table tables.Fvar) fvar { return table.FvarRecords.Axis }
|
||||
|
||||
type mvar struct {
|
||||
store tables.ItemVarStore
|
||||
values []tables.VarValueRecord
|
||||
}
|
||||
|
||||
func newMvar(mv tables.MVAR, axisCount int) (mvar, error) {
|
||||
if got := mv.ItemVariationStore.AxisCount(); got != axisCount {
|
||||
return mvar{}, fmt.Errorf("mvar: invalid number of axis (%d != %d)", got, axisCount)
|
||||
}
|
||||
return mvar{mv.ItemVariationStore, mv.ValueRecords}, nil
|
||||
}
|
||||
|
||||
// return 0 if `tag` is not found
|
||||
func (mv mvar) getVar(tag Tag, coords []VarCoord) float32 {
|
||||
// binary search
|
||||
for i, j := 0, len(mv.values); i < j; {
|
||||
h := i + (j-i)/2
|
||||
entry := mv.values[h]
|
||||
if tag < entry.ValueTag {
|
||||
j = h
|
||||
} else if entry.ValueTag < tag {
|
||||
i = h + 1
|
||||
} else {
|
||||
return mv.store.GetDelta(entry.Index, coords)
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// ---------------------------------- gvar ----------------------------------
|
||||
|
||||
type gvar struct {
|
||||
sharedTuples [][]VarCoord // with size tupleCount x axisCount
|
||||
variations [][]tupleVariation // with length glyphCount
|
||||
sharedTupleActiveIdx []int // with length tupleCount
|
||||
}
|
||||
|
||||
func newGvar(table tables.Gvar, glyf tables.Glyf) (gvar, error) {
|
||||
if len(table.GlyphVariationDatas) != len(glyf) {
|
||||
return gvar{}, fmt.Errorf("invalid 'gvar' table: mismatch in glyphs count")
|
||||
}
|
||||
|
||||
out := gvar{
|
||||
sharedTuples: make([][]VarCoord, len(table.SharedTuples.SharedTuples)),
|
||||
variations: make([][]tupleVariation, len(table.GlyphVariationDatas)),
|
||||
sharedTupleActiveIdx: make([]int, len(table.SharedTuples.SharedTuples)),
|
||||
}
|
||||
for i, ts := range table.SharedTuples.SharedTuples {
|
||||
out.sharedTuples[i] = ts.Values
|
||||
}
|
||||
for i, vs := range table.GlyphVariationDatas {
|
||||
tvs := make([]tupleVariation, len(vs.TupleVariationHeaders))
|
||||
for j, header := range vs.TupleVariationHeaders {
|
||||
tvs[j].TupleVariationHeader = header
|
||||
}
|
||||
|
||||
pointsNumberCountAll := pointNumbersCount(glyf[i]) + phantomCount
|
||||
err := parseGlyphVariationSerializedData(vs.SerializedData,
|
||||
vs.HasSharedPointNumbers(), pointsNumberCountAll, false, tvs)
|
||||
if err != nil {
|
||||
return out, err
|
||||
}
|
||||
out.variations[i] = tvs
|
||||
}
|
||||
|
||||
// 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 i, tuple := range out.sharedTuples {
|
||||
idx := -1
|
||||
for j, peak := range tuple {
|
||||
if peak != 0 {
|
||||
if idx != -1 { // two peaks or more, do not cache
|
||||
idx = -1
|
||||
break
|
||||
}
|
||||
idx = j
|
||||
}
|
||||
}
|
||||
out.sharedTupleActiveIdx[i] = idx
|
||||
}
|
||||
|
||||
return out, 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 tupleCount
|
||||
func (t tupleVariation) calculateScalar(coords []VarCoord, sharedTuples [][]VarCoord, sharedTupleActiveIdx []int) float32 {
|
||||
startIdx, endIdx := 0, len(coords)
|
||||
peakTuple := t.PeakTuple.Values
|
||||
if peakTuple == nil { // no peak specified -> use shared tuple
|
||||
index := t.Index()
|
||||
if int(index) >= len(sharedTuples) { // should not happend
|
||||
return 0.
|
||||
}
|
||||
peakTuple = sharedTuples[index]
|
||||
|
||||
// use the cache to restrict the range
|
||||
if v := sharedTupleActiveIdx[index]; v != -1 {
|
||||
startIdx = v
|
||||
endIdx = startIdx + 1
|
||||
}
|
||||
}
|
||||
|
||||
startTuple, endTuple := t.IntermediateTuples[0].Values, t.IntermediateTuples[1].Values
|
||||
hasIntermediate := startTuple != nil
|
||||
|
||||
var scalar float32 = 1.
|
||||
for i := startIdx; i < endIdx; i++ {
|
||||
v, peak := coords[i], peakTuple[i]
|
||||
if peak == 0 || v == peak {
|
||||
continue
|
||||
}
|
||||
|
||||
if hasIntermediate {
|
||||
start := startTuple[i]
|
||||
end := endTuple[i]
|
||||
if start > peak || peak > end || (start < 0 && end > 0 && peak != 0) {
|
||||
continue
|
||||
}
|
||||
if v < start || v > end {
|
||||
return 0.
|
||||
}
|
||||
if v < peak {
|
||||
if peak != start {
|
||||
scalar *= float32(v-start) / float32(peak-start)
|
||||
}
|
||||
} else {
|
||||
if peak != end {
|
||||
scalar *= float32(end-v) / float32(end-peak)
|
||||
}
|
||||
}
|
||||
} else if v == 0 || v < minC(0, peak) || v > maxC(0, peak) {
|
||||
return 0.
|
||||
} else {
|
||||
scalar *= float32(v) / float32(peak)
|
||||
}
|
||||
}
|
||||
return scalar
|
||||
}
|
||||
|
||||
// complete `out`, which contains the parsed tuple headers.
|
||||
// pointNumbersCountAll is used when the tuple variation data provides deltas for all glyph points
|
||||
func parseGlyphVariationSerializedData(data []byte, hasSharedPoints bool, pointNumbersCountAll int, isCvar bool, out []tupleVariation) error {
|
||||
var (
|
||||
sharedPointNumbers []uint16
|
||||
err error
|
||||
)
|
||||
if hasSharedPoints {
|
||||
sharedPointNumbers, data, err = parsePointNumbers(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
for i, h := range out {
|
||||
// adjust for the next iteration
|
||||
if len(data) < int(h.VariationDataSize) {
|
||||
return errors.New("invalid glyph variation serialized data (EOF)")
|
||||
}
|
||||
nextData := data[h.VariationDataSize:]
|
||||
|
||||
// default to shared points
|
||||
privatePointNumbers := sharedPointNumbers
|
||||
if h.HasPrivatePointNumbers() {
|
||||
privatePointNumbers, data, err = parsePointNumbers(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
// the number of point is precised or defaut to all the points
|
||||
pointCount := pointNumbersCountAll
|
||||
if privatePointNumbers != nil {
|
||||
pointCount = len(privatePointNumbers)
|
||||
}
|
||||
|
||||
out[i].pointNumbers = privatePointNumbers
|
||||
|
||||
if !isCvar {
|
||||
pointCount *= 2 // for X and Y
|
||||
}
|
||||
|
||||
out[i].deltas, err = unpackDeltas(data, pointCount)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
data = nextData
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// the returned slice is nil if all glyph points are used
|
||||
func parsePointNumbers(data []byte) ([]uint16, []byte, error) {
|
||||
count, data, err := getPackedPointCount(data)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if count == 0 {
|
||||
return nil, data, nil
|
||||
}
|
||||
|
||||
var lastPoint uint16
|
||||
points := make([]uint16, 0, count) // max value of count is 32767
|
||||
for len(points) < int(count) { // loop through the runs
|
||||
if len(data) == 0 {
|
||||
return nil, nil, errors.New("invalid glyph variation points numbers (EOF)")
|
||||
}
|
||||
control := data[0]
|
||||
is16bit := control&0x80 != 0
|
||||
runLength := int(control&0x7F + 1)
|
||||
if is16bit {
|
||||
pts, err := tables.ParseUint16s(data[1:], runLength)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("invalid glyph variation points numbers: %s", err)
|
||||
}
|
||||
for _, pt := range pts {
|
||||
actualValue := pt + lastPoint
|
||||
points = append(points, actualValue)
|
||||
lastPoint = actualValue
|
||||
}
|
||||
data = data[1+2*runLength:]
|
||||
} else {
|
||||
if len(data) < 1+runLength {
|
||||
return nil, nil, errors.New("invalid glyph variation points numbers (EOF)")
|
||||
}
|
||||
for _, b := range data[1 : 1+runLength] {
|
||||
actualValue := uint16(b) + lastPoint
|
||||
points = append(points, actualValue)
|
||||
lastPoint = actualValue
|
||||
}
|
||||
data = data[1+runLength:]
|
||||
}
|
||||
}
|
||||
|
||||
return points, data, nil
|
||||
}
|
||||
|
||||
// return the remaining data and special case of 00
|
||||
func getPackedPointCount(data []byte) (uint16, []byte, error) {
|
||||
const highOrderBit byte = 1 << 7
|
||||
if len(data) < 1 {
|
||||
return 0, nil, errors.New("invalid glyph variation points numbers (EOF)")
|
||||
}
|
||||
if data[0] == 0 {
|
||||
return 0, data[1:], nil
|
||||
} else if data[0]&highOrderBit == 0 {
|
||||
count := uint16(data[0])
|
||||
return count, data[1:], nil
|
||||
} else {
|
||||
if len(data) < 2 {
|
||||
return 0, nil, errors.New("invalid glyph variation points numbers (EOF)")
|
||||
}
|
||||
count := uint16(data[0]&^highOrderBit)<<8 | uint16(data[1])
|
||||
return count, data[2:], nil
|
||||
}
|
||||
}
|
||||
|
||||
func unpackDeltas(data []byte, pointNumbersCount int) ([]int16, error) {
|
||||
const (
|
||||
deltasAreZero = 0x80
|
||||
deltasAreWords = 0x40
|
||||
deltaRunCountMask = 0x3F
|
||||
)
|
||||
out := make([]int16, pointNumbersCount)
|
||||
nbRead := 0 // number of point read : out[:nbRead] is valid
|
||||
// The data is read until the expected logic count of deltas is obtained.
|
||||
for nbRead < pointNumbersCount {
|
||||
if len(data) == 0 {
|
||||
return nil, errors.New("invalid packed deltas (EOF)")
|
||||
}
|
||||
control := data[0]
|
||||
count := control&deltaRunCountMask + 1
|
||||
if isZero := control&deltasAreZero != 0; isZero {
|
||||
// no additional value to read, just fill with zeros
|
||||
nbRead += int(count)
|
||||
data = data[1:]
|
||||
} else {
|
||||
// we want to fill out[nbRead:nbRead+count-1], that is we must have
|
||||
// nbRead+count-1 < pointNumbersCount, ie
|
||||
// nbRead+count <= pointNumbersCount
|
||||
if got := nbRead + int(count); got > pointNumbersCount {
|
||||
return nil, fmt.Errorf("invalid packed deltas (expected %d point numbers, got %d)", pointNumbersCount, got)
|
||||
}
|
||||
isInt16 := control&deltasAreWords != 0
|
||||
if isInt16 {
|
||||
if len(data) < 1+2*int(count) {
|
||||
return nil, errors.New("invalid packed deltas (EOF)")
|
||||
}
|
||||
for i := byte(0); i < count; i++ { // count < 64 -> no overflow
|
||||
out[nbRead] = int16(binary.BigEndian.Uint16(data[1+2*i:]))
|
||||
nbRead++
|
||||
}
|
||||
data = data[1+2*count:]
|
||||
} else {
|
||||
if len(data) < 1+int(count) {
|
||||
return nil, errors.New("invalid packed deltas (EOF)")
|
||||
}
|
||||
for i := byte(0); i < count; i++ { // count < 64 -> no overflow
|
||||
out[nbRead] = int16(int8(data[1+i]))
|
||||
nbRead++
|
||||
}
|
||||
data = data[1+count:]
|
||||
}
|
||||
}
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// update `points` in place
|
||||
func (gvar gvar) applyDeltasToPoints(glyph gID, coords []VarCoord, points []contourPoint) {
|
||||
// adapted from harfbuzz/src/hb-ot-var-gvar-table.hh
|
||||
const phantomOnly = false
|
||||
|
||||
if int(glyph) >= len(gvar.variations) { // should not happend
|
||||
return
|
||||
}
|
||||
|
||||
// save original points for inferred delta calculation
|
||||
origPoints := append([]contourPoint(nil), points...)
|
||||
// flag is used to indicate referenced point
|
||||
deltas := make([]contourPoint, len(points))
|
||||
|
||||
varData := gvar.variations[glyph]
|
||||
for _, tuple := range varData {
|
||||
scalar := tuple.calculateScalar(coords, gvar.sharedTuples, gvar.sharedTupleActiveIdx)
|
||||
if scalar == 0 {
|
||||
continue
|
||||
}
|
||||
L := len(tuple.deltas)
|
||||
applyToAll := tuple.pointNumbers == nil
|
||||
xDeltas, yDeltas := tuple.deltas[:L/2], tuple.deltas[L/2:]
|
||||
|
||||
// reset the current deltas
|
||||
for i := range deltas {
|
||||
deltas[i] = contourPoint{}
|
||||
}
|
||||
|
||||
for i := range xDeltas {
|
||||
ptIndex := uint16(i)
|
||||
if !applyToAll {
|
||||
ptIndex = tuple.pointNumbers[i]
|
||||
}
|
||||
deltas[ptIndex].isExplicit = true
|
||||
deltas[ptIndex].X += float32(xDeltas[i]) * scalar
|
||||
deltas[ptIndex].Y += float32(yDeltas[i]) * scalar
|
||||
}
|
||||
|
||||
/* infer deltas for unreferenced points */
|
||||
if !applyToAll && !phantomOnly {
|
||||
startPoint, endPoint := 0, 0
|
||||
for {
|
||||
for endPoint < len(points) && !points[endPoint].isEndPoint {
|
||||
endPoint++
|
||||
}
|
||||
if endPoint == len(points) {
|
||||
break
|
||||
}
|
||||
|
||||
// check the number of unreferenced points in a contour.
|
||||
// If no unref points or no ref points, nothing to do.
|
||||
unrefCount := 0
|
||||
for _, p := range deltas[startPoint : endPoint+1] {
|
||||
if !p.isExplicit {
|
||||
unrefCount++
|
||||
}
|
||||
}
|
||||
j := startPoint
|
||||
if unrefCount == 0 || unrefCount > endPoint-startPoint {
|
||||
goto noMoreGaps
|
||||
}
|
||||
|
||||
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 prev, next, i int
|
||||
for {
|
||||
i = j
|
||||
j = nextIndex(i, startPoint, endPoint)
|
||||
if deltas[i].isExplicit && !deltas[j].isExplicit {
|
||||
break
|
||||
}
|
||||
}
|
||||
prev, j = i, i
|
||||
for {
|
||||
i = j
|
||||
j = nextIndex(i, startPoint, endPoint)
|
||||
if !deltas[i].isExplicit && deltas[j].isExplicit {
|
||||
break
|
||||
}
|
||||
}
|
||||
next = j
|
||||
/* Infer deltas for all unref points in the gap between prev and next */
|
||||
i = prev
|
||||
for {
|
||||
i = nextIndex(i, startPoint, endPoint)
|
||||
if i == next {
|
||||
break
|
||||
}
|
||||
deltas[i].X = inferDelta(origPoints[i].X, origPoints[prev].X, origPoints[next].X, deltas[prev].X, deltas[next].X)
|
||||
deltas[i].Y = inferDelta(origPoints[i].Y, origPoints[prev].Y, origPoints[next].Y, deltas[prev].Y, deltas[next].Y)
|
||||
unrefCount--
|
||||
if unrefCount == 0 {
|
||||
goto noMoreGaps
|
||||
}
|
||||
}
|
||||
}
|
||||
noMoreGaps:
|
||||
startPoint = endPoint + 1
|
||||
endPoint = startPoint
|
||||
}
|
||||
}
|
||||
|
||||
// apply specified / inferred deltas to points
|
||||
for i, d := range deltas {
|
||||
points[i].translate(d.X, d.Y)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func nextIndex(i, start, end int) int {
|
||||
if i >= end {
|
||||
return start
|
||||
}
|
||||
return i + 1
|
||||
}
|
||||
|
||||
func inferDelta(targetVal, prevVal, nextVal, prevDelta, nextDelta float32) float32 {
|
||||
if prevVal == nextVal {
|
||||
if prevDelta == nextDelta {
|
||||
return prevDelta
|
||||
}
|
||||
return 0
|
||||
} else if targetVal <= minF(prevVal, nextVal) {
|
||||
if prevVal < nextVal {
|
||||
return prevDelta
|
||||
}
|
||||
return nextDelta
|
||||
} else if targetVal >= maxF(prevVal, nextVal) {
|
||||
if prevVal > nextVal {
|
||||
return prevDelta
|
||||
}
|
||||
return nextDelta
|
||||
}
|
||||
|
||||
// linear interpolation
|
||||
r := (targetVal - prevVal) / (nextVal - prevVal)
|
||||
return prevDelta + r*(nextDelta-prevDelta)
|
||||
}
|
||||
|
||||
func sanitizeGDEF(table tables.GDEF, axisCount int) error {
|
||||
// check axis count
|
||||
if got := table.ItemVarStore.AxisCount(); got != -1 && got != axisCount {
|
||||
return fmt.Errorf("GDEF: invalid number of axis (%d != %d)", axisCount, got)
|
||||
}
|
||||
|
||||
// check LigCarets length
|
||||
if table.LigCaretList.Coverage != nil {
|
||||
expected := table.LigCaretList.Coverage.Len()
|
||||
got := len(table.LigCaretList.LigGlyphs)
|
||||
if expected != got {
|
||||
return fmt.Errorf("GDEF: invalid number of lig gyphs (%d != %d)", expected, got)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ------------------------------------- external API -------------------------------------
|
||||
|
||||
// Variation defines a value for a wanted variation axis.
|
||||
type Variation struct {
|
||||
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 *Face) SetVariations(variations []Variation) {
|
||||
if len(variations) == 0 {
|
||||
face.SetCoords(nil)
|
||||
return
|
||||
}
|
||||
|
||||
fv := face.Font.fvar
|
||||
if len(fv) == 0 { // the font is not variable...
|
||||
face.SetCoords(nil)
|
||||
return
|
||||
}
|
||||
|
||||
designCoords := fv.getDesignCoordsDefault(variations)
|
||||
|
||||
face.SetCoords(face.NormalizeVariations(designCoords))
|
||||
}
|
||||
|
||||
// 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 (fv fvar) getDesignCoordsDefault(variations []Variation) []float32 {
|
||||
designCoords := make([]float32, len(fv))
|
||||
// start with default values
|
||||
for i, axis := range fv {
|
||||
designCoords[i] = axis.Default
|
||||
}
|
||||
|
||||
fv.getDesignCoords(variations, designCoords)
|
||||
|
||||
return designCoords
|
||||
}
|
||||
|
||||
// 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 (fv fvar) getDesignCoords(variations []Variation, designCoords []float32) {
|
||||
for _, variation := range variations {
|
||||
// allow for multiple axis with the same tag
|
||||
for index, axis := range fv {
|
||||
if axis.Tag == variation.Tag {
|
||||
designCoords[index] = variation.Value
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// normalize based on the [min,def,max] values for the axis to be [-1,0,1].
|
||||
func (fv fvar) normalizeCoordinates(coords []float32) []VarCoord {
|
||||
normalized := make([]VarCoord, len(coords))
|
||||
for i, a := range fv {
|
||||
coord := coords[i]
|
||||
|
||||
// out of range: clamping
|
||||
if coord > a.Maximum {
|
||||
coord = a.Maximum
|
||||
} else if coord < a.Minimum {
|
||||
coord = a.Minimum
|
||||
}
|
||||
|
||||
if coord < a.Default {
|
||||
coord = -(coord - a.Default) / (a.Minimum - a.Default)
|
||||
} else if coord > a.Default {
|
||||
coord = (coord - a.Default) / (a.Maximum - a.Default)
|
||||
} else {
|
||||
coord = 0
|
||||
}
|
||||
|
||||
normalized[i] = VarCoord(math.Round(float64(coord * 16384))) // 1 << 14
|
||||
}
|
||||
return normalized
|
||||
}
|
||||
|
||||
// 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 (f *Font) NormalizeVariations(coords []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.
|
||||
normalized := f.fvar.normalizeCoordinates(coords)
|
||||
|
||||
// now applying 'avar'
|
||||
for i, av := range f.avar.AxisSegmentMaps {
|
||||
normalized[i] = av.Map(normalized[i])
|
||||
}
|
||||
|
||||
return normalized
|
||||
}
|
||||
+245
@@ -0,0 +1,245 @@
|
||||
// SPDX-License-Identifier: Unlicense OR BSD-3-Clause
|
||||
|
||||
package fontscan
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// fcVars captures the environment configuration that determines how fontconfig resolves configuration
|
||||
// files. It can be populated from the environment by [fcVarsFromEnv], but is decoupled from the environment
|
||||
// for ease of testing.
|
||||
type fcVars struct {
|
||||
// xdgDataHome is the location of the user's data files, extracted from $XDG_DATA_HOME.
|
||||
xdgDataHome string
|
||||
// xdgDataHome is the location of the user's config files, extracted from $XDG_CONFIG_HOME.
|
||||
xdgConfigHome string
|
||||
// userHome is the home directory of the current user, resolved from $HOME.
|
||||
userHome string
|
||||
// configFile is the name of the configuration file, extracted from $FONTCONFIG_FILE.
|
||||
configFile string
|
||||
// paths is the list of configuration paths, extracted from $FONTCONFIG_PATH
|
||||
paths []string
|
||||
// sysroot is the root directory of the fontconfig system logically. It is prepended
|
||||
// to all other paths, and is usually empty.
|
||||
sysroot string
|
||||
}
|
||||
|
||||
func fcVarsFromEnv() fcVars {
|
||||
home := os.Getenv("HOME")
|
||||
return fcVars{
|
||||
xdgDataHome: getEnvWithDefault("XDG_DATA_HOME", filepath.Join(home, ".local", "share")),
|
||||
xdgConfigHome: getEnvWithDefault("XDG_CONFIG_HOME", filepath.Join(home, ".config")),
|
||||
configFile: getEnvWithDefault("FONTCONFIG_FILE", "fonts.conf"),
|
||||
paths: filepath.SplitList(getEnvWithDefault("$FONTCONFIG_PATH", "/etc/fonts")),
|
||||
sysroot: os.Getenv("FONTCONFIG_SYSROOT"),
|
||||
userHome: home,
|
||||
}
|
||||
}
|
||||
|
||||
// resolveRoot returns the path of the root fontconfig file according to the fcVars.
|
||||
func (f fcVars) resolveRoot(logger Logger) string {
|
||||
return f.resolvePath(logger, f.configFile)
|
||||
}
|
||||
|
||||
// resolvePath applies fontconfig's heuristics for finding a path referenced within its config.
|
||||
func (f fcVars) resolvePath(logger Logger, path string) string {
|
||||
hasSysroot := len(f.sysroot) > 0
|
||||
if filepath.IsAbs(path) {
|
||||
if hasSysroot && !strings.HasPrefix(path, f.sysroot) {
|
||||
path = filepath.Join(f.sysroot, path)
|
||||
}
|
||||
return path
|
||||
}
|
||||
if strings.HasPrefix(path, "~") {
|
||||
path = filepath.Join(f.userHome, strings.TrimPrefix(path, "~"))
|
||||
if hasSysroot {
|
||||
path = filepath.Join(f.sysroot, path)
|
||||
}
|
||||
return path
|
||||
}
|
||||
for _, p := range f.paths {
|
||||
candidate := filepath.Join(p, path)
|
||||
if hasSysroot {
|
||||
candidate = filepath.Join(f.sysroot, candidate)
|
||||
}
|
||||
if _, err := os.Stat(candidate); err != nil {
|
||||
continue
|
||||
}
|
||||
return candidate
|
||||
}
|
||||
logger.Printf("fontconfig referenced path %q, but it could not be resolved to a real path", path)
|
||||
return ""
|
||||
}
|
||||
|
||||
func getEnvWithDefault(envVar string, defaultVal string) string {
|
||||
val, ok := os.LookupEnv(envVar)
|
||||
if !ok {
|
||||
return defaultVal
|
||||
}
|
||||
return val
|
||||
}
|
||||
|
||||
const (
|
||||
_ = iota
|
||||
fcDir
|
||||
fcInclude
|
||||
)
|
||||
|
||||
// fcDirective is either a <dir> or a <include> element,
|
||||
// as indicated by [kind]
|
||||
type fcDirective struct {
|
||||
dir struct {
|
||||
Dir string `xml:",chardata"`
|
||||
Prefix string `xml:"prefix,attr"`
|
||||
}
|
||||
include struct {
|
||||
Include string `xml:",chardata"`
|
||||
IgnoreMissing string `xml:"ignore_missing,attr"`
|
||||
Prefix string `xml:"prefix,attr"`
|
||||
}
|
||||
kind uint8
|
||||
}
|
||||
|
||||
func (directive *fcDirective) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
|
||||
switch start.Name.Local {
|
||||
case "dir":
|
||||
directive.kind = fcDir
|
||||
return d.DecodeElement(&directive.dir, &start)
|
||||
case "include":
|
||||
directive.kind = fcInclude
|
||||
return d.DecodeElement(&directive.include, &start)
|
||||
default:
|
||||
// ignore the element
|
||||
return d.Skip()
|
||||
}
|
||||
}
|
||||
|
||||
// parseFcFile opens and process a FontConfig config file,
|
||||
// returning the font directories to scan and the (optionnal)
|
||||
// supplementary config files (or directories) to include.
|
||||
// The file parameter is expected to already be resolved by
|
||||
// resolvePath().
|
||||
func (fc fcVars) parseFcFile(logger Logger, file, currentWorkingDir string) (fontDirs, includes []string, _ error) {
|
||||
f, err := os.Open(file)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("opening fontconfig config file: %s", err)
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
var config struct {
|
||||
Fontconfig []fcDirective `xml:",any"`
|
||||
}
|
||||
err = xml.NewDecoder(f).Decode(&config)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("parsing fontconfig config file: %s", err)
|
||||
}
|
||||
|
||||
// post-process : handle "prefix" attr and use absolute path
|
||||
for _, item := range config.Fontconfig {
|
||||
switch item.kind {
|
||||
case fcDir:
|
||||
dir := item.dir.Dir
|
||||
switch item.dir.Prefix {
|
||||
case "default", "cwd":
|
||||
dir = filepath.Join(currentWorkingDir, dir)
|
||||
case "relative":
|
||||
dir = filepath.Join(filepath.Dir(file), dir)
|
||||
case "xdg":
|
||||
dir = filepath.Join(fc.xdgDataHome, dir)
|
||||
}
|
||||
fontDirs = append(fontDirs, dir)
|
||||
case fcInclude:
|
||||
include := item.include.Include
|
||||
if item.include.Prefix == "xdg" {
|
||||
include = filepath.Join(fc.xdgConfigHome, include)
|
||||
}
|
||||
include = fc.resolvePath(logger, include)
|
||||
if len(include) > 0 {
|
||||
includes = append(includes, include)
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// parseFcDir processes all the files in [dir] matching the [09]*.conf pattern
|
||||
// seen is updated with the processed fontconfig files. The dir parameter is
|
||||
// expected to already be resolved by resolvePath.
|
||||
func (fc fcVars) parseFcDir(logger Logger, dir, currentWorkingDir string, seen map[string]bool) (fontDirs, includes []string, _ error) {
|
||||
entries, err := readDir(dir)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("reading fontconfig config directory: %s", err)
|
||||
}
|
||||
for _, entry := range entries {
|
||||
if entry.IsDir() {
|
||||
continue
|
||||
}
|
||||
|
||||
if name := entry.Name(); strings.HasSuffix(name, ".conf") {
|
||||
c := name[0]
|
||||
if '0' <= c && c <= '9' {
|
||||
file := filepath.Join(dir, name)
|
||||
seen[file] = true
|
||||
fds, incs, err := fc.parseFcFile(logger, file, currentWorkingDir)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
fontDirs = append(fontDirs, fds...)
|
||||
includes = append(includes, incs...)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// parseFcConfig recursively parses the fontconfig config file at [rootConfig]
|
||||
// and its includes, returning the font directories to scan
|
||||
func (fc fcVars) parseFcConfig(logger Logger) ([]string, error) {
|
||||
root := fc.resolveRoot(logger)
|
||||
seen := map[string]bool{root: true}
|
||||
|
||||
cwd, err := os.Getwd()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("processing fontconfig config file: %s", err)
|
||||
}
|
||||
|
||||
// includes is a queue
|
||||
dirs, includes, err := fc.parseFcFile(logger, root, cwd)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for i := 0; i < len(includes); i++ {
|
||||
include := includes[i]
|
||||
if seen[include] {
|
||||
continue
|
||||
}
|
||||
seen[include] = true
|
||||
|
||||
fi, err := os.Stat(include)
|
||||
if err != nil { // gracefully ignore broken includes
|
||||
logger.Printf("missing fontconfig include %s: skipping", include)
|
||||
continue
|
||||
}
|
||||
|
||||
var newDirs, newIncludes []string
|
||||
if fi.IsDir() {
|
||||
newDirs, newIncludes, err = fc.parseFcDir(logger, include, cwd, seen)
|
||||
} else {
|
||||
newDirs, newIncludes, err = fc.parseFcFile(logger, include, cwd)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
dirs = append(dirs, newDirs...)
|
||||
includes = append(includes, newIncludes...)
|
||||
}
|
||||
|
||||
return dirs, nil
|
||||
}
|
||||
+614
@@ -0,0 +1,614 @@
|
||||
package fontscan
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
|
||||
"github.com/go-text/typesetting/font"
|
||||
ot "github.com/go-text/typesetting/font/opentype"
|
||||
"github.com/go-text/typesetting/language"
|
||||
)
|
||||
|
||||
type cacheEntry struct {
|
||||
Location
|
||||
|
||||
Family string
|
||||
font.Aspect
|
||||
}
|
||||
|
||||
// Logger is a type that can log warnings.
|
||||
type Logger interface {
|
||||
Printf(format string, args ...interface{})
|
||||
}
|
||||
|
||||
// The family substitution algorithm is copied from fontconfig
|
||||
// and the match algorithm is inspired from Rust font-kit library
|
||||
|
||||
// SystemFonts loads the system fonts, using an index stored in [cacheDir].
|
||||
// See [FontMap.UseSystemFonts] for more details.
|
||||
//
|
||||
// If [logger] is nil, log.Default() is used.
|
||||
func SystemFonts(logger Logger, cacheDir string) ([]Footprint, error) {
|
||||
if logger == nil {
|
||||
logger = log.New(log.Writer(), "fontscan", log.Flags())
|
||||
}
|
||||
|
||||
// safe for concurrent use; subsequent calls are no-ops
|
||||
err := initSystemFonts(logger, cacheDir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// systemFonts is read-only, so may be used concurrently
|
||||
return systemFonts.flatten(), nil
|
||||
}
|
||||
|
||||
// FontMap provides a mechanism to select a [font.Face] from a font description.
|
||||
// It supports system and user-provided fonts, and implements the CSS font substitutions
|
||||
// rules.
|
||||
//
|
||||
// Note that [FontMap] is NOT safe for concurrent use, but several font maps may coexist
|
||||
// in an application.
|
||||
//
|
||||
// [FontMap] is mainly designed to work with an index built by scanning the system fonts :
|
||||
// see [UseSystemFonts] for more details.
|
||||
type FontMap struct {
|
||||
logger Logger
|
||||
// caches of already loaded faceCache : the two maps are updated conjointly
|
||||
firstFace *font.Face
|
||||
faceCache map[Location]*font.Face
|
||||
metaCache map[*font.Font]cacheEntry
|
||||
|
||||
// the database to query, either loaded from an index
|
||||
// or populated with the [UseSystemFonts], [AddFont], and/or [AddFace] method.
|
||||
database fontSet
|
||||
scriptMap map[language.Script][]int
|
||||
lru runeLRU
|
||||
|
||||
// built holds whether the candidates are populated.
|
||||
built bool
|
||||
// the candidates for the current query, which influences ResolveFace output
|
||||
candidates candidates
|
||||
|
||||
// internal buffers used in [buildCandidates]
|
||||
|
||||
footprintsBuffer scoredFootprints
|
||||
cribleBuffer familyCrible
|
||||
|
||||
query Query // current query
|
||||
script language.Script // current script
|
||||
}
|
||||
|
||||
// NewFontMap return a new font map, which should be filled with the `UseSystemFonts`
|
||||
// or `AddFont` methods. The provided logger will be used to record non-fatal errors
|
||||
// encountered during font loading. If logger is nil, log.Default() is used.
|
||||
func NewFontMap(logger Logger) *FontMap {
|
||||
if logger == nil {
|
||||
logger = log.New(log.Writer(), "fontscan", log.Flags())
|
||||
}
|
||||
fm := &FontMap{
|
||||
logger: logger,
|
||||
faceCache: make(map[Location]*font.Face),
|
||||
metaCache: make(map[*font.Font]cacheEntry),
|
||||
cribleBuffer: make(familyCrible, 150),
|
||||
scriptMap: make(map[language.Script][]int),
|
||||
}
|
||||
fm.lru.maxSize = 4096
|
||||
return fm
|
||||
}
|
||||
|
||||
// SetRuneCacheSize configures the size of the cache powering [FontMap.ResolveFace].
|
||||
// Applications displaying large quantities of text should tune this value to be greater
|
||||
// than the number of unique glyphs they expect to display at one time in order to achieve
|
||||
// optimal performance when segmenting text by face rune coverage.
|
||||
func (fm *FontMap) SetRuneCacheSize(size int) {
|
||||
fm.lru.maxSize = size
|
||||
}
|
||||
|
||||
// UseSystemFonts loads the system fonts and adds them to the font map.
|
||||
//
|
||||
// The first call of this method trigger a rather long scan.
|
||||
// A per-application on-disk cache is used to speed up subsequent initialisations.
|
||||
// Callers can provide an appropriate directory path within which this cache may be
|
||||
// stored. If the empty string is provided, the FontMap will attempt to infer a correct,
|
||||
// platform-dependent cache path.
|
||||
//
|
||||
// NOTE: On Android, callers *must* provide a writable path manually, as it cannot
|
||||
// be inferred without access to the Java runtime environment of the application.
|
||||
//
|
||||
// Multiple font maps may call this method concurrently, without duplicating
|
||||
// the work of finding the system fonts.
|
||||
func (fm *FontMap) UseSystemFonts(cacheDir string) error {
|
||||
// safe for concurrent use; subsequent calls are no-ops
|
||||
err := initSystemFonts(fm.logger, cacheDir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// systemFonts is read-only, so may be used concurrently
|
||||
fm.appendFootprints(systemFonts.flatten()...)
|
||||
|
||||
fm.built = false
|
||||
|
||||
fm.lru.Clear()
|
||||
return nil
|
||||
}
|
||||
|
||||
// appendFootprints adds the provided footprints to the database and maps their script
|
||||
// coverage.
|
||||
func (fm *FontMap) appendFootprints(footprints ...Footprint) {
|
||||
startIdx := len(fm.database)
|
||||
fm.database = append(fm.database, footprints...)
|
||||
// Insert entries into scriptMap for each footprint's covered scripts.
|
||||
for i, fp := range footprints {
|
||||
dbIdx := startIdx + i
|
||||
for _, script := range fp.Scripts {
|
||||
fm.scriptMap[script] = append(fm.scriptMap[script], dbIdx)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// systemFonts is a global index of the system fonts.
|
||||
// initSystemFontsOnce protects the initial assignment,
|
||||
// and `systemFonts` use is then read-only
|
||||
var (
|
||||
systemFonts systemFontsIndex
|
||||
initSystemFontsOnce sync.Once
|
||||
)
|
||||
|
||||
func cacheDir(userProvided string) (string, error) {
|
||||
if userProvided != "" {
|
||||
return userProvided, nil
|
||||
}
|
||||
// load an existing index
|
||||
return platformCacheDir()
|
||||
}
|
||||
|
||||
// initSystemFonts scan the system fonts and update `SystemFonts`.
|
||||
// If the returned error is nil, `SystemFonts` is guaranteed to contain
|
||||
// at least one valid font.Face.
|
||||
// It is protected by sync.Once, and is then safe to use by multiple goroutines.
|
||||
func initSystemFonts(logger Logger, userCacheDir string) error {
|
||||
var err error
|
||||
|
||||
initSystemFontsOnce.Do(func() {
|
||||
const cacheFilePattern = "font_index_v%d.cache"
|
||||
|
||||
// load an existing index
|
||||
var dir string
|
||||
dir, err = cacheDir(userCacheDir)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
cachePath := filepath.Join(dir, fmt.Sprintf(cacheFilePattern, cacheFormatVersion))
|
||||
|
||||
systemFonts, err = refreshSystemFontsIndex(logger, cachePath)
|
||||
})
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func refreshSystemFontsIndex(logger Logger, cachePath string) (systemFontsIndex, error) {
|
||||
fontDirectories, err := DefaultFontDirectories(logger)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("searching font directories: %s", err)
|
||||
}
|
||||
logger.Printf("using system font dirs %q", fontDirectories)
|
||||
|
||||
currentIndex, _ := deserializeIndexFile(cachePath)
|
||||
// if an error occured (the cache file does not exists or is invalid), we start from scratch
|
||||
|
||||
updatedIndex, err := scanFontFootprints(logger, currentIndex, fontDirectories...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("scanning system fonts: %s", err)
|
||||
}
|
||||
|
||||
// since ResolveFace must always return a valid face, we make sure
|
||||
// at least one font exists and is valid.
|
||||
// Otherwise, the font map is useless; this is an extreme case anyway.
|
||||
err = updatedIndex.assertValid()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("loading system fonts: %s", err)
|
||||
}
|
||||
|
||||
// write back the index in the cache file
|
||||
err = updatedIndex.serializeToFile(cachePath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("updating cache: %s", err)
|
||||
}
|
||||
|
||||
return updatedIndex, nil
|
||||
}
|
||||
|
||||
// [AddFont] loads the faces contained in [fontFile] and add them to
|
||||
// the font map.
|
||||
// [fileID] is used as the [Location.File] entry returned by [FontLocation].
|
||||
//
|
||||
// If `familyName` is not empty, it is used as the family name for `fontFile`
|
||||
// instead of the one found in the font file.
|
||||
//
|
||||
// An error is returned if the font resource is not supported.
|
||||
//
|
||||
// The order of calls to [AddFont] and [AddFace] determines relative priority
|
||||
// of manually loaded fonts. See [ResolveFace] for details about when this matters.
|
||||
func (fm *FontMap) AddFont(fontFile font.Resource, fileID, familyName string) error {
|
||||
loaders, err := ot.NewLoaders(fontFile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unsupported font resource: %s", err)
|
||||
}
|
||||
|
||||
// eagerly load the faces
|
||||
faces, err := font.ParseTTC(fontFile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unsupported font resource: %s", err)
|
||||
}
|
||||
|
||||
// by construction of fonts.Loader and fonts.FontDescriptor,
|
||||
// fontDescriptors and face have the same length
|
||||
if len(faces) != len(loaders) {
|
||||
panic("internal error: inconsistent font descriptors and loader")
|
||||
}
|
||||
|
||||
var addedFonts []Footprint
|
||||
for i, fontDesc := range loaders {
|
||||
fp, _, err := newFootprintFromLoader(fontDesc, true, scanBuffer{})
|
||||
// the font won't be usable, just ignore it
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
fp.Location.File = fileID
|
||||
fp.Location.Index = uint16(i)
|
||||
// TODO: for now, we do not handle variable fonts
|
||||
|
||||
if familyName != "" {
|
||||
// give priority to the user provided family
|
||||
fp.Family = font.NormalizeFamily(familyName)
|
||||
}
|
||||
|
||||
addedFonts = append(addedFonts, fp)
|
||||
fm.cache(fp, faces[i])
|
||||
}
|
||||
|
||||
if len(addedFonts) == 0 {
|
||||
return fmt.Errorf("empty font resource %s", fileID)
|
||||
}
|
||||
|
||||
fm.appendFootprints(addedFonts...)
|
||||
|
||||
fm.built = false
|
||||
|
||||
fm.lru.Clear()
|
||||
return nil
|
||||
}
|
||||
|
||||
// [AddFace] inserts an already-loaded font.Face into the FontMap. The caller
|
||||
// is responsible for ensuring that [md] is accurate for the face.
|
||||
//
|
||||
// The order of calls to [AddFont] and [AddFace] determines relative priority
|
||||
// of manually loaded fonts. See [ResolveFace] for details about when this matters.
|
||||
func (fm *FontMap) AddFace(face *font.Face, location Location, md font.Description) {
|
||||
fp := newFootprintFromFont(face.Font, location, md)
|
||||
fm.cache(fp, face)
|
||||
|
||||
fm.appendFootprints(fp)
|
||||
|
||||
fm.built = false
|
||||
fm.lru.Clear()
|
||||
}
|
||||
|
||||
func (fm *FontMap) cache(fp Footprint, face *font.Face) {
|
||||
if fm.firstFace == nil {
|
||||
fm.firstFace = face
|
||||
}
|
||||
fm.faceCache[fp.Location] = face
|
||||
fm.metaCache[face.Font] = cacheEntry{fp.Location, fp.Family, fp.Aspect}
|
||||
}
|
||||
|
||||
// FontLocation returns the origin of the provided font. If the font was not
|
||||
// previously returned from this FontMap by a call to ResolveFace, the zero
|
||||
// value will be returned instead.
|
||||
func (fm *FontMap) FontLocation(ft *font.Font) Location {
|
||||
return fm.metaCache[ft].Location
|
||||
}
|
||||
|
||||
// FontMetadata returns a description of the provided font. If the font was not
|
||||
// previously returned from this FontMap by a call to ResolveFace, the zero
|
||||
// value will be returned instead.
|
||||
//
|
||||
// Note that, for fonts added with [AddFace], it is the user provided description
|
||||
// that is returned, not the one returned by [Font.Describe]
|
||||
func (fm *FontMap) FontMetadata(ft *font.Font) (family string, aspect font.Aspect) {
|
||||
item := fm.metaCache[ft]
|
||||
return item.Family, item.Aspect
|
||||
}
|
||||
|
||||
// FindSystemFont looks for a system font with the given [family],
|
||||
// returning the first match, or false is no one is found.
|
||||
//
|
||||
// User added fonts are ignored, and the [FontMap] must have been
|
||||
// initialized with [UseSystemFonts] or this method will always return false.
|
||||
//
|
||||
// Family names are compared through [font.Normalize].
|
||||
func (fm *FontMap) FindSystemFont(family string) (Location, bool) {
|
||||
family = font.NormalizeFamily(family)
|
||||
for _, footprint := range fm.database {
|
||||
if footprint.isUserProvided {
|
||||
continue
|
||||
}
|
||||
if footprint.Family == family {
|
||||
return footprint.Location, true
|
||||
}
|
||||
}
|
||||
return Location{}, false
|
||||
}
|
||||
|
||||
// FindSystemFonts is the same as FindSystemFont, but returns all matched fonts.
|
||||
func (fm *FontMap) FindSystemFonts(family string) []Location {
|
||||
var locations []Location
|
||||
family = font.NormalizeFamily(family)
|
||||
for _, footprint := range fm.database {
|
||||
if footprint.isUserProvided {
|
||||
continue
|
||||
}
|
||||
if footprint.Family == family {
|
||||
locations = append(locations, footprint.Location)
|
||||
}
|
||||
}
|
||||
|
||||
return locations
|
||||
}
|
||||
|
||||
// SetQuery set the families and aspect required, influencing subsequent
|
||||
// [ResolveFace] calls. See also [SetScript].
|
||||
func (fm *FontMap) SetQuery(query Query) {
|
||||
if len(query.Families) == 0 {
|
||||
query.Families = []string{""}
|
||||
}
|
||||
fm.query = query
|
||||
fm.built = false
|
||||
}
|
||||
|
||||
// SetScript set the script to which the (next) runes passed to [ResolveFace]
|
||||
// belongs, influencing the choice of fallback fonts.
|
||||
func (fm *FontMap) SetScript(s language.Script) {
|
||||
fm.script = s
|
||||
fm.built = false
|
||||
}
|
||||
|
||||
// candidates is a cache storing the indices into FontMap.database of footprints matching a Query
|
||||
// families
|
||||
type candidates struct {
|
||||
// footprints with exact match :
|
||||
// for each queried family, at most one footprint is selected
|
||||
withoutFallback []int
|
||||
|
||||
// footprints matching the expanded query (where subsitutions have been applied)
|
||||
withFallback []int
|
||||
|
||||
manual []int // manually inserted faces to be tried if the other candidates fail.
|
||||
}
|
||||
|
||||
// reset slices, setting the capacity of withoutFallback to nbFamilies
|
||||
func (cd *candidates) resetWithSize(nbFamilies int) {
|
||||
if cap(cd.withoutFallback) < nbFamilies { // reallocate
|
||||
cd.withoutFallback = make([]int, nbFamilies)
|
||||
}
|
||||
|
||||
cd.withoutFallback = cd.withoutFallback[:0]
|
||||
cd.withFallback = cd.withFallback[:0]
|
||||
cd.manual = cd.manual[:0]
|
||||
}
|
||||
|
||||
func (fm *FontMap) buildCandidates() {
|
||||
if fm.built {
|
||||
return
|
||||
}
|
||||
fm.candidates.resetWithSize(len(fm.query.Families))
|
||||
|
||||
// first pass for an exact match
|
||||
{
|
||||
for _, family := range fm.query.Families {
|
||||
candidates := fm.database.selectByFamilyExact(family, fm.cribleBuffer, &fm.footprintsBuffer)
|
||||
if len(candidates) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
// select the correct aspects
|
||||
candidates = fm.database.retainsBestMatches(candidates, fm.query.Aspect)
|
||||
|
||||
// with no system fallback, the CSS spec says
|
||||
// that only one font among the candidates must be tried
|
||||
fm.candidates.withoutFallback = append(fm.candidates.withoutFallback, candidates[0])
|
||||
}
|
||||
}
|
||||
|
||||
// second pass with substitutions
|
||||
{
|
||||
candidates := fm.database.selectByFamilyWithSubs(fm.query.Families, fm.script, fm.cribleBuffer, &fm.footprintsBuffer)
|
||||
|
||||
// select the correct aspects
|
||||
candidates = fm.database.retainsBestMatches(candidates, fm.query.Aspect)
|
||||
|
||||
// candidates is owned by fm.footprintsBuffer: copy its content
|
||||
S := fm.candidates.withFallback
|
||||
if L := len(candidates); cap(S) < L {
|
||||
S = make([]int, L)
|
||||
} else {
|
||||
S = S[:L]
|
||||
}
|
||||
copy(S, candidates)
|
||||
fm.candidates.withFallback = S
|
||||
}
|
||||
|
||||
// third pass with user provided fonts
|
||||
{
|
||||
fm.candidates.manual = fm.database.filterUserProvided(fm.candidates.manual)
|
||||
fm.candidates.manual = fm.database.retainsBestMatches(fm.candidates.manual, fm.query.Aspect)
|
||||
}
|
||||
|
||||
fm.built = true
|
||||
}
|
||||
|
||||
// returns nil if not candidates supports the rune `r`
|
||||
func (fm *FontMap) resolveForRune(candidates []int, r rune) *font.Face {
|
||||
for _, footprintIndex := range candidates {
|
||||
// check the coverage
|
||||
if fp := fm.database[footprintIndex]; fp.Runes.Contains(r) {
|
||||
// try to use the font
|
||||
face, err := fm.loadFont(fp)
|
||||
if err != nil { // very unlikely; try another family
|
||||
fm.logger.Printf("failed loading face: %v", err)
|
||||
continue
|
||||
}
|
||||
|
||||
return face
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// returns nil if no candidates support the language `lang`
|
||||
func (fm *FontMap) resolveForLang(candidates []int, lang LangID) *font.Face {
|
||||
for _, footprintIndex := range candidates {
|
||||
// check the coverage
|
||||
if fp := fm.database[footprintIndex]; fp.Langs.Contains(lang) {
|
||||
// try to use the font
|
||||
face, err := fm.loadFont(fp)
|
||||
if err != nil { // very unlikely; try another family
|
||||
fm.logger.Printf("failed loading face: %v", err)
|
||||
continue
|
||||
}
|
||||
|
||||
return face
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ResolveFace select a font based on the current query (set by [FontMap.SetQuery] and [FontMap.SetScript]),
|
||||
// and supporting the given rune, applying CSS font selection rules.
|
||||
//
|
||||
// Fonts are tried with the following steps :
|
||||
//
|
||||
// 1 - Only fonts matching exacly one of the [Query.Families] are considered; the list
|
||||
// is prunned to keep the best match with [Query.Aspect]
|
||||
// 2 - Fallback fonts are considered, that is fonts with similar families and fonts
|
||||
// supporting the current script; the list is also prunned according to [Query.Aspect]
|
||||
// 3 - Fonts added manually by [AddFont] and [AddFace] (prunned according to [Query.Aspect]),
|
||||
// will be searched, in the order in which they were added.
|
||||
// 4 - All fonts matching the current script (set by [FontMap.SetScript]) are tried,
|
||||
// ignoring [Query.Aspect]
|
||||
//
|
||||
// If no fonts match after these steps, an arbitrary face will be returned.
|
||||
// This face will be nil only if the underlying font database is empty,
|
||||
// or if the file system is broken; otherwise the returned [font.Face] is always valid.
|
||||
func (fm *FontMap) ResolveFace(r rune) (face *font.Face) {
|
||||
key := fm.lru.KeyFor(fm.query, fm.script, r)
|
||||
face, ok := fm.lru.Get(key, fm.query)
|
||||
if ok {
|
||||
return face
|
||||
}
|
||||
defer func() {
|
||||
fm.lru.Put(key, fm.query, face)
|
||||
}()
|
||||
|
||||
// Build the candidates if we missed the cache. If they're already built this is a
|
||||
// no-op.
|
||||
fm.buildCandidates()
|
||||
|
||||
// we first look up for an exact family match, without substitutions
|
||||
if face := fm.resolveForRune(fm.candidates.withoutFallback, r); face != nil {
|
||||
return face
|
||||
}
|
||||
|
||||
// if no family has matched so far, try again with system fallback,
|
||||
// including fonts with matching script and user provided ones
|
||||
if face := fm.resolveForRune(fm.candidates.withFallback, r); face != nil {
|
||||
return face
|
||||
}
|
||||
|
||||
// try manually loaded faces even if the typeface doesn't match, looking for matching aspects
|
||||
// and rune coverage.
|
||||
// Note that, when [SetScript] has been called, this step is actually not needed,
|
||||
// since the fonts supporting the given script are already added in [withFallback] fonts
|
||||
if face := fm.resolveForRune(fm.candidates.manual, r); face != nil {
|
||||
return face
|
||||
}
|
||||
|
||||
fm.logger.Printf("No font matched for aspect %v, script %s, and rune %U (%c) -> searching by script coverage only", fm.query.Aspect, fm.script, r, r)
|
||||
scriptCandidates := fm.scriptMap[fm.script]
|
||||
if face := fm.resolveForRune(scriptCandidates, r); face != nil {
|
||||
return face
|
||||
}
|
||||
|
||||
fm.logger.Printf("No font matched for script %s and rune %U (%c) -> returning arbitrary face", fm.script, r, r)
|
||||
// return an arbitrary face
|
||||
if fm.firstFace == nil && len(fm.database) > 0 {
|
||||
for _, fp := range fm.database {
|
||||
face, err := fm.loadFont(fp)
|
||||
if err != nil {
|
||||
// very unlikely; warn and keep going
|
||||
fm.logger.Printf("failed loading face: %v", err)
|
||||
continue
|
||||
}
|
||||
return face
|
||||
}
|
||||
}
|
||||
|
||||
return fm.firstFace
|
||||
// refreshSystemFontsIndex makes sure at least one face is valid
|
||||
// and AddFont also check for valid font files, meaning that
|
||||
// a valid FontMap should always contain a valid face,
|
||||
// and we should never return a nil face.
|
||||
}
|
||||
|
||||
// ResolveForLang returns the first face supporting the given language
|
||||
// (for the actual query), or nil if no one is found.
|
||||
//
|
||||
// The matching logic is similar to the one used by [ResolveFace].
|
||||
func (fm *FontMap) ResolveFaceForLang(lang LangID) *font.Face {
|
||||
// no-op if already built
|
||||
fm.buildCandidates()
|
||||
|
||||
// we first look up for an exact family match, without substitutions
|
||||
if face := fm.resolveForLang(fm.candidates.withoutFallback, lang); face != nil {
|
||||
return face
|
||||
}
|
||||
|
||||
// if no family has matched so far, try again with system fallback
|
||||
if face := fm.resolveForLang(fm.candidates.withFallback, lang); face != nil {
|
||||
return face
|
||||
}
|
||||
|
||||
// try manually loaded faces even if the typeface doesn't match, looking for matching aspects
|
||||
// and rune coverage.
|
||||
if face := fm.resolveForLang(fm.candidates.manual, lang); face != nil {
|
||||
return face
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (fm *FontMap) loadFont(fp Footprint) (*font.Face, error) {
|
||||
if face, hasCached := fm.faceCache[fp.Location]; hasCached {
|
||||
return face, nil
|
||||
}
|
||||
|
||||
// since user provided fonts are added to `faceCache`
|
||||
// we may now assume the font is stored on the file system
|
||||
face, err := fp.loadFromDisk()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// add the face to the cache
|
||||
fm.cache(fp, face)
|
||||
|
||||
return face, nil
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
package fontscan
|
||||
|
||||
import "fmt"
|
||||
|
||||
func platformCacheDir() (string, error) {
|
||||
// There is no stable way to infer the proper place to store the cache
|
||||
// with access to the Java runtime for the application. Rather than
|
||||
// clutter our API with that, require the caller to provide a path.
|
||||
return "", fmt.Errorf("user must provide cache directory on android")
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
//go:build !android && !tinygo
|
||||
|
||||
package fontscan
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
)
|
||||
|
||||
func platformCacheDir() (string, error) {
|
||||
configDir, err := os.UserCacheDir()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("resolving index cache path: %s", err)
|
||||
}
|
||||
return configDir, nil
|
||||
}
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
//go:build tinygo
|
||||
|
||||
package fontscan
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
func platformCacheDir() (string, error) {
|
||||
// if no path is provided we cannot get cache dir with tinygo, so just make one up.
|
||||
homeDir, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("resolving index cache path: %s", err)
|
||||
}
|
||||
return filepath.Join(homeDir, ".cache"), nil
|
||||
}
|
||||
+146
@@ -0,0 +1,146 @@
|
||||
package fontscan
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/go-text/typesetting/font"
|
||||
ot "github.com/go-text/typesetting/font/opentype"
|
||||
"github.com/go-text/typesetting/font/opentype/tables"
|
||||
)
|
||||
|
||||
// Location identifies where a font.Face is stored.
|
||||
type Location = font.FontID
|
||||
|
||||
// Footprint is a condensed summary of the main information
|
||||
// about a font, serving as a lightweight surrogate
|
||||
// for the original font file.
|
||||
type Footprint struct {
|
||||
// Location stores the adress of the font resource.
|
||||
Location Location
|
||||
|
||||
// Family is the general nature of the font, like
|
||||
// "Arial"
|
||||
// Note that, for performance reason, we store the
|
||||
// normalized version of the family name.
|
||||
Family string
|
||||
|
||||
// Runes is the set of runes supported by the font.
|
||||
Runes RuneSet
|
||||
|
||||
// Scripts is the set of scripts deduced from [Runes]
|
||||
Scripts ScriptSet
|
||||
|
||||
// Langs is the set of languages deduced from [Runes]
|
||||
Langs LangSet
|
||||
|
||||
// Aspect precises the visual characteristics
|
||||
// of the font among a family, like "Bold Italic"
|
||||
Aspect font.Aspect
|
||||
|
||||
// isUserProvided is set to true for fonts add manually to
|
||||
// a FontMap
|
||||
// User fonts will always be tried if no other fonts match,
|
||||
// and will have priority among font with same family name.
|
||||
//
|
||||
// This field is not serialized in the index, since it is always false
|
||||
// for system fonts.
|
||||
isUserProvided bool
|
||||
}
|
||||
|
||||
func newFootprintFromFont(f *font.Font, location Location, md font.Description) (out Footprint) {
|
||||
out.Runes, out.Scripts, _ = newCoveragesFromCmap(f.Cmap, nil)
|
||||
out.Langs = newLangsetFromCoverage(out.Runes)
|
||||
out.Family = font.NormalizeFamily(md.Family)
|
||||
out.Aspect = md.Aspect
|
||||
out.Location = location
|
||||
out.isUserProvided = true
|
||||
return out
|
||||
}
|
||||
|
||||
func newFootprintFromLoader(ld *ot.Loader, isUserProvided bool, buffer scanBuffer) (out Footprint, _ scanBuffer, err error) {
|
||||
raw := buffer.tableBuffer
|
||||
|
||||
// since raw is shared, special car must be taken in the parsing order
|
||||
|
||||
raw, _ = ld.RawTableTo(ot.MustNewTag("OS/2"), raw)
|
||||
fp := tables.FPNone
|
||||
if os2, _, err := tables.ParseOs2(raw); err != nil {
|
||||
fp = os2.FontPage()
|
||||
}
|
||||
|
||||
// we can use the buffer since ProcessCmap do not keep any reference on
|
||||
// the input slice
|
||||
raw, err = ld.RawTableTo(ot.MustNewTag("cmap"), raw)
|
||||
if err != nil {
|
||||
return Footprint{}, buffer, err
|
||||
}
|
||||
tb, _, err := tables.ParseCmap(raw)
|
||||
if err != nil {
|
||||
return Footprint{}, buffer, err
|
||||
}
|
||||
cmap, _, err := font.ProcessCmap(tb, fp)
|
||||
if err != nil {
|
||||
return Footprint{}, buffer, err
|
||||
}
|
||||
|
||||
out.Runes, out.Scripts, buffer.cmapBuffer = newCoveragesFromCmap(cmap, buffer.cmapBuffer) // ... and build the corresponding rune set
|
||||
|
||||
out.Langs = newLangsetFromCoverage(out.Runes)
|
||||
|
||||
desc, raw := font.Describe(ld, raw)
|
||||
out.Family = font.NormalizeFamily(desc.Family)
|
||||
out.Aspect = desc.Aspect
|
||||
out.isUserProvided = isUserProvided
|
||||
|
||||
buffer.tableBuffer = raw
|
||||
|
||||
return out, buffer, nil
|
||||
}
|
||||
|
||||
// returns true for .ttf and .ttc font files
|
||||
func (fp *Footprint) isTruetypeHint() bool {
|
||||
switch strings.ToLower(filepath.Ext(fp.Location.File)) {
|
||||
case ".ttf", ".ttc":
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// isMonoHint returns true if "mono" is included in the family name
|
||||
// this is not very precise but much more efficient than using [font.Font.IsMonospace]
|
||||
func (fp *Footprint) isMonoHint() bool {
|
||||
return strings.Contains(fp.Family, "mono")
|
||||
}
|
||||
|
||||
// loadFromDisk assume the footprint location refers to the file system
|
||||
func (fp *Footprint) loadFromDisk() (*font.Face, error) {
|
||||
location := fp.Location
|
||||
|
||||
file, err := os.Open(location.File)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
loaders, err := ot.NewLoaders(file)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if index := int(location.Index); len(loaders) <= index {
|
||||
// this should only happen if the font file as changed
|
||||
// since the last scan (very unlikely)
|
||||
return nil, fmt.Errorf("invalid font index in collection: %d >= %d", index, len(loaders))
|
||||
}
|
||||
|
||||
ft, err := font.NewFont(loaders[location.Index])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("reading font at %s: %s", location.File, err)
|
||||
}
|
||||
|
||||
return font.NewFace(ft), nil
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user