136 lines
3.0 KiB
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
|
||
|
}
|