2017-07-05 23:10:57 +00:00
|
|
|
/*
|
|
|
|
* Copyright (c) 2013 Kurt Jung (Gmail: kurt.w.jung)
|
|
|
|
*
|
|
|
|
* Permission to use, copy, modify, and distribute this software for any
|
|
|
|
* purpose with or without fee is hereby granted, provided that the above
|
|
|
|
* copyright notice and this permission notice appear in all copies.
|
|
|
|
*
|
|
|
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
|
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
|
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
|
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
|
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
|
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
|
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
|
|
*/
|
2018-08-03 10:15:42 +00:00
|
|
|
/*
|
|
|
|
* Copyright (c) 2018 FoxyUtils ehf. to modifications of the original.
|
|
|
|
* Modifications of the original file are subject to the terms and conditions
|
|
|
|
* defined in file 'LICENSE.md', which is part of this source code package.
|
|
|
|
*/
|
2017-07-05 23:10:57 +00:00
|
|
|
|
|
|
|
package fonts
|
|
|
|
|
|
|
|
// Utility to parse TTF font files
|
|
|
|
// Version: 1.0
|
|
|
|
// Date: 2011-06-18
|
|
|
|
// Author: Olivier PLATHEY
|
|
|
|
// Port to Go: Kurt Jung, 2013-07-15
|
|
|
|
|
|
|
|
import (
|
2018-07-13 17:40:27 +10:00
|
|
|
"bytes"
|
2017-07-05 23:10:57 +00:00
|
|
|
"encoding/binary"
|
2018-07-13 17:40:27 +10:00
|
|
|
"errors"
|
2017-07-05 23:10:57 +00:00
|
|
|
"fmt"
|
2018-07-13 17:40:27 +10:00
|
|
|
"io"
|
2017-07-05 23:10:57 +00:00
|
|
|
"os"
|
|
|
|
"regexp"
|
2018-07-13 17:40:27 +10:00
|
|
|
"sort"
|
2017-07-05 23:10:57 +00:00
|
|
|
"strings"
|
2018-07-13 17:40:27 +10:00
|
|
|
|
|
|
|
"github.com/unidoc/unidoc/common"
|
2018-07-15 16:28:56 +10:00
|
|
|
"github.com/unidoc/unidoc/pdf/core"
|
2018-09-17 17:57:52 +10:00
|
|
|
"github.com/unidoc/unidoc/pdf/internal/cmap"
|
2018-11-01 21:33:51 +11:00
|
|
|
"github.com/unidoc/unidoc/pdf/internal/textencoding"
|
2017-07-05 23:10:57 +00:00
|
|
|
)
|
|
|
|
|
2018-10-29 09:08:32 +11:00
|
|
|
// MakeEncoder returns an encoder built from the tables in `rec`.
|
2018-12-30 16:18:56 +02:00
|
|
|
func (ttf *TtfType) MakeEncoder() (textencoding.SimpleEncoder, error) {
|
2018-11-29 23:24:40 +02:00
|
|
|
encoding := make(map[textencoding.CharCode]GlyphName)
|
2018-12-29 19:01:05 +02:00
|
|
|
// TODO(dennwc): this is a bit strange, since TTF may contain more than 256 characters
|
|
|
|
// should probably make a different encoder here
|
2018-11-29 04:30:37 +02:00
|
|
|
for code := textencoding.CharCode(0); code <= 256; code++ {
|
|
|
|
r := rune(code) // TODO(dennwc): make sure this conversion is valid
|
2018-12-09 21:25:30 +02:00
|
|
|
gid, ok := ttf.Chars[r]
|
2018-07-13 17:40:27 +10:00
|
|
|
if !ok {
|
|
|
|
continue
|
|
|
|
}
|
2018-11-29 23:24:40 +02:00
|
|
|
var glyph GlyphName
|
2018-12-09 21:25:30 +02:00
|
|
|
if int(gid) >= 0 && int(gid) < len(ttf.GlyphNames) {
|
|
|
|
glyph = ttf.GlyphNames[gid]
|
2018-07-13 17:40:27 +10:00
|
|
|
} else {
|
2018-10-29 09:08:32 +11:00
|
|
|
r := rune(gid)
|
|
|
|
if g, ok := textencoding.RuneToGlyph(r); ok {
|
|
|
|
glyph = g
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if glyph != "" {
|
|
|
|
encoding[code] = glyph
|
2018-07-13 17:40:27 +10:00
|
|
|
}
|
|
|
|
}
|
|
|
|
if len(encoding) == 0 {
|
2018-12-27 12:17:28 +02:00
|
|
|
common.Log.Debug("WARNING: Zero length TrueType encoding. ttf=%s Chars=[% 02x]",
|
2018-12-09 21:25:30 +02:00
|
|
|
ttf, ttf.Chars)
|
2018-07-13 17:40:27 +10:00
|
|
|
}
|
|
|
|
return textencoding.NewCustomSimpleTextEncoder(encoding, nil)
|
|
|
|
}
|
|
|
|
|
2018-11-29 04:02:20 +02:00
|
|
|
// GID is a glyph index.
|
|
|
|
type GID = textencoding.GID
|
|
|
|
|
2018-11-29 23:24:40 +02:00
|
|
|
// GlyphName is a name of a glyph.
|
|
|
|
type GlyphName = textencoding.GlyphName
|
|
|
|
|
2018-07-15 16:28:56 +10:00
|
|
|
// TtfType describes a TrueType font file.
|
|
|
|
// http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=iws-chapter08
|
2017-07-05 23:10:57 +00:00
|
|
|
type TtfType struct {
|
|
|
|
UnitsPerEm uint16
|
|
|
|
PostScriptName string
|
|
|
|
Bold bool
|
2018-07-13 17:40:27 +10:00
|
|
|
ItalicAngle float64
|
2017-07-05 23:10:57 +00:00
|
|
|
IsFixedPitch bool
|
|
|
|
TypoAscender int16
|
|
|
|
TypoDescender int16
|
|
|
|
UnderlinePosition int16
|
|
|
|
UnderlineThickness int16
|
|
|
|
Xmin, Ymin, Xmax, Ymax int16
|
|
|
|
CapHeight int16
|
2018-12-29 19:01:05 +02:00
|
|
|
// Widths is a list of glyph widths indexed by GID.
|
|
|
|
Widths []uint16
|
2017-09-01 13:20:51 +00:00
|
|
|
|
2018-12-07 18:30:37 +02:00
|
|
|
// Chars maps rune values (unicode) to GIDs (the indexes in GlyphNames). i.e. GlyphNames[Chars[r]] is
|
2018-07-15 16:28:56 +10:00
|
|
|
// the glyph corresponding to rune r.
|
2018-12-07 18:30:37 +02:00
|
|
|
//
|
2018-12-29 19:01:05 +02:00
|
|
|
// TODO(dennwc): CharCode is currently defined as uint16, but some tables may store 32 bit charcodes
|
|
|
|
// not the case right now, but make sure to update it once we support those tables
|
2018-12-07 18:30:37 +02:00
|
|
|
// TODO(dennwc,peterwilliams97): it should map char codes to GIDs
|
2018-11-29 04:02:20 +02:00
|
|
|
Chars map[rune]GID
|
2018-07-15 16:28:56 +10:00
|
|
|
// GlyphNames is a list of glyphs from the "post" section of the TrueType file.
|
2018-11-29 23:24:40 +02:00
|
|
|
GlyphNames []GlyphName
|
2017-07-05 23:10:57 +00:00
|
|
|
}
|
|
|
|
|
2018-09-17 17:57:52 +10:00
|
|
|
// MakeToUnicode returns a ToUnicode CMap based on the encoding of `ttf`.
|
2018-12-09 21:37:27 +02:00
|
|
|
// TODO(peterwilliams97): This currently gives a bad text mapping for creator_test.go but leads to an
|
2018-09-21 16:43:10 +10:00
|
|
|
// otherwise valid PDF file that Adobe Reader displays without error.
|
2018-09-17 17:57:52 +10:00
|
|
|
func (ttf *TtfType) MakeToUnicode() *cmap.CMap {
|
2018-11-29 03:22:46 +02:00
|
|
|
codeToUnicode := make(map[cmap.CharCode]rune)
|
2018-12-02 09:14:58 +11:00
|
|
|
for code, gid := range ttf.Chars {
|
2018-11-29 05:49:51 +02:00
|
|
|
// TODO(dennwc): this function is used only in one place and relies on
|
|
|
|
// the fact that the code uses identity CID<->GID mapping
|
|
|
|
charcode := cmap.CharCode(code)
|
|
|
|
|
2018-12-02 09:14:58 +11:00
|
|
|
glyph := ttf.GlyphNames[gid]
|
2018-09-17 17:57:52 +10:00
|
|
|
|
2018-11-29 04:30:37 +02:00
|
|
|
// TODO(dennwc): 'code' is already a rune; do we need this extra lookup?
|
2018-12-29 19:01:05 +02:00
|
|
|
// TODO(dennwc): this cannot be done here; glyphNames might be empty
|
|
|
|
// the parent font may specify a different encoding
|
|
|
|
// so we should remap on a higher level
|
2018-09-17 17:57:52 +10:00
|
|
|
r, ok := textencoding.GlyphToRune(glyph)
|
|
|
|
if !ok {
|
|
|
|
common.Log.Debug("No rune. code=0x%04x glyph=%q", code, glyph)
|
|
|
|
r = textencoding.MissingCodeRune
|
|
|
|
}
|
2018-11-29 05:49:51 +02:00
|
|
|
codeToUnicode[charcode] = r
|
2018-09-17 17:57:52 +10:00
|
|
|
}
|
|
|
|
return cmap.NewToUnicodeCMap(codeToUnicode)
|
|
|
|
}
|
|
|
|
|
2019-03-14 18:48:20 +02:00
|
|
|
// NewEncoder returns a new TrueType font encoder.
|
2018-11-29 05:18:39 +02:00
|
|
|
func (ttf *TtfType) NewEncoder() textencoding.TextEncoder {
|
|
|
|
return textencoding.NewTrueTypeFontEncoder(ttf.Chars)
|
|
|
|
}
|
|
|
|
|
2018-09-17 17:57:52 +10:00
|
|
|
// String returns a human readable representation of `ttf`.
|
2018-07-13 17:40:27 +10:00
|
|
|
func (ttf *TtfType) String() string {
|
2018-10-24 01:28:48 +03:00
|
|
|
return fmt.Sprintf("FONT_FILE2{%#q UnitsPerEm=%d Bold=%t ItalicAngle=%f "+
|
2018-07-13 17:40:27 +10:00
|
|
|
"CapHeight=%d Chars=%d GlyphNames=%d}",
|
2018-10-24 01:28:48 +03:00
|
|
|
ttf.PostScriptName, ttf.UnitsPerEm, ttf.Bold, ttf.ItalicAngle,
|
2018-07-13 17:40:27 +10:00
|
|
|
ttf.CapHeight, len(ttf.Chars), len(ttf.GlyphNames))
|
|
|
|
}
|
|
|
|
|
2018-07-15 16:28:56 +10:00
|
|
|
// ttfParser contains some state variables used to parse a TrueType file.
|
2017-07-05 23:10:57 +00:00
|
|
|
type ttfParser struct {
|
|
|
|
rec TtfType
|
2018-07-13 17:40:27 +10:00
|
|
|
f io.ReadSeeker
|
2017-07-05 23:10:57 +00:00
|
|
|
tables map[string]uint32
|
|
|
|
numberOfHMetrics uint16
|
|
|
|
numGlyphs uint16
|
|
|
|
}
|
|
|
|
|
2018-07-15 16:28:56 +10:00
|
|
|
// NewFontFile2FromPdfObject returns a TtfType describing the TrueType font file in PdfObject `obj`.
|
2018-07-25 12:00:49 +10:00
|
|
|
func NewFontFile2FromPdfObject(obj core.PdfObject) (TtfType, error) {
|
2018-07-15 16:28:56 +10:00
|
|
|
obj = core.TraceToDirectObject(obj)
|
|
|
|
streamObj, ok := obj.(*core.PdfObjectStream)
|
2018-07-13 17:40:27 +10:00
|
|
|
if !ok {
|
2018-07-19 10:28:23 +10:00
|
|
|
common.Log.Debug("ERROR: FontFile2 must be a stream (%T)", obj)
|
2018-07-25 12:00:49 +10:00
|
|
|
return TtfType{}, core.ErrTypeError
|
2018-07-13 17:40:27 +10:00
|
|
|
}
|
2018-07-15 16:28:56 +10:00
|
|
|
data, err := core.DecodeStream(streamObj)
|
2018-07-13 17:40:27 +10:00
|
|
|
if err != nil {
|
2018-07-25 12:00:49 +10:00
|
|
|
return TtfType{}, err
|
2018-07-13 17:40:27 +10:00
|
|
|
}
|
|
|
|
|
2018-07-24 21:32:02 +10:00
|
|
|
// Uncomment these lines to see the contents of the font file. For debugging.
|
2018-07-13 17:40:27 +10:00
|
|
|
// fmt.Println("===============&&&&===============")
|
|
|
|
// fmt.Printf("%#q", string(data))
|
|
|
|
// fmt.Println("===============####===============")
|
|
|
|
|
2018-07-15 16:28:56 +10:00
|
|
|
t := ttfParser{f: bytes.NewReader(data)}
|
2018-07-25 12:00:49 +10:00
|
|
|
return t.Parse()
|
2018-07-13 17:40:27 +10:00
|
|
|
}
|
|
|
|
|
2019-01-02 17:17:58 +02:00
|
|
|
// TtfParseFile returns a TtfType describing the TrueType font file in disk file `fileStr`.
|
|
|
|
func TtfParseFile(fileStr string) (TtfType, error) {
|
2018-07-13 17:40:27 +10:00
|
|
|
f, err := os.Open(fileStr)
|
2017-07-05 23:10:57 +00:00
|
|
|
if err != nil {
|
2018-07-25 12:00:49 +10:00
|
|
|
return TtfType{}, err
|
2017-07-05 23:10:57 +00:00
|
|
|
}
|
2018-07-13 17:40:27 +10:00
|
|
|
defer f.Close()
|
|
|
|
|
2019-01-02 17:17:58 +02:00
|
|
|
return TtfParse(f)
|
|
|
|
}
|
|
|
|
|
|
|
|
// TtfParse returns a TtfType describing the TrueType font.
|
|
|
|
func TtfParse(r io.ReadSeeker) (TtfType, error) {
|
|
|
|
t := &ttfParser{f: r}
|
2018-07-25 12:00:49 +10:00
|
|
|
return t.Parse()
|
2018-07-13 17:40:27 +10:00
|
|
|
}
|
|
|
|
|
2018-11-30 16:53:48 +00:00
|
|
|
// Parse returns a TtfType describing the TrueType font file in io.Reader `t`.f.
|
2018-07-25 12:00:49 +10:00
|
|
|
func (t *ttfParser) Parse() (TtfType, error) {
|
2018-06-27 12:25:59 +10:00
|
|
|
|
2017-07-05 23:10:57 +00:00
|
|
|
version, err := t.ReadStr(4)
|
|
|
|
if err != nil {
|
2018-07-25 12:00:49 +10:00
|
|
|
return TtfType{}, err
|
2017-07-05 23:10:57 +00:00
|
|
|
}
|
|
|
|
if version == "OTTO" {
|
2018-11-20 15:49:28 +11:00
|
|
|
// See https://docs.microsoft.com/en-us/typography/opentype/spec/otff
|
2018-07-25 12:00:49 +10:00
|
|
|
return TtfType{}, errors.New("fonts based on PostScript outlines are not supported")
|
2017-07-05 23:10:57 +00:00
|
|
|
}
|
2018-12-02 09:14:58 +11:00
|
|
|
if version != "\x00\x01\x00\x00" && version != "true" {
|
2018-08-17 08:41:35 +10:00
|
|
|
// This is not an error. In the font_test.go example axes.txt we see version "true".
|
|
|
|
common.Log.Debug("Unrecognized TrueType file format. version=%q", version)
|
2017-07-05 23:10:57 +00:00
|
|
|
}
|
|
|
|
numTables := int(t.ReadUShort())
|
|
|
|
t.Skip(3 * 2) // searchRange, entrySelector, rangeShift
|
|
|
|
t.tables = make(map[string]uint32)
|
|
|
|
var tag string
|
|
|
|
for j := 0; j < numTables; j++ {
|
|
|
|
tag, err = t.ReadStr(4)
|
|
|
|
if err != nil {
|
2018-07-25 12:00:49 +10:00
|
|
|
return TtfType{}, err
|
2017-07-05 23:10:57 +00:00
|
|
|
}
|
|
|
|
t.Skip(4) // checkSum
|
|
|
|
offset := t.ReadULong()
|
|
|
|
t.Skip(4) // length
|
|
|
|
t.tables[tag] = offset
|
|
|
|
}
|
2018-07-13 17:40:27 +10:00
|
|
|
|
2018-07-15 16:28:56 +10:00
|
|
|
common.Log.Trace(describeTables(t.tables))
|
2018-07-13 17:40:27 +10:00
|
|
|
|
2018-07-25 12:00:49 +10:00
|
|
|
if err = t.ParseComponents(); err != nil {
|
|
|
|
return TtfType{}, err
|
2017-07-05 23:10:57 +00:00
|
|
|
}
|
2018-07-25 12:00:49 +10:00
|
|
|
return t.rec, nil
|
2017-07-05 23:10:57 +00:00
|
|
|
}
|
|
|
|
|
2018-07-15 16:28:56 +10:00
|
|
|
// describeTables returns a string describing `tables`, the tables in a TrueType font file.
|
2018-07-13 17:40:27 +10:00
|
|
|
func describeTables(tables map[string]uint32) string {
|
2018-12-09 19:28:50 +02:00
|
|
|
var tags []string
|
2018-07-13 17:40:27 +10:00
|
|
|
for tag := range tables {
|
|
|
|
tags = append(tags, tag)
|
|
|
|
}
|
|
|
|
sort.Slice(tags, func(i, j int) bool { return tables[tags[i]] < tables[tags[j]] })
|
|
|
|
parts := []string{fmt.Sprintf("TrueType tables: %d", len(tables))}
|
|
|
|
for _, tag := range tags {
|
|
|
|
parts = append(parts, fmt.Sprintf("\t%q %5d", tag, tables[tag]))
|
|
|
|
}
|
|
|
|
return strings.Join(parts, "\n")
|
|
|
|
}
|
|
|
|
|
2018-11-20 15:49:28 +11:00
|
|
|
// ParseComponents parses the tables in a TrueType font file.
|
2018-07-15 16:28:56 +10:00
|
|
|
// The standard TrueType tables are
|
2018-07-13 17:40:27 +10:00
|
|
|
// "head"
|
|
|
|
// "hhea"
|
|
|
|
// "loca"
|
|
|
|
// "maxp"
|
|
|
|
// "cvt "
|
|
|
|
// "prep"
|
|
|
|
// "glyf"
|
|
|
|
// "hmtx"
|
|
|
|
// "fpgm"
|
|
|
|
// "gasp"
|
2018-07-25 12:00:49 +10:00
|
|
|
func (t *ttfParser) ParseComponents() error {
|
2018-07-15 16:28:56 +10:00
|
|
|
|
|
|
|
// Mandatory tables.
|
2018-07-25 12:00:49 +10:00
|
|
|
if err := t.ParseHead(); err != nil {
|
|
|
|
return err
|
2018-07-13 17:40:27 +10:00
|
|
|
}
|
2018-07-25 12:00:49 +10:00
|
|
|
if err := t.ParseHhea(); err != nil {
|
|
|
|
return err
|
2018-07-13 17:40:27 +10:00
|
|
|
}
|
2018-07-25 12:00:49 +10:00
|
|
|
if err := t.ParseMaxp(); err != nil {
|
|
|
|
return err
|
2018-07-13 17:40:27 +10:00
|
|
|
}
|
2018-07-25 12:00:49 +10:00
|
|
|
if err := t.ParseHmtx(); err != nil {
|
|
|
|
return err
|
2018-07-13 17:40:27 +10:00
|
|
|
}
|
|
|
|
|
2018-07-15 16:28:56 +10:00
|
|
|
// Optional tables.
|
2018-07-13 17:40:27 +10:00
|
|
|
if _, ok := t.tables["name"]; ok {
|
2018-07-25 12:00:49 +10:00
|
|
|
if err := t.ParseName(); err != nil {
|
|
|
|
return err
|
2018-07-13 17:40:27 +10:00
|
|
|
}
|
|
|
|
}
|
|
|
|
if _, ok := t.tables["OS/2"]; ok {
|
2018-07-25 12:00:49 +10:00
|
|
|
if err := t.ParseOS2(); err != nil {
|
|
|
|
return err
|
2018-07-13 17:40:27 +10:00
|
|
|
}
|
|
|
|
}
|
|
|
|
if _, ok := t.tables["post"]; ok {
|
2018-07-25 12:00:49 +10:00
|
|
|
if err := t.ParsePost(); err != nil {
|
|
|
|
return err
|
2018-07-13 17:40:27 +10:00
|
|
|
}
|
|
|
|
}
|
|
|
|
if _, ok := t.tables["cmap"]; ok {
|
2018-07-25 12:00:49 +10:00
|
|
|
if err := t.ParseCmap(); err != nil {
|
|
|
|
return err
|
2017-07-05 23:10:57 +00:00
|
|
|
}
|
|
|
|
}
|
2018-07-13 17:40:27 +10:00
|
|
|
|
2018-07-25 12:00:49 +10:00
|
|
|
return nil
|
2017-07-05 23:10:57 +00:00
|
|
|
}
|
|
|
|
|
2018-07-25 12:00:49 +10:00
|
|
|
func (t *ttfParser) ParseHead() error {
|
|
|
|
if err := t.Seek("head"); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2017-07-05 23:10:57 +00:00
|
|
|
t.Skip(3 * 4) // version, fontRevision, checkSumAdjustment
|
|
|
|
magicNumber := t.ReadULong()
|
|
|
|
if magicNumber != 0x5F0F3CF5 {
|
2018-11-30 23:01:04 +00:00
|
|
|
// outputmanager.pdf displays in Adobe Reader but has a bad magic
|
2018-11-21 13:14:11 +11:00
|
|
|
// number so we don't return an error here.
|
2018-12-27 12:17:28 +02:00
|
|
|
// TODO(dennwc): check if it's a "head" table different format - this should not blindly accept anything
|
2018-11-21 13:14:11 +11:00
|
|
|
common.Log.Debug("ERROR: Incorrect magic number. Font may not display correctly. %s", t)
|
2017-07-05 23:10:57 +00:00
|
|
|
}
|
|
|
|
t.Skip(2) // flags
|
|
|
|
t.rec.UnitsPerEm = t.ReadUShort()
|
|
|
|
t.Skip(2 * 8) // created, modified
|
|
|
|
t.rec.Xmin = t.ReadShort()
|
|
|
|
t.rec.Ymin = t.ReadShort()
|
|
|
|
t.rec.Xmax = t.ReadShort()
|
|
|
|
t.rec.Ymax = t.ReadShort()
|
2018-07-25 12:00:49 +10:00
|
|
|
return nil
|
2017-07-05 23:10:57 +00:00
|
|
|
}
|
|
|
|
|
2018-07-25 12:00:49 +10:00
|
|
|
func (t *ttfParser) ParseHhea() error {
|
|
|
|
if err := t.Seek("hhea"); err != nil {
|
|
|
|
return err
|
2017-07-05 23:10:57 +00:00
|
|
|
}
|
2018-07-25 12:00:49 +10:00
|
|
|
t.Skip(4 + 15*2)
|
|
|
|
t.numberOfHMetrics = t.ReadUShort()
|
|
|
|
return nil
|
2017-07-05 23:10:57 +00:00
|
|
|
}
|
|
|
|
|
2018-07-25 12:00:49 +10:00
|
|
|
func (t *ttfParser) ParseMaxp() error {
|
|
|
|
if err := t.Seek("maxp"); err != nil {
|
|
|
|
return err
|
2017-07-05 23:10:57 +00:00
|
|
|
}
|
2018-07-25 12:00:49 +10:00
|
|
|
t.Skip(4)
|
|
|
|
t.numGlyphs = t.ReadUShort()
|
|
|
|
return nil
|
2017-07-05 23:10:57 +00:00
|
|
|
}
|
|
|
|
|
2018-10-29 09:08:32 +11:00
|
|
|
// ParseHmtx parses the Horizontal Metrics table in a TrueType.
|
2018-07-25 12:00:49 +10:00
|
|
|
func (t *ttfParser) ParseHmtx() error {
|
|
|
|
if err := t.Seek("hmtx"); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
t.rec.Widths = make([]uint16, 0, 8)
|
|
|
|
for j := uint16(0); j < t.numberOfHMetrics; j++ {
|
|
|
|
t.rec.Widths = append(t.rec.Widths, t.ReadUShort())
|
|
|
|
t.Skip(2) // lsb
|
|
|
|
}
|
|
|
|
if t.numberOfHMetrics < t.numGlyphs {
|
|
|
|
lastWidth := t.rec.Widths[t.numberOfHMetrics-1]
|
|
|
|
for j := t.numberOfHMetrics; j < t.numGlyphs; j++ {
|
|
|
|
t.rec.Widths = append(t.rec.Widths, lastWidth)
|
2017-07-05 23:10:57 +00:00
|
|
|
}
|
|
|
|
}
|
2018-07-25 12:00:49 +10:00
|
|
|
|
|
|
|
return nil
|
2017-07-05 23:10:57 +00:00
|
|
|
}
|
|
|
|
|
2017-09-01 13:20:51 +00:00
|
|
|
// parseCmapSubtable31 parses information from an (3,1) subtable (Windows Unicode).
|
2018-07-25 12:00:49 +10:00
|
|
|
func (t *ttfParser) parseCmapSubtable31(offset31 int64) error {
|
2018-11-29 03:37:12 +02:00
|
|
|
startCount := make([]rune, 0, 8)
|
|
|
|
endCount := make([]rune, 0, 8)
|
2017-07-05 23:10:57 +00:00
|
|
|
idDelta := make([]int16, 0, 8)
|
|
|
|
idRangeOffset := make([]uint16, 0, 8)
|
2018-11-29 04:02:20 +02:00
|
|
|
t.rec.Chars = make(map[rune]GID)
|
2017-07-05 23:10:57 +00:00
|
|
|
t.f.Seek(int64(t.tables["cmap"])+offset31, os.SEEK_SET)
|
|
|
|
format := t.ReadUShort()
|
|
|
|
if format != 4 {
|
2018-07-25 12:00:49 +10:00
|
|
|
return fmt.Errorf("unexpected subtable format: %d", format)
|
2017-07-05 23:10:57 +00:00
|
|
|
}
|
|
|
|
t.Skip(2 * 2) // length, language
|
|
|
|
segCount := int(t.ReadUShort() / 2)
|
|
|
|
t.Skip(3 * 2) // searchRange, entrySelector, rangeShift
|
|
|
|
for j := 0; j < segCount; j++ {
|
2018-11-29 03:37:12 +02:00
|
|
|
endCount = append(endCount, rune(t.ReadUShort()))
|
2017-07-05 23:10:57 +00:00
|
|
|
}
|
|
|
|
t.Skip(2) // reservedPad
|
|
|
|
for j := 0; j < segCount; j++ {
|
2018-11-29 03:37:12 +02:00
|
|
|
startCount = append(startCount, rune(t.ReadUShort()))
|
2017-07-05 23:10:57 +00:00
|
|
|
}
|
|
|
|
for j := 0; j < segCount; j++ {
|
|
|
|
idDelta = append(idDelta, t.ReadShort())
|
|
|
|
}
|
2017-09-01 13:20:51 +00:00
|
|
|
offset, _ := t.f.Seek(int64(0), os.SEEK_CUR)
|
2017-07-05 23:10:57 +00:00
|
|
|
for j := 0; j < segCount; j++ {
|
|
|
|
idRangeOffset = append(idRangeOffset, t.ReadUShort())
|
|
|
|
}
|
|
|
|
for j := 0; j < segCount; j++ {
|
|
|
|
c1 := startCount[j]
|
|
|
|
c2 := endCount[j]
|
|
|
|
d := idDelta[j]
|
|
|
|
ro := idRangeOffset[j]
|
|
|
|
if ro > 0 {
|
|
|
|
t.f.Seek(offset+2*int64(j)+int64(ro), os.SEEK_SET)
|
|
|
|
}
|
|
|
|
for c := c1; c <= c2; c++ {
|
|
|
|
if c == 0xFFFF {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
var gid int32
|
|
|
|
if ro > 0 {
|
|
|
|
gid = int32(t.ReadUShort())
|
|
|
|
if gid > 0 {
|
|
|
|
gid += int32(d)
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
gid = int32(c) + int32(d)
|
|
|
|
}
|
|
|
|
if gid >= 65536 {
|
|
|
|
gid -= 65536
|
|
|
|
}
|
|
|
|
if gid > 0 {
|
2018-11-29 04:02:20 +02:00
|
|
|
t.rec.Chars[c] = GID(gid)
|
2017-07-05 23:10:57 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2018-07-25 12:00:49 +10:00
|
|
|
return nil
|
2017-07-05 23:10:57 +00:00
|
|
|
}
|
|
|
|
|
2017-09-01 13:20:51 +00:00
|
|
|
// parseCmapSubtable10 parses information from an (1,0) subtable (symbol).
|
2018-06-27 12:25:59 +10:00
|
|
|
func (t *ttfParser) parseCmapSubtable10(offset10 int64) error {
|
2017-09-01 13:20:51 +00:00
|
|
|
|
|
|
|
if t.rec.Chars == nil {
|
2018-11-29 04:02:20 +02:00
|
|
|
t.rec.Chars = make(map[rune]GID)
|
2017-09-01 13:20:51 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
t.f.Seek(int64(t.tables["cmap"])+offset10, os.SEEK_SET)
|
2018-07-13 17:40:27 +10:00
|
|
|
var length, language uint32
|
2017-09-01 13:20:51 +00:00
|
|
|
format := t.ReadUShort()
|
2018-07-13 17:40:27 +10:00
|
|
|
if format < 8 {
|
|
|
|
length = uint32(t.ReadUShort())
|
|
|
|
language = uint32(t.ReadUShort())
|
|
|
|
} else {
|
|
|
|
t.ReadUShort()
|
|
|
|
length = t.ReadULong()
|
|
|
|
language = t.ReadULong()
|
2017-09-01 13:20:51 +00:00
|
|
|
}
|
2018-08-14 21:28:57 +10:00
|
|
|
common.Log.Trace("parseCmapSubtable10: format=%d length=%d language=%d",
|
2018-07-13 17:40:27 +10:00
|
|
|
format, length, language)
|
2017-09-01 13:20:51 +00:00
|
|
|
|
2018-07-13 17:40:27 +10:00
|
|
|
if format != 0 {
|
|
|
|
return errors.New("unsupported cmap subtable format")
|
|
|
|
}
|
2017-09-01 13:20:51 +00:00
|
|
|
|
2018-07-13 17:40:27 +10:00
|
|
|
dataStr, err := t.ReadStr(256)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
2017-09-01 13:20:51 +00:00
|
|
|
}
|
2018-07-13 17:40:27 +10:00
|
|
|
data := []byte(dataStr)
|
2017-09-01 13:20:51 +00:00
|
|
|
|
2018-11-29 04:02:20 +02:00
|
|
|
for code, gid := range data {
|
|
|
|
t.rec.Chars[rune(code)] = GID(gid)
|
|
|
|
if gid != 0 {
|
|
|
|
fmt.Printf("\t0x%02x ➞ 0x%02x=%c\n", code, gid, rune(gid))
|
2018-07-13 17:40:27 +10:00
|
|
|
}
|
|
|
|
}
|
2018-06-27 12:25:59 +10:00
|
|
|
return nil
|
2017-09-01 13:20:51 +00:00
|
|
|
}
|
|
|
|
|
2018-07-13 17:40:27 +10:00
|
|
|
// ParseCmap parses the cmap table in a TrueType font.
|
2018-07-25 12:00:49 +10:00
|
|
|
func (t *ttfParser) ParseCmap() error {
|
2017-09-01 13:20:51 +00:00
|
|
|
var offset int64
|
2018-07-25 12:00:49 +10:00
|
|
|
if err := t.Seek("cmap"); err != nil {
|
|
|
|
return err
|
2017-09-01 13:20:51 +00:00
|
|
|
}
|
2018-08-14 21:28:57 +10:00
|
|
|
common.Log.Trace("ParseCmap")
|
2018-07-24 21:32:02 +10:00
|
|
|
t.ReadUShort() // version is ignored.
|
2017-09-01 13:20:51 +00:00
|
|
|
numTables := int(t.ReadUShort())
|
2018-07-13 17:40:27 +10:00
|
|
|
offset10 := int64(0)
|
2017-09-01 13:20:51 +00:00
|
|
|
offset31 := int64(0)
|
|
|
|
for j := 0; j < numTables; j++ {
|
|
|
|
platformID := t.ReadUShort()
|
|
|
|
encodingID := t.ReadUShort()
|
|
|
|
offset = int64(t.ReadULong())
|
|
|
|
if platformID == 3 && encodingID == 1 {
|
|
|
|
// (3,1) subtable. Windows Unicode.
|
|
|
|
offset31 = offset
|
2018-09-17 17:57:52 +10:00
|
|
|
} else if platformID == 1 && encodingID == 0 {
|
|
|
|
offset10 = offset
|
2018-07-13 17:40:27 +10:00
|
|
|
}
|
2017-09-01 13:20:51 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Latin font support based on (3,1) table encoding.
|
|
|
|
if offset31 != 0 {
|
2018-07-25 12:00:49 +10:00
|
|
|
if err := t.parseCmapSubtable31(offset31); err != nil {
|
|
|
|
return err
|
2017-09-01 13:20:51 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Many non-Latin fonts (including asian fonts) use subtable (1,0).
|
2018-07-13 17:40:27 +10:00
|
|
|
if offset10 != 0 {
|
2018-07-25 12:00:49 +10:00
|
|
|
if err := t.parseCmapVersion(offset10); err != nil {
|
|
|
|
return err
|
2017-09-01 13:20:51 +00:00
|
|
|
}
|
2018-07-13 17:40:27 +10:00
|
|
|
}
|
2018-09-17 17:57:52 +10:00
|
|
|
if offset31 == 0 && offset10 == 0 {
|
|
|
|
common.Log.Debug("ttfParser.ParseCmap. No 31 or 10 table.")
|
|
|
|
}
|
2017-09-01 13:20:51 +00:00
|
|
|
|
2018-07-25 12:00:49 +10:00
|
|
|
return nil
|
2017-09-01 13:20:51 +00:00
|
|
|
}
|
|
|
|
|
2018-07-13 17:40:27 +10:00
|
|
|
func (t *ttfParser) parseCmapVersion(offset int64) error {
|
2018-07-15 16:28:56 +10:00
|
|
|
common.Log.Trace("parseCmapVersion: offset=%d", offset)
|
2018-07-13 17:40:27 +10:00
|
|
|
|
|
|
|
if t.rec.Chars == nil {
|
2018-11-29 04:02:20 +02:00
|
|
|
t.rec.Chars = make(map[rune]GID)
|
2018-07-13 17:40:27 +10:00
|
|
|
}
|
|
|
|
|
|
|
|
t.f.Seek(int64(t.tables["cmap"])+offset, os.SEEK_SET)
|
|
|
|
var length, language uint32
|
|
|
|
format := t.ReadUShort()
|
|
|
|
if format < 8 {
|
|
|
|
length = uint32(t.ReadUShort())
|
|
|
|
language = uint32(t.ReadUShort())
|
|
|
|
} else {
|
|
|
|
t.ReadUShort()
|
|
|
|
length = t.ReadULong()
|
|
|
|
language = t.ReadULong()
|
|
|
|
}
|
|
|
|
common.Log.Debug("parseCmapVersion: format=%d length=%d language=%d",
|
|
|
|
format, length, language)
|
|
|
|
|
|
|
|
switch format {
|
|
|
|
case 0:
|
|
|
|
return t.parseCmapFormat0()
|
|
|
|
case 6:
|
|
|
|
return t.parseCmapFormat6()
|
2018-08-17 08:41:35 +10:00
|
|
|
case 12:
|
|
|
|
return t.parseCmapFormat12()
|
2018-07-13 17:40:27 +10:00
|
|
|
default:
|
|
|
|
common.Log.Debug("ERROR: Unsupported cmap format=%d", format)
|
2018-12-09 21:37:27 +02:00
|
|
|
return nil // TODO(peterwilliams97): Can't return an error here if creator_test.go is to pass.
|
2018-07-13 17:40:27 +10:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t *ttfParser) parseCmapFormat0() error {
|
|
|
|
dataStr, err := t.ReadStr(256)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
data := []byte(dataStr)
|
2018-07-15 16:28:56 +10:00
|
|
|
common.Log.Trace("parseCmapFormat0: %s\ndataStr=%+q\ndata=[% 02x]", t.rec.String(), dataStr, data)
|
2018-07-13 17:40:27 +10:00
|
|
|
|
2019-03-14 18:48:20 +02:00
|
|
|
for code, glyphID := range data {
|
|
|
|
t.rec.Chars[rune(code)] = GID(glyphID)
|
2018-07-13 17:40:27 +10:00
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t *ttfParser) parseCmapFormat6() error {
|
|
|
|
|
|
|
|
firstCode := int(t.ReadUShort())
|
|
|
|
entryCount := int(t.ReadUShort())
|
|
|
|
|
2018-07-15 16:28:56 +10:00
|
|
|
common.Log.Trace("parseCmapFormat6: %s firstCode=%d entryCount=%d",
|
2018-07-13 17:40:27 +10:00
|
|
|
t.rec.String(), firstCode, entryCount)
|
|
|
|
|
|
|
|
for i := 0; i < entryCount; i++ {
|
2019-03-14 18:48:20 +02:00
|
|
|
glyphID := GID(t.ReadUShort())
|
|
|
|
t.rec.Chars[rune(i+firstCode)] = glyphID
|
2018-07-13 17:40:27 +10:00
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2018-08-17 08:41:35 +10:00
|
|
|
func (t *ttfParser) parseCmapFormat12() error {
|
|
|
|
|
|
|
|
numGroups := t.ReadULong()
|
|
|
|
|
|
|
|
common.Log.Trace("parseCmapFormat12: %s numGroups=%d", t.rec.String(), numGroups)
|
|
|
|
|
|
|
|
for i := uint32(0); i < numGroups; i++ {
|
|
|
|
firstCode := t.ReadULong()
|
|
|
|
endCode := t.ReadULong()
|
|
|
|
startGlyph := t.ReadULong()
|
|
|
|
|
|
|
|
if firstCode > 0x0010FFFF || (0xD800 <= firstCode && firstCode <= 0xDFFF) {
|
2018-09-17 17:57:52 +10:00
|
|
|
return errors.New("invalid characters codes")
|
2018-08-17 08:41:35 +10:00
|
|
|
}
|
|
|
|
|
|
|
|
if endCode < firstCode || endCode > 0x0010FFFF || (0xD800 <= endCode && endCode <= 0xDFFF) {
|
2018-09-17 17:57:52 +10:00
|
|
|
return errors.New("invalid characters codes")
|
2018-08-17 08:41:35 +10:00
|
|
|
}
|
|
|
|
|
|
|
|
for j := uint32(0); j <= endCode-firstCode; j++ {
|
2019-03-14 18:48:20 +02:00
|
|
|
glyphID := startGlyph + j
|
2018-08-17 08:41:35 +10:00
|
|
|
if firstCode+j > 0x10FFFF {
|
|
|
|
common.Log.Debug("Format 12 cmap contains character beyond UCS-4")
|
|
|
|
}
|
|
|
|
|
2019-03-14 18:48:20 +02:00
|
|
|
t.rec.Chars[rune(i+firstCode)] = GID(glyphID)
|
2018-08-17 08:41:35 +10:00
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2018-09-24 17:53:12 +10:00
|
|
|
// ParseName parses the "name" table.
|
2018-07-25 12:00:49 +10:00
|
|
|
func (t *ttfParser) ParseName() error {
|
|
|
|
if err := t.Seek("name"); err != nil {
|
|
|
|
return err
|
2018-06-27 12:25:59 +10:00
|
|
|
}
|
|
|
|
tableOffset, _ := t.f.Seek(0, os.SEEK_CUR)
|
|
|
|
t.rec.PostScriptName = ""
|
|
|
|
t.Skip(2) // format
|
|
|
|
count := t.ReadUShort()
|
|
|
|
stringOffset := t.ReadUShort()
|
|
|
|
for j := uint16(0); j < count && t.rec.PostScriptName == ""; j++ {
|
|
|
|
t.Skip(3 * 2) // platformID, encodingID, languageID
|
|
|
|
nameID := t.ReadUShort()
|
|
|
|
length := t.ReadUShort()
|
|
|
|
offset := t.ReadUShort()
|
|
|
|
if nameID == 6 {
|
|
|
|
// PostScript name
|
|
|
|
t.f.Seek(int64(tableOffset)+int64(stringOffset)+int64(offset), os.SEEK_SET)
|
2018-07-25 12:00:49 +10:00
|
|
|
s, err := t.ReadStr(int(length))
|
2018-06-27 12:25:59 +10:00
|
|
|
if err != nil {
|
2018-07-25 12:00:49 +10:00
|
|
|
return err
|
2017-07-05 23:10:57 +00:00
|
|
|
}
|
2018-06-27 12:25:59 +10:00
|
|
|
s = strings.Replace(s, "\x00", "", -1)
|
2018-07-25 12:00:49 +10:00
|
|
|
re, err := regexp.Compile("[(){}<> /%[\\]]")
|
|
|
|
if err != nil {
|
|
|
|
return err
|
2018-06-27 12:25:59 +10:00
|
|
|
}
|
|
|
|
t.rec.PostScriptName = re.ReplaceAllString(s, "")
|
2017-07-05 23:10:57 +00:00
|
|
|
}
|
2018-06-27 12:25:59 +10:00
|
|
|
}
|
|
|
|
if t.rec.PostScriptName == "" {
|
2018-09-24 17:53:12 +10:00
|
|
|
common.Log.Debug("ParseName: The name PostScript was not found.")
|
2017-07-05 23:10:57 +00:00
|
|
|
}
|
2018-07-25 12:00:49 +10:00
|
|
|
return nil
|
2017-07-05 23:10:57 +00:00
|
|
|
}
|
|
|
|
|
2018-07-25 12:00:49 +10:00
|
|
|
func (t *ttfParser) ParseOS2() error {
|
|
|
|
if err := t.Seek("OS/2"); err != nil {
|
|
|
|
return err
|
2018-06-27 12:25:59 +10:00
|
|
|
}
|
|
|
|
version := t.ReadUShort()
|
2018-10-24 01:28:48 +03:00
|
|
|
t.Skip(4 * 2) // xAvgCharWidth, usWeightClass, usWidthClass
|
2018-06-27 12:25:59 +10:00
|
|
|
t.Skip(11*2 + 10 + 4*4 + 4)
|
|
|
|
fsSelection := t.ReadUShort()
|
|
|
|
t.rec.Bold = (fsSelection & 32) != 0
|
|
|
|
t.Skip(2 * 2) // usFirstCharIndex, usLastCharIndex
|
|
|
|
t.rec.TypoAscender = t.ReadShort()
|
|
|
|
t.rec.TypoDescender = t.ReadShort()
|
|
|
|
if version >= 2 {
|
|
|
|
t.Skip(3*2 + 2*4 + 2)
|
|
|
|
t.rec.CapHeight = t.ReadShort()
|
|
|
|
} else {
|
|
|
|
t.rec.CapHeight = 0
|
2017-07-05 23:10:57 +00:00
|
|
|
}
|
2018-07-25 12:00:49 +10:00
|
|
|
return nil
|
2017-07-05 23:10:57 +00:00
|
|
|
}
|
|
|
|
|
2018-07-15 16:28:56 +10:00
|
|
|
// ParsePost reads the "post" section in a TrueType font table and sets t.rec.GlyphNames.
|
2018-07-25 12:00:49 +10:00
|
|
|
func (t *ttfParser) ParsePost() error {
|
|
|
|
if err := t.Seek("post"); err != nil {
|
|
|
|
return err
|
2018-06-27 12:25:59 +10:00
|
|
|
}
|
|
|
|
|
2018-07-13 17:40:27 +10:00
|
|
|
formatType := t.Read32Fixed()
|
|
|
|
t.rec.ItalicAngle = t.Read32Fixed()
|
2018-06-27 12:25:59 +10:00
|
|
|
t.rec.UnderlinePosition = t.ReadShort()
|
|
|
|
t.rec.UnderlineThickness = t.ReadShort()
|
|
|
|
t.rec.IsFixedPitch = t.ReadULong() != 0
|
2018-07-24 21:32:02 +10:00
|
|
|
t.ReadULong() // minMemType42 ignored.
|
|
|
|
t.ReadULong() // maxMemType42 ignored.
|
|
|
|
t.ReadULong() // mimMemType1 ignored.
|
|
|
|
t.ReadULong() // maxMemType1 ignored.
|
2018-07-13 17:40:27 +10:00
|
|
|
|
2018-07-15 16:28:56 +10:00
|
|
|
common.Log.Trace("ParsePost: formatType=%f", formatType)
|
2018-07-13 17:40:27 +10:00
|
|
|
|
|
|
|
switch formatType {
|
2018-07-15 16:28:56 +10:00
|
|
|
case 1.0: // This font file contains the standard Macintosh TrueTyp 258 glyphs.
|
2018-07-13 17:40:27 +10:00
|
|
|
t.rec.GlyphNames = macGlyphNames
|
|
|
|
case 2.0:
|
|
|
|
numGlyphs := int(t.ReadUShort())
|
|
|
|
glyphNameIndex := make([]int, numGlyphs)
|
2018-11-29 23:24:40 +02:00
|
|
|
t.rec.GlyphNames = make([]GlyphName, numGlyphs)
|
2018-07-13 17:40:27 +10:00
|
|
|
maxIndex := -1
|
|
|
|
for i := 0; i < numGlyphs; i++ {
|
|
|
|
index := int(t.ReadUShort())
|
|
|
|
glyphNameIndex[i] = index
|
|
|
|
// Index numbers between 0x7fff and 0xffff are reserved for future use
|
|
|
|
if index <= 0x7fff && index > maxIndex {
|
|
|
|
maxIndex = index
|
2018-06-27 12:25:59 +10:00
|
|
|
}
|
2018-07-13 17:40:27 +10:00
|
|
|
}
|
2018-11-29 23:24:40 +02:00
|
|
|
var nameArray []GlyphName
|
2018-07-13 17:40:27 +10:00
|
|
|
if maxIndex >= len(macGlyphNames) {
|
2018-11-29 23:24:40 +02:00
|
|
|
nameArray = make([]GlyphName, maxIndex-len(macGlyphNames)+1)
|
2018-07-13 17:40:27 +10:00
|
|
|
for i := 0; i < maxIndex-len(macGlyphNames)+1; i++ {
|
2019-03-09 20:45:19 +00:00
|
|
|
numberOfChars := int(t.readByte())
|
2018-07-13 17:40:27 +10:00
|
|
|
names, err := t.ReadStr(numberOfChars)
|
2018-06-27 12:25:59 +10:00
|
|
|
if err != nil {
|
|
|
|
return err
|
2017-09-01 13:20:51 +00:00
|
|
|
}
|
2018-11-29 23:24:40 +02:00
|
|
|
nameArray[i] = GlyphName(names)
|
2018-07-13 17:40:27 +10:00
|
|
|
}
|
|
|
|
}
|
|
|
|
for i := 0; i < numGlyphs; i++ {
|
|
|
|
index := glyphNameIndex[i]
|
|
|
|
if index < len(macGlyphNames) {
|
|
|
|
t.rec.GlyphNames[i] = macGlyphNames[index]
|
|
|
|
} else if index >= len(macGlyphNames) && index <= 32767 {
|
|
|
|
t.rec.GlyphNames[i] = nameArray[index-len(macGlyphNames)]
|
|
|
|
} else {
|
|
|
|
t.rec.GlyphNames[i] = ".undefined"
|
2017-09-01 13:20:51 +00:00
|
|
|
}
|
2018-06-27 12:25:59 +10:00
|
|
|
}
|
2018-07-13 17:40:27 +10:00
|
|
|
case 2.5:
|
2018-07-24 21:32:02 +10:00
|
|
|
glyphNameIndex := make([]int, t.numGlyphs)
|
2018-07-13 17:40:27 +10:00
|
|
|
for i := 0; i < len(glyphNameIndex); i++ {
|
|
|
|
offset := int(t.ReadSByte())
|
|
|
|
glyphNameIndex[i] = i + 1 + offset
|
|
|
|
}
|
2018-11-29 23:24:40 +02:00
|
|
|
t.rec.GlyphNames = make([]GlyphName, len(glyphNameIndex))
|
2018-07-13 17:40:27 +10:00
|
|
|
for i := 0; i < len(t.rec.GlyphNames); i++ {
|
|
|
|
name := macGlyphNames[glyphNameIndex[i]]
|
|
|
|
t.rec.GlyphNames[i] = name
|
|
|
|
}
|
|
|
|
case 3.0:
|
2018-11-20 15:49:28 +11:00
|
|
|
// No PostScript information is provided.
|
2018-07-13 17:40:27 +10:00
|
|
|
common.Log.Debug("No PostScript name information is provided for the font.")
|
|
|
|
default:
|
|
|
|
common.Log.Debug("ERROR: Unknown formatType=%f", formatType)
|
|
|
|
}
|
|
|
|
|
2018-07-25 12:00:49 +10:00
|
|
|
return nil
|
2017-07-05 23:10:57 +00:00
|
|
|
}
|
|
|
|
|
2018-07-15 16:28:56 +10:00
|
|
|
// The 258 standard mac glyph names used in 'post' format 1 and 2.
|
2018-11-29 23:24:40 +02:00
|
|
|
var macGlyphNames = []GlyphName{
|
2018-07-13 17:40:27 +10:00
|
|
|
".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",
|
|
|
|
}
|
|
|
|
|
|
|
|
// Seek moves the file pointer to the table named `tag`.
|
2018-06-27 12:25:59 +10:00
|
|
|
func (t *ttfParser) Seek(tag string) error {
|
2017-07-05 23:10:57 +00:00
|
|
|
ofs, ok := t.tables[tag]
|
2018-06-27 12:25:59 +10:00
|
|
|
if !ok {
|
|
|
|
return fmt.Errorf("table not found: %s", tag)
|
2017-07-05 23:10:57 +00:00
|
|
|
}
|
2018-06-27 12:25:59 +10:00
|
|
|
t.f.Seek(int64(ofs), os.SEEK_SET)
|
|
|
|
return nil
|
2017-07-05 23:10:57 +00:00
|
|
|
}
|
|
|
|
|
2018-07-24 21:32:02 +10:00
|
|
|
// Skip moves the file point n bytes forward.
|
2017-07-05 23:10:57 +00:00
|
|
|
func (t *ttfParser) Skip(n int) {
|
|
|
|
t.f.Seek(int64(n), os.SEEK_CUR)
|
|
|
|
}
|
|
|
|
|
2018-07-24 21:32:02 +10:00
|
|
|
// ReadStr reads `length` bytes from the file and returns them as a string, or an error if there was
|
|
|
|
// a problem.
|
2018-07-25 12:00:49 +10:00
|
|
|
func (t *ttfParser) ReadStr(length int) (string, error) {
|
2017-07-05 23:10:57 +00:00
|
|
|
buf := make([]byte, length)
|
2018-07-25 12:00:49 +10:00
|
|
|
n, err := t.f.Read(buf)
|
2018-06-27 12:25:59 +10:00
|
|
|
if err != nil {
|
2018-07-25 12:00:49 +10:00
|
|
|
return "", err
|
|
|
|
} else if n != length {
|
|
|
|
return "", fmt.Errorf("unable to read %d bytes", length)
|
2017-07-05 23:10:57 +00:00
|
|
|
}
|
2018-07-25 12:00:49 +10:00
|
|
|
return string(buf), nil
|
2017-07-05 23:10:57 +00:00
|
|
|
}
|
|
|
|
|
2019-03-09 20:45:19 +00:00
|
|
|
// readByte reads a byte and returns it as unsigned.
|
|
|
|
func (t *ttfParser) readByte() (val uint8) {
|
2017-09-01 13:20:51 +00:00
|
|
|
binary.Read(t.f, binary.BigEndian, &val)
|
2018-07-25 12:00:49 +10:00
|
|
|
return val
|
2017-09-01 13:20:51 +00:00
|
|
|
}
|
|
|
|
|
2018-07-24 21:32:02 +10:00
|
|
|
// ReadSByte reads a byte and returns it as signed.
|
2018-07-13 17:40:27 +10:00
|
|
|
func (t *ttfParser) ReadSByte() (val int8) {
|
|
|
|
binary.Read(t.f, binary.BigEndian, &val)
|
2018-07-25 12:00:49 +10:00
|
|
|
return val
|
2018-07-13 17:40:27 +10:00
|
|
|
}
|
|
|
|
|
2018-07-24 21:32:02 +10:00
|
|
|
// ReadUShort reads 2 bytes and returns them as a big endian unsigned 16 bit integer.
|
2017-07-05 23:10:57 +00:00
|
|
|
func (t *ttfParser) ReadUShort() (val uint16) {
|
|
|
|
binary.Read(t.f, binary.BigEndian, &val)
|
2018-07-25 12:00:49 +10:00
|
|
|
return val
|
2017-07-05 23:10:57 +00:00
|
|
|
}
|
|
|
|
|
2018-07-24 21:32:02 +10:00
|
|
|
// ReadShort reads 2 bytes and returns them as a big endian signed 16 bit integer.
|
2017-07-05 23:10:57 +00:00
|
|
|
func (t *ttfParser) ReadShort() (val int16) {
|
|
|
|
binary.Read(t.f, binary.BigEndian, &val)
|
2018-07-25 12:00:49 +10:00
|
|
|
return val
|
2017-07-05 23:10:57 +00:00
|
|
|
}
|
|
|
|
|
2018-07-24 21:32:02 +10:00
|
|
|
// ReadULong reads 4 bytes and returns them as a big endian unsigned 32 bit integer.
|
2017-07-05 23:10:57 +00:00
|
|
|
func (t *ttfParser) ReadULong() (val uint32) {
|
|
|
|
binary.Read(t.f, binary.BigEndian, &val)
|
2018-07-25 12:00:49 +10:00
|
|
|
return val
|
2017-07-05 23:10:57 +00:00
|
|
|
}
|
2018-07-13 17:40:27 +10:00
|
|
|
|
2018-12-09 20:22:33 +02:00
|
|
|
// Read32Fixed reads 4 bytes and returns them as a float, the first 2 bytes for the whole number and
|
2018-07-24 21:32:02 +10:00
|
|
|
// the second 2 bytes for the fraction.
|
2018-07-13 17:40:27 +10:00
|
|
|
func (t *ttfParser) Read32Fixed() float64 {
|
2018-10-24 01:30:44 +03:00
|
|
|
whole := float64(t.ReadShort())
|
2018-07-13 17:40:27 +10:00
|
|
|
frac := float64(t.ReadUShort()) / 65536.0
|
|
|
|
return whole + frac
|
|
|
|
}
|