| // Copyright 2017 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. |
| |
| package sfnt |
| |
| import ( |
| "unicode/utf8" |
| |
| "golang.org/x/text/encoding/charmap" |
| ) |
| |
| // Platform IDs and Platform Specific IDs as per |
| // https://www.microsoft.com/typography/otspec/name.htm |
| const ( |
| pidUnicode = 0 |
| pidMacintosh = 1 |
| pidWindows = 3 |
| |
| psidUnicode2BMPOnly = 3 |
| psidUnicode2FullRepertoire = 4 |
| // Note that FontForge may generate a bogus Platform Specific ID (value 10) |
| // for the Unicode Platform ID (value 0). See |
| // https://github.com/fontforge/fontforge/issues/2728 |
| |
| psidMacintoshRoman = 0 |
| |
| psidWindowsUCS2 = 1 |
| psidWindowsUCS4 = 10 |
| ) |
| |
| // platformEncodingWidth returns the number of bytes per character assumed by |
| // the given Platform ID and Platform Specific ID. |
| // |
| // Very old fonts, from before Unicode was widely adopted, assume only 1 byte |
| // per character: a character map. |
| // |
| // Old fonts, from when Unicode meant the Basic Multilingual Plane (BMP), |
| // assume that 2 bytes per character is sufficient. |
| // |
| // Recent fonts naturally support the full range of Unicode code points, which |
| // can take up to 4 bytes per character. Such fonts might still choose one of |
| // the legacy encodings if e.g. their repertoire is limited to the BMP, for |
| // greater compatibility with older software, or because the resultant file |
| // size can be smaller. |
| func platformEncodingWidth(pid, psid uint16) int { |
| switch pid { |
| case pidUnicode: |
| switch psid { |
| case psidUnicode2BMPOnly: |
| return 2 |
| case psidUnicode2FullRepertoire: |
| return 4 |
| } |
| |
| case pidMacintosh: |
| switch psid { |
| case psidMacintoshRoman: |
| return 1 |
| } |
| |
| case pidWindows: |
| switch psid { |
| case psidWindowsUCS2: |
| return 2 |
| case psidWindowsUCS4: |
| return 4 |
| } |
| } |
| return 0 |
| } |
| |
| // The various cmap formats are described at |
| // https://www.microsoft.com/typography/otspec/cmap.htm |
| |
| var supportedCmapFormat = func(format, pid, psid uint16) bool { |
| switch format { |
| case 0: |
| return pid == pidMacintosh && psid == psidMacintoshRoman |
| case 4: |
| return true |
| case 12: |
| // TODO: implement. |
| } |
| return false |
| } |
| |
| func (f *Font) makeCachedGlyphIndex(buf []byte, offset, length uint32, format uint16) ([]byte, error) { |
| switch format { |
| case 0: |
| return f.makeCachedGlyphIndexFormat0(buf, offset, length) |
| case 4: |
| return f.makeCachedGlyphIndexFormat4(buf, offset, length) |
| case 12: |
| // TODO: implement, including a cmapEntry32 type (32, not 16). |
| } |
| panic("unreachable") |
| } |
| |
| func (f *Font) makeCachedGlyphIndexFormat0(buf []byte, offset, length uint32) ([]byte, error) { |
| if length != 6+256 || offset+length > f.cmap.length { |
| return nil, errInvalidCmapTable |
| } |
| var err error |
| buf, err = f.src.view(buf, int(f.cmap.offset+offset), int(length)) |
| if err != nil { |
| return nil, err |
| } |
| var table [256]byte |
| copy(table[:], buf[6:]) |
| f.cached.glyphIndex = func(f *Font, b *Buffer, r rune) (GlyphIndex, error) { |
| // TODO: for this closure to be goroutine-safe, the |
| // golang.org/x/text/encoding/charmap API needs to allocate a new |
| // Encoder and new []byte buffers, for every call to this closure, even |
| // though all we want to do is to encode one rune as one byte. We could |
| // possibly add some fields in the Buffer struct to re-use these |
| // allocations, but a better solution is to improve the charmap API. |
| var dst, src [utf8.UTFMax]byte |
| n := utf8.EncodeRune(src[:], r) |
| _, _, err = charmap.Macintosh.NewEncoder().Transform(dst[:], src[:n], true) |
| if err != nil { |
| // The source rune r is not representable in the Macintosh-Roman encoding. |
| return 0, nil |
| } |
| return GlyphIndex(table[dst[0]]), nil |
| } |
| return buf, nil |
| } |
| |
| func (f *Font) makeCachedGlyphIndexFormat4(buf []byte, offset, length uint32) ([]byte, error) { |
| const headerSize = 14 |
| if offset+headerSize > f.cmap.length { |
| return nil, errInvalidCmapTable |
| } |
| var err error |
| buf, err = f.src.view(buf, int(f.cmap.offset+offset), headerSize) |
| if err != nil { |
| return nil, err |
| } |
| offset += headerSize |
| |
| segCount := u16(buf[6:]) |
| if segCount&1 != 0 { |
| return nil, errInvalidCmapTable |
| } |
| segCount /= 2 |
| if segCount > maxCmapSegments { |
| return nil, errUnsupportedNumberOfCmapSegments |
| } |
| |
| eLength := 8*uint32(segCount) + 2 |
| if offset+eLength > f.cmap.length { |
| return nil, errInvalidCmapTable |
| } |
| buf, err = f.src.view(buf, int(f.cmap.offset+offset), int(eLength)) |
| if err != nil { |
| return nil, err |
| } |
| offset += eLength |
| |
| entries := make([]cmapEntry16, segCount) |
| for i := range entries { |
| entries[i] = cmapEntry16{ |
| end: u16(buf[0*len(entries)+0+2*i:]), |
| start: u16(buf[2*len(entries)+2+2*i:]), |
| delta: u16(buf[4*len(entries)+2+2*i:]), |
| offset: u16(buf[6*len(entries)+2+2*i:]), |
| } |
| } |
| |
| f.cached.glyphIndex = func(f *Font, b *Buffer, r rune) (GlyphIndex, error) { |
| if uint32(r) > 0xffff { |
| return 0, nil |
| } |
| |
| c := uint16(r) |
| for i, j := 0, len(entries); i < j; { |
| h := i + (j-i)/2 |
| entry := &entries[h] |
| if c < entry.start { |
| j = h |
| } else if entry.end < c { |
| i = h + 1 |
| } else if entry.offset == 0 { |
| return GlyphIndex(c + entry.delta), nil |
| } else { |
| // TODO: support the glyphIdArray as per |
| // https://www.microsoft.com/typography/OTSPEC/cmap.htm |
| // |
| // This will probably use the *Font and *Buffer arguments. |
| return 0, errUnsupportedCmapFormat |
| } |
| } |
| return 0, nil |
| } |
| return buf, nil |
| } |
| |
| type cmapEntry16 struct { |
| end, start, delta, offset uint16 |
| } |