-
Notifications
You must be signed in to change notification settings - Fork 12
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[opentype] support packing font tables into a .ttf file
- Loading branch information
1 parent
afa02a8
commit 825df63
Showing
4 changed files
with
142 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
package loader | ||
|
||
import ( | ||
"encoding/binary" | ||
"math" | ||
) | ||
|
||
// Table is one opentype binary table and its tag. | ||
type Table struct { | ||
Content []byte | ||
Tag Tag | ||
} | ||
|
||
// Write creates a single font file from the given [tables] slice, | ||
// which must be sorted by Tag | ||
func Write(tables []Table) []byte { | ||
buffer := writeTTF(tables) | ||
return buffer | ||
} | ||
|
||
func writeTTF(tables []Table) []byte { | ||
introLength := uint32(otfHeaderSize + len(tables)*otfEntrySize) | ||
buffer := make([]byte, introLength) | ||
|
||
writeTTFHeader(len(tables), buffer) | ||
|
||
tableOffset := introLength // the actual content will start after the header + table directory | ||
for i, table := range tables { | ||
cs := checksum(table.Content) | ||
tableLength := uint32(len(table.Content)) | ||
|
||
slice := buffer[otfHeaderSize+i*otfEntrySize:] | ||
binary.BigEndian.PutUint32(slice, uint32(table.Tag)) | ||
binary.BigEndian.PutUint32(slice[4:], cs) | ||
binary.BigEndian.PutUint32(slice[8:], tableOffset) | ||
binary.BigEndian.PutUint32(slice[12:], tableLength) | ||
|
||
// update the offset | ||
tableOffset = tableOffset + tableLength | ||
} | ||
|
||
// append the actual table content : | ||
// allocate only once | ||
buffer = append(buffer, make([]byte, tableOffset-introLength)...) | ||
tableOffset = introLength | ||
for _, table := range tables { | ||
copy(buffer[tableOffset:], table.Content) | ||
tableOffset = tableOffset + uint32(len(table.Content)) | ||
} | ||
|
||
return buffer | ||
} | ||
|
||
// out is assumed to have a length >= ttfHeaderSize | ||
func writeTTFHeader(nTables int, out []byte) { | ||
log2 := math.Floor(math.Log2(float64(nTables))) | ||
// Maximum power of 2 less than or equal to numTables, times 16 ((2**floor(log2(numTables))) * 16, where “**” is an exponentiation operator). | ||
searchRange := math.Pow(2, log2) * 16 | ||
// Log2 of the maximum power of 2 less than or equal to numTables (log2(searchRange/16), which is equal to floor(log2(numTables))). | ||
entrySelector := log2 | ||
// numTables times 16, minus searchRange ((numTables * 16) - searchRange). | ||
rangeShift := nTables*16 - int(searchRange) | ||
|
||
binary.BigEndian.PutUint32(out[:], uint32(TrueType)) | ||
binary.BigEndian.PutUint16(out[4:], uint16(nTables)) | ||
binary.BigEndian.PutUint16(out[6:], uint16(searchRange)) | ||
binary.BigEndian.PutUint16(out[8:], uint16(entrySelector)) | ||
binary.BigEndian.PutUint16(out[10:], uint16(rangeShift)) | ||
} | ||
|
||
func checksum(table []byte) uint32 { | ||
// "To accommodate data with a length that is not a multiple of four, | ||
// the above algorithm must be modified to treat the data as though | ||
// it contains zero padding to a length that is a multiple of four." | ||
if r := len(table) % 4; r != 0 { | ||
table = append(table, make([]byte, r)...) | ||
} | ||
|
||
var sum uint32 | ||
for i := 0; i < len(table)/4; i++ { | ||
sum += binary.BigEndian.Uint32(table[i*4:]) | ||
} | ||
|
||
return sum | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
package loader | ||
|
||
import ( | ||
"bytes" | ||
"testing" | ||
|
||
td "github.com/go-text/typesetting-utils/opentype" | ||
tu "github.com/go-text/typesetting/opentype/testutils" | ||
) | ||
|
||
func TestWrite(t *testing.T) { | ||
for _, filename := range tu.Filenames(t, "common") { | ||
f, err := td.Files.ReadFile(filename) | ||
tu.AssertNoErr(t, err) | ||
|
||
font, err := NewLoader(bytes.NewReader(f)) | ||
tu.AssertNoErr(t, err) | ||
|
||
tags := font.Tables() | ||
tables := make([]Table, len(tags)) | ||
for i, tag := range tags { | ||
tables[i].Tag = tag | ||
tables[i].Content, err = font.RawTable(tag) | ||
tu.AssertNoErr(t, err) | ||
} | ||
|
||
content := Write(tables) | ||
font2, err := NewLoader(bytes.NewReader(content)) | ||
tu.AssertNoErr(t, err) | ||
|
||
for _, table := range tables { | ||
t2, err := font2.RawTable(table.Tag) | ||
tu.AssertNoErr(t, err) | ||
|
||
tu.Assert(t, bytes.Equal(table.Content, t2)) | ||
} | ||
} | ||
} |