Commit 14ba70d
Suggestions for a new OTF table: FK fixed kerning
Changed files (15)
addedfixed-kerning-example/FK-example Mac.otfaddedfixed-kerning-example/FK-example Mac.ttfaddedfixed-kerning-example/FK-example.otfaddedfixed-kerning-example/FK-example.ttfaddedfixed-kerning-example/README.txtaddedfixed-kerning-example/build.shaddedfixed-kerning-example/clean.shaddedfixed-kerning-example/fk-example.birdfontaddedfixed-kerning-example/fk-font-editor.caddedfixed-kerning-example/fk-font-editor.haddedfixed-kerning-example/fk-test.caddedfixed-kerning-example/fk.caddedfixed-kerning-example/fk.haddedfixed-kerning-example/otf-fk-documentation.htmladdedfixed-kerning-example/run.sh
Diff
new file mode 100644Binary files /dev/null and b/fixed-kerning-example/FK-example Mac.otf differnew file mode 100644Binary files /dev/null and b/fixed-kerning-example/FK-example Mac.ttf differnew file mode 100644Binary files /dev/null and b/fixed-kerning-example/FK-example.otf differnew file mode 100644Binary files /dev/null and b/fixed-kerning-example/FK-example.ttf differnew file mode 100644@@ -0,0 +1,22 @@+The FK Table++This is a reference implementation of a new kerning table that stores fixed +point 16.16 values. The idea is to get better precision for kerning of OTF +and TTF fonts. The goal is also to create a solution that is is simple and fast.++The code is small and hopefully easy to understand. The parser needs around 200+lines of code and around 40 more lines are needed to add support for writing +the table.++Please ask if you need help or clarifications.++All code related to the FK feature in this folder is example code. Copy and+paste of this code into any project with any license is permitted.++An OTF parser only needs fk.c and fk.h. A font editor might also use+fk-font-editor.c and fk-font-editor.h. The file fk-test.c provides an +example of how the code could be used.++Regards+Johan Mattsson+johan.mattsson.m@gmail.comnew file mode 100755@@ -0,0 +1,2 @@+#!/bin/sh+gcc *.c -o kerning-example -lmnew file mode 100755@@ -0,0 +1,2 @@+#!/bin/sh+rm -f kerning-example fixedkerningtable.test basic.testnew file mode 100644@@ -0,0 +1,100 @@+<?xml version="1.0" encoding="utf-8" standalone="yes"?>+<font>+<format>3.22</format>+<program version="4.27.2" os="*nix" />++<postscript_name>FK-example</postscript_name>+<name>FK-example</name>+<subfamily>Regular</subfamily>+<bold>false</bold>+<italic>false</italic>+<full_name>FK-example</full_name>+<unique_identifier>FK-example</unique_identifier>+<version>Version 1.0</version>+<description></description>+<copyright>SIL Open Font License</copyright>+<license>SIL Open Font License</license>+<license_url>http://scripts.sil.org/OFL</license_url>+<weight>400</weight>+<units_per_em>1024</units_per_em>+<trademark></trademark>+<manufacturer></manufacturer>+<designer></designer>+<vendor_url></vendor_url>+<vendor_id>Bird</vendor_id>+<designer_url></designer_url>+<underline_position>-10</underline_position>+<underline_thickness>3.5</underline_thickness>+<open_engraving>false</open_engraving>+<same_way_back_engraving>false</same_way_back_engraving>+<external_backgrunds>false</external_backgrunds>++<horizontal>+ <top_limit>78</top_limit>+ <top_position>72</top_position>+ <x-height>56</x-height>+ <base_line>0</base_line>+ <bottom_position>-20</bottom_position>+ <bottom_limit>-21</bottom_limit>+ <line_gap>10</line_gap>+</horizontal>++<grid width="1"/>+<grid width="2"/>+<grid width="4"/>++<background scale="1" />++<base_filename>FK-example</base_filename>+<base_filename_mac>FK-example Mac</base_filename_mac>+<export_ttf_font>true</export_ttf_font>+<export_otf_font>true</export_otf_font>+<export_eot_font>false</export_eot_font>+<export_svg_font>false</export_svg_font>+<export_colr_table>false</export_colr_table>+<export_svg_table>false</export_svg_table>+<optimize_otf_for_size>false</optimize_otf_for_size>+<optimize_otf_for_details>true</optimize_otf_for_details>++<default_foreground_color></default_foreground_color>++<svg_units_per_em>100</svg_units_per_em>+<svg_decimals>5</svg_decimals>++<collection unicode="U+20" svg="false" colr="false" auto_generate="false" meta_name="">+ <selected id="0"/>+ <glyph id="0" left="0" right="27">+ </glyph>+</collection>++<collection name=".notdef" svg="false" colr="false" auto_generate="false" meta_name="">+ <selected id="0"/>+ <glyph id="0" left="-20" right="33">+ <layer name="Layer" visible="true">+ <path data="S -15,10 L 15,10 L 15,62 M -15,62 M -15,10" />+ <path data="S -20,67 L 20,67 L 20,5 M -20,5 M -20,67" />+ </layer>+ </glyph>+</collection>++<collection unicode="U+41" svg="false" colr="false" auto_generate="false" meta_name="">+ <selected id="0"/>+ <glyph id="0" left="-28" right="26">+ <layer name="Layer" visible="true">+ <path data="S -28,0 L -2,72 L 26,0 L -28,0" />+ </layer>+ </glyph>+</collection>++<collection unicode="U+56" svg="false" colr="false" auto_generate="false" meta_name="">+ <selected id="1"/>+ <glyph id="1" left="-28" right="26">+ <layer name="Layer" visible="true">+ <path data="S 26,72 L -2,0 L -28,72 L 26,72" />+ </layer>+ </glyph>+</collection>++<kerning left="A" right="V" hadjustment="-15" />+<kerning left="V" right="A" hadjustment="-13" />+</font>new file mode 100644@@ -0,0 +1,48 @@+/*+ * This is example code. Using this code in any project with any license is permitted.+ * Author: Johan Mattsson+ */+#include <stdint.h>+#include <stdio.h>+#include <stdbool.h>+#include <math.h>+#include <assert.h>+#include <stdlib.h>++#include "fk.h"++void fk_write_fixed_kerning_table_header (FILE* file, uint32_t num_kerning_pairs) {+ uint16_t version_upper = 1;+ uint16_t version_lower = 0;+ uint16_t pairs = num_kerning_pairs;+ + fk_write_u16 (file, version_upper);+ fk_write_u16 (file, version_lower);+ fk_write_u32 (file, pairs);+}++Fixed16_16 fk_double_to_fixed (double v) {+ int32_t val;+ int32_t mant;+ + val = (int32_t) floor (v);+ mant = (int32_t) floor (0x10000 * (v - val));+ val = (val << 16) | mant;++ return val;+}++void fk_write_fixed_kerning_entry (FILE* file, uint32_t glyph_index_first, uint32_t glyph_index_second, double kerning) {+ int32_t fixed_kerning;+ + if (kerning > INT16_MAX || kerning < INT16_MIN) {+ fprintf(stderr, "Kerning is out of bounds.\n");+ kerning = 0;+ }+ + fixed_kerning = fk_double_to_fixed (kerning);++ fk_write_u32 (file, glyph_index_first);+ fk_write_u32 (file, glyph_index_second);+ fk_write_u32 (file, fixed_kerning);+}new file mode 100644@@ -0,0 +1,14 @@+/*+ * This is example code. Using this code in any project with any license is permitted.+ * Author: Johan Mattsson+ */+#ifndef FILE_FK_FONT_EDITOR+#define FILE_FK_FONT_EDITOR++#include <stdio.h>+#include "fk.h"++void fk_write_fixed_kerning_table_header (FILE* file, uint32_t num_kerning_pairs);+void fk_write_fixed_kerning_entry (FILE* file, uint32_t glyh_index_first, uint32_t glyh_index_second, double kerning);++#endifnew file mode 100644@@ -0,0 +1,189 @@+/*+ * This is example code. Using this code in any project with any license is permitted.+ * Author: Johan Mattsson+ */+#include <assert.h>+#include "fk.h"+#include "fk-font-editor.h"++uint8_t* read_content (const char* file_name, size_t* buffer_size) {+ FILE* file;+ file = fopen (file_name, "rb");+ assert(file);+ + fseek (file, 0, SEEK_END);+ size_t size = ftell (file);+ *buffer_size = size;+ rewind (file);+ + uint8_t* table_buffer = (uint8_t*) malloc(size);+ assert (table_buffer);+ + fread (table_buffer, size, sizeof (uint8_t), file);+ fclose (file);++ return table_buffer;+}++int test_write () {+ FILE* file;+ uint32_t num_pairs = 10;+ + file = fopen ("fixedkerningtable.test", "wb");++ fk_write_fixed_kerning_table_header (file, num_pairs);+ + for (uint32_t i = 0; i < num_pairs; i++) {+ uint32_t gid1 = i;+ uint32_t gid2 = i;+ double kerning = -0.2;++ gid1 = 0x45678abc;+ gid2 = 0x6EEEEEE2 + i;++ // all entries needs to be sorted on gid1 and gid2.+ fk_write_fixed_kerning_entry (file, gid1, gid2, kerning);+ }++ fclose (file);+ return 0;+}++void test_datatypes () {+ FILE* file;+ file = fopen ("basic.test", "wb");++ fk_write_u16 (file, 0x1234);+ fk_write_u32 (file, 0x567abcde);++ fclose (file);++ uint8_t* table_buffer = NULL;+ size_t buffer_size;+ table_buffer = read_content ("basic.test", &buffer_size);++ assert (fk_get_uint16 (table_buffer, buffer_size, 0) == 0x1234);+ assert (fk_get_uint32 (table_buffer, buffer_size, 2) == 0x567abcde);++ free (table_buffer);+}++void test_fixed_conversion () {+ Fixed16_16 f;+ double d;++ int32_t x = 0xffffcccc;+ assert (x == 0xffffcccc);++ f = fk_double_to_fixed (-0.2);+ assert (f == 0xffffcccc);++ f = fk_double_to_fixed (-0.1);+ assert (f == 0xffffe666);++ d = fk_fixed_to_double (0xffffcccc);+ assert ((int) (d * 1000) == -200);++ d = fk_fixed_to_double (0xffffe666);+ assert ((int) (d * 1000) == -100);+}++void test_read_all () {+ FILE* file;+ uint8_t* table_buffer = NULL;+ size_t buffer_size;+ uint32_t num_pairs;+ + table_buffer = read_content ("fixedkerningtable.test", &buffer_size);+ + bool has_data = fk_has_fixed_kerning (table_buffer, buffer_size);+ assert (has_data);+ + num_pairs = fk_get_num_kerning_pairs(table_buffer, buffer_size);+ assert (num_pairs > 0);+ + printf ("Table size : %zu\n", buffer_size);+ printf ("List all kerning pairs in table. Number of pairs: %d\n", num_pairs);+ + for (uint32_t i = 0; i < num_pairs; i++) {+ uint32_t gid1 = 0;+ uint32_t gid2 = 0;+ Fixed16_16 kern = 0;+ + fk_get_fixed_kerning_by_index (table_buffer, buffer_size, i, &gid1, &gid2, &kern);+ + Fixed16_16 fixed_kerning;+ fixed_kerning = fk_get_fixed_kerning (table_buffer, buffer_size, gid1, gid2);+ double kerning = fk_fixed_to_double (fixed_kerning);+ + printf ("Kerning for gid1: %d and gid2: %d, kerning %4.4f, (raw fixed: %d)\n", gid1, gid2, kerning, fixed_kerning);+ assert (kern == fixed_kerning);+ }+ + printf ("Total pairs %d\n\n", num_pairs);+ + free (table_buffer);+}++void test_order () { + uint8_t* table_buffer = NULL;+ size_t buffer_size;+ uint32_t num_pairs;+ + table_buffer = read_content ("fixedkerningtable.test", &buffer_size);+ + bool has_data = fk_has_fixed_kerning (table_buffer, buffer_size);+ assert (has_data);+ + num_pairs = fk_get_num_kerning_pairs(table_buffer, buffer_size);+ assert (num_pairs > 0);+ + uint32_t read_index = FIXED_KERNING_HEADER_SIZE;+ + uint32_t last_gid1 = 0;+ uint32_t last_gid2 = 0;+ + for (uint32_t i = 0; i < num_pairs; i++) {+ uint32_t gid1 = fk_get_uint32 (table_buffer, buffer_size, read_index);+ read_index += sizeof (uint32_t);++ uint32_t gid2 = fk_get_uint32 (table_buffer, buffer_size, read_index);+ read_index += sizeof (uint32_t);++ Fixed16_16 kern = fk_get_uint32 (table_buffer, buffer_size, read_index);+ read_index += sizeof (uint32_t);++ double kerning = fk_fixed_to_double (kern);++ printf ("Row: %d gid1: %x, gid2 %x, kerning: %.4f (raw kerning %d)\n", i, gid1, gid2, kerning, kern);++ bool is_sorted = false;++ if (gid1 != last_gid1) {+ is_sorted = gid1 >= last_gid1;+ last_gid2 = 0;+ } else {+ is_sorted = gid2 >= last_gid2;+ }+ + last_gid1 = gid1;+ last_gid2 = gid2;++ assert (is_sorted);+ assert (gid1 == 0x45678abc);+ assert (gid2 == 0x6EEEEEE2 + i);+ }+ + printf ("Total pairs %d\n\n", num_pairs);++ free (table_buffer);+}++int main () {+ test_fixed_conversion ();+ test_datatypes ();+ test_write ();+ test_read_all ();+ test_order ();+ return 0;+}new file mode 100644@@ -0,0 +1,181 @@+/*+ * This is example code. Using this code in any project with any license is permitted.+ * Author: Johan Mattsson+ */+#include <stdint.h>+#include <stdio.h>+#include <stdbool.h>+#include <math.h>+#include <assert.h>+#include <stdlib.h>++#include "fk.h"++uint8_t* fk_get_subtable_entries(uint8_t* otf_table, size_t table_size, size_t* subtable_size);++double fk_fixed_to_double (Fixed16_16 val) {+ return val / 65536.0;+}++uint16_t fk_get_uint16 (uint8_t* otf_table, size_t table_size, size_t index) {+ if (index + 1 >= table_size) {+ fprintf(stderr, "Index out of bounds: %zu Buffer size: %zu.\n", index, table_size);+ return 0;+ }+ + uint16_t val = otf_table[index + 1] << 8;+ val |= otf_table[index];+ return val;+}++uint32_t fk_get_uint32 (uint8_t* otf_table, size_t table_size, size_t index) {+ if (index + 3 >= table_size) {+ fprintf(stderr, "Index out of bounds: %zu Buffer size: %zu.\n", index, table_size);+ return 0;+ }++ uint32_t val = fk_get_uint16 (otf_table, table_size, index) << 16;+ val |= fk_get_uint16 (otf_table, table_size, index + 2);++ return val;+}++void fk_write_u8 (FILE* file, uint8_t val) {+ fwrite(&val, 1, sizeof(uint8_t), file);+}++void fk_write_u16 (FILE* file, uint16_t val) {+ fk_write_u8 (file, (uint8_t) ((val & 0x00FF) >> (8 * 0)));+ fk_write_u8 (file, (uint8_t) ((val & 0xFF00) >> (8 * 1)));+}++void fk_write_u32 (FILE* file, uint32_t val) {+ uint16_t s = (uint16_t) (val >> 16);+ + fk_write_u16 (file, (val & 0xFFFF0000) >> 16);+ fk_write_u16 (file, val & 0x0000FFFF);+}++uint32_t fk_get_int32 (uint8_t* otf_table, size_t table_size, size_t index) {+ return (uint32_t) fk_get_uint32 (otf_table, table_size, index);+}++/*+* This function ensures that we have data for the fixed kerning table. +* The get_fixed_kerning assumes that has_fixed_kerning has been used to+* check if we have a valid subtable with kerning entries.+*/+bool fk_has_fixed_kerning (uint8_t* otf_table, size_t table_size) {+ if (otf_table == NULL) {+ fprintf(stderr, "No table for fixed kerning.\n");+ return false;+ }+ + if (table_size <= FIXED_KERNING_HEADER_SIZE) {+ // No data in table+ return false;+ }+ + if (table_size % 4 != 0) {+ fprintf(stderr, "Bad padding in fixed kerning table, size: %zu.\n", table_size);+ return false;+ }+ + uint16_t version_upper = fk_get_uint16(otf_table, table_size, 0);+ uint16_t version_lower = fk_get_uint16(otf_table, table_size, 2);+ + if (version_upper != 1 || version_lower != 0) {+ fprintf(stderr,"Bad version of fixed kerning table. Expecting 1.0 got %d.%d\n", version_upper, version_lower);+ return false;+ }+ + uint32_t entries = fk_get_num_kerning_pairs (otf_table, table_size);+ size_t subtable_size = table_size - FIXED_KERNING_HEADER_SIZE;+ size_t expected_subtable_size = entries * 3 * sizeof(uint32_t);+ + if (subtable_size != expected_subtable_size) {+ fprintf(stderr,"Bad subtable size in fixed kerning table. Size %zu bytes expecting %zu bytes (added entries: %d).\n", subtable_size, expected_subtable_size, entries);+ return false;+ }+ + return true;+}++Fixed16_16 fk_get_fixed_kerning (uint8_t* otf_table, size_t table_size, uint32_t first_glyph_index, uint32_t second_glyph_index) {+ uint32_t entries = fk_get_num_kerning_pairs(otf_table, table_size);+ size_t subtable_size = 0;+ uint8_t* subtable = fk_get_subtable_entries(otf_table, table_size, &subtable_size);++ int lower = 0;+ int upper = entries;+ const int second_gid_offset = sizeof(uint32_t); // offset from start of an kerning entry to the second glyph + const int kerning_offset = 2 * sizeof(uint32_t); // offset from start of an kerning entry to the kerning value+ + uint64_t gid1 = first_glyph_index;+ uint64_t gid2 = second_glyph_index;+ uint64_t search_key = (gid1 << 32) | gid2;+ + while (lower <= upper) {+ size_t middle = lower + (upper - lower) / 2;+ size_t index = middle;+ + size_t row_index = index * 3 * sizeof(uint32_t);+ uint64_t pair_key = fk_get_uint32 (subtable, subtable_size, row_index);+ pair_key <<= 32;+ pair_key |= fk_get_uint32 (subtable, subtable_size, row_index + second_gid_offset);+ + if (pair_key == search_key) {+ return (Fixed16_16) fk_get_uint32 (subtable, subtable_size, row_index + kerning_offset);+ }+ + if (pair_key < search_key) {+ lower = middle + 1;+ } else {+ upper = middle - 1;+ } + }+ + return 0;+}++uint8_t* fk_get_subtable_entries(uint8_t* otf_table, size_t table_size, size_t* subtable_size) {+ if (table_size <= FIXED_KERNING_HEADER_SIZE) {+ *subtable_size = 0;+ return NULL;+ }+ + *subtable_size = table_size - FIXED_KERNING_HEADER_SIZE;+ + return otf_table + FIXED_KERNING_HEADER_SIZE;+}++uint32_t fk_get_num_kerning_pairs (uint8_t* otf_table, size_t table_size) {+ if (table_size < FIXED_KERNING_HEADER_SIZE) {+ printf("Kerning table is too short.");+ return 0;+ }+ + return fk_get_uint32 (otf_table, table_size, 4);+}++void fk_get_fixed_kerning_by_index (uint8_t* otf_table, size_t table_size, size_t index, uint32_t* first_glyph_index, uint32_t* second_glyph_index, Fixed16_16* kerning) {+ uint32_t num_pairs = fk_get_num_kerning_pairs (otf_table, table_size);+ size_t subtable_size = 0;+ uint8_t* subtable = fk_get_subtable_entries (otf_table, table_size, &subtable_size);+ + index *= FIXED_KERNING_ENTRY_SIZE;+ + if (index + FIXED_KERNING_ENTRY_SIZE > subtable_size) {+ fprintf (stderr, "Index out of bounds in fixed kerning table, index: %zu subtable size: %zu, item size: %ld\n", index, subtable_size, FIXED_KERNING_ENTRY_SIZE);+ *first_glyph_index = 0;+ *second_glyph_index = 0;+ *kerning = 0;+ return;+ }+ + *first_glyph_index = fk_get_uint32 (subtable, subtable_size, index);+ *second_glyph_index = fk_get_uint32 (subtable, subtable_size, index + sizeof(uint32_t));++ int32_t fixed_kerning = fk_get_int32 (subtable, subtable_size, index + 2 * sizeof(uint32_t));+ *kerning = fixed_kerning;+}new file mode 100644@@ -0,0 +1,50 @@+/*+ * This is example code. Using this code in any project with any license is permitted.+ * Author: Johan Mattsson+ */+#ifndef FILE_FK_H+#define FILE_FK_H++#include <stdbool.h>+#include <stdint.h>+#include <stdlib.h>+#include <stdio.h>++typedef int32_t Fixed16_16;++#define FIXED_KERNING_HEADER_SIZE 8+#define FIXED_KERNING_ENTRY_SIZE (3 * sizeof (uint32_t))++/* Convert fixed 16.16 values to double. */+double fk_fixed_to_double (Fixed16_16 val);++Fixed16_16 fk_double_to_fixed (double v);++/*+ * All parser functions assumes that this function has been used to check if+ * the table is valid and we have kerning pairs in the subtable.+ */+bool fk_has_fixed_kerning (uint8_t* otf_table, size_t table_size);++uint32_t fk_get_num_kerning_pairs (uint8_t* otf_table, size_t table_size);++/*+ * A function that makes it possible iterate over all kerning pairs in the+ * table from 0 to fk_get_num_kerning_pairs.+ */+void fk_get_fixed_kerning_by_index (uint8_t* otf_table, size_t table_size, size_t index, uint32_t* first_glyph_index, uint32_t* second_glyph_index, Fixed16_16* kerning);++/* + * Get kerning for two gids, (glyph ID). The Fixed 16.16 value can be converted+ * to a floating point value with the with the function fixed_to_double.+ */+Fixed16_16 fk_get_fixed_kerning (uint8_t* otf_table, size_t table_size, uint32_t first_glyph_index, uint32_t second_glyph_index);++uint32_t fk_get_uint32 (uint8_t* otf_table, size_t table_size, size_t index);+uint16_t fk_get_uint16 (uint8_t* otf_table, size_t table_size, size_t index);++void fk_write_u8 (FILE* file, uint8_t val);+void fk_write_u16 (FILE* file, uint16_t val);+void fk_write_u32 (FILE* file, uint32_t val);++#endifnew file mode 100644@@ -0,0 +1,181 @@+<html>+<head>+<title>Fixed Point Kerning Table in OpenType fonts</title>++<link rel="preconnect" href="https://fonts.googleapis.com">+<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>+<link href="https://fonts.googleapis.com/css2?family=Roboto&display=swap" rel="stylesheet">+<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@500&display=swap" rel="stylesheet">+<link href="https://fonts.googleapis.com/css2?family=Roboto+Mono:wght@100;400&display=swap" rel="stylesheet">++<style media="screen">+ .empty {+ height: 1em;+ }+ + td {+ font-family: 'Roboto Mono', monospace;+ padding-right: 1em;+ font-size: 1em;+ }+ + td.right {+ text-align: right;+ }+ + body{+ font-family: 'Roboto', sans-serif;+ }+ + pre {+ font-family: 'Roboto Mono', monospace;+ font-size: 1em;+ }++ section.main {+ width: 40em;+ }++ section.data {+ width: 60em;+ }++ h1 {+ font-size: 2em;+ font-family: 'Roboto', sans-serif;+ font-weight: 500;+ margin-bottom: 10px;+ }++ h2 {+ font-size: 1.6em;+ font-family: 'Roboto', sans-serif;+ font-weight: 500;+ margin-bottom: 10px;+ }++ pre {+ margin-top: 0px;+ }+</style>+</head>+<body>+<section class="main">++<h1>Fixed Point Kerning Table (FK, Version 1.0)</h1>+The FK table is a simple and efficient solution that makes it possible to store+kerning information with a high accuracy in OpenType fonts.++<h2>Names</h2>+The table name for horizontal kerning is "FK" and the table name for vertical +kerning is "FKV". The name should be padded up to four bytes with space in the+TTF and OTF lists of tables (just like the CFF table). Name: FK space space++<h2>Table</h2>+<pre>+uint16 version_upper = 1;+uint16 version_lower = 0;+uint32 num_kerning_pair_entries;+KerningEntry[]++KerningEntry+uint32 first_glyph_index+uint32 second_glyph_index+fixed16_16 kerning+</pre>++<p>Padding up to four bytes will not be needed because the table already is aligned.</p>++<p>The type fixed16_16 is a fixed point value (16.16) that uses 32 bits, similar to the 255 type in the CFF table.</p>++<p>All kerning entries needs to be sorted on the first and second glyph ID (GID).</p>+</section>++<section class="data">++<h2>Example data</h2>+<pre>+<table>+<tr>+ <td>Type</td>+ <td>Hex</td>+ <td>Decimal</td>+ <td>Description</td>+</tr>++<tr>+ <td>USHORT</td>+ <td>0x0001</td>+ <td class="right">1</td>+ <td>Major verison</td>+</tr>++<tr>+ <td>USHORT</td>+ <td>0x0000</td>+ <td class="right">0</td>+ <td>Minor version</td>+</tr>++<tr>+ <td>ULONG</td>+ <td>0x00000002</td>+ <td class="right">2</td>+ <td>Number of kerning pairs</td>+</tr>++<tr class="empty">+ <td colspan="4"></td>+</tr>++<tr>+ <td>ULONG</td>+ <td>0x00000007</td>+ <td class="right">7</td>+ <td>First GID of the pair (left in LTR languages)</td>+</tr>++<tr>+ <td>ULONG</td>+ <td>0x00000008</td>+ <td class="right">8</td>+ <td>Second GID of the pair (right in LTR languages)</td>+</tr>++<tr>+ <td>Fixed</td>+ <td>0xffffcccc</td>+ <td class="right">-0.2</td>+ <td>Kerning for this pair</td>+</tr>++<tr class="empty">+ <td colspan="4"></td>+</tr>++<tr>+ <td>ULONG</td>+ <td>0x0000000A</td>+ <td class="right">10</td>+ <td>First GID of the second pair (left in LTR languages)</td>+</tr>++<tr>+ <td>ULONG</td>+ <td>0x0000000F</td>+ <td class="right">15</td>+ <td>Second GID of second the pair (right in LTR languages)</td>+</tr>++<tr>+ <td>Fixed</td>+ <td>0xffffe666</td>+ <td class="right">-0.1</td>+ <td>Kerning for this pair</td>+</tr>+</table>+</pre>++</section>+</body>+</html>new file mode 100755@@ -0,0 +1,2 @@+#!/bin/sh+./kerning-example