codeberg.org/go-pdf/fpdf@v0.11.1/ttfparser.go (about)

     1  // Copyright ©2023 The go-pdf Authors. All rights reserved.
     2  // Use of this source code is governed by a MIT-style
     3  // license that can be found in the LICENSE file.
     4  
     5  /*
     6   * Copyright (c) 2013 Kurt Jung (Gmail: kurt.w.jung)
     7   *
     8   * Permission to use, copy, modify, and distribute this software for any
     9   * purpose with or without fee is hereby granted, provided that the above
    10   * copyright notice and this permission notice appear in all copies.
    11   *
    12   * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
    13   * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
    14   * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
    15   * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
    16   * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
    17   * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
    18   * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
    19   */
    20  
    21  package fpdf
    22  
    23  // Utility to parse TTF font files
    24  // Version:    1.0
    25  // Date:       2011-06-18
    26  // Author:     Olivier PLATHEY
    27  // Port to Go: Kurt Jung, 2013-07-15
    28  
    29  import (
    30  	"encoding/binary"
    31  	"fmt"
    32  	"io"
    33  	"os"
    34  	"regexp"
    35  	"strings"
    36  )
    37  
    38  // TtfType contains metrics of a TrueType font.
    39  type TtfType struct {
    40  	Embeddable             bool
    41  	UnitsPerEm             uint16
    42  	PostScriptName         string
    43  	Bold                   bool
    44  	ItalicAngle            int16
    45  	IsFixedPitch           bool
    46  	TypoAscender           int16
    47  	TypoDescender          int16
    48  	UnderlinePosition      int16
    49  	UnderlineThickness     int16
    50  	Xmin, Ymin, Xmax, Ymax int16
    51  	CapHeight              int16
    52  	Widths                 []uint16
    53  	Chars                  map[uint16]uint16
    54  }
    55  
    56  type ttfParser struct {
    57  	rec              TtfType
    58  	f                *os.File
    59  	tables           map[string]uint32
    60  	numberOfHMetrics uint16
    61  	numGlyphs        uint16
    62  }
    63  
    64  // TtfParse extracts various metrics from a TrueType font file.
    65  func TtfParse(fileStr string) (TtfRec TtfType, err error) {
    66  	var t ttfParser
    67  	t.f, err = os.Open(fileStr)
    68  	if err != nil {
    69  		return
    70  	}
    71  	version, err := t.ReadStr(4)
    72  	if err != nil {
    73  		return
    74  	}
    75  	if version == "OTTO" {
    76  		err = fmt.Errorf("fonts based on PostScript outlines are not supported")
    77  		return
    78  	}
    79  	if version != "\x00\x01\x00\x00" {
    80  		err = fmt.Errorf("unrecognized file format")
    81  		return
    82  	}
    83  	numTables := int(t.ReadUShort())
    84  	t.Skip(3 * 2) // searchRange, entrySelector, rangeShift
    85  	t.tables = make(map[string]uint32)
    86  	var tag string
    87  	for j := 0; j < numTables; j++ {
    88  		tag, err = t.ReadStr(4)
    89  		if err != nil {
    90  			return
    91  		}
    92  		t.Skip(4) // checkSum
    93  		offset := t.ReadULong()
    94  		t.Skip(4) // length
    95  		t.tables[tag] = offset
    96  	}
    97  	err = t.ParseComponents()
    98  	if err != nil {
    99  		return
   100  	}
   101  	t.f.Close()
   102  	TtfRec = t.rec
   103  	return
   104  }
   105  
   106  func (t *ttfParser) ParseComponents() (err error) {
   107  	err = t.ParseHead()
   108  	if err == nil {
   109  		err = t.ParseHhea()
   110  		if err == nil {
   111  			err = t.ParseMaxp()
   112  			if err == nil {
   113  				err = t.ParseHmtx()
   114  				if err == nil {
   115  					err = t.ParseCmap()
   116  					if err == nil {
   117  						err = t.ParseName()
   118  						if err == nil {
   119  							err = t.ParseOS2()
   120  							if err == nil {
   121  								err = t.ParsePost()
   122  							}
   123  						}
   124  					}
   125  				}
   126  			}
   127  		}
   128  	}
   129  	return
   130  }
   131  
   132  func (t *ttfParser) ParseHead() (err error) {
   133  	err = t.Seek("head")
   134  	t.Skip(3 * 4) // version, fontRevision, checkSumAdjustment
   135  	magicNumber := t.ReadULong()
   136  	if magicNumber != 0x5F0F3CF5 {
   137  		err = fmt.Errorf("incorrect magic number")
   138  		return
   139  	}
   140  	t.Skip(2) // flags
   141  	t.rec.UnitsPerEm = t.ReadUShort()
   142  	t.Skip(2 * 8) // created, modified
   143  	t.rec.Xmin = t.ReadShort()
   144  	t.rec.Ymin = t.ReadShort()
   145  	t.rec.Xmax = t.ReadShort()
   146  	t.rec.Ymax = t.ReadShort()
   147  	return
   148  }
   149  
   150  func (t *ttfParser) ParseHhea() (err error) {
   151  	err = t.Seek("hhea")
   152  	if err == nil {
   153  		t.Skip(4 + 15*2)
   154  		t.numberOfHMetrics = t.ReadUShort()
   155  	}
   156  	return
   157  }
   158  
   159  func (t *ttfParser) ParseMaxp() (err error) {
   160  	err = t.Seek("maxp")
   161  	if err == nil {
   162  		t.Skip(4)
   163  		t.numGlyphs = t.ReadUShort()
   164  	}
   165  	return
   166  }
   167  
   168  func (t *ttfParser) ParseHmtx() (err error) {
   169  	err = t.Seek("hmtx")
   170  	if err == nil {
   171  		t.rec.Widths = make([]uint16, 0, 8)
   172  		for j := uint16(0); j < t.numberOfHMetrics; j++ {
   173  			t.rec.Widths = append(t.rec.Widths, t.ReadUShort())
   174  			t.Skip(2) // lsb
   175  		}
   176  		if t.numberOfHMetrics < t.numGlyphs {
   177  			lastWidth := t.rec.Widths[t.numberOfHMetrics-1]
   178  			for j := t.numberOfHMetrics; j < t.numGlyphs; j++ {
   179  				t.rec.Widths = append(t.rec.Widths, lastWidth)
   180  			}
   181  		}
   182  	}
   183  	return
   184  }
   185  
   186  func (t *ttfParser) ParseCmap() (err error) {
   187  	var offset int64
   188  	if err = t.Seek("cmap"); err != nil {
   189  		return
   190  	}
   191  	t.Skip(2) // version
   192  	numTables := int(t.ReadUShort())
   193  	offset31 := int64(0)
   194  	for j := 0; j < numTables; j++ {
   195  		platformID := t.ReadUShort()
   196  		encodingID := t.ReadUShort()
   197  		offset = int64(t.ReadULong())
   198  		if platformID == 3 && encodingID == 1 {
   199  			offset31 = offset
   200  		}
   201  	}
   202  	if offset31 == 0 {
   203  		err = fmt.Errorf("no Unicode encoding found")
   204  		return
   205  	}
   206  	startCount := make([]uint16, 0, 8)
   207  	endCount := make([]uint16, 0, 8)
   208  	idDelta := make([]int16, 0, 8)
   209  	idRangeOffset := make([]uint16, 0, 8)
   210  	t.rec.Chars = make(map[uint16]uint16)
   211  	_, err = t.f.Seek(int64(t.tables["cmap"])+offset31, io.SeekStart)
   212  	if err != nil {
   213  		err = fmt.Errorf("could not seek to cmap table: %w", err)
   214  		return
   215  	}
   216  	format := t.ReadUShort()
   217  	if format != 4 {
   218  		err = fmt.Errorf("unexpected subtable format: %d", format)
   219  		return
   220  	}
   221  	t.Skip(2 * 2) // length, language
   222  	segCount := int(t.ReadUShort() / 2)
   223  	t.Skip(3 * 2) // searchRange, entrySelector, rangeShift
   224  	for j := 0; j < segCount; j++ {
   225  		endCount = append(endCount, t.ReadUShort())
   226  	}
   227  	t.Skip(2) // reservedPad
   228  	for j := 0; j < segCount; j++ {
   229  		startCount = append(startCount, t.ReadUShort())
   230  	}
   231  	for j := 0; j < segCount; j++ {
   232  		idDelta = append(idDelta, t.ReadShort())
   233  	}
   234  	offset, _ = t.f.Seek(int64(0), io.SeekCurrent)
   235  	for j := 0; j < segCount; j++ {
   236  		idRangeOffset = append(idRangeOffset, t.ReadUShort())
   237  	}
   238  	for j := 0; j < segCount; j++ {
   239  		c1 := startCount[j]
   240  		c2 := endCount[j]
   241  		d := idDelta[j]
   242  		ro := idRangeOffset[j]
   243  		if ro > 0 {
   244  			_, err = t.f.Seek(offset+2*int64(j)+int64(ro), io.SeekStart)
   245  			if err != nil {
   246  				return fmt.Errorf("could not seek to id range offset: %w", err)
   247  			}
   248  		}
   249  		for c := c1; c <= c2; c++ {
   250  			if c == 0xFFFF {
   251  				break
   252  			}
   253  			var gid int32
   254  			if ro > 0 {
   255  				gid = int32(t.ReadUShort())
   256  				if gid > 0 {
   257  					gid += int32(d)
   258  				}
   259  			} else {
   260  				gid = int32(c) + int32(d)
   261  			}
   262  			if gid >= 65536 {
   263  				gid -= 65536
   264  			}
   265  			if gid > 0 {
   266  				t.rec.Chars[c] = uint16(gid)
   267  			}
   268  		}
   269  	}
   270  	return
   271  }
   272  
   273  func (t *ttfParser) ParseName() (err error) {
   274  	err = t.Seek("name")
   275  	if err == nil {
   276  		tableOffset, _ := t.f.Seek(0, io.SeekCurrent)
   277  		t.rec.PostScriptName = ""
   278  		t.Skip(2) // format
   279  		count := t.ReadUShort()
   280  		stringOffset := t.ReadUShort()
   281  		for j := uint16(0); j < count && t.rec.PostScriptName == ""; j++ {
   282  			t.Skip(3 * 2) // platformID, encodingID, languageID
   283  			nameID := t.ReadUShort()
   284  			length := t.ReadUShort()
   285  			offset := t.ReadUShort()
   286  			if nameID == 6 {
   287  				// PostScript name
   288  				_, err = t.f.Seek(int64(tableOffset)+int64(stringOffset)+int64(offset), io.SeekStart)
   289  				if err != nil {
   290  					return
   291  				}
   292  				var s string
   293  				s, err = t.ReadStr(int(length))
   294  				if err != nil {
   295  					return
   296  				}
   297  				s = strings.Replace(s, "\x00", "", -1)
   298  				var re *regexp.Regexp
   299  				if re, err = regexp.Compile(`[(){}<> /%[\]]`); err != nil {
   300  					return
   301  				}
   302  				t.rec.PostScriptName = re.ReplaceAllString(s, "")
   303  			}
   304  		}
   305  		if t.rec.PostScriptName == "" {
   306  			err = fmt.Errorf("the name PostScript was not found")
   307  		}
   308  	}
   309  	return
   310  }
   311  
   312  func (t *ttfParser) ParseOS2() (err error) {
   313  	err = t.Seek("OS/2")
   314  	if err == nil {
   315  		version := t.ReadUShort()
   316  		t.Skip(3 * 2) // xAvgCharWidth, usWeightClass, usWidthClass
   317  		fsType := t.ReadUShort()
   318  		t.rec.Embeddable = (fsType != 2) && (fsType&0x200) == 0
   319  		t.Skip(11*2 + 10 + 4*4 + 4)
   320  		fsSelection := t.ReadUShort()
   321  		t.rec.Bold = (fsSelection & 32) != 0
   322  		t.Skip(2 * 2) // usFirstCharIndex, usLastCharIndex
   323  		t.rec.TypoAscender = t.ReadShort()
   324  		t.rec.TypoDescender = t.ReadShort()
   325  		if version >= 2 {
   326  			t.Skip(3*2 + 2*4 + 2)
   327  			t.rec.CapHeight = t.ReadShort()
   328  		} else {
   329  			t.rec.CapHeight = 0
   330  		}
   331  	}
   332  	return
   333  }
   334  
   335  func (t *ttfParser) ParsePost() (err error) {
   336  	err = t.Seek("post")
   337  	if err == nil {
   338  		t.Skip(4) // version
   339  		t.rec.ItalicAngle = t.ReadShort()
   340  		t.Skip(2) // Skip decimal part
   341  		t.rec.UnderlinePosition = t.ReadShort()
   342  		t.rec.UnderlineThickness = t.ReadShort()
   343  		t.rec.IsFixedPitch = t.ReadULong() != 0
   344  	}
   345  	return
   346  }
   347  
   348  func (t *ttfParser) Seek(tag string) (err error) {
   349  	ofs, ok := t.tables[tag]
   350  	if !ok {
   351  		return fmt.Errorf("table not found: %s", tag)
   352  	}
   353  
   354  	_, err = t.f.Seek(int64(ofs), io.SeekStart)
   355  	if err != nil {
   356  		return fmt.Errorf("could not seek to table %q: %w", tag, err)
   357  	}
   358  	return
   359  }
   360  
   361  func (t *ttfParser) Skip(n int) {
   362  	_, err := t.f.Seek(int64(n), io.SeekCurrent)
   363  	if err != nil {
   364  		panic(fmt.Errorf("could not skip %d bytes: %w", n, err))
   365  	}
   366  }
   367  
   368  func (t *ttfParser) ReadStr(length int) (str string, err error) {
   369  	var n int
   370  	buf := make([]byte, length)
   371  	n, err = t.f.Read(buf)
   372  	if err == nil {
   373  		if n == length {
   374  			str = string(buf)
   375  		} else {
   376  			err = fmt.Errorf("unable to read %d bytes", length)
   377  		}
   378  	}
   379  	return
   380  }
   381  
   382  func (t *ttfParser) ReadUShort() (val uint16) {
   383  	err := binary.Read(t.f, binary.BigEndian, &val)
   384  	if err != nil {
   385  		panic(fmt.Errorf("could not read u16: %w", err))
   386  	}
   387  	return
   388  }
   389  
   390  func (t *ttfParser) ReadShort() (val int16) {
   391  	err := binary.Read(t.f, binary.BigEndian, &val)
   392  	if err != nil {
   393  		panic(fmt.Errorf("could not read i16: %w", err))
   394  	}
   395  	return
   396  }
   397  
   398  func (t *ttfParser) ReadULong() (val uint32) {
   399  	err := binary.Read(t.f, binary.BigEndian, &val)
   400  	if err != nil {
   401  		panic(fmt.Errorf("could not read u32: %w", err))
   402  	}
   403  	return
   404  }