OpenFontFormatWriter.vala
/*
Copyright (C) 2012 Johan Mattsson
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
using Math;
namespace Supplement {
const double UNITS = 10;
class OpenFontFormatWriter : Object {
DataOutputStream os;
DirectoryTable directory_table;
public static Font font;
public OpenFontFormatWriter () {
directory_table = new DirectoryTable ();
}
public static Font get_current_font () {
return font;
}
public void open (File file) throws Error {
assert (!is_null (file));
if (file.query_exists ()) {
warning ("File exists in export.");
throw new FileError.EXIST("OpenFontFormatWriter: file exists.");
}
os = new DataOutputStream(file.create (FileCreateFlags.REPLACE_DESTINATION));
}
public void write_ttf_font (Font font) throws Error {
long dl;
uint8* data;
unowned List<Table> tables;
FontData fd;
uint l;
this.font = font;
directory_table.process ();
tables = directory_table.get_tables ();
dl = directory_table.get_font_file_size ();
if (dl == 0) {
warning ("font is of zero size.");
return;
}
foreach (Table t in tables) {
fd = t.get_font_data ();
data = fd.table_data;
l = fd.length_with_padding ();
for (int j = 0; j < l; j++) {
os.put_byte (data[j]);
}
}
}
public void close () throws Error {
os.close ();
}
}
/** Reader for otf data types. */
class OtfInputStream : Object {
public FileInputStream fin;
public DataInputStream din;
public OtfInputStream (FileInputStream i) throws Error {
din = new DataInputStream (i);
fin = i;
}
public void seek (int64 pos) throws Error
requires (fin.can_seek ()) {
int64 p = fin.tell ();
fin.seek (pos - p, SeekType.CUR);
}
public uint8 read_byte () throws Error {
return din.read_byte ();
}
public void close () {
try {
fin.close ();
din.close ();
} catch (GLib.IOError e) {
warning (e.message);
}
}
}
class FontData : Object {
// Read pointer
uint rp = 0;
// Write pointer
uint wp = 0;
// length without padding
uint32 len = 0;
uint32 padding = 0;
uint32 capacity;
public uint8* table_data = null;
public FontData (uint32 size = 1024) {
capacity = size;
table_data = malloc (size);
}
~FontData () {
if (table_data != null) {
delete table_data;
table_data = null;
}
}
public void write_at (uint pos, uint8 new_data) throws GLib.Error
requires (pos <= capacity)
{
if (unlikely (pos >= len)) {
warning ("end of table reached");
assert (false);
}
table_data[pos]= new_data;
}
public void write_table_data (FontData fd, uint32 offset, uint32 length) {
fd.seek (offset);
for (uint32 i = 0; i < length; i++) {
add (table_data [rp++]);
}
fd.seek (0);
}
public void write_table (OtfInputStream dis, uint32 offset, uint32 length) throws Error {
uint32 l = length + (length % 4); // padding after end of table
uint8 b;
if (length >= l) {
expand (l);
}
if (table_data == null) {
warning ("Failed to allocate memory for ttf data.");
return;
}
seek (0);
dis.seek (offset);
wp = 0;
while (wp < l) {
b = dis.read_byte ();
add (b);
}
rp = 0;
}
public uint length_with_padding () {
return len;
}
public uint length () {
return len - padding;
}
public void pad () {
while (len % 4 != 0) {
add (0);
padding++;
}
}
/** Add additional checksum data to this checksum. */
public void continous_check_sum (ref uint32 current_check_sum) {
uint trp = rp;
uint l;
if (length_with_padding () % 4 != 0) {
warning ("Table is not padded to correct size.");
}
seek (0);
l = (length () % 4 > 0) ? length () / 4 + 1 : length () / 4;
for (uint32 i = 0; i < l; i++) {
current_check_sum += read_uint32 ();
}
rp = trp;
return;
}
public uint32 check_sum () {
uint32 sum = 0;
continous_check_sum (ref sum);
return sum;
}
public void seek_end () {
seek (len);
}
public void seek (uint i) {
rp = i;
wp = i;
}
public uint8 read () {
if (unlikely (rp >= len)) {
warning ("end of table reached");
return 0;
}
return table_data [rp++];
}
public Fixed read_fixed () {
return (Fixed) read_uint32 ();
}
public uint32 read_ulong () {
return read_uint32 ();
}
public uint16 read_ushort () {
uint16 f;
f = read () << 8;
f += read ();
return f;
}
public int16 read_int16 () {
int16 f;
f = read () << 8;
f += read ();
return f;
}
public int16 read_short () throws Error {
return read_int16 ();
}
public uint32 read_uint32 () {
uint32 f;
f = read () << 8 * 3;
f += read () << 8 * 2;
f += read () << 8 * 1;
f += read () << 8 * 0;
return f;
}
public uint64 read_uint64 () {
uint64 f;
f = read () << 8 * 7;
f += read () << 8 * 6;
f += read () << 8 * 5;
f += read () << 8 * 4;
f += read () << 8 * 3;
f += read () << 8 * 2;
f += read () << 8 * 1;
f += read () << 8 * 0;
return f;
}
public F2Dot14 read_f2dot14 () throws Error {
F2Dot14 f = (F2Dot14) read_int16 ();
return f;
}
public uint64 read_udate () throws Error {
return read_uint64 ();
}
public uint8 read_byte () throws Error {
return read ();
}
public char read_char () throws Error {
return (char) read_byte ();
}
public void add_udate (int64 d) throws Error {
add_64 (d);
}
public void add_fixed (Fixed f) throws Error {
add_u32 (f);
}
public void add_fword (int16 word) {
add_16 (word);
}
public void add_f2dot14 (F2Dot14 f) throws Error {
add_u32 (f);
}
public void add_short (int16 d) throws Error {
add_16 (d);
}
public void add_ushort (uint16 d) throws Error {
add_u16 (d);
}
public void add_ulong (uint32 d) throws Error {
add_u32 (d);
}
public void add_byte (uint8 b) throws Error {
add (b);
}
private void expand (uint extra_bytes = 1024) {
capacity += extra_bytes;
table_data = (uint8*) try_realloc (table_data, capacity);
if (table_data == null) {
warning ("Out of memory.");
capacity = 0;
}
}
public void add (uint8 d) {
if (unlikely (len == capacity)) {
expand ();
}
table_data[wp] = d;
if (wp == len) {
len++;
}
wp++;
}
public void add_littleendian_u16 (uint32 i) {
add ((int8) (i & 0x00FF));
add ((int8) ((i & 0xFF00) >> 8));
}
public void add_u16 (uint16 d) {
uint16 n = d >> 8;
add ((uint8)n);
add ((uint8)(d - (n << 8)));
}
public void add_16 (int16 i) {
uint8 s = (uint8) (i >> 8);
add ((uint8) s);
add ((uint8) (i - (s << 8)));
}
public void add_littleendian_u32 (uint32 i) {
add ((int8) (i & 0x000000FF));
add ((int8) ((i & 0x0000FF00) >> 8));
add ((int8) ((i & 0x00FF0000) >> 16));
add ((int8) ((i & 0xFF000000) >> 24));
}
public void add_u32 (uint32 i) {
uint32 s = (uint16) (i >> 16);
add_u16 ((uint16) s);
add_u16 ((uint16) (i - (s << 16)));
}
public void add_32 (int32 i) {
int32 s = (int16) (i >> 16);
add_16 ((int16) s);
add_16 ((int16) (i - (s << 16)));
}
public void add_u64(uint64 i) {
uint64 s = (uint32) (i >> 32);
add_u32 ((uint32) s);
add_u32 ((uint32)(i - (s << 32)));
}
public void add_64(int64 i) {
int64 s = (int32) (i >> 32);
add_u32 ((int32) s);
add_u32 ((int32)(i - (s << 32)));
}
public void add_str_littleendian_utf16 (string s) {
add_str_utf16 (s, true);
}
public void add_str_utf16 (string s, bool little_endian = false) {
int index = 0;
unichar c;
uint8 c0;
uint8 c1;
int l = 0;
while (s.get_next_char (ref index, out c)) {
c0 = (uint8) (c >> 8);
c1 = (uint8) (c - (c0 << 8));
if (little_endian) {
add (c1);
add (c0);
} else {
add (c0);
add (c1);
}
l += 2;
}
assert (l == 2 * s.char_count ());
}
public void add_str (string s) {
uint8[] data = s.data;
for (int n = 0; n < data.length; n++) {
add (data[n]);
}
}
public void add_tag (string s)
requires (s.length == 4 && s.data.length == 4) {
add_str (s);
}
}
class Coordinate {
/** TTF coordinate flags. */
public static const uint8 NONE = 0;
public static const uint8 ON_PATH = 1 << 0;
public static const uint8 X_SHORT_VECTOR = 1 << 1;
public static const uint8 Y_SHORT_VECTOR = 1 << 2;
public static const uint8 REPEAT = 1 << 3;
// same flag or short vector sign flag
public static const uint8 X_IS_SAME = 1 << 4;
public static const uint8 Y_IS_SAME = 1 << 5;
public static const uint8 X_SHORT_VECTOR_POSITIVE = 1 << 4;
public static const uint8 Y_SHORT_VECTOR_POSITIVE = 1 << 5;
}
class Table : Object {
public string id = "NO_ID";
public uint32 checksum = 0;
public uint32 offset = 0;
public uint32 length = 0;
protected FontData? font_data = null;
public virtual string get_id () {
return id;
}
public FontData get_font_data () {
if (font_data == null) {
warning (@"No font data for $(id).");
font_data = new FontData ();
}
return (!) font_data;
}
public bool has_data () {
return length > 0;
}
public virtual void parse (FontData dis) throws GLib.Error {
warning (@"Parse is not implemented for $(id).");
}
/** Validate table checksum. */
public bool validate (FontData dis) {
bool valid;
if (length == 0) {
stderr.printf (@"Table $id is of zero length.\n");
valid = false;
} else {
valid = Table.validate_table (dis, checksum, offset, length, id);
}
if (!valid) {
stderr.printf (@"Table $id is invalid.\n");
}
return valid;
}
public static bool validate_table (FontData dis, uint32 checksum, uint32 offset, uint32 length, string name) {
uint32 ch = calculate_checksum (dis, offset, length, name);
bool c;
c = (ch == checksum);
if (!c) {
stderr.printf(@"Checksum does not match data for $(name).\n");
stderr.printf(@"name: $name, checksum: $checksum, offset: $offset, length: $length\n");
stderr.printf(@"calculated checksum $(ch)\n");
}
return c;
}
public static uint32 calculate_checksum (FontData dis, uint32 offset, uint32 length, string name) {
uint32 checksum = 0;
uint32 l;
dis.seek (offset);
l = (length % 4 > 0) ? length / 4 + 1 : length / 4;
for (uint32 i = 0; i < l; i++) {
checksum += dis.read_ulong ();
}
return checksum;
}
public static uint16 max_pow_2_less_than_i (uint16 ind) {
uint16 last = 0;
uint16 i = 1;
while ((i <<= 1) < ind) {
last = i;
}
return last;
}
public static uint16 max_log_2_less_than_i (uint16 ind) {
return (uint16) (Math.log (ind) / Math.log (2));
}
}
class LocaTable : Table {
uint32* glyph_offsets = null;
public uint32 size = 0;
public LocaTable () {
id = "loca";
}
~LocaTable () {
if (glyph_offsets != null) delete glyph_offsets;
}
public uint32 get_offset (uint32 i) {
return_if_fail (glyph_offsets != null);
if (size == 0) {
warning ("No glyphs in loca table");
}
if (!(0 <= i < size + 1)) {
warning (@"No offset for glyph $i. Requires (0 <= $i < $(size + 1)");
}
return glyph_offsets [i];
}
/** Returns true if glyph at index i is empty and have no body to parse. */
public bool is_empty (uint32 i) {
return_if_fail (glyph_offsets != null);
if (size == 0) {
warning ("No glyphs in loca table");
}
if (!(0 <= i < size + 1)) {
warning (@"No offset for glyph $i. Requires (0 <= $i < $(size + 1)");
}
return glyph_offsets[i] == glyph_offsets[i + 1];
}
public new void parse (FontData dis, HeadTable head_table, MaxpTable maxp_table) throws GLib.Error {
size = maxp_table.num_glyphs;
glyph_offsets = new uint32[size + 1];
dis.seek (offset);
printd (@"size: $size\n");
printd (@"length: $length\n");
printd (@"length/4-1: $(length / 4 - 1)\n");
printd (@"length/2-1: $(length / 2 - 1)\n");
printd (@"head_table.loca_offset_size: $(head_table.loca_offset_size)\n");
switch (head_table.loca_offset_size) {
case 0:
for (long i = 0; i < size + 1; i++) {
glyph_offsets[i] = 2 * dis.read_ushort ();
if (0 < i < size && glyph_offsets[i - 1] > glyph_offsets[i]) {
warning (@"Invalid loca table, it must be sorted. ($(glyph_offsets[i - 1]) > $(glyph_offsets[i]))");
}
}
break;
case 1:
for (long i = 0; i < size + 1; i++) {
glyph_offsets[i] = dis.read_ulong ();
if (0 < i < size && glyph_offsets[i - 1] > glyph_offsets[i]) {
warning (@"Invalid loca table, it must be sorted. ($(glyph_offsets[i - 1]) > $(glyph_offsets[i]))");
}
}
break;
default:
warning ("unknown size for offset in loca table");
break;
}
}
public void print_offsets () {
for (int i = 0; i < size; i++) {
print (@"get_offset ($i): $(get_offset (i))\n");
}
}
public void process (GlyfTable glyf_table, HeadTable head_table) {
FontData fd = new FontData ();
uint32 last = 0;
uint32 prev = 0;
int i = 0;
foreach (uint32 o in glyf_table.location_offsets) {
if (i != 0 && (o - prev) % 4 != 0) {
warning (@"glyph length is not a multiple of four in gid $i");
}
if (o % 4 != 0) {
warning ("glyph is not on a four byte boundry");
assert_not_reached ();
}
prev = o;
i++;
}
if (head_table.loca_offset_size == 0) {
foreach (uint32 o in glyf_table.location_offsets) {
fd.add_u16 ((uint16) (o / 2));
if (o < last) {
warning (@"Loca table must be sorted. ($o < $last)");
}
last = o;
}
} else if (head_table.loca_offset_size == 1) {
foreach (uint32 o in glyf_table.location_offsets) {
fd.add_u32 (o);
if (o < last) {
warning (@"Loca table must be sorted. ($o < $last)");
}
last = o;
}
} else {
warn_if_reached ();
}
if (!(glyf_table.location_offsets.length () == glyf_table.glyphs.length () + 1)) {
warning (@"(glyf_table.location_offsets.length () == glyf_table.glyphs.length () + 1) ($(glyf_table.location_offsets.length ()) == $(glyf_table.glyphs.length () + 1))");
}
fd.pad ();
font_data = fd;
}
}
class GlyfTable : Table {
// Flags for composite glyph
static const uint16 BOTH_ARE_WORDS = 1 << 0;
static const uint16 BOTH_ARE_XY_VALUES = 1 << 1;
static const uint16 ROUND_TO_GRID = 1 << 2;
static const uint16 SCALE = 1 << 3;
static const uint16 RESERVED = 1 << 4;
static const uint16 MORE_COMPONENTS = 1 << 5;
static const uint16 SCALE_X_Y = 1 << 6;
static const uint16 SCALE_WITH_ROTATTION = 1 << 7;
static const uint16 INSTRUCTIONS = 1 << 8;
public int16 xmin = int16.MAX;
public int16 ymin = int16.MAX;
public int16 xmax = int16.MIN;
public int16 ymax = int16.MIN;
public FontData dis;
public HeadTable head_table;
public HmtxTable hmtx_table;
public LocaTable loca_table;
public CmapTable cmap_table; // hack, hack, hack. cmap and post is null when inistialized and set in parse method
public PostTable post_table;
public KernTable kern_table;
public List<uint32> location_offsets;
// list of glyphs sorted in the order we expect to find them in a
// ttf font. notdef is the firs glyph followed by null and nonmarkingreturn.
// after that will all assigned glyphs appear in sorted order, all
// remaining unassigned glyphs live in the last part of the file.
public List<Glyph> glyphs;
uint16 max_points = 0;
uint16 max_contours = 0;
double total_width = 0;
int non_zero_glyphs = 0;
public GlyfTable (LocaTable l) {
id = "glyf";
loca_table = l;
location_offsets = new List<uint32> ();
glyphs = new List<Glyph> ();
}
public int get_gid (string name) {
int i = 0;
foreach (Glyph g in glyphs) {
if (g.name == name) {
return i;
}
i++;
}
return -1;
}
public int16 get_average_width () {
return (int16) (total_width / non_zero_glyphs);
}
public uint16 get_max_contours () {
return max_contours;
}
public uint16 get_max_points () {
return max_points;
}
public uint16 get_first_char () {
return 32; // space
}
public uint16 get_last_char () {
unowned List<Glyph> gl = glyphs.last ();
Glyph g = (!) gl.data;
while (gl != glyphs.first ()) {
g = (!) gl.data;
if (!g.is_unassigned ()) {
break;
}
gl = gl.prev;
}
return (uint16)g.unichar_code;
}
public Glyph? read_glyph (string name) throws GLib.Error {
Glyph? glyph;
Glyph g;
int i;
KernList kl;
string right;
double units_per_em;
double kern_val;
i = post_table.get_gid (name);
if (i == -1) {
return null;
}
glyph = parse_index (i, dis, loca_table, hmtx_table, head_table, post_table);
if (glyph != null) {
printd (@"kern: ");
units_per_em = head_table.get_units_per_em ();
kl = kern_table.get_all_pairs (i);
foreach (Kern k in kl.kernings) {
g = (!) glyph;
right = post_table.get_name (k.right);
kern_val = k.kerning * 1000.0 / units_per_em;
g.add_kerning (right, kern_val);
}
}
return glyph;
}
public new void parse (FontData dis, CmapTable cmap_table, LocaTable loca, HmtxTable hmtx_table, HeadTable head_table, PostTable post_table, KernTable kern_table) throws GLib.Error {
this.cmap_table = cmap_table;
this.post_table = post_table;
this.loca_table = loca;
this.hmtx_table = hmtx_table;
this.head_table = head_table;
this.kern_table = kern_table;
this.dis = dis;
}
Glyph parse_index (int index, FontData dis, LocaTable loca, HmtxTable hmtx_table, HeadTable head_table, PostTable post_table) throws GLib.Error {
Glyph glyph = new Glyph ("");
double xmin, xmax;
double units_per_em = head_table.get_units_per_em ();
unichar character = 0;
string name;
uint32 end;
character = cmap_table.get_char (index);
name = post_table.get_name (index);
if (name == "") {
StringBuilder name_c = new StringBuilder ();
name_c.append_unichar (character);
name = name_c.str;
}
printd (@"name: $(name)\n");
if (!loca.is_empty (index)) {
glyph = parse_next_glyf (dis, character, index, out xmin, out xmax, units_per_em);
glyph.left_limit = xmin - hmtx_table.get_lsb (index);
glyph.left_limit = 0;
glyph.right_limit = glyph.left_limit + hmtx_table.get_advance (index);
} else {
// add empty glyph
glyph = new Glyph (name, character);
glyph.left_limit = -hmtx_table.get_lsb (index);
glyph.right_limit = hmtx_table.get_advance (index) - hmtx_table.get_lsb (index);
}
glyph.name = name;
if (character == 0) {
glyph.set_unassigned (true);
}
if (character == 0 && name != "") {
stderr.printf (@"Got null character\n");
stderr.printf (@"gid: $index\n");
stderr.printf (@"char: $((uint) character)\n");
stderr.printf (@"name: $(name)\n");
}
return glyph;
}
Glyph parse_next_composite_glyf (FontData dis, unichar character, int pgid) throws Error {
uint16 component_flags = 0;
uint16 glyph_index;
int16 arg1 = 0;
int16 arg2 = 0;
uint16 arg1and2;
F2Dot14 scale;
F2Dot14 scalex;
F2Dot14 scaley;
F2Dot14 scale01;
F2Dot14 scale10;
Glyph glyph, linked_glyph;
List<int> x = new List<int> ();
List<int> y = new List<int> ();
List<int> gid = new List<int> ();
double xmin, xmax;
double units_per_em = head_table.get_units_per_em ();
int glid;
StringBuilder name = new StringBuilder ();
name.append_unichar (character);
glyph = new Glyph (name.str, character);
do {
component_flags = dis.read_ushort ();
glyph_index = dis.read_ushort ();
if ((component_flags & BOTH_ARE_WORDS) > 0) {
arg1 = dis.read_short ();
arg2 = dis.read_short ();
} else if ((component_flags & BOTH_ARE_XY_VALUES) > 0) {
arg1 = dis.read_byte ();
arg2 = dis.read_byte ();
}
gid.append (glyph_index);
x.append (arg1);
y.append (arg2);
// if ((component_flags & RESERVED) > 0)
if ((component_flags & SCALE) > 0) {
scale = dis.read_f2dot14 ();
} else if ((component_flags & SCALE_X_Y) > 0) {
scalex = dis.read_f2dot14 ();
scaley = dis.read_f2dot14 ();
} else if ((component_flags & SCALE_WITH_ROTATTION) > 0) {
scalex = dis.read_f2dot14 ();
scale01 = dis.read_f2dot14 ();
scale10 = dis.read_f2dot14 ();
scaley = dis.read_f2dot14 ();
}
} while ((component_flags & MORE_COMPONENTS) > 0);
for (int i = 0; i < gid.length (); i++) {
// compensate xmax ymax with coordinate
glid = gid.nth (i).data;
if (glid == pgid) {
warning ("Cannot link a glyph to it self.");
continue;
}
linked_glyph = parse_next_glyf (dis, character, glid, out xmin, out xmax, units_per_em);
}
/*
if ((component_flags & INSTRUCTIONS) > 0) {
num_instructions = dis.read_ushort ();
for (int i = 0; i < num_instructions; i++) {
dis.read_byte ();
}
}
*/
return glyph;
}
Glyph parse_next_glyf (FontData dis, unichar character, int gid, out double xmin, out double xmax, double units_per_em) throws Error {
uint16* end_points = null;
uint8* instructions = null;
uint8* flags = null;
int16* xcoordinates = null;
int16* ycoordinates = null;
int npoints = 0;
int16 ncontours;
int16 ixmin; // set boundries
int16 iymin;
int16 ixmax;
int16 iymax;
uint16 ninstructions;
int16 rxmin = int16.MAX; // real xmin
int16 rymin = int16.MAX;;
int16 rxmax = int16.MIN;
int16 rymax = int16.MIN;
int nflags;
Error? error = null;
uint start, end, len;
StringBuilder name = new StringBuilder ();
name.append_unichar (character);
start = loca_table.get_offset (gid);
end = loca_table.get_offset (gid + 1);
len = start - end;
dis.seek (offset + start);
ncontours = dis.read_short ();
return_val_if_fail (start < end, new Glyph (""));
if (ncontours == 0) {
warning (@"Got zero contours in glyph $(name.str).");
// should skip body
}
if (ncontours == -1) {
return parse_next_composite_glyf (dis, character, gid);
}
return_val_if_fail (ncontours < len, new Glyph (""));
if (ncontours < -1) {
warning (@"Got $ncontours contours in glyf table.");
error = new BadFormat.PARSE ("Invalid glyf");
throw error;
}
ixmin = dis.read_short ();
iymin = dis.read_short ();
ixmax = dis.read_short ();
iymax = dis.read_short ();
end_points = new uint16[ncontours + 1];
for (int i = 0; i < ncontours; i++) {
end_points[i] = dis.read_ushort (); // FIXA: mind shot vector is negative
if (i > 0 && end_points[i] < end_points[i -1]) {
warning (@"Next endpoint has bad value in $(name.str). (end_points[i] > end_points[i -1]) ($(end_points[i]) > $(end_points[i -1])) i: $i ncontours: $ncontours");
}
}
if (ncontours > 0) {
npoints = end_points[ncontours - 1] + 1;
} else {
npoints = 0;
}
return_val_if_fail (npoints < len, new Glyph.no_lines (""));
ninstructions = dis.read_ushort ();
return_val_if_fail (ninstructions < len, new Glyph.no_lines (""));
instructions = new uint8[ninstructions + 1];
uint8 repeat;
for (int i = 0; i < ninstructions; i++) {
instructions[i] = dis.read_byte ();
}
nflags = 0;
flags = new uint8[npoints + 1];
for (int i = 0; i < npoints; i++) {
flags[i] = dis.read_byte ();
if ((flags[i] & Coordinate.REPEAT) > 0) {
repeat = dis.read_byte ();
if (i + repeat >= npoints) {
error = new BadFormat.PARSE ("Too many flags in glyf in glyph $(name.str). (i >= ninstructions).");
break;
}
for (int j = 0; j < repeat; j++) {
flags[j + i + 1] = flags[i];
}
nflags += repeat;
i += repeat;
}
nflags++;
}
if (nflags != npoints) {
warning (@"(nflags != npoints) ($nflags != $npoints) in $(name.str)");
error = new BadFormat.PARSE (@"Wrong number of flags in glyph $(name.str). (nflags != npoints) ($nflags != $npoints)");
}
warn_if_fail (nflags == npoints);
printd (@"npoints: $npoints\n");
printd (@"ncontours: $ncontours\n");
printd (@"ninstructions: $ninstructions\n");
printd (@"nflags: $nflags\n");
int16 last = 0;
xcoordinates = new int16[npoints + 1];
for (int i = 0; i < npoints; i++) {
if ((flags[i] & Coordinate.X_SHORT_VECTOR) > 0) {
if ((flags[i] & Coordinate.X_SHORT_VECTOR_POSITIVE) > 0) {
xcoordinates[i] = last + dis.read_byte ();
} else {
xcoordinates[i] = last - dis.read_byte ();
}
} else {
if ((flags[i] & Coordinate.X_IS_SAME) > 0) {
xcoordinates[i] = last;
} else {
xcoordinates[i] = last + dis.read_short ();
}
}
last = xcoordinates[i];
if (last > rxmax) rxmax = last;
if (last < rxmin) rxmin = last;
if (!(ixmin <= last <= ixmax)) {
stderr.printf (@"x is out of bounds in glyph $(name.str). ($ixmin <= $last <= $ixmax) char $((uint)character)\n");
}
if (!(head_table.xmin <= last <= head_table.xmax)) {
stderr.printf (@"x is outside of of font bounding box in glyph $(name.str). ($(head_table.xmin) <= $last <= $(head_table.xmax)) char $((uint)character)\n");
}
}
last = 0;
ycoordinates = new int16[npoints + 1];
for (int i = 0; i < npoints; i++) {
if ((flags[i] & Coordinate.Y_SHORT_VECTOR) > 0) {
if ((flags[i] & Coordinate.Y_SHORT_VECTOR_POSITIVE) > 0) {
ycoordinates[i] = last + dis.read_byte ();
} else {
ycoordinates[i] = last - dis.read_byte ();
}
} else {
if ((flags[i] & Coordinate.Y_IS_SAME) > 0) {
ycoordinates[i] = last;
} else {
ycoordinates[i] = last + dis.read_short ();
}
}
last = ycoordinates[i];
if (last > rymax) rymax = last;
if (last < rymin) rymin = last;
if (!(iymin <= last <= iymax)) {
stderr.printf (@"y is out of bounds in glyph $(name.str). ($iymin <= $last <= $iymax) char $((uint)character)\n");
}
if (!(head_table.ymin <= last <= head_table.ymax)) {
stderr.printf (@"y is outside of of font bounding box in glyph $(name.str). ($(head_table.ymin) <= $last <= $(head_table.ymax)) char $((uint)character)\n");
}
}
if (rymin != iymin || rxmin != ixmin || rxmax != ixmax || rymax != iymax) {
warning (@"Warning real boundry for glyph does not match boundry set in glyph header for glyph $(name.str).");
stderr.printf (@"ymin: $rymin header: $iymin\n");
stderr.printf (@"xmin: $rxmin header: $ixmin\n");
stderr.printf (@"ymax: $rymax header: $iymax\n");
stderr.printf (@"xmax: $rxmax header: $ixmax\n");
}
int j = 0;
int first_point;
int last_point = 0;
Glyph glyph;
double x, y;
glyph = new Glyph (name.str, character);
xmin = ixmin * 1000.0 / units_per_em;
xmax = ixmax * 1000.0 / units_per_em;
for (int i = 0; i < ncontours; i++) {
x = 0;
y = 0;
Path path = new Path ();
EditPoint edit_point = new EditPoint ();
bool prev_is_curve = false;
first_point = j;
last_point = end_points[i];
for (; j <= end_points[i]; j++) {
if (j >= npoints) {
warning (@"j >= npoints in glyph $(name.str). (j: $j, end_points[i]: $(end_points[i]), npoints: $npoints)");
break;
}
x = xcoordinates[j] * 1000.0 / units_per_em; // in proportion to em width
y = ycoordinates[j] * 1000.0 / units_per_em;
if ((flags[j] & Coordinate.ON_PATH) > 0) {
// Point
edit_point = new EditPoint ();
edit_point.set_position (x, y);
path.add_point (edit_point);
if (prev_is_curve) {
edit_point.get_left_handle ().set_point_type (PointType.NONE);
edit_point.get_left_handle ().length = 0;
} else {
edit_point.recalculate_linear_handles ();
}
prev_is_curve = false;
} else {
if (prev_is_curve) {
x = x - (x - edit_point.right_handle.x ()) / 2;
y = y - (y - edit_point.right_handle.y ()) / 2;
edit_point = new EditPoint ();
edit_point.set_position (x, y);
path.add_point (edit_point);
}
x = xcoordinates[j] * 1000.0 / units_per_em; // in proportion to em width
y = ycoordinates[j] * 1000.0 / units_per_em;
edit_point.get_left_handle ().set_point_type (PointType.NONE);
edit_point.get_left_handle ().length = 0;
edit_point.type = PointType.CURVE;
edit_point.get_right_handle ().set_point_type (PointType.CURVE);
edit_point.get_right_handle ().move_to_coordinate (x, y);
prev_is_curve = true;
}
}
// last to first point
if (prev_is_curve) {
x = xcoordinates[first_point] * 1000.0 / units_per_em; // in proportion to em width
y = ycoordinates[first_point] * 1000.0 / units_per_em;
x = x - (x - edit_point.right_handle.x ()) / 2;
y = y - (y - edit_point.right_handle.y ()) / 2;
edit_point = new EditPoint ();
edit_point.set_position (x, y);
path.add_point (edit_point);
x = xcoordinates[first_point] * 1000.0 / units_per_em; // in proportion to em width
y = ycoordinates[first_point] * 1000.0 / units_per_em;
edit_point.get_left_handle ().set_point_type (PointType.CURVE);
edit_point.get_left_handle ().length = 0;
edit_point.type = PointType.CURVE;
edit_point.get_right_handle ().set_point_type (PointType.CURVE);
edit_point.get_right_handle ().move_to_coordinate (x, y);
}
// curve last to first
x = xcoordinates[first_point] * 1000.0 / units_per_em; // in proportion to em width
y = ycoordinates[first_point] * 1000.0 / units_per_em;
edit_point.type = PointType.CURVE;
edit_point.get_right_handle ().set_point_type (PointType.CURVE);
edit_point.get_right_handle ().move_to_coordinate (x, y);
path.close ();
glyph.add_path (path);
}
// glyphs with no bounding boxes
if (ixmax <= ixmin) {
warning (@"Bounding box is bad. (xmax == xmin) ($xmax == $xmin)");
if (glyph.path_list.length () > 0) {
Path ps = ((!) glyph.path_list.first ()).data;
ps.update_region_boundries ();
xmin = ps.xmin;
xmax = ps.xmax;
foreach (Path p in glyph.path_list) {
p.update_region_boundries ();
if (p.xmin < xmin) {
xmin = p.xmin;
}
if (p.xmax > xmax) {
xmax = p.xmax;
}
}
}
}
if (end_points != null) delete end_points;
if (instructions != null) delete instructions;
if (flags != null) delete flags;
if (xcoordinates != null) delete xcoordinates;
if (ycoordinates != null) delete ycoordinates;
if (error != null) {
warning ("failed to parse glyph");
throw (!) error;
}
return glyph;
}
public void process_glyph (Glyph g, FontData fd) throws GLib.Error {
int16 txmin, tymin, txmax, tymax;
int16 end_point;
int16 last_end_point;
int16 npoints;
int16 ncontours;
int16 nflags;
double x, y;
Font font = OpenFontFormatWriter.get_current_font ();
int glyph_offset;
uint len;
uint coordinate_length;
fd.seek_end (); // append glyph
glyph_offset = (int) fd.length ();
printd (@"glyph_offset: $(glyph_offset)\n");
g.remove_empty_paths ();
if (g.path_list.length () == 0) {
// location_offsets == location_offset + 1 to tell parser that this glyf does not have a body
return;
}
non_zero_glyphs++;
ncontours = (int16) g.path_list.length ();
fd.add_short (ncontours);
txmin = int16.MAX;
tymin = int16.MAX;
txmax = int16.MIN;
tymax = int16.MIN;
// bounding box will be set again after coordinate arrays have been created
fd.add_16 (10);
fd.add_16 (20);
fd.add_16 (30);
fd.add_16 (40);
// end points
end_point = 0;
last_end_point = 0;
foreach (Path p in g.path_list) {
p = p.get_quadratic_points ();
foreach (EditPoint e in p.points) {
end_point++;
if (e.get_right_handle ().type == PointType.CURVE) {
end_point++;
}
}
fd.add_u16 (end_point - 1);
if (end_point - 1 < last_end_point) {
warning (@"Next endpoint has bad value. (end_point - 1 < last_end_point) ($(end_point - 1) < $last_end_point");
}
last_end_point = end_point - 1;
}
fd.add_u16 (0); // instruction length
uint glyph_header = 12 + ncontours * 2;
printd (@"\next glyf: $(g.name)\n");
printd (@"glyf header length: $(glyph_header)\n");
// instructions should go here
npoints = (ncontours > 0) ? end_point : 0; // +1?
if (npoints > max_points) {
max_points = npoints;
}
if (ncontours > max_contours) {
max_contours = ncontours;
}
// flags
nflags = 0;
List<uint8> flags = new List<uint8> ();
foreach (Path p in g.path_list) {
p = p.get_quadratic_points ();
foreach (EditPoint e in p.points) {
fd.add_byte (Coordinate.ON_PATH);
flags.append (Coordinate.ON_PATH);
nflags++;
if (e.get_right_handle ().type == PointType.CURVE) {
fd.add_byte (Coordinate.NONE);
flags.append (Coordinate.NONE);
nflags++;
}
}
}
if (nflags != npoints) {
warning (@"(nflags != npoints) ($nflags != $npoints) in glyph $(g.name). ncontours: $ncontours");
}
assert (nflags == npoints);
printd (@"flags: $(nflags)\n");
// x coordinates
List<int16> coordinate_x = new List<int16> ();
List<int16> coordinate_y = new List<int16> ();
double prev = 0;
foreach (Path p in g.path_list) {
p = p.get_quadratic_points ();
foreach (EditPoint e in p.points) {
x = rint (e.x * UNITS - prev - g.left_limit * UNITS);
fd.add_16 ((int16) x);
coordinate_x.append ((int16) x);
prev = rint (e.x * UNITS - g.left_limit * UNITS);
if (e.get_right_handle ().type == PointType.CURVE) {
x = rint (e.get_right_handle ().x () * UNITS - prev - g.left_limit * UNITS);
fd.add_16 ((int16) x);
coordinate_x.append ((int16) x);
prev = rint (e.get_right_handle ().x () * UNITS - g.left_limit * UNITS);
}
}
}
// y coordinates
prev = 0;
foreach (Path p in g.path_list) {
p = p.get_quadratic_points ();
foreach (EditPoint e in p.points) {
y = rint (e.y * UNITS - prev + font.base_line * UNITS);
fd.add_16 ((int16) y);
coordinate_y.append ((int16) y);
prev = rint (e.y * UNITS + font.base_line * UNITS);
if (e.get_right_handle ().type == PointType.CURVE) {
y = rint (e.get_right_handle ().y () * UNITS - prev + font.base_line * UNITS);
fd.add_16 ((int16) y);
coordinate_y.append ((int16) y);
prev = rint (e.get_right_handle ().y () * UNITS + font.base_line * UNITS);
}
}
}
len = fd.length ();
printd (@"fd.length (): $(fd.length ())\n");
coordinate_length = fd.length () - nflags - glyph_header;
printd (@"coordinate_length: $(coordinate_length)\n");
assert (fd.length () > nflags + glyph_header);
// bounding box
int16 last = 0;
int i = 0;
foreach (int16 c in coordinate_x) {
c += last;
// Only on curve points are good for calculating bounding box
if ((flags.nth (i).data & Coordinate.ON_PATH) > 0) {
if (c < txmin) txmin = c;
if (c > txmax) txmax = c;
}
last = c;
i++;
}
last = 0;
i = 0;
foreach (int16 c in coordinate_y) {
c += last;
if ((flags.nth (i).data & Coordinate.ON_PATH) > 0) {
if (c < tymin) tymin = c;
if (c > tymax) tymax = c;
}
last = c;
i++;
}
fd.seek_end ();
printd (@"glyph_offset: $(glyph_offset)\n");
printd (@"len: $(len)\n");
fd.seek (glyph_offset + 2); // go to box boundries for this glyf
// assert (fd.read_short () == int16.MAX);
// add bounding box
fd.add_16 (txmin);
fd.add_16 (tymin);
fd.add_16 (txmax);
fd.add_16 (tymax);
fd.seek_end ();
assert (len == fd.length ());
fd.seek (glyph_offset + 2);
assert (fd.read_int16 () == txmin);
assert (fd.read_int16 () == tymin);
assert (fd.read_int16 () == txmax);
assert (fd.read_int16 () == tymax);
fd.seek_end ();
printd (@"\n");
printd (@"txmin: $txmin\n");
printd (@"tymin: $tymin\n");
printd (@"txmax: $txmax\n");
printd (@"tymax: $tymax\n");
// save this for head table
if (txmin < this.xmin) this.xmin = txmin;
if (tymin < this.ymin) this.ymin = tymin;
if (txmax > this.xmax) this.xmax = txmax;
if (tymax > this.ymax) this.ymax = tymax;
// part of average width calculation for OS/2 table
total_width += xmax - xmin;
printd (@"length before padding: $(fd.length ())\n");
// all glyphs needs padding for loca table to be correct
while (fd.length () % 4 != 0) {
fd.add (0);
}
printd (@"length after padding: $(fd.length ())\n");
}
// necessary in order to have glyphs sorted according to ttf specification
public void create_glyph_table () {
Glyph? gl;
Glyph g;
Font font = OpenFontFormatWriter.get_current_font ();
uint32 indice;
List<Glyph> unassigned_glyphs;
// add notdef. character at index zero + other special chars first
glyphs.append (font.get_not_def_character ());
glyphs.append (font.get_null_character ());
glyphs.append (font.get_nonmarking_return ());
glyphs.append (font.get_space ());
unassigned_glyphs = new List<Glyph> ();
// add glyphs, first all assigned then the unassigned ones
for (indice = 0; (gl = font.get_glyph_indice (indice)) != null; indice++) {
g = (!) gl;
if (g.unichar_code <= 27) { // skip control characters
continue;
}
if (g.unichar_code == 32) { // skip space
continue;
}
if (g.name == ".notdef") {
continue;
}
g.remove_empty_paths ();
if (!g.is_unassigned ()) {
glyphs.append (g);
} else {
unassigned_glyphs.append (g);
}
}
foreach (Glyph ug in unassigned_glyphs) {
// glyphs.append (ug);
}
}
public void process () throws GLib.Error {
FontData fd = new FontData ();
create_glyph_table ();
uint last_len = 0;
foreach (Glyph g in glyphs) {
// set values for loca table
assert (fd.length () % 4 == 0);
location_offsets.append (fd.length ());
process_glyph (g, fd);
printd (@"glyf length: $(fd.length () - last_len)\n");
printd (@"loca fd.length (): $(fd.length ())\n");
last_len = fd.length ();
}
location_offsets.append (fd.length ()); // last entry in loca table is special
// every glyph is padded, no padding to be done here
assert (fd.length () % 4 == 0);
font_data = fd;
}
}
class CmapSubtable : Table {
// Override these methods in subtables for each format.
/** Obtain length of subtable in bytes. */
public virtual uint get_length () {
warning ("Invalid CmapSubtable");
return 0;
}
/** Get char code for a glyph id. */
public virtual unichar get_char (uint32 i) {
warning ("Invalid CmapSubtable");
return 0;
}
public void print_cmap () {
StringBuilder s;
unichar c;
for (uint32 i = 0; i < get_length (); i++) {
s = new StringBuilder ();
c = get_char (i);
s.append_unichar (c);
printd (@"Char: $(s.str) val ($((uint32)c))\tindice: $(i)\n");
}
}
}
/** Format 4 cmap subtable */
class CmapSubtableWindowsUnicode : CmapSubtable {
uint16 format = 0;
HashTable <uint64?, unichar> table = new HashTable <uint64?, unichar> (int64_hash, int_equal);
public CmapSubtableWindowsUnicode () {
}
~CmapSubtableWindowsUnicode () {
}
public override uint get_length () {
return table.size ();
}
public override unichar get_char (uint32 indice) {
int64? c = table.lookup (indice);
if (c == 0 && indice == 0) {
return 0;
}
if (c == 0) {
while (table.lookup (--indice) == 0) {
if (indice == 0) {
return 0;
}
}
warning (@"There is no char for glyph number $indice in cmap table. table.size: $(table.size ()))");
return 0;
}
return (unichar) c;
}
public override void parse (FontData dis) throws GLib.Error {
dis.seek (offset);
format = dis.read_ushort ();
switch (format) {
case 4:
parse_format4 (dis);
break;
default:
stderr.printf (@"CmapSubtable is in format $format, it is not supportet (yet).\n");
break;
}
}
public void parse_format4 (FontData dis) throws GLib.Error {
uint16 lang;
uint16 seg_count_x2;
uint16 seg_count;
uint16 search_range;
uint16 entry_selector;
uint16 range_shift;
uint16* end_char = null;
uint16* start_char = null;
int16* id_delta = null;
uint16* id_range_offset = null;
uint16* glyph_id_array = null;
uint32 gid_len;
length = dis.read_ushort ();
lang = dis.read_ushort ();
seg_count_x2 = dis.read_ushort ();
search_range = dis.read_ushort ();
entry_selector = dis.read_ushort ();
range_shift = dis.read_ushort ();
return_if_fail (seg_count_x2 % 2 == 0);
seg_count = seg_count_x2 / 2;
end_char = new uint16[seg_count];
for (int i = 0; i < seg_count; i++) {
end_char[i] = dis.read_ushort ();
}
if (end_char[seg_count - 1] != 0xFFFF) {
warning ("end_char is $(end_char[seg_count - 1]), expecting 0xFFFF.");
}
dis.read_ushort (); // Reserved
start_char = new uint16[seg_count];
for (int i = 0; i < seg_count; i++) {
start_char[i] = dis.read_ushort ();
}
id_delta = new int16[seg_count];
for (int i = 0; i < seg_count; i++) {
id_delta[i] = dis.read_short ();
}
id_range_offset = new uint16[seg_count];
for (int i = 0; i < seg_count; i++) {
id_range_offset[i] = dis.read_ushort ();
}
if (length == 0) {
warning ("cmap subtable version 4 has length 0.");
return;
}
gid_len = (length - 16 - 8 * seg_count) / 2;
glyph_id_array = new uint16[gid_len];
for (int i = 0; i < gid_len; i++) {
glyph_id_array[i] = dis.read_ushort ();
}
// map all values in a hashtable
int indice = 0;
unichar character = 0;
uint32 id;
for (uint16 i = 0; i < seg_count && start_char[i] != 0xFFFF; i++) {
// print_range (start_char[i], end_char[i], id_delta[i], id_range_offset[i]);
uint16 j = 0;
do {
character = start_char[i] + j;
indice = start_char[i] + id_delta[i] + j;
if (id_range_offset[i] == 0) {
table.insert (indice, character);
} else {
// the awkward indexing trick:
id = id_range_offset[i] / 2 + j + i - seg_count;
if (!(0 <= id < gid_len)) {
warning (@"(0 <= id < gid_len) (0 <= $id < $gid_len)");
break;
}
indice = glyph_id_array [id] + id_delta[i];
StringBuilder s = new StringBuilder ();
s.append_unichar (character);
table.insert (indice, character);
}
j++;
} while (character != end_char[i]);
}
if (end_char != null) delete end_char;
if (start_char != null) delete start_char;
if (id_delta != null) delete id_delta;
if (id_range_offset != null) delete id_range_offset;
if (glyph_id_array != null) delete glyph_id_array;
// it has a character for every glyph indice
// assert (validate_subtable ());
}
void print_range (unichar start_char, unichar end_char, uint16 delta_offset, uint16 range_offset) {
StringBuilder s = new StringBuilder ();
StringBuilder e = new StringBuilder ();
s.append_unichar (start_char);
e.append_unichar (end_char);
print (@"New range $(s.str) - $(e.str) delta: $delta_offset, range: $range_offset\n");
}
public void process (FontData fd, GlyfTable glyf_table) throws GLib.Error {
GlyphRange glyph_range = new GlyphRange ();
unowned List<UniRange> ranges;
uint16 seg_count_2;
uint16 seg_count;
uint16 search_range;
uint16 entry_selector;
uint16 range_shift;
uint16 gid_length = 0;
uint32 indice;
uint32 first_assigned = 1;
foreach (Glyph g in glyf_table.glyphs) {
if (!g.is_unassigned ()) {
glyph_range.add_single (g.unichar_code);
}
}
// glyph_range.print_all ();
ranges = glyph_range.get_ranges ();
seg_count = (uint16) ranges.length () + 1;
seg_count_2 = seg_count * 2;
search_range = 2 * largest_pow2 (seg_count);
entry_selector = largest_pow2_exponent (seg_count);
range_shift = seg_count_2 - search_range;
// format
fd.add_ushort (4);
// length of subtable
fd.add_ushort (16 + 8 * seg_count + gid_length);
// language
fd.add_ushort (0);
fd.add_ushort (seg_count_2);
fd.add_ushort (search_range);
fd.add_ushort (entry_selector);
fd.add_ushort (range_shift);
// end codes
indice = first_assigned;
foreach (UniRange u in ranges) {
if (u.stop >= 0xFFFF) {
warning ("Not implemented yet.");
}
fd.add_ushort ((uint16) u.stop);
indice += u.length ();
}
fd.add_ushort (0xFFFF);
fd.add_ushort (0); // Reserved
// start codes
indice = first_assigned; // since first glyph are notdef, null and nonmarkingreturn
foreach (UniRange u in ranges) {
if (u.start >= 0xFFFF) {
warning ("Not implemented yet.");
}
fd.add_ushort ((uint16) u.start);
indice += u.length ();
}
fd.add_ushort (0xFFFF);
// delta
indice = first_assigned;
foreach (UniRange u in ranges) {
if ((u.start - indice) > 0xFFFF && u.start > indice) {
warning ("Need range offset.");
}
fd.add_ushort ((uint16) (indice - u.start));
indice += u.length ();
}
fd.add_ushort (1);
// range offset
foreach (UniRange u in ranges) {
if (u.stop <= 0xFFFF) {
fd.add_ushort (0);
} else {
warning ("Not implemented yet.");
}
}
fd.add_ushort (0);
// Fixa: implement rest of type 4 (mind gid_length in length field)
}
public bool validate_subtable () {
uint32 length = get_length ();
unichar c;
unichar prev;
uint32 i = 0;
uint32 err = 0;
StringBuilder s;
c = get_char (i);
if (c != 0) {
s = new StringBuilder ();
s.append_unichar (c);
warning ("nodef. is mapped to $(s.str)");
}
i++;
while (i < length) {
if (c == 0) {
s = new StringBuilder ();
s.append_unichar (c);
warning (@"No entry in cmap for index $i. Last avalable char is $(s.str) Got $(s.str), ($((uint32)c))");
err++;
return false;
} else {
prev = c;
}
c = get_char (i);
i++;
}
if (err > 0) {
stderr.printf (@"$err glyphs without mapping to a charactercode were found in this font.\n");
}
return true;
}
}
class CmapTable : Table {
GlyfTable glyf_table;
List<CmapSubtable> subtables;
public CmapTable(GlyfTable gt) {
glyf_table = gt;
subtables = new List<CmapSubtable> ();
id = "cmap";
}
public uint32 get_length () {
return get_prefered_table ().get_length ();
}
public unichar get_char (uint32 i) {
return get_prefered_table ().get_char (i) ;
}
CmapSubtable get_prefered_table () {
if (subtables.length () == 0) {
warning ("No cmap table has been parsed.");
return new CmapSubtable ();
}
return subtables.first ().data;
}
public override string get_id () {
return "cmap";
}
public override void parse (FontData dis) throws GLib.Error {
uint16 version;
uint16 nsubtables;
uint16 platform;
uint16 encoding;
uint32 sub_offset;
CmapSubtable subtable;
return_if_fail (offset > 0 && length > 0);
dis.seek (offset);
version = dis.read_ushort ();
nsubtables = dis.read_ushort ();
printd (@"cmap version: $version\n");
printd (@"cmap subtables: $nsubtables\n");
if (version != 0) {
warning (@"Bad version for cmap table: $version expecting 0. Number of subtables: $nsubtables");
return;
}
for (uint i = 0; i < nsubtables; i++) {
platform = dis.read_ushort ();
encoding = dis.read_ushort ();
sub_offset = dis.read_ulong ();
if (platform == 3 && encoding == 1) {
printd (@"Parsing Unicode BMP (UCS-2) Platform: $platform Encoding: $encoding\n");
subtable = new CmapSubtableWindowsUnicode ();
subtable.offset = offset + sub_offset;
subtables.append (subtable);
} else {
stderr.printf (@"Unknown cmap format. Platform: $platform Encoding: $encoding.\n");
}
if (encoding == 3) {
stderr.printf ("Font contains a cmap table with the obsolete encoding 3.\n");
}
}
if (subtables.length () == 0) {
warning ("No suitable cmap subtable found.");
}
foreach (CmapSubtable t in subtables) {
t.parse (dis);
t.print_cmap ();
}
}
/** Character to glyph mapping */
public void process (GlyfTable glyf_table) throws GLib.Error {
FontData fd = new FontData ();
CmapSubtableWindowsUnicode cmap = new CmapSubtableWindowsUnicode ();
uint16 n_encoding_tables;
uint32 subtable_offset = 0;
n_encoding_tables = 1;
fd.add_u16 (0); // table version
fd.add_u16 (n_encoding_tables);
fd.add_u16 (3); // platform
fd.add_u16 (1); // encoding (Format Unicode UCS-4)
subtable_offset = fd.length () + 4;
printd (@"subtable_offset: $(subtable_offset)\n");
fd.add_ulong (subtable_offset);
cmap.process (fd, glyf_table);
// padding
fd.pad ();
this.font_data = fd;
}
}
class HeadTable : Table {
public int16 xmin = int16.MIN;
public int16 ymin = int16.MIN;
public int16 xmax = int16.MAX;
public int16 ymax = int16.MAX;
uint32 adjusted_checksum = 0;
uint16 mac_style;
uint16 lowest_PPEM;
int16 font_direction_hint;
public int16 loca_offset_size = 0; // 0 for int16 1 for int32
int16 glyph_data_format;
Fixed version;
Fixed font_revision;
uint32 magic_number;
uint16 flags;
uint64 created;
uint64 modified;
uint16 units_per_em = 2048;
const uint8 BASELINE_AT_ZERO = 1 << 0;
const uint8 LSB_AT_ZERO = 1 << 1;
GlyfTable glyf_table;
public HeadTable (GlyfTable gt) {
glyf_table = gt;
id = "head";
}
public uint32 get_adjusted_checksum () {
return adjusted_checksum;
}
public double get_units_per_em () {
return units_per_em * 10; // Fixa: we can refactor this number
}
public override void parse (FontData dis) throws GLib.Error {
return_if_fail (offset > 0 && length > 0);
dis.seek (offset);
font_data = new FontData ();
version = dis.read_fixed ();
if (!version.equals (1, 0)) {
warning (@"Expecting head version 1.0 got $(version.get_string ())\n");
}
font_revision = dis.read_fixed ();
adjusted_checksum = dis.read_ulong ();
magic_number = dis.read_ulong ();
if (magic_number != 0x5F0F3CF5) {
warning (@"Magic number is invalid. Got $(magic_number).");
return;
}
flags = dis.read_ushort ();
if ((flags & BASELINE_AT_ZERO) > 0) {
printd ("Flag BASELINE_AT_ZERO has been set.\n");
}
if ((flags & LSB_AT_ZERO) > 0) {
printd ("Flags LSB_AT_ZERO has been set.\n");
}
units_per_em = dis.read_ushort ();
created = dis.read_udate ();
modified = dis.read_udate ();
xmin = dis.read_short ();
ymin = dis.read_short ();
xmax = dis.read_short ();
ymax = dis.read_short ();
printd (@"font boundries:\n");
printd (@"xmin: $xmin\n");
printd (@"ymin: $ymin\n");
printd (@"xmax: $xmax\n");
printd (@"ymax: $ymax\n");
mac_style = dis.read_ushort ();
lowest_PPEM = dis.read_ushort ();
font_direction_hint = dis.read_short ();
loca_offset_size = dis.read_short ();
glyph_data_format = dis.read_short ();
if (glyph_data_format != 0) {
warning (@"Unknown glyph data format. Expecting 0 got $glyph_data_format.");
}
// print_values ();
// Some deprecated values follow here ...
}
void print_values () {
print (@"Version: $(version.get_string ())\n");
print (@"flags: $flags\n");
print (@"font_revision: $(font_revision.get_string ())\n");
print (@"flags: $flags\n");
print (@"Units per em: $units_per_em\n");
print (@"lowest_PPEM: $lowest_PPEM\n");
print (@"font_direction_hint: $font_direction_hint\n");
print (@"loca_offset_size: $loca_offset_size\n");
print (@"glyph_data_format: $glyph_data_format\n");
}
public uint32 get_font_checksum () {
return adjusted_checksum;
}
public void set_check_sum_adjustment (uint32 csa) {
this.adjusted_checksum = csa;
}
public uint32 get_checksum_position () {
return 8;
}
public void process () throws GLib.Error {
FontData font_data = new FontData ();
Fixed version = 1 << 16;
Fixed font_revision = 1 << 16;
font_data.add_fixed (version);
font_data.add_fixed (font_revision);
// Zero on the first run and updated by directory tables checksum calculation
// for the entire font.
font_data.add_u32 (adjusted_checksum);
font_data.add_u32 (0x5F0F3CF5); // magic number
//font_data.add_u16 (BASELINE_AT_ZERO | LSB_AT_ZERO);
font_data.add_u16 (0); // flags
font_data.add_u16 (1000); // units per em (should be a power of two for ttf fonts)
font_data.add_64 (0); // creation time since 1904-01-01
font_data.add_64 (0); // modified time since 1904-01-01
xmin = glyf_table.xmin;
ymin = glyf_table.ymin;
xmax = glyf_table.xmax;
ymax = glyf_table.ymax;
printd (@"font boundries:\n");
printd (@"xmin: $xmin\n");
printd (@"ymin: $ymin\n");
printd (@"xmax: $xmax\n");
printd (@"ymax: $ymax\n");
font_data.add_short (xmin);
font_data.add_short (ymin);
font_data.add_short (xmax);
font_data.add_short (ymax);
font_data.add_u16 (0); // mac style
font_data.add_u16 (2); // smallest recommended size in pixels, ppem
font_data.add_16 (2); // deprecated direction hint
font_data.add_16 (loca_offset_size); // long offset
font_data.add_16 (0); // Use current glyph data format
font_data.pad ();
this.font_data = font_data;
}
}
class HheaTable : Table {
Fixed version;
int16 ascender;
int16 descender;
int16 linegap;
uint16 max_advance;
int16 min_lsb;
int16 min_rsb;
int16 xmax_extent;
int16 carret_slope;
int16 carret_slope_run;
int16 carret_offset;
int16 metric_format;
public int16 num_horizontal_metrics;
GlyfTable glyf_table;
HeadTable head_table;
HmtxTable hmtx_table;
public HheaTable (GlyfTable g, HeadTable h, HmtxTable hm) {
glyf_table = g;
head_table = h;
hmtx_table = hm;
id = "hhea";
}
public double get_ascender () {
return ascender * 1000 / head_table.get_units_per_em ();
}
public double get_descender () {
return descender * 1000 / head_table.get_units_per_em ();
}
public override void parse (FontData dis) throws GLib.Error {
dis.seek (offset);
version = dis.read_fixed ();
if (!version.equals (1, 0)) {
warning (@"wrong version in hhea table $(version.get_string ())");
}
ascender = dis.read_short ();
descender = dis.read_short ();
linegap = dis.read_short ();
max_advance = dis.read_ushort ();
min_lsb = dis.read_short ();
min_rsb = dis.read_short ();
xmax_extent = dis.read_short ();
carret_slope = dis.read_short ();
carret_slope_run = dis.read_short ();
carret_offset = dis.read_short ();
// reserved x 4
dis.read_short ();
dis.read_short ();
dis.read_short ();
dis.read_short ();
metric_format = dis.read_short ();
num_horizontal_metrics = dis.read_short ();
}
public void process () throws GLib.Error {
Font font = OpenFontFormatWriter.get_current_font ();
FontData fd = new FontData ();
Fixed version = 1 << 16;
fd.add_fixed (version); // table version
fd.add_16 ((int16) (-1 * (font.top_position - font.base_line) * UNITS)); // Ascender
fd.add_16 ((int16) (-1 * font.bottom_position * UNITS)); // Descender
fd.add_16 (0); // LineGap
fd.add_u16 (hmtx_table.max_advance); // maximum advance width value in 'hmtx' table.
fd.add_16 (hmtx_table.min_lsb); // min left side bearing
fd.add_16 (hmtx_table.min_rsb); // min right side bearing
fd.add_16 (hmtx_table.max_extent); // x max extent Max(lsb + (xMax - xMin))
fd.add_16 (1); // caretSlopeRise
fd.add_16 (0); // caretSlopeRun
fd.add_16 (0); // caretOffset
// reserved
fd.add_16 (0);
fd.add_16 (0);
fd.add_16 (0);
fd.add_16 (0);
fd.add_16 (0); // metricDataFormat 0 for current format.
fd.add_u16 ((uint16) glyf_table.glyphs.length()); // numberOfHMetrics Number of hMetric entries in 'hmtx' table
// padding
fd.pad ();
this.font_data = fd;
}
}
class HmtxTable : Table {
uint32 nmetrics;
uint32 nmonospaced;
uint16* advance_width = null;
uint16* left_side_bearing = null;
uint16* left_side_bearing_monospaced = null;
public int16 max_advance = 0;
public int16 max_extent = 0;
public int16 min_lsb = 0;
public int16 min_rsb = 0;
HeadTable head_table;
GlyfTable glyf_table;
public HmtxTable (HeadTable h, GlyfTable gt) {
head_table = h;
glyf_table = gt;
id = "hmtx";
}
~HmtxTable () {
if (advance_width != null) delete advance_width;
if (left_side_bearing != null) delete left_side_bearing;
}
public double get_advance (uint32 i) {
if (i >= nmetrics) {
warning (@"i >= nmetrics $i >= $nmetrics");
return 0;
}
return_val_if_fail (advance_width != null, 0.0);
return advance_width[i] * 1000 / head_table.get_units_per_em ();
}
/** Get left side bearing relative to xmin. */
public double get_lsb (uint32 i) {
return_val_if_fail (i < nmetrics, 0.0);
return_val_if_fail (left_side_bearing != null, 0.0);
return left_side_bearing[i] * 1000 / head_table.get_units_per_em ();
}
/** Get left side bearing relative to xmin for monospaces fonts. */
public double get_lsb_mono (uint32 i)
requires (i < nmonospaced && left_side_bearing_monospaced != null) {
return left_side_bearing_monospaced[i] * 1000 / head_table.get_units_per_em ();
}
public void print_all () {
return_if_fail (advance_width != null);
return_if_fail (left_side_bearing != null);
for (int i = 0; i < nmetrics; i++) {
print (@"get_lsb ($i): $(get_lsb(i))\n");
print (@"get_advance ($i): $(get_advance(i))\n");
}
if (left_side_bearing_monospaced != null) {
for (int i = 0; i < nmonospaced; i++) {
print (@"get_lsb_mono ($i): $(get_lsb_mono(i))\n");
}
}
}
public new void parse (FontData dis, HheaTable hhea_table, LocaTable loca_table) throws GLib.Error {
nmetrics = hhea_table.num_horizontal_metrics;
nmonospaced = loca_table.size - nmetrics;
dis.seek (offset);
if (nmetrics > loca_table.size) {
warning (@"(nmetrics > loca_table.size) ($nmetrics > $(loca_table.size))");
return;
}
printd (@"nmetrics: $nmetrics\n");
printd (@"loca_table.size: $(loca_table.size)\n");
advance_width = new uint16[nmetrics];
left_side_bearing = new uint16[nmetrics];
left_side_bearing_monospaced = new uint16[nmonospaced];
for (int i = 0; i < nmetrics; i++) {
advance_width[i] = dis.read_ushort ();
left_side_bearing[i] = dis.read_short ();
}
for (int i = 0; i < nmonospaced; i++) {
left_side_bearing_monospaced[i] = dis.read_short ();
}
}
public void process () {
FontData fd = new FontData ();
int16 advance;
int16 extent;
int16 rsb;
int16 lsb;
double xmin;
double ymin;
double xmax;
double ymax;
// advance and lsb
foreach (Glyph g in glyf_table.glyphs) {
g.boundries (out xmin, out ymin, out xmax, out ymax);
lsb = (int16) (-1 * (g.left_limit - xmin) * UNITS);
advance = (int16) ((g.right_limit - g.left_limit) * UNITS);
extent = (int16) (lsb + (xmax - xmin) * UNITS);
rsb = (int16) (advance - extent);
fd.add_u16 (advance);
fd.add_16 (lsb);
if (advance > max_advance) {
max_advance = advance;
}
if (extent > max_extent) {
max_extent = extent;
}
if (rsb < min_rsb) {
min_rsb = rsb;
}
if (lsb < min_lsb) {
min_lsb = lsb;
}
}
// monospaced lsb ...
font_data = fd;
warn_if_fail (max_advance != 0);
}
}
class MaxpTable : Table {
GlyfTable glyf_table;
public uint16 num_glyphs = 0;
public MaxpTable (GlyfTable g) {
glyf_table = g;
id = "maxp";
}
public override void parse (FontData dis)
requires (offset > 0 && length > 0) {
Fixed format;
dis.seek (offset);
format = dis.read_fixed ();
printd (@"Maxp version: $(format.get_string ())\n");
num_glyphs = dis.read_ushort ();
if (format == 0x00005000) {
return;
}
// Format 1.0 continues here
}
public void process () {
FontData fd = new FontData();
// Version 0.5 for fonts with cff data and 1.0 for ttf
fd.add_u32 (0x00010000);
if (glyf_table.glyphs.length () == 0) {
warning ("Zero glyphs in maxp table.");
}
fd.add_u16 ((uint16) glyf_table.glyphs.length ()); // numGlyphs in the font
fd.add_u16 (glyf_table.get_max_points ()); // max points
fd.add_u16 (glyf_table.get_max_contours ()); // max contours
fd.add_u16 (0); // max composite points
fd.add_u16 (0); // max composite contours
fd.add_u16 (1); // max zones
fd.add_u16 (0); // twilight points
fd.add_u16 (0); // max storage
fd.add_u16 (0); // max function defs
fd.add_u16 (0); // max instruction defs
fd.add_u16 (0); // max stack elements
fd.add_u16 (0); // max size of instructions
fd.add_u16 (0); // max component elements
fd.add_u16 (0); // component depth
fd.pad ();
this.font_data = fd;
}
}
class OffsetTable : Table {
DirectoryTable directory_table;
public uint16 num_tables = 0;
uint16 search_range = 0;
uint16 entry_selector = 0;
uint16 range_shift = 0;
public OffsetTable (DirectoryTable t) {
id = "Offset table";
directory_table = t;
}
public override void parse (FontData dis) throws Error {
Fixed version;
dis.seek (offset);
version = dis.read_fixed ();
num_tables = dis.read_ushort ();
search_range = dis.read_ushort ();
entry_selector = dis.read_ushort ();
range_shift = dis.read_ushort ();
printd (@"Font file version $(version.get_string ())\n");
printd (@"Number of tables $num_tables\n");
}
public void process () throws GLib.Error {
FontData fd = new FontData ();
Fixed version = 0x00010000; // sfnt version 1.0 for TTF CFF else use OTTO
num_tables = (uint16) directory_table.get_tables ().length () - 2; // number of tables, skip DirectoryTable and OffsetTable
search_range = max_pow_2_less_than_i (num_tables) * 16;
entry_selector = max_log_2_less_than_i (num_tables);
range_shift = 16 * num_tables - search_range;
fd.add_fixed (version);
fd.add_u16 (num_tables);
fd.add_u16 (search_range);
fd.add_u16 (entry_selector);
fd.add_u16 (range_shift);
// skip padding for offset table
this.font_data = fd;
}
}
class NameTable : Table {
public static const uint16 COPYRIGHT_NOTICE = 0;
public static const uint16 FONT_NAME = 1;
public static const uint16 SUBFAMILY_NAME = 2;
public static const uint16 UNIQUE_IDENTIFIER = 3;
public static const uint16 FULL_FONT_NAME = 4; // name + subfamily
public static const uint16 VERSION = 5;
public static const uint16 DESCRIPTION = 10;
public static const uint16 PREFERED_FAMILY = 16;
public static const uint16 PREFERED_SUB_FAMILY = 17;
List<uint16> identifiers;
List<string> text;
public NameTable () {
id = "name";
text = new List<string> ();
identifiers = new List<uint16> ();
}
public string get_name (uint16 identifier) {
int i = 0;
foreach (uint16 n in identifiers) {
if (n == identifier) {
return text.nth (i).data;
}
i++;
}
return "";
}
public void print_all () {
stdout.printf (@"$(text.length ()) name table texts:\n");
foreach (string s in text) {
stdout.printf (s);
stdout.printf ("\n");
}
}
public override void parse (FontData dis) throws Error {
uint16 format;
dis.seek (offset);
format = dis.read_ushort ();
switch (format) {
case 0:
parse_format0 (dis);
break;
case 1:
warning ("name table format 1 is not implemented yet");
break;
default:
warning (@"unknown format $format in name table");
break;
}
}
public void parse_format0 (FontData dis) throws Error {
uint16 count;
uint16 storage_offset;
List<uint16> strlen = new List<uint16> ();
List<uint16> off = new List<uint16> ();
List<uint16> name_id = new List<uint16> ();
List<uint16> encoding_id = new List<uint16> ();
List<uint16> platform = new List<uint16> ();
List<uint16> lang = new List<uint16> ();
count = dis.read_ushort ();
storage_offset = dis.read_ushort ();
for (int i = 0; i < count; i++) {
platform.append (dis.read_ushort ());
encoding_id.append (dis.read_ushort ());
lang.append (dis.read_ushort ());
name_id.append (dis.read_ushort ());
strlen.append (dis.read_ushort ());
off.append (dis.read_ushort ());
identifiers.append (name_id.last ().data);
}
int plat;
StringBuilder str;
for (int i = 0; i < count; i++) {
plat = platform.nth (i).data;
dis.seek (offset + storage_offset + off.nth (i).data);
str = new StringBuilder ();
switch (plat) {
case 1:
for (int j = 0; j < strlen.nth (i).data; j++) {
char c = dis.read_char ();
str.append_c (c);
}
break;
case 3:
for (int j = 0; j < strlen.nth (i).data; j += 2) {
unichar c;
char c0 = dis.read_char ();
char c1 = dis.read_char ();
c = c0 << 8;
c += c1;
str.append_unichar (c);
}
break;
default:
break;
}
text.append (str.str);
printd (@"Name id: $(name_id.nth (i).data) platform: $(platform.nth (i).data) enc: $(encoding_id.nth (i).data) lang: $(lang.nth (i).data) len: $(strlen.nth (i).data) str: \"$(str.str)\"\n");
}
}
public void process () throws GLib.Error {
FontData fd = new FontData ();
Font font = OpenFontFormatWriter.get_current_font ();
uint16 len = 0;
string t;
uint16 p;
uint16 l;
uint16 num_records;
List<uint16> type = new List<uint16> ();
List<string> text = new List<string> ();
text.append ("Copyright");
type.append (COPYRIGHT_NOTICE);
text.append (font.get_name ());
type.append (FONT_NAME);
text.append ("Regular");
type.append (SUBFAMILY_NAME);
text.append (font.get_name ()); // TODO: validate
type.append (UNIQUE_IDENTIFIER);
text.append (font.get_name ());
type.append (FULL_FONT_NAME);
// This does for some reason cause an internal error in ms fontvalidatior utility.
// Head table can't parse integer from string.
text.append ("Version 1.0");
type.append (VERSION);
text.append ("");
type.append (DESCRIPTION);
text.append (font.get_name ());
type.append (PREFERED_FAMILY);
text.append ("Regular");
type.append (PREFERED_SUB_FAMILY);
num_records = (uint16) text.length ();
fd.add_ushort (0); // format 1
fd.add_ushort (2 * num_records); // nplatforms * nrecords
fd.add_ushort (6 + 12 * 2 * num_records); // string storage offset
for (int i = 0; i < num_records; i++) {
t = (!) text.nth (i).data;
p = (!) type.nth (i).data;
l = (uint16) t.length;
fd.add_ushort (1); // platform
fd.add_ushort (0); // encoding id
fd.add_ushort (0); // language
fd.add_ushort (p); // name id
fd.add_ushort (l); // strlen
fd.add_ushort (len); // offset from begining of string storage
len += l;
}
for (int i = 0; i < num_records; i++) {
t = (!) text.nth (i).data;
p = (!) type.nth (i).data;
l = (uint16) (2 * t.char_count ());
fd.add_ushort (3); // platform
fd.add_ushort (1); // encoding id
fd.add_ushort (0x0409); // language
fd.add_ushort (p); // name id
fd.add_ushort (l); // strlen
fd.add_ushort (len); // offset from begining of string storage
len += l;
}
// platform 1
foreach (string s in text) {
fd.add_str (s);
}
// platform 3
foreach (string s in text) {
fd.add_str_utf16 (s);
}
fd.pad ();
this.font_data = fd;
}
}
class Os2Table : Table {
public Os2Table () {
id = "OS/2";
}
public override void parse (FontData dis) throws Error {
}
public void process (GlyfTable glyf_table) {
FontData fd = new FontData ();
Font font = OpenFontFormatWriter.get_current_font ();
int16 ascender;
int16 descender;
fd.add_u16 (0x0002); // USHORT Version 0x0000, 0x0001, 0x0002, 0x0003, 0x0004
fd.add_16 (glyf_table.get_average_width ()); // SHORT xAvgCharWidth
fd.add_u16 (400); // USHORT usWeightClass (400 is normal)
fd.add_u16 (5); // USHORT usWidthClass (5 is normal)
fd.add_u16 (0); // USHORT fsType
fd.add_16 (40); // SHORT ySubscriptXSize
fd.add_16 (40); // SHORT ySubscriptYSize
fd.add_16 (40); // SHORT ySubscriptXOffset
fd.add_16 (40); // SHORT ySubscriptYOffset
fd.add_16 (40); // SHORT ySuperscriptXSize
fd.add_16 (40); // SHORT ySuperscriptYSize
fd.add_16 (40); // SHORT ySuperscriptXOffset
fd.add_16 (40); // SHORT ySuperscriptYOffset
fd.add_16 (40); // SHORT yStrikeoutSize
fd.add_16 (200); // SHORT yStrikeoutPosition
fd.add_16 (40); // SHORT sFamilyClass
// PANOSE
fd.add (0);
fd.add (0);
fd.add (0);
fd.add (0);
fd.add (0);
fd.add (0);
fd.add (0);
fd.add (0);
fd.add (0);
fd.add (0);
// FIXA:
fd.add_u32 (0); // ulUnicodeRange1 Bits 0-31
fd.add_u32 (0); // ULONG ulUnicodeRange2 Bits 32-63
fd.add_u32 (0); // ULONG ulUnicodeRange3 Bits 64-95
fd.add_u32 (0); // ULONG ulUnicodeRange4 Bits 96-127
fd.add_tag ("----"); // VendID
fd.add_u16 (0); // USHORT fsSelection
fd.add_u16 (glyf_table.get_first_char ()); // USHORT usFirstCharIndex
fd.add_u16 (glyf_table.get_last_char ()); // USHORT usLastCharIndex
ascender = (int16) (glyf_table.xmax + font.base_line * UNITS);
descender = (int16) (-glyf_table.xmin + font.base_line * UNITS);
fd.add_16 (ascender); // SHORT sTypoAscender
fd.add_16 (descender); // SHORT sTypoDescender
fd.add_16 (3); // SHORT sTypoLineGap
fd.add_u16 (ascender); // USHORT usWinAscent
fd.add_u16 (descender); // USHORT usWinDescent
// FIXA:
fd.add_u32 (0); // ULONG ulCodePageRange1 Bits 0-31
fd.add_u32 (0); // ULONG ulCodePageRange2 Bits 32-63
fd.add_16 (ascender); // SHORT sxHeight version 0x0002 and later
fd.add_16 (ascender); // SHORT sCapHeight version 0x0002 and later
fd.add_16 (0); // USHORT usDefaultChar version 0x0002 and later
fd.add_16 (0x0020); // USHORT usBreakChar version 0x0002 and later, also known as space
// FIXA: calculate these values
fd.add_16 (1); // USHORT usMaxContext version 0x0002 and later
// padding
fd.pad ();
this.font_data = fd;
}
}
class PostTable : Table {
GlyfTable glyf_table;
List<uint16> index = new List<uint16> ();
List<string> names = new List<string> ();
List<string> available_names = new List<string> ();
public PostTable (GlyfTable g) {
id = "post";
glyf_table = g;
}
public unowned List<string> get_all_names () {
return available_names;
}
public int get_gid (string name) { // fixa do fast lookup
int i = 0;
int j = 0;
foreach (string n in names) {
if (n == name) {
j = 0;
foreach (uint16 k in index) {
if (k == i) {
return j;
}
j++;
}
return i;
}
i++;
}
// warn_if_reached ();
return -1;
}
public string get_name (int gid) {
int k;
if (!(0 <= gid < index.length ())) {
warning ("gid is out of range.");
return "";
}
k = (!) index.nth (gid).data;
if (gid != 0 && k == 0) {
warning (@"Glyph $gid is assigned to name .notdef, only gid 0 can be .notdef character.");
return "";
}
if (!(0 <= k < names.length ())) {
warning ("k is out of range.");
return "";
}
return (!) names.nth (k).data;
}
public override void parse (FontData dis) throws Error {
dis.seek (offset);
Fixed format = dis.read_fixed ();
Fixed italic = dis.read_fixed ();
int16 underlie_pos = dis.read_short ();
int16 underlie_thickness = dis.read_short ();
uint32 is_fixed_pitch = dis.read_ulong ();
uint32 mem_min42 = dis.read_ulong ();
uint32 mem_max42 = dis.read_ulong ();
uint32 mem_min1 = dis.read_ulong ();
uint32 mem_max1 = dis.read_ulong ();
uint16 nnames = dis.read_ushort ();
if (format != 0x00020000) {
warning ("Only post tables of version 2 will parset got $(format.get_string ())");
return;
}
printd (@"format: $(format.get_string ())\n");
printd (@"italic: $(italic.get_string ())\n");
printd (@"underlie_pos: $(underlie_pos)\n");
printd (@"underlie_thickness: $(underlie_thickness)\n");
printd (@"is_fixed_pitch: $(is_fixed_pitch)\n");
printd (@"mem_min42: $(mem_min42)\n");
printd (@"mem_max42: $(mem_max42)\n");
printd (@"mem_min1: $(mem_min1)\n");
printd (@"mem_max1: $(mem_max1)\n");
printd (@"\n");
printd (@"Num names: $(nnames)\n");
uint16 k;
int non_standard_names = 0;
for (uint16 i = 0; i < nnames; i++) {
k = dis.read_ushort ();
index.append (k);
if (k >= 258) {
non_standard_names++;
}
}
add_standard_names ();
// read non standard names
for (int i = 0; i < non_standard_names; i++) {
uint8 len = dis.read_byte ();
StringBuilder name = new StringBuilder ();
for (int j = 0; j < len; j++) {
name.append_c (dis.read_char ());
}
names.append (name.str);
}
populate_available ();
}
void populate_available () {
for (int i = 0; i < index.length (); i++) {
available_names.append (get_name (i));
}
}
public void print_all () {
print (@"PostScript glyph mapping:\n");
for (int i = 0; i < index.length (); i++) {
print (@"gid $i -> $(get_name (i))\n");
}
}
public void print_all_names () {
print (@"Post table names:\n");
foreach (var n in names) {
print (@"$n\n");
}
}
// the Macintosh standard order
private void add_standard_names () {
names.append (".notdef");
names.append (".null");
names.append ("nonmarkingreturn");
names.append ("space");
names.append ("exclam");
names.append ("quotedbl");
names.append ("numbersign");
names.append ("dollar");
names.append ("percent");
names.append ("ampersand");
names.append ("quotesingle");
names.append ("parenleft");
names.append ("parenright");
names.append ("asterisk");
names.append ("plus");
names.append ("comma");
names.append ("hyphen");
names.append ("period");
names.append ("slash");
names.append ("zero");
names.append ("one");
names.append ("two");
names.append ("three");
names.append ("four");
names.append ("five");
names.append ("six");
names.append ("seven");
names.append ("eight");
names.append ("nine");
names.append ("colon");
names.append ("semicolon");
names.append ("less");
names.append ("equal");
names.append ("greater");
names.append ("question");
names.append ("at");
names.append ("A");
names.append ("B");
names.append ("C");
names.append ("D");
names.append ("E");
names.append ("F");
names.append ("G");
names.append ("H");
names.append ("I");
names.append ("J");
names.append ("K");
names.append ("L");
names.append ("M");
names.append ("N");
names.append ("O");
names.append ("P");
names.append ("Q");
names.append ("R");
names.append ("S");
names.append ("T");
names.append ("U");
names.append ("V");
names.append ("W");
names.append ("X");
names.append ("Y");
names.append ("Z");
names.append ("bracketleft");
names.append ("backslash");
names.append ("bracketright");
names.append ("asciicircum");
names.append ("underscore");
names.append ("grave");
names.append ("a");
names.append ("b");
names.append ("c");
names.append ("d");
names.append ("e");
names.append ("f");
names.append ("g");
names.append ("h");
names.append ("i");
names.append ("j");
names.append ("k");
names.append ("l");
names.append ("m");
names.append ("n");
names.append ("o");
names.append ("p");
names.append ("q");
names.append ("r");
names.append ("s");
names.append ("t");
names.append ("u");
names.append ("v");
names.append ("w");
names.append ("x");
names.append ("y");
names.append ("z");
names.append ("braceleft");
names.append ("bar");
names.append ("braceright");
names.append ("asciitilde");
names.append ("Adieresis");
names.append ("Aring");
names.append ("Ccedilla");
names.append ("Eacute");
names.append ("Ntilde");
names.append ("Odieresis");
names.append ("Udieresis");
names.append ("aacute");
names.append ("agrave");
names.append ("acircumflex");
names.append ("adieresis");
names.append ("atilde");
names.append ("aring");
names.append ("ccedilla");
names.append ("eacute");
names.append ("egrave");
names.append ("ecircumflex");
names.append ("edieresis");
names.append ("iacute");
names.append ("igrave");
names.append ("icircumflex");
names.append ("idieresis");
names.append ("ntilde");
names.append ("oacute");
names.append ("ograve");
names.append ("ocircumflex");
names.append ("odieresis");
names.append ("otilde");
names.append ("uacute");
names.append ("ugrave");
names.append ("ucircumflex");
names.append ("udieresis");
names.append ("dagger");
names.append ("degree");
names.append ("cent");
names.append ("sterling");
names.append ("section");
names.append ("bullet");
names.append ("paragraph");
names.append ("germandbls");
names.append ("registered");
names.append ("copyright");
names.append ("trademark");
names.append ("acute");
names.append ("dieresis");
names.append ("notequal");
names.append ("AE");
names.append ("Oslash");
names.append ("infinity");
names.append ("plusminus");
names.append ("lessequal");
names.append ("greaterequal");
names.append ("yen");
names.append ("mu");
names.append ("partialdiff");
names.append ("summation");
names.append ("product");
names.append ("pi");
names.append ("integral");
names.append ("ordfeminine");
names.append ("ordmasculine");
names.append ("Omega");
names.append ("ae");
names.append ("oslash");
names.append ("questiondown");
names.append ("exclamdown");
names.append ("logicalnot");
names.append ("radical");
names.append ("florin");
names.append ("approxequal");
names.append ("Delta");
names.append ("guillemotleft");
names.append ("guillemotright");
names.append ("ellipsis");
names.append ("nonbreakingspace");
names.append ("Agrave");
names.append ("Atilde");
names.append ("Otilde");
names.append ("OE");
names.append ("oe");
names.append ("endash");
names.append ("emdash");
names.append ("quotedblleft");
names.append ("quotedblright");
names.append ("quoteleft");
names.append ("quoteright");
names.append ("divide");
names.append ("lozenge");
names.append ("ydieresis");
names.append ("Ydieresis");
names.append ("fraction");
names.append ("currency");
names.append ("guilsinglleft");
names.append ("guilsinglright");
names.append ("fi");
names.append ("fl");
names.append ("daggerdbl");
names.append ("periodcentered");
names.append ("quotesinglbase");
names.append ("quotedblbase");
names.append ("perthousand");
names.append ("Acircumflex");
names.append ("Ecircumflex");
names.append ("Aacute");
names.append ("Edieresis");
names.append ("Egrave");
names.append ("Iacute");
names.append ("Icircumflex");
names.append ("Idieresis");
names.append ("Igrave");
names.append ("Oacute");
names.append ("Ocircumflex");
names.append ("apple");
names.append ("Ograve");
names.append ("Uacute");
names.append ("Ucircumflex");
names.append ("Ugrave");
names.append ("dotlessi");
names.append ("circumflex");
names.append ("tilde");
names.append ("macron");
names.append ("breve");
names.append ("dotaccent");
names.append ("ring");
names.append ("cedilla");
names.append ("hungarumlaut");
names.append ("ogonek");
names.append ("caron");
names.append ("Lslash");
names.append ("lslash");
names.append ("Scaron");
names.append ("scaron");
names.append ("Zcaron");
names.append ("zcaron");
names.append ("brokenbar");
names.append ("Eth");
names.append ("eth");
names.append ("Yacute");
names.append ("yacute");
names.append ("Thorn");
names.append ("thorn");
names.append ("minus");
names.append ("multiply");
names.append ("onesuperior");
names.append ("twosuperior");
names.append ("threesuperior");
names.append ("onehalf");
names.append ("onequarter");
names.append ("threequarters");
names.append ("franc");
names.append ("Gbreve");
names.append ("gbreve");
names.append ("Idotaccent");
names.append ("Scedilla");
names.append ("scedilla");
names.append ("Cacute");
names.append ("cacute");
names.append ("Ccaron");
names.append ("ccaron");
names.append ("dcroat");
}
// mapping with char code to standard order
int get_standard_index (unichar c) {
switch (c) {
// entry 0 is the .notdef
case '\0':
return 1;
case '\r':
return 2;
case ' ': // space
return 3;
case '!':
return 4;
case '"':
return 5;
case '#':
return 6;
case '$':
return 7;
case '%':
return 8;
case '&':
return 9;
case '\'':
return 10;
case '(':
return 11;
case ')':
return 12;
case '*':
return 13;
case '+':
return 14;
case ',':
return 15;
case '-':
return 16;
case '.':
return 17;
case '/':
return 18;
case '0':
return 19;
case '1':
return 20;
case '2':
return 21;
case '3':
return 22;
case '4':
return 23;
case '5':
return 24;
case '6':
return 25;
case '7':
return 26;
case '8':
return 27;
case '9':
return 28;
case ':':
return 29;
case ';':
return 30;
case '<':
return 31;
case '=':
return 32;
case '>':
return 33;
case '?':
return 34;
case '@':
return 35;
case 'A':
return 36;
case 'B':
return 37;
case 'C':
return 38;
case 'D':
return 39;
case 'E':
return 40;
case 'F':
return 41;
case 'G':
return 42;
case 'H':
return 43;
case 'I':
return 44;
case 'J':
return 45;
case 'K':
return 46;
case 'L':
return 47;
case 'M':
return 48;
case 'N':
return 49;
case 'O':
return 50;
case 'P':
return 51;
case 'Q':
return 52;
case 'R':
return 53;
case 'S':
return 54;
case 'T':
return 55;
case 'U':
return 56;
case 'V':
return 57;
case 'W':
return 58;
case 'X':
return 59;
case 'Y':
return 60;
case 'Z':
return 61;
case '[':
return 62;
case '\\':
return 63;
case ']':
return 64;
case '^':
return 65;
case '_':
return 66;
case '`':
return 67;
case 'a':
return 68;
case 'b':
return 69;
case 'c':
return 70;
case 'd':
return 71;
case 'e':
return 72;
case 'f':
return 73;
case 'g':
return 74;
case 'h':
return 75;
case 'i':
return 76;
case 'j':
return 77;
case 'k':
return 78;
case 'l':
return 79;
case 'm':
return 80;
case 'n':
return 81;
case 'o':
return 82;
case 'p':
return 83;
case 'q':
return 84;
case 'r':
return 85;
case 's':
return 86;
case 't':
return 87;
case 'u':
return 88;
case 'v':
return 89;
case 'w':
return 90;
case 'x':
return 91;
case 'y':
return 92;
case 'z':
return 93;
case '{':
return 94;
case '|':
return 95;
case '}':
return 96;
case '~':
return 97;
case 'Ä':
return 98;
case 'Å':
return 99;
case 'Ç':
return 100;
case 'É':
return 101;
case 'Ñ':
return 102;
case 'Ö':
return 103;
case 'Ü':
return 104;
case 'á':
return 105;
case 'à':
return 106;
case 'â':
return 107;
case 'ä':
return 108;
case 'ã':
return 109;
case 'å':
return 110;
case 'ç':
return 111;
case 'é':
return 112;
case 'è':
return 113;
case 'ê':
return 114;
case 'ë':
return 115;
case 'í':
return 116;
case 'ì':
return 117;
case 'î':
return 118;
case 'ï':
return 119;
case 'ñ':
return 120;
case 'ó':
return 121;
case 'ò':
return 122;
case 'ô':
return 123;
case 'ö':
return 124;
case 'õ':
return 125;
case 'ú':
return 126;
case 'ù':
return 127;
case 'û':
return 128;
case 'ü':
return 129;
case '†':
return 130;
case '°':
return 131;
case '¢':
return 132;
case '£':
return 133;
case '§':
return 134;
case '•':
return 135;
case '¶':
return 136;
case 'ß':
return 137;
case '®':
return 138;
case '©':
return 139;
case '™':
return 140;
case '´':
return 141;
case '¨':
return 142;
case '≠':
return 143;
case 'Æ':
return 144;
case 'Ø':
return 145;
case '∞':
return 146;
case '±':
return 147;
case '≤':
return 148;
case '≥':
return 149;
case '¥':
return 150;
case 'µ':
return 151;
case '∂':
return 152;
case '∑':
return 153;
case '∏':
return 154;
case 'π':
return 155;
case '∫':
return 156;
case 'ª':
return 157;
case 'º':
return 158;
case 'Ω':
return 159;
case 'æ':
return 160;
case 'ø':
return 161;
case '¿':
return 162;
case '¡':
return 163;
case '¬':
return 164;
case '√':
return 165;
case 'ƒ':
return 166;
case '≈':
return 167;
case '∆':
return 168;
case '«':
return 169;
case '»':
return 170;
case '…':
return 171;
case ' ': // non breaking space
return 172;
case 'À':
return 173;
case 'Ã':
return 174;
case 'Õ':
return 175;
case 'Œ':
return 176;
case 'œ':
return 177;
case '–':
return 178;
case '—':
return 179;
case '“':
return 180;
case '”':
return 181;
case '‘':
return 182;
case '’':
return 183;
case '÷':
return 184;
case '◊':
return 185;
case 'ÿ':
return 186;
case 'Ÿ':
return 187;
case '⁄':
return 188;
case '¤':
return 189;
case '‹':
return 190;
case '›':
return 191;
case 'fi':
return 192;
case 'fl':
return 193;
case '‡':
return 194;
case '·':
return 195;
case '‚':
return 196;
case '„':
return 197;
case '‰':
return 198;
case 'Â':
return 199;
case 'Ê':
return 200;
case 'Á':
return 201;
case 'Ë':
return 202;
case 'È':
return 203;
case 'Í':
return 204;
case 'Î':
return 205;
case 'Ï':
return 206;
case 'Ì':
return 207;
case 'Ó':
return 208;
case 'Ô':
return 209;
// Machintosh apple goes here
// return 210;
case 'Ò':
return 211;
case 'Ú':
return 212;
case 'Û':
return 213;
case 'Ù':
return 214;
case 'ı':
return 215;
case 'ˆ':
return 216;
case '˜':
return 217;
case '¯':
return 218;
case '˘':
return 219;
case '˙':
return 220;
case '˚':
return 221;
case '¸':
return 222;
case '˝':
return 223;
case '˛':
return 224;
case 'ˇ':
return 225;
case 'Ł':
return 226;
case 'ł':
return 227;
case 'Š':
return 228;
case 'š':
return 229;
case 'Ž':
return 230;
case 'ž':
return 231;
case '¦':
return 232;
case 'Ð':
return 233;
case 'ð':
return 234;
case 'Ý':
return 235;
case 'ý':
return 236;
case 'Þ':
return 237;
case 'þ':
return 238;
case '−':
return 239;
case '×':
return 240;
case '¹':
return 241;
case '²':
return 242;
case '³':
return 243;
case '½':
return 244;
case '¼':
return 245;
case '¾':
return 246;
case '₣':
return 247;
case 'Ğ':
return 248;
case 'ğ':
return 249;
case 'İ':
return 250;
case 'Ş':
return 251;
case 'ş':
return 252;
case 'Ć':
return 253;
case 'ć':
return 254;
case 'Č':
return 255;
case 'č':
return 256;
case 'đ':
return 257;
}
return 0;
}
public void process () throws Error {
FontData fd = new FontData ();
string n;
fd.add_fixed (0x00020000); // Version
fd.add_fixed (0x00000000); // italicAngle
fd.add_short (-2); // underlinePosition
fd.add_short (1); // underlineThickness
fd.add_ulong (0); // non zero for monospaced font
// mem boundries may be omitted
fd.add_ulong (0); // min mem for type 42
fd.add_ulong (0); // max mem for type 42
fd.add_ulong (0); // min mem for Type1
fd.add_ulong (0); // max mem for Type1
fd.add_ushort ((uint16) glyf_table.glyphs.length ());
// this part of the spec is so weird
fd.add_ushort ((uint16) 0); // first index is .notdef
index.append (0);
assert (names.length () == 0);
add_standard_names ();
int index;
Glyph g;
for (int i = 1; i < glyf_table.glyphs.length (); i++) {
g = (!) glyf_table.glyphs.nth (i).data;
index = get_standard_index (g.unichar_code);
if (index != 0) {
fd.add_ushort ((uint16) index); // use standard name
} else {
printd ("Adding non standard postscript name $(g.get_name ())\n");
index = (int) names.length (); // use font specific name
fd.add_ushort ((uint16) index);
names.append (g.get_name ());
}
this.index.append ((uint16) index);
}
for (int i = 258; i < names.length (); i++) {
n = (!) names.nth (i).data;
if (n.length > 0xFF) {
warning ("too long name for glyph $n");
}
fd.add ((uint8) n.length); // length of string
fd.add_str (n);
}
fd.pad ();
this.font_data = fd;
}
}
class GaspTable : Table {
public GaspTable () {
id = "gasp";
}
public override void parse (FontData dis) throws Error {
}
public void process () throws GLib.Error {
FontData fd = new FontData ();
fd.add_ushort (0);
fd.add_ushort (0);
fd.pad ();
this.font_data = fd;
}
}
class GdefTable : Table {
public GdefTable () {
id = "GDEF";
}
public override void parse (FontData dis) throws Error {
}
public void process () throws GLib.Error {
FontData fd = new FontData ();
fd.add_ulong (0x00010002);
fd.add_ushort (0); // class def
fd.add_ushort (0); // attach list
fd.add_ushort (0); // ligature carret
fd.add_ushort (0); // mark attach
fd.add_ushort (0); // mark glyf
fd.add_ushort (0); // mark glyf set def
fd.pad ();
this.font_data = fd;
}
}
class CvtTable : Table {
public CvtTable () {
id = "cvt ";
}
public override void parse (FontData dis) throws Error {
}
public void process () throws GLib.Error {
FontData fd = new FontData ();
fd.add_ushort (0);
fd.pad ();
this.font_data = fd;
}
}
class Kern : GLib.Object {
public uint16 left;
public uint16 right;
public int16 kerning;
public Kern (uint16 l, uint16 r, int16 k) {
left = l;
right = r;
kerning = k;
}
}
class KernList : GLib.Object {
public List<Kern> kernings;
public KernList () {
kernings = new List<Kern> ();
}
}
class KernTable : Table {
public static const uint16 HORIZONTAL = 1;
public static const uint16 MINIMUM = 1 << 1;
public static const uint16 CROSS_STREAM = 1 << 2;
public static const uint16 OVERRIDE = 1 << 3;
public static const uint16 FORMAT = 1 << 8;
GlyfTable glyf_table;
List<Kern> kernings = new List<Kern> ();
public int kerning_pairs = 0;
public KernTable (GlyfTable gt) {
glyf_table = gt;
id = "kern";
}
public KernList get_all_pairs (int gid) {
KernList kl = new KernList ();
foreach (Kern k in kernings) {
if (k.left == gid) {
kl.kernings.append (k);
}
}
return kl;
}
public override void parse (FontData dis) throws GLib.Error {
uint16 version;
uint16 sub_tables;
uint16 subtable_version;
uint16 subtable_length;
uint16 subtable_flags;
uint16 search_range;
uint16 entry_selector;
uint16 range_shift;
uint16 n_pairs;
dis.seek (offset);
version = dis.read_ushort ();
warn_if_fail (version == 0);
sub_tables = dis.read_ushort ();
for (uint16 i = 0; i < sub_tables; i++) {
subtable_version = dis.read_ushort ();
subtable_length = dis.read_ushort ();
subtable_flags = dis.read_ushort ();
n_pairs = dis.read_ushort ();
search_range = dis.read_ushort ();
entry_selector = dis.read_ushort ();
range_shift = dis.read_ushort ();
// TODO: check more flags
if ((subtable_flags & HORIZONTAL) > 0 && (subtable_flags & CROSS_STREAM) == 0 && (subtable_flags & MINIMUM) == 0) {
parse_pairs (dis, n_pairs);
}
}
}
public void parse_pairs (FontData dis, uint16 n_pairs) throws Error {
uint16 left;
uint16 right;
int16 kerning;
for (int i = 0; i < n_pairs; i++) {
left = dis.read_ushort ();
right = dis.read_ushort ();
kerning = dis.read_short ();
kernings.append (new Kern (left, right, kerning));
}
}
public void process () throws GLib.Error {
FontData fd = new FontData ();
uint16 n_pairs = 0;
uint16 gid_left;
int gid_right;
uint16 range_shift = 0;
uint16 entry_selector = 0;
uint16 search_range = 0;
fd.add_ushort (0); // version
fd.add_ushort (1); // n subtables
fd.add_ushort (0); // subtable version
foreach (Glyph g in glyf_table.glyphs) {
foreach (Kerning k in g.kerning) {
n_pairs++;
}
}
this.kerning_pairs = n_pairs;
fd.add_ushort (6 * n_pairs + 14); // subtable length
fd.add_ushort (HORIZONTAL); // subtable flags
fd.add_ushort (n_pairs);
search_range = 6 * largest_pow2 (n_pairs);
entry_selector = largest_pow2_exponent (n_pairs);
range_shift = 6 * n_pairs - search_range;
fd.add_ushort (search_range);
fd.add_ushort (entry_selector);
fd.add_ushort (range_shift);
gid_left = 0;
foreach (Glyph g in glyf_table.glyphs) {
foreach (Kerning k in g.kerning) {
gid_right = glyf_table.get_gid (k.glyph_right);
if (gid_right == -1) {
warning ("right glyph not found in kerning table");
}
fd.add_ushort (gid_left);
fd.add_ushort ((uint16)gid_right);
fd.add_short ((int16)k.val);
}
gid_left++;
}
fd.pad ();
this.font_data = fd;
}
}
/** Table with list of tables sorted by table tag. */
class DirectoryTable : Table {
public CmapTable cmap_table;
public CvtTable cvt_table;
public GaspTable gasp_table;
public GdefTable gdef_table;
public GlyfTable glyf_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;
List<Table> 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);
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);
maxp_table = new MaxpTable (glyf_table);
name_table = new NameTable ();
os_2_table = new Os2Table ();
post_table = new PostTable (glyf_table);
id = "Directory table";
}
public void process () throws GLib.Error {
// generate font data
glyf_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 (glyf_table);
head_table.process ();
loca_table.process (glyf_table, head_table);
post_table.process ();
kern_table.process ();
offset_table.process ();
process_directory (); // this table
}
public unowned List<Table> get_tables () {
if (tables.length () == 0) {
tables.append (offset_table);
tables.append (this);
// tables.append (gdef_table); // invalid table
tables.append (os_2_table);
tables.append (cmap_table);
// tables.append (cvt_table);
// tables.append (gasp_table);
tables.append (glyf_table);
tables.append (head_table);
tables.append (hhea_table);
tables.append (hmtx_table);
if (kern_table.kerning_pairs > 0) {
tables.append (kern_table);
}
tables.append (loca_table);
tables.append (maxp_table);
tables.append (name_table);
tables.append (post_table);
}
return tables;
}
public void set_offset_table (OffsetTable ot) {
offset_table = ot;
}
public new void parse (FontData dis, File file, 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 == "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;
}
}
head_table.parse (dis);
if (!validate_tables (dis, file)) {
warning ("Missing required table or bad checksum.");
}
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);
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);
}
}
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 realy simple but 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;
}
} 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.check_sum ());
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 (Table 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 check_sum = 0;
foreach (Table t in tables) {
t.get_font_data ().continous_check_sum (ref check_sum);
}
return check_sum;
}
public void create_directory () throws GLib.Error {
FontData fd;
uint32 table_offset = 0;
uint32 table_length = 0;
uint32 check_sum = 0;
fd = new FontData ();
return_val_if_fail (offset_table.num_tables > 0, fd);
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_check_sum_adjustment (0); // Set this to zero, calculate checksums and update the value
head_table.process ();
// write the directory
foreach (Table 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 ().check_sum ());
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;
check_sum = get_font_file_checksum ();
head_table.set_check_sum_adjustment ((uint32)(0xB1B0AFBA - check_sum));
head_table.process (); // update the value
}
}
/** Largest power of two less than max. */
internal static uint16 largest_pow2 (uint16 max) {
uint16 x = 1;
uint16 l = 0;
while (x <= max) {
l = x;
x = x << 1;
}
return l;
}
internal static uint16 largest_pow2_exponent (uint16 max) {
uint16 exp = 0;
uint16 l = 0;
uint16 x = 0;
while (x <= max) {
l = exp;
exp++;
x = 1 << exp;
}
return l;
}
void printd (string s) {
// print (s);
}
}