DirectoryTable.vala
/*
Copyright (C) 2012 2013 2014 Johan Mattsson
This library is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as
published by the Free Software Foundation; either version 3 of the
License, or (at your option) any later version.
This library is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
*/
namespace BirdFont {
/** Table with list of tables sorted by table tag. */
public class DirectoryTable : OtfTable {
public CmapTable cmap_table;
public CvtTable cvt_table;
public FkTable fk_table;
public GaspTable gasp_table;
public GdefTable gdef_table;
public GlyfTable glyf_table;
public GposTable gpos_table;
public GsubTable gsub_table;
public HeadTable head_table;
public HheaTable hhea_table;
public HmtxTable hmtx_table;
public KernTable kern_table;
public MaxpTable maxp_table;
public NameTable name_table;
public Os2Table os_2_table;
public PostTable post_table;
public LocaTable loca_table;
public OffsetTable offset_table;
Gee.ArrayList<OtfTable> tables;
public DirectoryTable () {
offset_table = new OffsetTable (this);
loca_table = new LocaTable ();
gasp_table = new GaspTable ();
gdef_table = new GdefTable ();
glyf_table = new GlyfTable (loca_table);
gsub_table = new GsubTable (glyf_table);
cmap_table = new CmapTable (glyf_table);
cvt_table = new CvtTable ();
head_table = new HeadTable (glyf_table);
hmtx_table = new HmtxTable (head_table, glyf_table);
hhea_table = new HheaTable (glyf_table, head_table, hmtx_table);
kern_table = new KernTable (glyf_table);
gpos_table = new GposTable ();
maxp_table = new MaxpTable (glyf_table);
name_table = new NameTable ();
os_2_table = new Os2Table (glyf_table, hmtx_table, hhea_table);
post_table = new PostTable (glyf_table);
fk_table = new FkTable (glyf_table, kern_table);
id = "Directory table";
tables = new Gee.ArrayList<OtfTable> ();
}
public void process () throws GLib.Error {
// generate font data
glyf_table.process ();
gsub_table.process ();
gasp_table.process ();
gdef_table.process ();
cmap_table.process (glyf_table);
cvt_table.process ();
hmtx_table.process ();
hhea_table.process ();
maxp_table.process ();
name_table.process ();
os_2_table.process ();
head_table.process ();
loca_table.process (glyf_table, head_table);
post_table.process ();
kern_table.process ();
gpos_table.process (glyf_table);
offset_table.process ();
process_directory (); // this table
}
public void process_mac () throws GLib.Error {
os_2_table.process_mac ();
offset_table.process ();
process_directory (); // this table
}
public Gee.ArrayList<OtfTable> get_tables () {
if (tables.size == 0) {
tables.add (offset_table);
tables.add (this);
tables.add (fk_table);
tables.add (gpos_table);
tables.add (gsub_table);
tables.add (os_2_table);
// tables.append (gdef_table); // invalid table
tables.add (cmap_table);
// tables.append (cvt_table);
tables.add (gasp_table);
tables.add (glyf_table);
tables.add (head_table);
tables.add (hhea_table);
tables.add (hmtx_table);
// It looks like the old kerning table is no longer needed
// since most browsers uses the GPOS table
// but Windows does not accept fonts without a kern table.
tables.add (kern_table);
tables.add (loca_table);
tables.add (maxp_table);
tables.add (name_table);
tables.add (post_table);
}
return tables;
}
public void set_offset_table (OffsetTable ot) {
offset_table = ot;
}
public new void parse (FontData dis, OpenFontFormatReader reader_callback) throws Error {
StringBuilder tag = new StringBuilder ();
uint32 checksum;
uint32 offset;
uint32 length;
return_if_fail (offset_table.num_tables > 0);
for (uint i = 0; i < offset_table.num_tables; i++) {
tag.erase ();
tag.append_unichar ((unichar) dis.read_byte ());
tag.append_unichar ((unichar) dis.read_byte ());
tag.append_unichar ((unichar) dis.read_byte ());
tag.append_unichar ((unichar) dis.read_byte ());
checksum = dis.read_ulong ();
offset = dis.read_ulong ();
length = dis.read_ulong ();
printd (@"$(tag.str) \toffset: $offset \tlength: $length \tchecksum: $checksum.\n");
if (tag.str == "FK ") {
fk_table.id = tag.str;
fk_table.checksum = checksum;
fk_table.offset = offset;
fk_table.length = length;
} else if (tag.str == "cvt") {
cvt_table.id = tag.str;
cvt_table.checksum = checksum;
cvt_table.offset = offset;
cvt_table.length = length;
} else if (tag.str == "hmtx") {
hmtx_table.id = tag.str;
hmtx_table.checksum = checksum;
hmtx_table.offset = offset;
hmtx_table.length = length;
} else if (tag.str == "hhea") {
hhea_table.id = tag.str;
hhea_table.checksum = checksum;
hhea_table.offset = offset;
hhea_table.length = length;
} else if (tag.str == "loca") {
loca_table.id = tag.str;
loca_table.checksum = checksum;
loca_table.offset = offset;
loca_table.length = length;
} else if (tag.str == "cmap") {
cmap_table.id = tag.str;
cmap_table.checksum = checksum;
cmap_table.offset = offset;
cmap_table.length = length;
} else if (tag.str == "maxp") {
maxp_table.id = tag.str;
maxp_table.checksum = checksum;
maxp_table.offset = offset;
maxp_table.length = length;
} else if (tag.str == "gasp") {
gasp_table.id = tag.str;
gasp_table.checksum = checksum;
gasp_table.offset = offset;
gasp_table.length = length;
} else if (tag.str == "gdef") {
gdef_table.id = tag.str;
gdef_table.checksum = checksum;
gdef_table.offset = offset;
gdef_table.length = length;
} else if (tag.str == "glyf") {
glyf_table.id = tag.str;
glyf_table.checksum = checksum;
glyf_table.offset = offset;
glyf_table.length = length;
} else if (tag.str == "head") {
head_table.id = tag.str;
head_table.checksum = checksum;
head_table.offset = offset;
head_table.length = length;
} else if (tag.str == "name") {
name_table.id = tag.str;
name_table.checksum = checksum;
name_table.offset = offset;
name_table.length = length;
} else if (tag.str == "OS/2") {
os_2_table.id = tag.str;
os_2_table.checksum = checksum;
os_2_table.offset = offset;
os_2_table.length = length;
} else if (tag.str == "post") {
post_table.id = tag.str;
post_table.checksum = checksum;
post_table.offset = offset;
post_table.length = length;
} else if (tag.str == "kern") {
kern_table.id = tag.str;
kern_table.checksum = checksum;
kern_table.offset = offset;
kern_table.length = length;
} else if (tag.str == "GPOS") {
gpos_table.id = tag.str;
gpos_table.checksum = checksum;
gpos_table.offset = offset;
gpos_table.length = length;
}
}
}
public void parse_all_tables (FontData dis, OpenFontFormatReader reader_callback) throws Error {
head_table.parse (dis);
hhea_table.parse (dis);
reader_callback.set_limits ();
name_table.parse (dis);
post_table.parse (dis);
os_2_table.parse (dis);
maxp_table.parse (dis);
loca_table.parse (dis, head_table, maxp_table);
hmtx_table.parse (dis, hhea_table, loca_table);
cmap_table.parse (dis);
gpos_table.parse (dis);
if (kern_table.has_data ()) {
kern_table.parse (dis);
}
glyf_table.parse (dis, cmap_table, loca_table, hmtx_table, head_table, post_table, kern_table);
if (kern_table.has_data ()) {
gasp_table.parse (dis);
}
if (kern_table.has_data ()) {
cvt_table.parse (dis);
}
if (fk_table.has_data ()) {
fk_table.parse (dis);
}
}
public void parse_fk_table (FontData dis) throws Error {
if (fk_table.has_data ()) {
fk_table.parse (dis);
} else {
warning ("Fk table is empty.");
}
}
public void parse_kern_table (FontData dis) throws Error {
if (kern_table.has_data ()) {
kern_table.parse (dis);
} else {
warning ("Kern table is empty.");
}
}
public void parse_cmap_table (FontData dis) throws Error {
if (cmap_table.has_data ()) {
cmap_table.parse (dis);
} else {
warning ("Cmap table is empty.");
}
}
public void parse_head_table (FontData dis) throws Error {
if (head_table.has_data ()) {
head_table.parse (dis);
} else {
warning ("Head table is empty.");
}
}
public bool validate_tables (FontData dis, File file) {
bool valid = true;
try {
dis.seek (0);
if (!validate_checksum_for_entire_font (dis, file)) {
warning ("file has invalid checksum");
} else {
printd ("Font file has valid checksum.\n");
}
// Skip validation of head table for now it should be simple but it seems to
// be broken in some funny way.
if (!glyf_table.validate (dis)) {
warning ("glyf_table has invalid checksum");
valid = false;
}
if (!maxp_table.validate (dis)) {
warning ("maxp_table has is invalid checksum");
valid = false;
}
if (!loca_table.validate (dis)) {
warning ("loca_table has invalid checksum");
valid = false;
}
if (!cmap_table.validate (dis)) {
warning ("cmap_table has invalid checksum");
valid = false;
}
if (!hhea_table.validate (dis)) {
warning ("hhea_table has invalid checksum");
valid = false;
}
if (!hmtx_table.validate (dis)) {
warning ("hmtx_table has invalid checksum");
valid = false;
}
if (!name_table.validate (dis)) {
warning ("name_table has invalid checksum");
valid = false;
}
if (!os_2_table.validate (dis)) {
warning ("os_2_table has invalid checksum");
valid = false;
}
if (!post_table.validate (dis)) {
warning ("post_table has invalid checksum");
valid = false;
}
if (kern_table.has_data () && !kern_table.validate (dis)) {
warning ("kern_table has invalid checksum");
valid = false;
}
if (fk_table.has_data () && !fk_table.validate (dis)) {
warning ("fk_table has invalid checksum");
valid = false;
}
if (!gpos_table.validate (dis)) {
warning (@"gpos_table has invalid checksum");
if (gpos_table.font_data != null) {
warning (@"Length: $(((!)gpos_table.font_data).length ())\n");
} else {
warning ("font_data is null");
}
valid = false;
}
} catch (GLib.Error e) {
warning (e.message);
valid = false;
}
return valid;
}
bool validate_checksum_for_entire_font (FontData dis, File f) throws GLib.Error {
uint p = head_table.offset + head_table.get_checksum_position ();
uint32 checksum_font, checksum_head;
checksum_head = head_table.get_font_checksum ();
dis.seek (0);
// zero out checksum entry in head table before validating it
dis.write_at (p + 0, 0);
dis.write_at (p + 1, 0);
dis.write_at (p + 2, 0);
dis.write_at (p + 3, 0);
checksum_font = (uint32) (0xB1B0AFBA - dis.checksum ());
if (checksum_font != checksum_head) {
warning (@"Fontfile checksum in head table does not match calculated checksum. checksum_font: $checksum_font checksum_head: $checksum_head");
return false;
}
return true;
}
public long get_font_file_size () {
long length = 0;
foreach (OtfTable t in tables) {
length += t.get_font_data ().length_with_padding ();
}
return length;
}
public void process_directory () throws GLib.Error {
create_directory (); // create directory without offsets to calculate length of offset table and checksum for entre file
create_directory (); // generate a valid directory
}
// Check sum adjustment for the entire font
public uint32 get_font_file_checksum () {
uint32 checksum = 0;
foreach (OtfTable t in tables) {
t.get_font_data ().continous_checksum (ref checksum);
}
return checksum;
}
public void create_directory () throws GLib.Error {
FontData fd;
uint32 table_offset = 0;
uint32 table_length = 0;
uint32 checksum = 0;
fd = new FontData ();
return_if_fail (offset_table.num_tables > 0);
table_offset += offset_table.get_font_data ().length_with_padding ();
if (this.font_data != null) {
table_offset += this.get_font_data ().length_with_padding ();
}
head_table.set_checksum_adjustment (0); // Set this to zero, calculate checksums and update the value
head_table.process ();
// write the directory
foreach (OtfTable t in tables) {
if (t is DirectoryTable || t is OffsetTable) {
continue;
}
printd (@"c $(t.id) offset: $(table_offset) len with pad $(t.get_font_data ().length_with_padding ())\n");
table_length = t.get_font_data ().length (); // without padding
fd.add_tag (t.get_id ()); // name of table
fd.add_u32 (t.get_font_data ().checksum ());
fd.add_u32 (table_offset);
fd.add_u32 (table_length);
table_offset += t.get_font_data ().length_with_padding ();
}
// padding
fd.pad ();
this.font_data = fd;
checksum = get_font_file_checksum ();
head_table.set_checksum_adjustment ((uint32)(0xB1B0AFBA - checksum));
head_table.process (); // update the value
}
}
}