googlefonts / fontations

Reading and writing font files
Apache License 2.0
397 stars 26 forks source link

Legacy kern table #1183

Open simoncozens opened 1 month ago

simoncozens commented 1 month ago

fontations doesn't currently read or write the kern table. However, support for this table is needed for kerning in Microsoft Powerpoint. It is also the subject of a fontbakery check.

rsheeter commented 1 month ago

Do you mean Powerpoint only supports kern?! That's amazing.

simoncozens commented 1 month ago

Exactly.

LucasHimself commented 1 month ago

PowerPoint also supports GPOS kerning, but not on all platforms :-)

khaledhosny commented 1 month ago

Do you mean Powerpoint only supports kern?! That's amazing.

Unless it is a variable font: https://forum.glyphsapp.com/t/ppt-microsoft-opentype-and-gpos-kerning-work-with-vf/31467

rsheeter commented 1 month ago

I immediately wonder if adding a single axis with min = default = max would suffice to make it think it's a variable font

khaledhosny commented 1 month ago

I’d start with an empty (or minimum valid) fvar table.

simoncozens commented 1 month ago

Incidentally I did try implementing this and I am not sure that it is a "good first issue". :-) KernSubtableFormat0 is OK, but KernSubtableFormat2 is followed by a two-dimensional array of data where the number of rows and columns are not directly encoded.

OTOH, it's not clear that KernSubtableFormat2 is needed, as Windows only supports format 0, and FontTools does not know of it either. FontTools also mentions a "new" Apple-specific format kern table (not subtable) with version 1.0 and completely different structures.

If we are happy with just version 0 tables (ignoring Apple) and also ignoring KernSubtableFormat2, the below should do:

/// The [kern (Kerning)](https://docs.microsoft.com/en-us/typography/opentype/spec/kern) table
#[tag = "kern"]
table Kern {
    /// Table version number — set to 0.
    #[compile(0)]
    version: u16,
    /// Number of subtables in the kerning table
    #[compile(array_len($subtables))]
    num_tables: u16,
    #[count($num_tables)]
    subtables: [KernSubtable],
}

/// The different kern subtable formats.
format u16 KernSubtable {
    Format0(Kern0),
    // Nope.
    // Format2(Kern2),
}

/// [kern Format 0](https://docs.microsoft.com/en-us/typography/opentype/spec/kern#format-0)
table Kern0 {
    /// Format number is set to 0.
    #[format = 0]
    format: u16,
    /// The length of the subtable, in bytes (including this header).
    length: u16,
    /// What type of information is contained in this table.
    coverage: KernCoverage,
    /// This gives the number of kerning pairs in the table.
    num_pairs: u16,
    /// The largest power of two less than or equal to the value of num_pairs, multiplied by the
    /// size in bytes of an entry in the table.
    search_range: u16,
    /// This is calculated as log2 of the largest power of two less than or equal to the value of num_pairs.
    /// This value indicates how many iterations of the search loop will have to be made.
    /// (For example, in a list of eight items, there would have to be three iterations of the loop).
    entry_selector: u16,
    /// The value of num_pairs minus the largest power of two less than or equal to num_pairs,
    /// and then multiplied by the size in bytes of an entry in the table.
    range_shift: u16,
    /// Kern pairs
    #[count($num_pairs)]
    kerning_pairs: [KernPair],
}

table KernPair {
    /// The glyph index for the left-hand glyph in the kerning pair.
    left: u16,
    /// The glyph index for the right-hand glyph in the kerning pair.
    right: u16,
    /// The kerning value for the above pair, in font design units.
    /// If this value is greater than zero, the characters will be moved apart.
    /// If this value is less than zero, the character will be moved closer together.
    value: FWORD,
}
cmyr commented 1 month ago

If format 0 is what we need to generate I think that's a good starting point, and we can revisit when the need arises? I'll take a look.