erm/app/fontinfo/parse.go

136 lines
3.0 KiB
Go

package fontinfo
import (
"bytes"
"fmt"
"io"
"unicode/utf8"
"golang.org/x/text/encoding/unicode"
)
func read(r io.Reader, length int) ([]byte, error) {
buf := make([]byte, length)
if n, err := r.Read(buf); err != nil {
return nil, err
} else if n < length {
return nil, fmt.Errorf("invalid length")
}
return buf, nil
}
func u16(buf []byte) uint16 {
return (uint16(buf[0]) << 8) + uint16(buf[1])
}
func u32(buf []byte) uint32 {
return (uint32(buf[0]) << 24) + (uint32(buf[1]) << 16) + (uint32(buf[2]) << 8) + uint32(buf[3])
}
type FontMetadata struct {
FontFamily string
FontStyle string
}
type FontParser interface {
Parse(r io.ReadSeeker) (*FontMetadata, error)
}
// for ttf and otf files
type TruetypeParser struct {
}
func (t *TruetypeParser) Parse(r io.ReadSeeker) (*FontMetadata, error) {
buf, err := read(r, 12)
if err != nil {
return nil, err
}
tableCount := u16(buf[4:6])
for i := 0; i < int(tableCount); i++ {
if _, err := r.Seek(12+(int64(i)*16), 0); err != nil {
return nil, err
}
table, err := read(r, 16)
if err != nil {
return nil, err
}
if string(table[0:4]) != "name" {
continue
}
offset := u32(table[8:12])
return t.readNameTable(r, offset)
}
return nil, fmt.Errorf("name table not found")
}
func (t *TruetypeParser) readNameTable(r io.ReadSeeker, offset uint32) (*FontMetadata, error) {
if _, err := r.Seek(int64(offset), 0); err != nil {
return nil, fmt.Errorf("invalid font file")
}
nameTable, err := read(r, 6)
if err != nil {
return nil, err
}
nameCount := u16(nameTable[2:4])
stringOffset := int64(u16(nameTable[4:6])) + int64(offset)
var done uint8
var metadata FontMetadata
nameRecordStart := offset + 6
for j := 0; j < int(nameCount); j++ {
recordOffset := nameRecordStart + uint32(12*j)
if _, err := r.Seek(int64(recordOffset), 0); err != nil {
return nil, err
}
buf, err := read(r, 12)
if err != nil {
return nil, err
}
language := u16(buf[4:6])
if language != 0 && language != 1033 { // not english or english us
continue
}
nameID := u16(buf[6:8])
switch nameID {
case 1, 2: //family, style
if _, err := r.Seek(int64(stringOffset)+int64(u16(buf[10:12])), 0); err != nil {
return nil, err
}
raw, err := read(r, int(u16(buf[8:10])))
if err != nil {
return nil, err
}
if nameID == 1 {
done |= 1
metadata.FontFamily = lazyUnicode(raw)
} else {
done |= 2
metadata.FontStyle = lazyUnicode(raw)
}
if done == 3 { // bail early if we have what we need
return &metadata, nil
}
}
}
return &metadata, nil
}
func lazyUnicode(r []byte) string {
var back bool
if bytes.HasSuffix(r, []byte{0}) {
r = bytes.TrimSuffix(r, []byte{0})
back = true
}
if utf8.Valid(r) && (!bytes.Contains(r, []byte{0})) {
return string(r)
}
if back {
r = append(r, 0)
}
dec := unicode.UTF16(unicode.BigEndian, unicode.UseBOM).NewDecoder()
ans, err := dec.String(string(r))
if err != nil {
return "Invalid_UTF16_Encoding"
}
return ans
}