gofontschecksumtruetype

How to obtain head.ChecksumAdjustment?


How to calculate the checksumAdjustment(ms, apple) field in the head table of a TTF file? Here is my method of calculation.

package main

import (
    "encoding/binary"
    "os"
)

func CalcCheckSum(data []byte) uint32 {
    var sum uint32
    step := 3
    for _, c := range data {
        sum += uint32(c) << ((step & 3) * 8)
        step--
    }
    return sum
}

func main() {
    var bs []byte
    bs, _ = os.ReadFile("myFont.ttf")
    // Handle TableHeader
    // ...
    // Handle TableRecord
    // ...
    var headOffset uint32 // Assume it is provided by `TableRecord["head"].Offset` and it's correct.

    binary.BigEndian.PutUint32(bs[headOffset+8:headOffset+12], 0) // 8~12 is checksumAdjustment: https://learn.microsoft.com/en-us/typography/opentype/spec/head
    sum := CalcCheckSum(bs)
    checksumAdjustment := 0xB1B0AFBA - sum
    binary.BigEndian.PutUint32(bs[headOffset+8:headOffset+12], checksumAdjustment)
}

In some fonts (I am sorry for not being able to provide details due to licensing issues), I used this calculation method. When I submit the resulting font file for verification to FontVal-2.1.2

It complains that head.ChecksumAdjustment calculation is incorrect. I'm going crazy, please help me.


Solution

  • you can't directly read all data content for calculation.

    Reason: If some tables need to be padded with zeros, then there will be a difference between doing it separately and doing it all at once. Doing it all at once will only add zeros at the end of the data, but doing it separately will check each piece of data to determine if zeros need to be added, resulting in differences. see below:

    func CalcCheckSum(data []byte) uint32 {
        var sum uint32
        step := 3
        for _, c := range data {
            sum += uint32(c) << ((step & 3) * 8)
            step--
        }
        return sum
    }
    
    func Test(t *testing.T) {
        // Doing it separately
        allData := []byte{0x01, 0x03}
        v1 := CalcCheckSum(allData)      // 0x0103_0000
        v2 := CalcCheckSum(allData[0:1]) // 0x0100_0000
        v3 := CalcCheckSum(allData[1:])  // 0x0300_0000
        fmt.Printf("%08X, %08X, %08X\n", v1, v2, v3)
        if v1 != v2+v3 {
            fmt.Println("test1")
        }
    
        // Doing it all at once. If the data does not need to be padded with zeros, then there is no difference between the two methods.
        allData = []byte{1, 2, 3, 4, 5, 6, 7, 8}
        v1 = CalcCheckSum(allData)      // 0x01020304 + 0x05060708 = 0x0608_0A0C
        v2 = CalcCheckSum(allData[0:4]) // 0x01020304
        v3 = CalcCheckSum(allData[4:])  // 0x05060708
        fmt.Printf("%08X, %08X, %08X\n", v1, v2, v3)
        if v1 == v2+v3 {
            fmt.Println("test2")
        }
    
        // Output:
        // test1
        // test2
    }
    

    playground

    so you should do it separately:

    0xB1B0AFBA - CalcCheckSum(bs[:recordsOffset]) + (CalcCheckSum(table1Bytes) + CalcCheckSum(table2Bytes) + ... + CalcCheckSum[tableNBytes])
    

    The following code for detailed process.

    Note, the following code assumes that the CheckSum of each TableRecord is correct.

    package main
    
    import (
        "bytes"
        "encoding/binary"
        "fmt"
        "os"
    )
    
    func CalcCheckSum(data []byte) uint32 {
        var sum uint32
        step := 3
        for _, c := range data {
            sum += uint32(c) << ((step & 3) * 8)
            step--
        }
        return sum
    }
    
    type TableHeader struct {
        SFNTVersion   uint32
        NumTables     uint16
        SearchRange   uint16
        EntrySelector uint16
        RangeShift    uint16
    }
    
    type TableRecord struct {
        Tag      [4]byte
        CheckSum uint32
        Offset   uint32
        Length   uint32
    }
    
    type TTFont struct {
        TableHeader
        TableRecords []TableRecord
    }
    
    func main() {
        var bs []byte
        var font TTFont
        bs, _ = os.ReadFile("test.ttf")
        r := bytes.NewReader(bs)
        _ = binary.Read(r, binary.BigEndian, &font.TableHeader)
        font.TableRecords = make([]TableRecord, font.NumTables)
        _ = binary.Read(r, binary.BigEndian, &font.TableRecords)
    
        /*
            set head.ChecksumAdjustment = 0
            and Compile each table...
            update each TableRecords...
        */
    
        var sum uint32
        recordsOffset := 12 /*header size*/ + font.NumTables*16
        sum += CalcCheckSum(bs[:recordsOffset])
    
        for _, record := range font.TableRecords {
            sum += record.CheckSum
        }
        checksumAdjustment := 0xB1B0AFBA - sum
        fmt.Printf("0x%X", checksumAdjustment)
    }