| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002 |
- // Copyright 2016 The Go Authors. All rights reserved.
- // Use of this source code is governed by a BSD-style
- // license that can be found in the LICENSE file.
- //go:generate go run gen.go
- // Package sfnt implements a decoder for TTF (TrueType Fonts) and OTF (OpenType
- // Fonts). Such fonts are also known as SFNT fonts.
- //
- // This package provides a low-level API and does not depend on vector
- // rasterization packages. Glyphs are represented as vectors, not pixels.
- //
- // The sibling golang.org/x/image/font/opentype package provides a high-level
- // API, including glyph rasterization.
- //
- // This package provides a decoder in that it produces a TTF's glyphs (and
- // other metadata such as advance width and kerning pairs): give me the 'A'
- // from times_new_roman.ttf.
- //
- // Unlike the image.Image decoder functions (gif.Decode, jpeg.Decode and
- // png.Decode) in Go's standard library, an sfnt.Font needs ongoing access to
- // the TTF data (as a []byte or io.ReaderAt) after the sfnt.ParseXxx functions
- // return. If parsing a []byte, its elements are assumed immutable while the
- // sfnt.Font remains in use. If parsing an *os.File, you should not close the
- // file until after you're done with the sfnt.Font.
- //
- // The []byte or io.ReaderAt data given to ParseXxx can be re-written to
- // another io.Writer, copying the underlying TTF file, but this package does
- // not provide an encoder. Specifically, there is no API to build a different
- // TTF file, whether 'from scratch' or by modifying an existing one.
- package sfnt // import "golang.org/x/image/font/sfnt"
- // This implementation was written primarily to the
- // https://www.microsoft.com/en-us/Typography/OpenTypeSpecification.aspx
- // specification. Additional documentation is at
- // http://developer.apple.com/fonts/TTRefMan/
- //
- // The pyftinspect tool from https://github.com/fonttools/fonttools is useful
- // for inspecting SFNT fonts.
- //
- // The ttfdump tool is also useful. For example:
- // ttfdump -t cmap ../testdata/CFFTest.otf dump.txt
- import (
- "errors"
- "image"
- "io"
- "golang.org/x/image/font"
- "golang.org/x/image/math/fixed"
- "golang.org/x/text/encoding/charmap"
- )
- // These constants are not part of the specifications, but are limitations used
- // by this implementation.
- const (
- // This value is arbitrary, but defends against parsing malicious font
- // files causing excessive memory allocations. For reference, Adobe's
- // SourceHanSansSC-Regular.otf has 65535 glyphs and:
- // - its format-4 cmap table has 1581 segments.
- // - its format-12 cmap table has 16498 segments.
- //
- // TODO: eliminate this constraint? If the cmap table is very large, load
- // some or all of it lazily (at the time Font.GlyphIndex is called) instead
- // of all of it eagerly (at the time Font.initialize is called), while
- // keeping an upper bound on the memory used? This will make the code in
- // cmap.go more complicated, considering that all of the Font methods are
- // safe to call concurrently, as long as each call has a different *Buffer.
- maxCmapSegments = 20000
- // TODO: similarly, load subroutine locations lazily. Adobe's
- // SourceHanSansSC-Regular.otf has up to 30000 subroutines.
- maxNumSubroutines = 40000
- maxCompoundRecursionDepth = 8
- maxCompoundStackSize = 64
- maxGlyphDataLength = 64 * 1024
- maxHintBits = 256
- maxNumFontDicts = 256
- maxNumFonts = 256
- maxNumTables = 256
- maxRealNumberStrLen = 64 // Maximum length in bytes of the "-123.456E-7" representation.
- // (maxTableOffset + maxTableLength) will not overflow an int32.
- maxTableLength = 1 << 29
- maxTableOffset = 1 << 29
- )
- var (
- // ErrColoredGlyph indicates that the requested glyph is not a monochrome
- // vector glyph, such as a colored (bitmap or vector) emoji glyph.
- ErrColoredGlyph = errors.New("sfnt: colored glyph")
- // ErrNotFound indicates that the requested value was not found.
- ErrNotFound = errors.New("sfnt: not found")
- errInvalidBounds = errors.New("sfnt: invalid bounds")
- errInvalidCFFTable = errors.New("sfnt: invalid CFF table")
- errInvalidCmapTable = errors.New("sfnt: invalid cmap table")
- errInvalidDfont = errors.New("sfnt: invalid dfont")
- errInvalidFont = errors.New("sfnt: invalid font")
- errInvalidFontCollection = errors.New("sfnt: invalid font collection")
- errInvalidGPOSTable = errors.New("sfnt: invalid GPOS table")
- errInvalidGlyphData = errors.New("sfnt: invalid glyph data")
- errInvalidGlyphDataLength = errors.New("sfnt: invalid glyph data length")
- errInvalidHeadTable = errors.New("sfnt: invalid head table")
- errInvalidHheaTable = errors.New("sfnt: invalid hhea table")
- errInvalidHmtxTable = errors.New("sfnt: invalid hmtx table")
- errInvalidKernTable = errors.New("sfnt: invalid kern table")
- errInvalidLocaTable = errors.New("sfnt: invalid loca table")
- errInvalidLocationData = errors.New("sfnt: invalid location data")
- errInvalidMaxpTable = errors.New("sfnt: invalid maxp table")
- errInvalidNameTable = errors.New("sfnt: invalid name table")
- errInvalidOS2Table = errors.New("sfnt: invalid OS/2 table")
- errInvalidPostTable = errors.New("sfnt: invalid post table")
- errInvalidSingleFont = errors.New("sfnt: invalid single font (data is a font collection)")
- errInvalidSourceData = errors.New("sfnt: invalid source data")
- errInvalidTableOffset = errors.New("sfnt: invalid table offset")
- errInvalidTableTagOrder = errors.New("sfnt: invalid table tag order")
- errInvalidUCS2String = errors.New("sfnt: invalid UCS-2 string")
- errUnsupportedCFFFDSelectTable = errors.New("sfnt: unsupported CFF FDSelect table")
- errUnsupportedCFFVersion = errors.New("sfnt: unsupported CFF version")
- errUnsupportedClassDefFormat = errors.New("sfnt: unsupported class definition format")
- errUnsupportedCmapEncodings = errors.New("sfnt: unsupported cmap encodings")
- errUnsupportedCollection = errors.New("sfnt: unsupported collection")
- errUnsupportedCompoundGlyph = errors.New("sfnt: unsupported compound glyph")
- errUnsupportedCoverageFormat = errors.New("sfnt: unsupported coverage format")
- errUnsupportedExtensionPosFormat = errors.New("sfnt: unsupported extension positioning format")
- errUnsupportedGPOSTable = errors.New("sfnt: unsupported GPOS table")
- errUnsupportedGlyphDataLength = errors.New("sfnt: unsupported glyph data length")
- errUnsupportedKernTable = errors.New("sfnt: unsupported kern table")
- errUnsupportedNumberOfCmapSegments = errors.New("sfnt: unsupported number of cmap segments")
- errUnsupportedNumberOfFontDicts = errors.New("sfnt: unsupported number of font dicts")
- errUnsupportedNumberOfFonts = errors.New("sfnt: unsupported number of fonts")
- errUnsupportedNumberOfHints = errors.New("sfnt: unsupported number of hints")
- errUnsupportedNumberOfSubroutines = errors.New("sfnt: unsupported number of subroutines")
- errUnsupportedNumberOfTables = errors.New("sfnt: unsupported number of tables")
- errUnsupportedPlatformEncoding = errors.New("sfnt: unsupported platform encoding")
- errUnsupportedPostTable = errors.New("sfnt: unsupported post table")
- errUnsupportedRealNumberEncoding = errors.New("sfnt: unsupported real number encoding")
- errUnsupportedTableOffsetLength = errors.New("sfnt: unsupported table offset or length")
- errUnsupportedType2Charstring = errors.New("sfnt: unsupported Type 2 Charstring")
- )
- // GlyphIndex is a glyph index in a Font.
- type GlyphIndex uint16
- // NameID identifies a name table entry.
- //
- // See the "Name IDs" section of
- // https://www.microsoft.com/typography/otspec/name.htm
- type NameID uint16
- const (
- NameIDCopyright NameID = 0
- NameIDFamily NameID = 1
- NameIDSubfamily NameID = 2
- NameIDUniqueIdentifier NameID = 3
- NameIDFull NameID = 4
- NameIDVersion NameID = 5
- NameIDPostScript NameID = 6
- NameIDTrademark NameID = 7
- NameIDManufacturer NameID = 8
- NameIDDesigner NameID = 9
- NameIDDescription NameID = 10
- NameIDVendorURL NameID = 11
- NameIDDesignerURL NameID = 12
- NameIDLicense NameID = 13
- NameIDLicenseURL NameID = 14
- NameIDTypographicFamily NameID = 16
- NameIDTypographicSubfamily NameID = 17
- NameIDCompatibleFull NameID = 18
- NameIDSampleText NameID = 19
- NameIDPostScriptCID NameID = 20
- NameIDWWSFamily NameID = 21
- NameIDWWSSubfamily NameID = 22
- NameIDLightBackgroundPalette NameID = 23
- NameIDDarkBackgroundPalette NameID = 24
- NameIDVariationsPostScriptPrefix NameID = 25
- )
- // Units are an integral number of abstract, scalable "font units". The em
- // square is typically 1000 or 2048 "font units". This would map to a certain
- // number (e.g. 30 pixels) of physical pixels, depending on things like the
- // display resolution (DPI) and font size (e.g. a 12 point font).
- type Units int32
- // scale returns x divided by unitsPerEm, rounded to the nearest fixed.Int26_6
- // value (1/64th of a pixel).
- func scale(x fixed.Int26_6, unitsPerEm Units) fixed.Int26_6 {
- if x >= 0 {
- x += fixed.Int26_6(unitsPerEm) / 2
- } else {
- x -= fixed.Int26_6(unitsPerEm) / 2
- }
- return x / fixed.Int26_6(unitsPerEm)
- }
- func u16(b []byte) uint16 {
- _ = b[1] // Bounds check hint to compiler.
- return uint16(b[0])<<8 | uint16(b[1])<<0
- }
- func u32(b []byte) uint32 {
- _ = b[3] // Bounds check hint to compiler.
- return uint32(b[0])<<24 | uint32(b[1])<<16 | uint32(b[2])<<8 | uint32(b[3])<<0
- }
- // source is a source of byte data. Conceptually, it is like an io.ReaderAt,
- // except that a common source of SFNT font data is in-memory instead of
- // on-disk: a []byte containing the entire data, either as a global variable
- // (e.g. "goregular.TTF") or the result of an ioutil.ReadFile call. In such
- // cases, as an optimization, we skip the io.Reader / io.ReaderAt model of
- // copying from the source to a caller-supplied buffer, and instead provide
- // direct access to the underlying []byte data.
- type source struct {
- b []byte
- r io.ReaderAt
- // TODO: add a caching layer, if we're using the io.ReaderAt? Note that
- // this might make a source no longer safe to use concurrently.
- }
- // valid returns whether exactly one of s.b and s.r is nil.
- func (s *source) valid() bool {
- return (s.b == nil) != (s.r == nil)
- }
- // viewBufferWritable returns whether the []byte returned by source.view can be
- // written to by the caller, including by passing it to the same method
- // (source.view) on other receivers (i.e. different sources).
- //
- // In other words, it returns whether the source's underlying data is an
- // io.ReaderAt, not a []byte.
- func (s *source) viewBufferWritable() bool {
- return s.b == nil
- }
- // view returns the length bytes at the given offset. buf is an optional
- // scratch buffer to reduce allocations when calling view multiple times. A nil
- // buf is valid. The []byte returned may be a sub-slice of buf[:cap(buf)], or
- // it may be an unrelated slice. In any case, the caller should not modify the
- // contents of the returned []byte, other than passing that []byte back to this
- // method on the same source s.
- func (s *source) view(buf []byte, offset, length int) ([]byte, error) {
- if 0 > offset || offset > offset+length {
- return nil, errInvalidBounds
- }
- // Try reading from the []byte.
- if s.b != nil {
- if offset+length > len(s.b) {
- return nil, errInvalidBounds
- }
- return s.b[offset : offset+length], nil
- }
- // Read from the io.ReaderAt.
- if length <= cap(buf) {
- buf = buf[:length]
- } else {
- // Round length up to the nearest KiB. The slack can lead to fewer
- // allocations if the buffer is re-used for multiple source.view calls.
- n := length
- n += 1023
- n &^= 1023
- buf = make([]byte, length, n)
- }
- if n, err := s.r.ReadAt(buf, int64(offset)); n != length {
- return nil, err
- }
- return buf, nil
- }
- // varLenView returns bytes from the given offset for sub-tables with varying
- // length. The length of bytes is determined by staticLength plus n*itemLength,
- // where n is read as uint16 from countOffset (relative to offset). buf is an
- // optional scratch buffer (see source.view())
- func (s *source) varLenView(buf []byte, offset, staticLength, countOffset, itemLength int) ([]byte, int, error) {
- if 0 > offset || offset > offset+staticLength {
- return nil, 0, errInvalidBounds
- }
- if 0 > countOffset || countOffset+1 >= staticLength {
- return nil, 0, errInvalidBounds
- }
- // read static part which contains our count
- buf, err := s.view(buf, offset, staticLength)
- if err != nil {
- return nil, 0, err
- }
- count := int(u16(buf[countOffset:]))
- buf, err = s.view(buf, offset, staticLength+count*itemLength)
- if err != nil {
- return nil, 0, err
- }
- return buf, count, nil
- }
- // u16 returns the uint16 in the table t at the relative offset i.
- //
- // buf is an optional scratch buffer as per the source.view method.
- func (s *source) u16(buf []byte, t table, i int) (uint16, error) {
- if i < 0 || uint(t.length) < uint(i+2) {
- return 0, errInvalidBounds
- }
- buf, err := s.view(buf, int(t.offset)+i, 2)
- if err != nil {
- return 0, err
- }
- return u16(buf), nil
- }
- // u32 returns the uint32 in the table t at the relative offset i.
- //
- // buf is an optional scratch buffer as per the source.view method.
- func (s *source) u32(buf []byte, t table, i int) (uint32, error) {
- if i < 0 || uint(t.length) < uint(i+4) {
- return 0, errInvalidBounds
- }
- buf, err := s.view(buf, int(t.offset)+i, 4)
- if err != nil {
- return 0, err
- }
- return u32(buf), nil
- }
- // table is a section of the font data.
- type table struct {
- offset, length uint32
- }
- // ParseCollection parses an SFNT font collection, such as TTC or OTC data,
- // from a []byte data source.
- //
- // If passed data for a single font, a TTF or OTF instead of a TTC or OTC, it
- // will return a collection containing 1 font.
- //
- // The caller should not modify src while the Collection or its Fonts remain in
- // use. See the package documentation for details.
- func ParseCollection(src []byte) (*Collection, error) {
- c := &Collection{src: source{b: src}}
- if err := c.initialize(); err != nil {
- return nil, err
- }
- return c, nil
- }
- // ParseCollectionReaderAt parses an SFNT collection, such as TTC or OTC data,
- // from an io.ReaderAt data source.
- //
- // If passed data for a single font, a TTF or OTF instead of a TTC or OTC, it
- // will return a collection containing 1 font.
- //
- // The caller should not modify or close src while the Collection or its Fonts
- // remain in use. See the package documentation for details.
- func ParseCollectionReaderAt(src io.ReaderAt) (*Collection, error) {
- c := &Collection{src: source{r: src}}
- if err := c.initialize(); err != nil {
- return nil, err
- }
- return c, nil
- }
- // Collection is a collection of one or more fonts.
- //
- // All of the Collection methods are safe to call concurrently.
- type Collection struct {
- src source
- offsets []uint32
- isDfont bool
- }
- // NumFonts returns the number of fonts in the collection.
- func (c *Collection) NumFonts() int { return len(c.offsets) }
- func (c *Collection) initialize() error {
- // The https://www.microsoft.com/typography/otspec/otff.htm "Font
- // Collections" section describes the TTC header.
- //
- // https://github.com/kreativekorp/ksfl/wiki/Macintosh-Resource-File-Format
- // describes the dfont header.
- //
- // 16 is the maximum of sizeof(TTCHeader) and sizeof(DfontHeader).
- buf, err := c.src.view(nil, 0, 16)
- if err != nil {
- return err
- }
- // These cases match the switch statement in Font.initializeTables.
- switch u32(buf) {
- default:
- return errInvalidFontCollection
- case dfontResourceDataOffset:
- return c.parseDfont(buf, u32(buf[4:]), u32(buf[12:]))
- case 0x00010000, 0x4f54544f, 0x74727565: // 0x10000, "OTTO", "true"
- // Try parsing it as a single font instead of a collection.
- c.offsets = []uint32{0}
- case 0x74746366: // "ttcf".
- numFonts := u32(buf[8:])
- if numFonts == 0 || numFonts > maxNumFonts {
- return errUnsupportedNumberOfFonts
- }
- buf, err = c.src.view(nil, 12, int(4*numFonts))
- if err != nil {
- return err
- }
- c.offsets = make([]uint32, numFonts)
- for i := range c.offsets {
- o := u32(buf[4*i:])
- if o > maxTableOffset {
- return errUnsupportedTableOffsetLength
- }
- c.offsets[i] = o
- }
- }
- return nil
- }
- // 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
- // 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 (c *Collection) parseDfont(buf []byte, resourceMapOffset, resourceMapLength uint32) error {
- if resourceMapOffset > maxTableOffset || resourceMapLength > maxTableLength {
- return errUnsupportedTableOffsetLength
- }
- const headerSize = 28
- if resourceMapLength < headerSize {
- return errInvalidDfont
- }
- buf, err := c.src.view(buf, int(resourceMapOffset+24), 2)
- if err != nil {
- return err
- }
- typeListOffset := int(int16(u16(buf)))
- if typeListOffset < headerSize || resourceMapLength < uint32(typeListOffset)+2 {
- return errInvalidDfont
- }
- buf, err = c.src.view(buf, int(resourceMapOffset)+typeListOffset, 2)
- if err != nil {
- return err
- }
- typeCount := int(int16(u16(buf)))
- const tSize = 8
- if typeCount < 0 || tSize*uint32(typeCount) > resourceMapLength-uint32(typeListOffset)-2 {
- return errInvalidDfont
- }
- buf, err = c.src.view(buf, int(resourceMapOffset)+typeListOffset+2, tSize*typeCount)
- if err != nil {
- return err
- }
- resourceCount, resourceListOffset := 0, 0
- for i := 0; i < typeCount; i++ {
- if u32(buf[tSize*i:]) != 0x73666e74 { // "sfnt".
- continue
- }
- resourceCount = int(int16(u16(buf[tSize*i+4:])))
- if resourceCount < 0 {
- return 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."
- resourceCount++
- resourceListOffset = int(int16(u16(buf[tSize*i+6:])))
- if resourceListOffset < 0 {
- return errInvalidDfont
- }
- break
- }
- if resourceCount == 0 {
- return errInvalidDfont
- }
- if resourceCount > maxNumFonts {
- return errUnsupportedNumberOfFonts
- }
- const rSize = 12
- if o, n := uint32(typeListOffset+resourceListOffset), rSize*uint32(resourceCount); o > resourceMapLength || n > resourceMapLength-o {
- return errInvalidDfont
- } else {
- buf, err = c.src.view(buf, int(resourceMapOffset+o), int(n))
- if err != nil {
- return err
- }
- }
- c.offsets = make([]uint32, resourceCount)
- for i := range c.offsets {
- o := 0xffffff & u32(buf[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 errUnsupportedTableOffsetLength
- }
- c.offsets[i] = o
- }
- c.isDfont = true
- return nil
- }
- // Font returns the i'th font in the collection.
- func (c *Collection) Font(i int) (*Font, error) {
- if i < 0 || len(c.offsets) <= i {
- return nil, ErrNotFound
- }
- f := &Font{src: c.src}
- if err := f.initialize(int(c.offsets[i]), c.isDfont); err != nil {
- return nil, err
- }
- return f, nil
- }
- // Parse parses an SFNT font, such as TTF or OTF data, from a []byte data
- // source.
- //
- // The caller should not modify src while the Font remains in use. See the
- // package documentation for details.
- func Parse(src []byte) (*Font, error) {
- f := &Font{src: source{b: src}}
- if err := f.initialize(0, false); err != nil {
- return nil, err
- }
- return f, nil
- }
- // ParseReaderAt parses an SFNT font, such as TTF or OTF data, from an
- // io.ReaderAt data source.
- //
- // The caller should not modify or close src while the Font remains in use. See
- // the package documentation for details.
- func ParseReaderAt(src io.ReaderAt) (*Font, error) {
- f := &Font{src: source{r: src}}
- if err := f.initialize(0, false); err != nil {
- return nil, err
- }
- return f, nil
- }
- // Font is an SFNT font.
- //
- // Many of its methods take a *Buffer argument, as re-using buffers can reduce
- // the total memory allocation of repeated Font method calls, such as measuring
- // and rasterizing every unique glyph in a string of text. If efficiency is not
- // a concern, passing a nil *Buffer is valid, and implies using a temporary
- // buffer for a single call.
- //
- // It is valid to re-use a *Buffer with multiple Font method calls, even with
- // different *Font receivers, as long as they are not concurrent calls.
- //
- // All of the Font methods are safe to call concurrently, as long as each call
- // has a different *Buffer (or nil).
- //
- // The Font methods that don't take a *Buffer argument are always safe to call
- // concurrently.
- //
- // Some methods provide lengths or coordinates, e.g. bounds, font metrics and
- // control points. All of these methods take a ppem parameter, which is the
- // number of pixels in 1 em, expressed as a 26.6 fixed point value. For
- // example, if 1 em is 10 pixels then ppem is fixed.I(10), which equals
- // fixed.Int26_6(10 << 6).
- //
- // To get those lengths or coordinates in terms of font units instead of
- // pixels, use ppem = fixed.Int26_6(f.UnitsPerEm()) and if those methods take a
- // font.Hinting parameter, use font.HintingNone. The return values will have
- // type fixed.Int26_6, but those numbers can be converted back to Units with no
- // further scaling necessary.
- type Font struct {
- src source
- // initialOffset is the file offset of the start of the font. This may be
- // non-zero for fonts within a font collection.
- initialOffset int32
- // https://www.microsoft.com/typography/otspec/otff.htm#otttables
- // "Required Tables".
- cmap table
- head table
- hhea table
- hmtx table
- maxp table
- name table
- os2 table
- post table
- // https://www.microsoft.com/typography/otspec/otff.htm#otttables
- // "Tables Related to TrueType Outlines".
- //
- // This implementation does not support hinting, so it does not read the
- // cvt, fpgm gasp or prep tables.
- glyf table
- loca table
- // https://www.microsoft.com/typography/otspec/otff.htm#otttables
- // "Tables Related to PostScript Outlines".
- //
- // TODO: cff2, vorg?
- cff table
- // https://www.microsoft.com/typography/otspec/otff.htm#otttables
- // "Tables Related to Bitmap Glyphs".
- //
- // TODO: Others?
- cblc table
- // https://www.microsoft.com/typography/otspec/otff.htm#otttables
- // "Advanced Typographic Tables".
- //
- // TODO: base, gdef, gsub, jstf, math?
- gpos table
- // https://www.microsoft.com/typography/otspec/otff.htm#otttables
- // "Other OpenType Tables".
- //
- // TODO: hdmx, vmtx? Others?
- kern table
- cached struct {
- ascent int32
- capHeight int32
- finalTableOffset int32
- glyphData glyphData
- glyphIndex glyphIndexFunc
- bounds [4]int16
- descent int32
- indexToLocFormat bool // false means short, true means long.
- isColorBitmap bool
- isPostScript bool
- kernNumPairs int32
- kernOffset int32
- kernFuncs []kernFunc
- lineGap int32
- numHMetrics int32
- post *PostTable
- slope [2]int32
- unitsPerEm Units
- xHeight int32
- }
- }
- // NumGlyphs returns the number of glyphs in f.
- func (f *Font) NumGlyphs() int { return len(f.cached.glyphData.locations) - 1 }
- // UnitsPerEm returns the number of units per em for f.
- func (f *Font) UnitsPerEm() Units { return f.cached.unitsPerEm }
- func (f *Font) initialize(offset int, isDfont bool) error {
- if !f.src.valid() {
- return errInvalidSourceData
- }
- buf, finalTableOffset, isPostScript, err := f.initializeTables(offset, isDfont)
- if err != nil {
- return err
- }
- // The order of these parseXxx calls matters. Later calls may depend on
- // information parsed by earlier calls, such as the maxp table's numGlyphs.
- // To enforce these dependencies, such information is passed and returned
- // explicitly, and the f.cached fields are only set afterwards.
- //
- // When implementing new parseXxx methods, take care not to call methods
- // such as Font.NumGlyphs that implicitly depend on f.cached fields.
- buf, bounds, indexToLocFormat, unitsPerEm, err := f.parseHead(buf)
- if err != nil {
- return err
- }
- buf, numGlyphs, err := f.parseMaxp(buf, isPostScript)
- if err != nil {
- return err
- }
- buf, glyphData, isColorBitmap, err := f.parseGlyphData(buf, numGlyphs, indexToLocFormat, isPostScript)
- if err != nil {
- return err
- }
- buf, glyphIndex, err := f.parseCmap(buf)
- if err != nil {
- return err
- }
- buf, kernNumPairs, kernOffset, err := f.parseKern(buf)
- if err != nil {
- return err
- }
- buf, kernFuncs, err := f.parseGPOSKern(buf)
- if err != nil {
- return err
- }
- buf, ascent, descent, lineGap, run, rise, numHMetrics, err := f.parseHhea(buf, numGlyphs)
- if err != nil {
- return err
- }
- buf, err = f.parseHmtx(buf, numGlyphs, numHMetrics)
- if err != nil {
- return err
- }
- buf, hasXHeightCapHeight, xHeight, capHeight, err := f.parseOS2(buf)
- if err != nil {
- return err
- }
- buf, post, err := f.parsePost(buf, numGlyphs)
- if err != nil {
- return err
- }
- f.cached.ascent = ascent
- f.cached.capHeight = capHeight
- f.cached.finalTableOffset = finalTableOffset
- f.cached.glyphData = glyphData
- f.cached.glyphIndex = glyphIndex
- f.cached.bounds = bounds
- f.cached.descent = descent
- f.cached.indexToLocFormat = indexToLocFormat
- f.cached.isColorBitmap = isColorBitmap
- f.cached.isPostScript = isPostScript
- f.cached.kernNumPairs = kernNumPairs
- f.cached.kernOffset = kernOffset
- f.cached.kernFuncs = kernFuncs
- f.cached.lineGap = lineGap
- f.cached.numHMetrics = numHMetrics
- f.cached.post = post
- f.cached.slope = [2]int32{run, rise}
- f.cached.unitsPerEm = unitsPerEm
- f.cached.xHeight = xHeight
- if !hasXHeightCapHeight {
- xh, ch, err := f.initOS2VersionBelow2()
- if err != nil {
- return err
- }
- f.cached.xHeight = xh
- f.cached.capHeight = ch
- }
- return nil
- }
- func (f *Font) initializeTables(offset int, isDfont bool) (buf1 []byte, finalTableOffset int32, isPostScript bool, err error) {
- f.initialOffset = int32(offset)
- if int(f.initialOffset) != offset {
- return nil, 0, false, errUnsupportedTableOffsetLength
- }
- // https://www.microsoft.com/typography/otspec/otff.htm "Organization of an
- // OpenType Font" says that "The OpenType font starts with the Offset
- // Table", which is 12 bytes.
- buf, err := f.src.view(nil, offset, 12)
- if err != nil {
- return nil, 0, false, err
- }
- // When updating the cases in this switch statement, also update the
- // Collection.initialize method.
- switch u32(buf) {
- default:
- return nil, 0, false, errInvalidFont
- case dfontResourceDataOffset:
- return nil, 0, false, errInvalidSingleFont
- case 0x00010000:
- // No-op.
- case 0x4f54544f: // "OTTO".
- isPostScript = true
- case 0x74727565: // "true"
- // No-op.
- case 0x74746366: // "ttcf".
- return nil, 0, false, errInvalidSingleFont
- }
- numTables := int(u16(buf[4:]))
- if numTables > maxNumTables {
- return nil, 0, false, errUnsupportedNumberOfTables
- }
- // "The Offset Table is followed immediately by the Table Record entries...
- // sorted in ascending order by tag", 16 bytes each.
- buf, err = f.src.view(buf, offset+12, 16*numTables)
- if err != nil {
- return nil, 0, false, err
- }
- for b, first, prevTag := buf, true, uint32(0); len(b) > 0; b = b[16:] {
- tag := u32(b)
- if first {
- first = false
- } else if tag <= prevTag {
- return nil, 0, false, errInvalidTableTagOrder
- }
- prevTag = tag
- o, n := u32(b[8:12]), u32(b[12:16])
- // For dfont files, the offset is relative to the resource, not the
- // file.
- if isDfont {
- origO := o
- o += uint32(offset)
- if o < origO {
- return nil, 0, false, errUnsupportedTableOffsetLength
- }
- }
- if o > maxTableOffset || n > maxTableLength {
- return nil, 0, false, errUnsupportedTableOffsetLength
- }
- // We ignore the checksums, but "all tables must begin on four byte
- // boundries [sic]".
- if o&3 != 0 {
- return nil, 0, false, errInvalidTableOffset
- }
- if finalTableOffset < int32(o+n) {
- finalTableOffset = int32(o + n)
- }
- // Match the 4-byte tag as a uint32. For example, "OS/2" is 0x4f532f32.
- switch tag {
- case 0x43424c43:
- f.cblc = table{o, n}
- case 0x43464620:
- f.cff = table{o, n}
- case 0x4f532f32:
- f.os2 = table{o, n}
- case 0x636d6170:
- f.cmap = table{o, n}
- case 0x676c7966:
- f.glyf = table{o, n}
- case 0x47504f53:
- f.gpos = table{o, n}
- case 0x68656164:
- f.head = table{o, n}
- case 0x68686561:
- f.hhea = table{o, n}
- case 0x686d7478:
- f.hmtx = table{o, n}
- case 0x6b65726e:
- f.kern = table{o, n}
- case 0x6c6f6361:
- f.loca = table{o, n}
- case 0x6d617870:
- f.maxp = table{o, n}
- case 0x6e616d65:
- f.name = table{o, n}
- case 0x706f7374:
- f.post = table{o, n}
- }
- }
- if (f.src.b != nil) && (int(finalTableOffset) > len(f.src.b)) {
- return nil, 0, false, errInvalidSourceData
- }
- return buf, finalTableOffset, isPostScript, nil
- }
- func (f *Font) parseCmap(buf []byte) (buf1 []byte, glyphIndex glyphIndexFunc, err error) {
- // https://www.microsoft.com/typography/OTSPEC/cmap.htm
- const headerSize, entrySize = 4, 8
- if f.cmap.length < headerSize {
- return nil, nil, errInvalidCmapTable
- }
- u, err := f.src.u16(buf, f.cmap, 2)
- if err != nil {
- return nil, nil, err
- }
- numSubtables := int(u)
- if f.cmap.length < headerSize+entrySize*uint32(numSubtables) {
- return nil, nil, errInvalidCmapTable
- }
- var (
- bestWidth int
- bestOffset uint32
- bestLength uint32
- bestFormat uint16
- )
- // Scan all of the subtables, picking the widest supported one. See the
- // platformEncodingWidth comment for more discussion of width.
- for i := 0; i < numSubtables; i++ {
- buf, err = f.src.view(buf, int(f.cmap.offset)+headerSize+entrySize*i, entrySize)
- if err != nil {
- return nil, nil, err
- }
- pid := u16(buf)
- psid := u16(buf[2:])
- width := platformEncodingWidth(pid, psid)
- if width <= bestWidth {
- continue
- }
- offset := u32(buf[4:])
- if offset > f.cmap.length-4 {
- return nil, nil, errInvalidCmapTable
- }
- buf, err = f.src.view(buf, int(f.cmap.offset+offset), 4)
- if err != nil {
- return nil, nil, err
- }
- format := u16(buf)
- if !supportedCmapFormat(format, pid, psid) {
- continue
- }
- length := uint32(u16(buf[2:]))
- bestWidth = width
- bestOffset = offset
- bestLength = length
- bestFormat = format
- }
- if bestWidth == 0 {
- return nil, nil, errUnsupportedCmapEncodings
- }
- return f.makeCachedGlyphIndex(buf, bestOffset, bestLength, bestFormat)
- }
- func (f *Font) parseHead(buf []byte) (buf1 []byte, bounds [4]int16, indexToLocFormat bool, unitsPerEm Units, err error) {
- // https://www.microsoft.com/typography/otspec/head.htm
- if f.head.length != 54 {
- return nil, [4]int16{}, false, 0, errInvalidHeadTable
- }
- u, err := f.src.u16(buf, f.head, 18)
- if err != nil {
- return nil, [4]int16{}, false, 0, err
- }
- if u == 0 {
- return nil, [4]int16{}, false, 0, errInvalidHeadTable
- }
- unitsPerEm = Units(u)
- for i := range bounds {
- u, err := f.src.u16(buf, f.head, 36+2*i)
- if err != nil {
- return nil, [4]int16{}, false, 0, err
- }
- bounds[i] = int16(u)
- }
- u, err = f.src.u16(buf, f.head, 50)
- if err != nil {
- return nil, [4]int16{}, false, 0, err
- }
- indexToLocFormat = u != 0
- return buf, bounds, indexToLocFormat, unitsPerEm, nil
- }
- func (f *Font) parseHhea(buf []byte, numGlyphs int32) (buf1 []byte, ascent, descent, lineGap, run, rise, numHMetrics int32, err error) {
- // https://www.microsoft.com/typography/OTSPEC/hhea.htm
- if f.hhea.length != 36 {
- return nil, 0, 0, 0, 0, 0, 0, errInvalidHheaTable
- }
- u, err := f.src.u16(buf, f.hhea, 34)
- if err != nil {
- return nil, 0, 0, 0, 0, 0, 0, err
- }
- if int32(u) > numGlyphs || u == 0 {
- return nil, 0, 0, 0, 0, 0, 0, errInvalidHheaTable
- }
- a, err := f.src.u16(buf, f.hhea, 4)
- if err != nil {
- return nil, 0, 0, 0, 0, 0, 0, err
- }
- d, err := f.src.u16(buf, f.hhea, 6)
- if err != nil {
- return nil, 0, 0, 0, 0, 0, 0, err
- }
- l, err := f.src.u16(buf, f.hhea, 8)
- if err != nil {
- return nil, 0, 0, 0, 0, 0, 0, err
- }
- ru, err := f.src.u16(buf, f.hhea, 20)
- if err != nil {
- return nil, 0, 0, 0, 0, 0, 0, err
- }
- ri, err := f.src.u16(buf, f.hhea, 18)
- if err != nil {
- return nil, 0, 0, 0, 0, 0, 0, err
- }
- return buf, int32(int16(a)), int32(int16(d)), int32(int16(l)), int32(int16(ru)), int32(int16(ri)), int32(u), nil
- }
- func (f *Font) parseHmtx(buf []byte, numGlyphs, numHMetrics int32) (buf1 []byte, err error) {
- // https://www.microsoft.com/typography/OTSPEC/hmtx.htm
- // The spec says that the hmtx table's length should be
- // "4*numHMetrics+2*(numGlyphs-numHMetrics)". However, some fonts seen in the
- // wild omit the "2*(nG-nHM)". See https://github.com/golang/go/issues/28379
- if f.hmtx.length != uint32(4*numHMetrics) && f.hmtx.length != uint32(4*numHMetrics+2*(numGlyphs-numHMetrics)) {
- return nil, errInvalidHmtxTable
- }
- return buf, nil
- }
- func (f *Font) parseKern(buf []byte) (buf1 []byte, kernNumPairs, kernOffset int32, err error) {
- // https://www.microsoft.com/typography/otspec/kern.htm
- if f.kern.length == 0 {
- return buf, 0, 0, nil
- }
- const headerSize = 4
- if f.kern.length < headerSize {
- return nil, 0, 0, errInvalidKernTable
- }
- buf, err = f.src.view(buf, int(f.kern.offset), headerSize)
- if err != nil {
- return nil, 0, 0, err
- }
- offset := int(f.kern.offset) + headerSize
- length := int(f.kern.length) - headerSize
- switch version := u16(buf); version {
- case 0:
- if numTables := int(u16(buf[2:])); numTables == 0 {
- return buf, 0, 0, nil
- } else if numTables > 1 {
- // TODO: support multiple subtables. For now, fall through and use
- // only the first one.
- }
- return f.parseKernVersion0(buf, offset, length)
- case 1:
- if buf[2] != 0 || buf[3] != 0 {
- return nil, 0, 0, errUnsupportedKernTable
- }
- // Microsoft's https://www.microsoft.com/typography/otspec/kern.htm
- // says that "Apple has extended the definition of the 'kern' table to
- // provide additional functionality. The Apple extensions are not
- // supported on Windows."
- //
- // The format is relatively complicated, including encoding a state
- // machine, but rarely seen. We follow Microsoft's and FreeType's
- // behavior and simply ignore it. Theoretically, we could follow
- // https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6kern.html
- // but it doesn't seem worth the effort.
- return buf, 0, 0, nil
- }
- return nil, 0, 0, errUnsupportedKernTable
- }
- func (f *Font) parseKernVersion0(buf []byte, offset, length int) (buf1 []byte, kernNumPairs, kernOffset int32, err error) {
- const headerSize = 6
- if length < headerSize {
- return nil, 0, 0, errInvalidKernTable
- }
- buf, err = f.src.view(buf, offset, headerSize)
- if err != nil {
- return nil, 0, 0, err
- }
- if version := u16(buf); version != 0 {
- return nil, 0, 0, errUnsupportedKernTable
- }
- subtableLengthU16 := u16(buf[2:])
- if int(subtableLengthU16) < headerSize || length < int(subtableLengthU16) {
- return nil, 0, 0, errInvalidKernTable
- }
- if coverageBits := buf[5]; coverageBits != 0x01 {
- // We only support horizontal kerning.
- return nil, 0, 0, errUnsupportedKernTable
- }
- offset += headerSize
- length -= headerSize
- subtableLengthU16 -= headerSize
- switch format := buf[4]; format {
- case 0:
- return f.parseKernFormat0(buf, offset, length, subtableLengthU16)
- case 2:
- // If we could find such a font, we could write code to support it, but
- // a comment in the equivalent FreeType code (sfnt/ttkern.c) says that
- // they've never seen such a font.
- }
- return nil, 0, 0, errUnsupportedKernTable
- }
- func (f *Font) parseKernFormat0(buf []byte, offset, length int, subtableLengthU16 uint16) (buf1 []byte, kernNumPairs, kernOffset int32, err error) {
- const headerSize, entrySize = 8, 6
- if length < headerSize {
- return nil, 0, 0, errInvalidKernTable
- }
- buf, err = f.src.view(buf, offset, headerSize)
- if err != nil {
- return nil, 0, 0, err
- }
- kernNumPairs = int32(u16(buf))
- // The subtable length from the kern table is only uint16. Fonts like
- // Cambria, Calibri or Corbel have more then 10k kerning pairs and the
- // actual subtable size is truncated to uint16. Compare size with KERN
- // length and truncated size with subtable length.
- n := headerSize + entrySize*int(kernNumPairs)
- if (length < n) || (subtableLengthU16 != uint16(n)) {
- return nil, 0, 0, errInvalidKernTable
- }
- return buf, kernNumPairs, int32(offset) + headerSize, nil
- }
- func (f *Font) parseMaxp(buf []byte, isPostScript bool) (buf1 []byte, numGlyphs int32, err error) {
- // https://www.microsoft.com/typography/otspec/maxp.htm
- if isPostScript {
- if f.maxp.length != 6 {
- return nil, 0, errInvalidMaxpTable
- }
- } else {
- if f.maxp.length != 32 {
- return nil, 0, errInvalidMaxpTable
- }
- }
- u, err := f.src.u16(buf, f.maxp, 4)
- if err != nil {
- return nil, 0, err
- }
- return buf, int32(u), nil
- }
- type glyphData struct {
- // The glyph data for the i'th glyph index is in
- // src[locations[i+0]:locations[i+1]].
- //
- // The slice length equals 1 plus the number of glyphs.
- locations []uint32
- // For PostScript fonts, the bytecode for the i'th global or local
- // subroutine is in src[x[i+0]:x[i+1]].
- //
- // The []uint32 slice length equals 1 plus the number of subroutines
- gsubrs []uint32
- singleSubrs []uint32
- multiSubrs [][]uint32
- fdSelect fdSelect
- }
- func (f *Font) parseGlyphData(buf []byte, numGlyphs int32, indexToLocFormat, isPostScript bool) (buf1 []byte, ret glyphData, isColorBitmap bool, err error) {
- if isPostScript {
- p := cffParser{
- src: &f.src,
- base: int(f.cff.offset),
- offset: int(f.cff.offset),
- end: int(f.cff.offset + f.cff.length),
- }
- ret, err = p.parse(numGlyphs)
- if err != nil {
- return nil, glyphData{}, false, err
- }
- } else if f.loca.length != 0 {
- ret.locations, err = parseLoca(&f.src, f.loca, f.glyf.offset, indexToLocFormat, numGlyphs)
- if err != nil {
- return nil, glyphData{}, false, err
- }
- } else if f.cblc.length != 0 {
- isColorBitmap = true
- // TODO: parse the CBLC (and CBDT) tables. For now, we return a font
- // with empty glyphs.
- ret.locations = make([]uint32, numGlyphs+1)
- }
- if len(ret.locations) != int(numGlyphs+1) {
- return nil, glyphData{}, false, errInvalidLocationData
- }
- return buf, ret, isColorBitmap, nil
- }
- func (f *Font) glyphTopOS2(b *Buffer, ppem fixed.Int26_6, r rune) (int32, error) {
- ind, err := f.GlyphIndex(b, r)
- if err != nil && err != ErrNotFound {
- return 0, err
- } else if ind == 0 {
- return 0, nil
- }
- // Y axis points down
- var min fixed.Int26_6
- seg, err := f.LoadGlyph(b, ind, ppem, nil)
- if err != nil {
- return 0, err
- }
- for _, s := range seg {
- for _, p := range s.Args {
- if p.Y < min {
- min = p.Y
- }
- }
- }
- return int32(min), nil
- }
- func (f *Font) initOS2VersionBelow2() (xHeight, capHeight int32, err error) {
- ppem := fixed.Int26_6(f.UnitsPerEm())
- var b Buffer
- // sxHeight equal to the top of the unscaled and unhinted glyph bounding box
- // of the glyph encoded at U+0078 (LATIN SMALL LETTER X).
- xh, err := f.glyphTopOS2(&b, ppem, 'x')
- if err != nil {
- return 0, 0, err
- }
- // 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).
- ch, err := f.glyphTopOS2(&b, ppem, 'H')
- if err != nil {
- return 0, 0, err
- }
- return int32(xh), int32(ch), nil
- }
- func (f *Font) parseOS2(buf []byte) (buf1 []byte, hasXHeightCapHeight bool, xHeight, capHeight int32, err error) {
- // https://docs.microsoft.com/da-dk/typography/opentype/spec/os2
- if f.os2.length == 0 {
- // Apple TrueType fonts might omit the OS/2 table.
- return buf, false, 0, 0, nil
- } else if f.os2.length < 2 {
- return nil, false, 0, 0, errInvalidOS2Table
- }
- vers, err := f.src.u16(buf, f.os2, 0)
- if err != nil {
- return nil, false, 0, 0, err
- }
- if vers < 2 {
- // "The original TrueType specification had this table at 68 bytes long."
- // https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6OS2.html
- const headerSize = 68
- if f.os2.length < headerSize {
- return nil, false, 0, 0, errInvalidOS2Table
- }
- // Will resolve xHeight and capHeight later, see initOS2VersionBelow2.
- return buf, false, 0, 0, nil
- }
- const headerSize = 96
- if f.os2.length < headerSize {
- return nil, false, 0, 0, errInvalidOS2Table
- }
- xh, err := f.src.u16(buf, f.os2, 86)
- if err != nil {
- return nil, false, 0, 0, err
- }
- ch, err := f.src.u16(buf, f.os2, 88)
- if err != nil {
- return nil, false, 0, 0, err
- }
- return buf, true, int32(int16(xh)), int32(int16(ch)), nil
- }
- // PostTable represents an information stored in the PostScript font section.
- type PostTable struct {
- // Version of the version tag of the "post" table.
- Version uint32
- // ItalicAngle in counter-clockwise degrees from the vertical. Zero for
- // upright text, negative for text that leans to the right (forward).
- ItalicAngle float64
- // 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 bool
- }
- // PostTable returns the information from the font's "post" table. It can
- // return nil, if the font doesn't have such a table.
- //
- // See https://docs.microsoft.com/en-us/typography/opentype/spec/post
- func (f *Font) PostTable() *PostTable {
- return f.cached.post
- }
- func (f *Font) parsePost(buf []byte, numGlyphs int32) (buf1 []byte, post *PostTable, err error) {
- // https://www.microsoft.com/typography/otspec/post.htm
- const headerSize = 32
- if f.post.length < headerSize {
- return nil, nil, errInvalidPostTable
- }
- u, err := f.src.u32(buf, f.post, 0)
- if err != nil {
- return nil, nil, err
- }
- switch u {
- case 0x10000:
- // No-op.
- case 0x20000:
- if f.post.length < headerSize+2+2*uint32(numGlyphs) {
- return nil, nil, errInvalidPostTable
- }
- case 0x30000:
- // No-op.
- default:
- return nil, nil, errUnsupportedPostTable
- }
- ang, err := f.src.u32(buf, f.post, 4)
- if err != nil {
- return nil, nil, err
- }
- up, err := f.src.u16(buf, f.post, 8)
- if err != nil {
- return nil, nil, err
- }
- ut, err := f.src.u16(buf, f.post, 10)
- if err != nil {
- return nil, nil, err
- }
- fp, err := f.src.u32(buf, f.post, 12)
- if err != nil {
- return nil, nil, err
- }
- post = &PostTable{
- Version: u,
- ItalicAngle: float64(int32(ang)) / 0x10000,
- UnderlinePosition: int16(up),
- UnderlineThickness: int16(ut),
- IsFixedPitch: fp != 0,
- }
- return buf, post, nil
- }
- // Bounds returns the union of a Font's glyphs' bounds.
- //
- // In the returned Rectangle26_6's (x, y) coordinates, the Y axis increases
- // down.
- func (f *Font) Bounds(b *Buffer, ppem fixed.Int26_6, h font.Hinting) (fixed.Rectangle26_6, error) {
- // The 0, 3, 2, 1 indices are to flip the Y coordinates. OpenType's Y axis
- // increases up. Go's standard graphics libraries' Y axis increases down.
- r := fixed.Rectangle26_6{
- Min: fixed.Point26_6{
- X: +scale(fixed.Int26_6(f.cached.bounds[0])*ppem, f.cached.unitsPerEm),
- Y: -scale(fixed.Int26_6(f.cached.bounds[3])*ppem, f.cached.unitsPerEm),
- },
- Max: fixed.Point26_6{
- X: +scale(fixed.Int26_6(f.cached.bounds[2])*ppem, f.cached.unitsPerEm),
- Y: -scale(fixed.Int26_6(f.cached.bounds[1])*ppem, f.cached.unitsPerEm),
- },
- }
- if h == font.HintingFull {
- // Quantize the Min down and Max up to a whole pixel.
- r.Min.X = (r.Min.X + 0) &^ 63
- r.Min.Y = (r.Min.Y + 0) &^ 63
- r.Max.X = (r.Max.X + 63) &^ 63
- r.Max.Y = (r.Max.Y + 63) &^ 63
- }
- return r, nil
- }
- // TODO: API for looking up glyph variants?? For example, some fonts may
- // provide both slashed and dotted zero glyphs ('0'), or regular and 'old
- // style' numerals, and users can direct software to choose a variant.
- type glyphIndexFunc func(f *Font, b *Buffer, r rune) (GlyphIndex, error)
- // GlyphIndex returns the glyph index for the given rune.
- //
- // It returns (0, nil) if there is no glyph for r.
- // https://www.microsoft.com/typography/OTSPEC/cmap.htm says that "Character
- // codes that do not correspond to any glyph in the font should be mapped to
- // glyph index 0. The glyph at this location must be a special glyph
- // representing a missing character, commonly known as .notdef."
- func (f *Font) GlyphIndex(b *Buffer, r rune) (GlyphIndex, error) {
- return f.cached.glyphIndex(f, b, r)
- }
- func (f *Font) viewGlyphData(b *Buffer, x GlyphIndex) (buf []byte, offset, length uint32, err error) {
- xx := int(x)
- if f.NumGlyphs() <= xx {
- return nil, 0, 0, ErrNotFound
- }
- i := f.cached.glyphData.locations[xx+0]
- j := f.cached.glyphData.locations[xx+1]
- if j < i {
- return nil, 0, 0, errInvalidGlyphDataLength
- }
- if j-i > maxGlyphDataLength {
- return nil, 0, 0, errUnsupportedGlyphDataLength
- }
- buf, err = b.view(&f.src, int(i), int(j-i))
- return buf, i, j - i, err
- }
- // LoadGlyphOptions are the options to the Font.LoadGlyph method.
- type LoadGlyphOptions struct {
- // TODO: transform / hinting.
- }
- // LoadGlyph returns the vector segments for the x'th glyph. ppem is the number
- // of pixels in 1 em.
- //
- // If b is non-nil, the segments become invalid to use once b is re-used.
- //
- // In the returned Segments' (x, y) coordinates, the Y axis increases down.
- //
- // It returns ErrNotFound if the glyph index is out of range. It returns
- // ErrColoredGlyph if the glyph is not a monochrome vector glyph, such as a
- // colored (bitmap or vector) emoji glyph.
- func (f *Font) LoadGlyph(b *Buffer, x GlyphIndex, ppem fixed.Int26_6, opts *LoadGlyphOptions) (Segments, error) {
- if b == nil {
- b = &Buffer{}
- }
- b.segments = b.segments[:0]
- if f.cached.isColorBitmap {
- return nil, ErrColoredGlyph
- }
- if f.cached.isPostScript {
- buf, offset, length, err := f.viewGlyphData(b, x)
- if err != nil {
- return nil, err
- }
- b.psi.type2Charstrings.initialize(f, b, x)
- if err := b.psi.run(psContextType2Charstring, buf, offset, length); err != nil {
- return nil, err
- }
- if !b.psi.type2Charstrings.ended {
- return nil, errInvalidCFFTable
- }
- } else if err := loadGlyf(f, b, x, 0, 0); err != nil {
- return nil, err
- }
- // Scale the segments. If we want to support hinting, we'll have to push
- // the scaling computation into the PostScript / TrueType specific glyph
- // loading code, such as the appendGlyfSegments body, since TrueType
- // hinting bytecode works on the scaled glyph vectors. For now, though,
- // it's simpler to scale as a post-processing step.
- //
- // We also flip the Y coordinates. OpenType's Y axis increases up. Go's
- // standard graphics libraries' Y axis increases down.
- for i := range b.segments {
- a := &b.segments[i].Args
- for j := range a {
- a[j].X = +scale(a[j].X*ppem, f.cached.unitsPerEm)
- a[j].Y = -scale(a[j].Y*ppem, f.cached.unitsPerEm)
- }
- }
- // TODO: look at opts to transform / hint the Buffer.segments.
- return b.segments, nil
- }
- func (f *Font) glyphNameFormat10(x GlyphIndex) (string, error) {
- if x >= numBuiltInPostNames {
- return "", ErrNotFound
- }
- // https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6post.html
- i := builtInPostNamesOffsets[x+0]
- j := builtInPostNamesOffsets[x+1]
- return builtInPostNamesData[i:j], nil
- }
- func (f *Font) glyphNameFormat20(b *Buffer, x GlyphIndex) (string, error) {
- if b == nil {
- b = &Buffer{}
- }
- // The wire format for a Version 2 post table is documented at:
- // https://www.microsoft.com/typography/otspec/post.htm
- const glyphNameIndexOffset = 34
- buf, err := b.view(&f.src, int(f.post.offset)+glyphNameIndexOffset+2*int(x), 2)
- if err != nil {
- return "", err
- }
- u := u16(buf)
- if u < numBuiltInPostNames {
- i := builtInPostNamesOffsets[u+0]
- j := builtInPostNamesOffsets[u+1]
- return builtInPostNamesData[i:j], nil
- }
- // 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 "", errUnsupportedPostTable
- }
- u -= numBuiltInPostNames
- // Iterate through the list of Pascal-formatted strings. A linear scan is
- // clearly O(u), which isn't great (as the obvious loop, calling
- // Font.GlyphName, to get all of the glyph names in a font has quadratic
- // complexity), but the wire format doesn't suggest a better alternative.
- offset := glyphNameIndexOffset + 2*f.NumGlyphs()
- buf, err = b.view(&f.src, int(f.post.offset)+offset, int(f.post.length)-offset)
- if err != nil {
- return "", err
- }
- for {
- if len(buf) == 0 {
- return "", errInvalidPostTable
- }
- n := 1 + int(buf[0])
- if len(buf) < n {
- return "", errInvalidPostTable
- }
- if u == 0 {
- return string(buf[1:n]), nil
- }
- buf = buf[n:]
- u--
- }
- }
- // GlyphName returns the name of the x'th glyph.
- //
- // Not every font contains glyph names. If not present, GlyphName will return
- // ("", nil).
- //
- // If present, the glyph name, provided by the font, is assumed to follow the
- // Adobe Glyph List Specification:
- // https://github.com/adobe-type-tools/agl-specification/blob/master/README.md
- //
- // This is also known as the "Adobe Glyph Naming convention", the "Adobe
- // document [for] Unicode and Glyph Names" or "PostScript glyph names".
- //
- // It returns ErrNotFound if the glyph index is out of range.
- func (f *Font) GlyphName(b *Buffer, x GlyphIndex) (string, error) {
- if int(x) >= f.NumGlyphs() {
- return "", ErrNotFound
- }
- if f.cached.post == nil {
- return "", nil
- }
- switch f.cached.post.Version {
- case 0x10000:
- return f.glyphNameFormat10(x)
- case 0x20000:
- return f.glyphNameFormat20(b, x)
- default:
- return "", nil
- }
- }
- // GlyphBounds returns the bounding box of the x'th glyph, drawn at a dot equal
- // to the origin, and that glyph's advance width. ppem is the number of pixels
- // in 1 em.
- //
- // It returns ErrNotFound if the glyph index is out of range.
- //
- // The glyph's ascent and descent are equal to -bounds.Min.Y and +bounds.Max.Y.
- // The glyph's left-side and right-side bearings are equal to bounds.Min.X and
- // advance-bounds.Max.X. A visual depiction of what these metrics are is at
- // https://developer.apple.com/library/archive/documentation/TextFonts/Conceptual/CocoaTextArchitecture/Art/glyphterms_2x.png
- func (f *Font) GlyphBounds(b *Buffer, x GlyphIndex, ppem fixed.Int26_6, h font.Hinting) (bounds fixed.Rectangle26_6, advance fixed.Int26_6, err error) {
- if int(x) >= f.NumGlyphs() {
- return fixed.Rectangle26_6{}, 0, ErrNotFound
- }
- if b == nil {
- b = &Buffer{}
- }
- // https://www.microsoft.com/typography/OTSPEC/hmtx.htm says that "As an
- // optimization, the number of records can be less than the number of
- // glyphs, in which case the advance width value of the last record applies
- // to all remaining glyph IDs."
- metricIndex := x
- if n := GlyphIndex(f.cached.numHMetrics - 1); x > n {
- metricIndex = n
- }
- buf, err := b.view(&f.src, int(f.hmtx.offset)+4*int(metricIndex), 2)
- if err != nil {
- return fixed.Rectangle26_6{}, 0, err
- }
- advance = fixed.Int26_6(u16(buf))
- advance = scale(advance*ppem, f.cached.unitsPerEm)
- if h == font.HintingFull {
- // Quantize the fixed.Int26_6 value to the nearest pixel.
- advance = (advance + 32) &^ 63
- }
- // Ignore the hmtx LSB entries and the glyf bounding boxes. Instead, always
- // calculate bounds from the segments. OpenType does contain the bounds for
- // each glyph in the glyf table, but the bounds are not available for
- // compound glyphs. CFF/PostScript also have no explicit bounds and must be
- // obtained from the segments.
- segments, err := f.LoadGlyph(b, x, ppem, &LoadGlyphOptions{
- // TODO: pass h, the font.Hinting.
- })
- if err != nil {
- return fixed.Rectangle26_6{}, 0, err
- }
- return segments.Bounds(), advance, nil
- }
- // GlyphAdvance returns the advance width for the x'th glyph. ppem is the
- // number of pixels in 1 em.
- //
- // It returns ErrNotFound if the glyph index is out of range.
- func (f *Font) GlyphAdvance(b *Buffer, x GlyphIndex, ppem fixed.Int26_6, h font.Hinting) (fixed.Int26_6, error) {
- if int(x) >= f.NumGlyphs() {
- return 0, ErrNotFound
- }
- if b == nil {
- b = &Buffer{}
- }
- // https://www.microsoft.com/typography/OTSPEC/hmtx.htm says that "As an
- // optimization, the number of records can be less than the number of
- // glyphs, in which case the advance width value of the last record applies
- // to all remaining glyph IDs."
- if n := GlyphIndex(f.cached.numHMetrics - 1); x > n {
- x = n
- }
- buf, err := b.view(&f.src, int(f.hmtx.offset)+4*int(x), 2)
- if err != nil {
- return 0, err
- }
- adv := fixed.Int26_6(u16(buf))
- adv = scale(adv*ppem, f.cached.unitsPerEm)
- if h == font.HintingFull {
- // Quantize the fixed.Int26_6 value to the nearest pixel.
- adv = (adv + 32) &^ 63
- }
- return adv, nil
- }
- // Kern returns the horizontal adjustment for the kerning pair (x0, x1). A
- // positive kern means to move the glyphs further apart. ppem is the number of
- // pixels in 1 em.
- //
- // It returns ErrNotFound if either glyph index is out of range.
- func (f *Font) Kern(b *Buffer, x0, x1 GlyphIndex, ppem fixed.Int26_6, h font.Hinting) (fixed.Int26_6, error) {
- // Use GPOS kern tables if available.
- if f.cached.kernFuncs != nil {
- for _, kf := range f.cached.kernFuncs {
- adv, err := kf(x0, x1)
- if err == ErrNotFound {
- continue
- }
- if err != nil {
- return 0, err
- }
- kern := fixed.Int26_6(adv)
- kern = scale(kern*ppem, f.cached.unitsPerEm)
- if h == font.HintingFull {
- // Quantize the fixed.Int26_6 value to the nearest pixel.
- kern = (kern + 32) &^ 63
- }
- return kern, nil
- }
- return 0, ErrNotFound
- }
- // Fallback to kern table.
- // TODO: Convert kern table handling into kernFunc and decide in Parse if
- // GPOS or kern should be used.
- if n := f.NumGlyphs(); int(x0) >= n || int(x1) >= n {
- return 0, ErrNotFound
- }
- // Not every font has a kern table. If it doesn't, or if that table is
- // ignored, there's no need to allocate a Buffer.
- if f.cached.kernNumPairs == 0 {
- return 0, nil
- }
- if b == nil {
- b = &Buffer{}
- }
- key := uint32(x0)<<16 | uint32(x1)
- lo, hi := int32(0), f.cached.kernNumPairs
- for lo < hi {
- i := (lo + hi) / 2
- // TODO: this view call inside the inner loop can lead to many small
- // reads instead of fewer larger reads, which can be expensive. We
- // should be able to do better, although we don't want to make (one)
- // arbitrarily large read. Perhaps we should round up reads to 4K or 8K
- // chunks. For reference, Arial.ttf's kern table is 5472 bytes.
- // Times_New_Roman.ttf's kern table is 5220 bytes.
- const entrySize = 6
- buf, err := b.view(&f.src, int(f.cached.kernOffset+i*entrySize), entrySize)
- if err != nil {
- return 0, err
- }
- k := u32(buf)
- if k < key {
- lo = i + 1
- } else if k > key {
- hi = i
- } else {
- kern := fixed.Int26_6(int16(u16(buf[4:])))
- kern = scale(kern*ppem, f.cached.unitsPerEm)
- if h == font.HintingFull {
- // Quantize the fixed.Int26_6 value to the nearest pixel.
- kern = (kern + 32) &^ 63
- }
- return kern, nil
- }
- }
- return 0, nil
- }
- // Metrics returns the metrics of this font.
- func (f *Font) Metrics(b *Buffer, ppem fixed.Int26_6, h font.Hinting) (font.Metrics, error) {
- m := font.Metrics{
- Height: scale(fixed.Int26_6(f.cached.ascent-f.cached.descent+f.cached.lineGap)*ppem, f.cached.unitsPerEm),
- Ascent: +scale(fixed.Int26_6(f.cached.ascent)*ppem, f.cached.unitsPerEm),
- Descent: -scale(fixed.Int26_6(f.cached.descent)*ppem, f.cached.unitsPerEm),
- XHeight: scale(fixed.Int26_6(f.cached.xHeight)*ppem, f.cached.unitsPerEm),
- CapHeight: scale(fixed.Int26_6(f.cached.capHeight)*ppem, f.cached.unitsPerEm),
- CaretSlope: image.Point{X: int(f.cached.slope[0]), Y: int(f.cached.slope[1])},
- }
- if h == font.HintingFull {
- // Quantize up to a whole pixel.
- m.Height = (m.Height + 63) &^ 63
- m.Ascent = (m.Ascent + 63) &^ 63
- m.Descent = (m.Descent + 63) &^ 63
- m.XHeight = (m.XHeight + 63) &^ 63
- m.CapHeight = (m.CapHeight + 63) &^ 63
- }
- return m, nil
- }
- // WriteSourceTo writes the source data (the []byte or io.ReaderAt passed to
- // Parse or ParseReaderAt) to w.
- //
- // It returns the number of bytes written. On success, this is the final offset
- // of the furthest SFNT table in the source. This may be less than the length
- // of the []byte or io.ReaderAt originally passed.
- func (f *Font) WriteSourceTo(b *Buffer, w io.Writer) (int64, error) {
- if f.initialOffset != 0 {
- // TODO: when extracting a single font (i.e. TTF) out of a font
- // collection (i.e. TTC), write only the i'th font and not the (i-1)
- // previous fonts. Subtly, in the file format, table offsets may be
- // relative to the start of the resource (for dfont collections) or the
- // start of the file (otherwise). If we were to extract a single font
- // here, we might need to dynamically patch the table offsets, bearing
- // in mind that f.src.b is conceptually a 'read-only' slice of bytes.
- return 0, errUnsupportedCollection
- }
- if f.src.b != nil {
- n, err := w.Write(f.src.b[:f.cached.finalTableOffset])
- return int64(n), err
- }
- // We have an io.ReaderAt source, not a []byte. It is tempting to see if
- // the io.ReaderAt optionally implements the io.WriterTo interface, but we
- // don't for two reasons:
- // - We want to write exactly f.cached.finalTableOffset bytes, even if the
- // underlying 'file' is larger, to be consistent with the []byte flavor.
- // - We document that "Font methods are safe to call concurrently" and
- // while io.ReaderAt is stateless (the offset is an argument), the
- // io.Reader / io.Writer abstractions are stateful (the current position
- // is a field) and mutable state generally isn't concurrent-safe.
- if b == nil {
- b = &Buffer{}
- }
- finalTableOffset := int(f.cached.finalTableOffset)
- numBytesWritten := int64(0)
- for offset := 0; offset < finalTableOffset; {
- length := finalTableOffset - offset
- if length > 4096 {
- length = 4096
- }
- view, err := b.view(&f.src, offset, length)
- if err != nil {
- return numBytesWritten, err
- }
- n, err := w.Write(view)
- numBytesWritten += int64(n)
- if err != nil {
- return numBytesWritten, err
- }
- offset += length
- }
- return numBytesWritten, nil
- }
- // Name returns the name value keyed by the given NameID.
- //
- // It returns ErrNotFound if there is no value for that key.
- func (f *Font) Name(b *Buffer, id NameID) (string, error) {
- if b == nil {
- b = &Buffer{}
- }
- const headerSize, entrySize = 6, 12
- if f.name.length < headerSize {
- return "", errInvalidNameTable
- }
- buf, err := b.view(&f.src, int(f.name.offset), headerSize)
- if err != nil {
- return "", err
- }
- numSubtables := u16(buf[2:])
- if f.name.length < headerSize+entrySize*uint32(numSubtables) {
- return "", errInvalidNameTable
- }
- stringOffset := u16(buf[4:])
- seen := false
- for i, n := 0, int(numSubtables); i < n; i++ {
- buf, err := b.view(&f.src, int(f.name.offset)+headerSize+entrySize*i, entrySize)
- if err != nil {
- return "", err
- }
- if u16(buf[6:]) != uint16(id) {
- continue
- }
- seen = true
- var stringify func([]byte) (string, error)
- switch u32(buf) {
- default:
- continue
- case pidMacintosh<<16 | psidMacintoshRoman:
- stringify = stringifyMacintosh
- case pidWindows<<16 | psidWindowsUCS2:
- stringify = stringifyUCS2
- }
- nameLength := u16(buf[8:])
- nameOffset := u16(buf[10:])
- buf, err = b.view(&f.src, int(f.name.offset)+int(nameOffset)+int(stringOffset), int(nameLength))
- if err != nil {
- return "", err
- }
- return stringify(buf)
- }
- if seen {
- return "", errUnsupportedPlatformEncoding
- }
- return "", ErrNotFound
- }
- func stringifyMacintosh(b []byte) (string, error) {
- for _, c := range b {
- if c >= 0x80 {
- // b contains some non-ASCII bytes.
- s, _ := charmap.Macintosh.NewDecoder().Bytes(b)
- return string(s), nil
- }
- }
- // b contains only ASCII bytes.
- return string(b), nil
- }
- func stringifyUCS2(b []byte) (string, error) {
- if len(b)&1 != 0 {
- return "", errInvalidUCS2String
- }
- r := make([]rune, len(b)/2)
- for i := range r {
- r[i] = rune(u16(b))
- b = b[2:]
- }
- return string(r), nil
- }
- // Buffer holds re-usable buffers that can reduce the total memory allocation
- // of repeated Font method calls.
- //
- // See the Font type's documentation comment for more details.
- type Buffer struct {
- // buf is a byte buffer for when a Font's source is an io.ReaderAt.
- buf []byte
- // segments holds glyph vector path segments.
- segments Segments
- // compoundStack holds the components of a TrueType compound glyph.
- compoundStack [maxCompoundStackSize]struct {
- glyphIndex GlyphIndex
- dx, dy int16
- hasTransform bool
- transformXX int16
- transformXY int16
- transformYX int16
- transformYY int16
- }
- // psi is a PostScript interpreter for when the Font is an OpenType/CFF
- // font.
- psi psInterpreter
- }
- func (b *Buffer) view(src *source, offset, length int) ([]byte, error) {
- buf, err := src.view(b.buf, offset, length)
- if err != nil {
- return nil, err
- }
- // Only update b.buf if it is safe to re-use buf.
- if src.viewBufferWritable() {
- b.buf = buf
- }
- return buf, nil
- }
- // Segment is a segment of a vector path.
- type Segment struct {
- // Op is the operator.
- Op SegmentOp
- // Args is up to three (x, y) coordinates. The Y axis increases down.
- Args [3]fixed.Point26_6
- }
- // SegmentOp is a vector path segment's operator.
- type SegmentOp uint32
- const (
- SegmentOpMoveTo SegmentOp = iota
- SegmentOpLineTo
- SegmentOpQuadTo
- SegmentOpCubeTo
- )
- // Segments is a slice of Segment.
- type Segments []Segment
- // Bounds returns s' bounding box. It returns an empty rectangle if s is empty.
- func (s Segments) Bounds() (bounds fixed.Rectangle26_6) {
- if len(s) == 0 {
- return fixed.Rectangle26_6{}
- }
- bounds.Min.X = fixed.Int26_6(+(1 << 31) - 1)
- bounds.Min.Y = fixed.Int26_6(+(1 << 31) - 1)
- bounds.Max.X = fixed.Int26_6(-(1 << 31) + 0)
- bounds.Max.Y = fixed.Int26_6(-(1 << 31) + 0)
- for _, seg := range s {
- n := 1
- switch seg.Op {
- case SegmentOpQuadTo:
- n = 2
- case SegmentOpCubeTo:
- n = 3
- }
- for i := 0; i < n; i++ {
- if bounds.Max.X < seg.Args[i].X {
- bounds.Max.X = seg.Args[i].X
- }
- if bounds.Min.X > seg.Args[i].X {
- bounds.Min.X = seg.Args[i].X
- }
- if bounds.Max.Y < seg.Args[i].Y {
- bounds.Max.Y = seg.Args[i].Y
- }
- if bounds.Min.Y > seg.Args[i].Y {
- bounds.Min.Y = seg.Args[i].Y
- }
- }
- }
- return bounds
- }
- // translateArgs applies a translation to args.
- func translateArgs(args *[3]fixed.Point26_6, dx, dy fixed.Int26_6) {
- args[0].X += dx
- args[0].Y += dy
- args[1].X += dx
- args[1].Y += dy
- args[2].X += dx
- args[2].Y += dy
- }
- // transformArgs applies an affine transformation to args. The t?? arguments
- // are 2.14 fixed point values.
- func transformArgs(args *[3]fixed.Point26_6, txx, txy, tyx, tyy int16, dx, dy fixed.Int26_6) {
- args[0] = tform(txx, txy, tyx, tyy, dx, dy, args[0])
- args[1] = tform(txx, txy, tyx, tyy, dx, dy, args[1])
- args[2] = tform(txx, txy, tyx, tyy, dx, dy, args[2])
- }
- func tform(txx, txy, tyx, tyy int16, dx, dy fixed.Int26_6, p fixed.Point26_6) fixed.Point26_6 {
- const half = 1 << 13
- return fixed.Point26_6{
- X: dx +
- fixed.Int26_6((int64(p.X)*int64(txx)+half)>>14) +
- fixed.Int26_6((int64(p.Y)*int64(tyx)+half)>>14),
- Y: dy +
- fixed.Int26_6((int64(p.X)*int64(txy)+half)>>14) +
- fixed.Int26_6((int64(p.Y)*int64(tyy)+half)>>14),
- }
- }
|