1/*
2	Copyright (C) 2012 2013 2014 2017 Johan Mattsson
3
4	This library is free software; you can redistribute it and/or modify
5	it under the terms of the GNU Lesser General Public License as
6	published by the Free Software Foundation; either version 3 of the
7	License, or (at your option) any later version.
8
9	This library is distributed in the hope that it will be useful, but
10	WITHOUT ANY WARRANTY; without even the implied warranty of
11	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12	Lesser General Public License for more details.
13*/
14using Math;
15
16namespace BirdFont {
17
18public class GposTable : OtfTable {
19
20	GlyfTable glyf_table;
21	KernList pairs;
22
23	public GposTable () {
24		id = "GPOS";
25	}
26
27	public override void parse (FontData dis) throws Error {
28		// Not implemented, freetype2 is used for loading fonts
29	}
30
31	public void process (GlyfTable glyf_table) throws GLib.Error {
32		FontData fd = new FontData ();
33
34		this.glyf_table = glyf_table;
35		this.pairs = new KernList (glyf_table);
36
37		printd ("Processing GPOS\n");
38
39		fd.add_ulong (0x00010000); // table version
40		fd.add_ushort (10); // offset to script list
41		fd.add_ushort (30); // offset to feature list
42		fd.add_ushort (44); // offset to lookup list
43
44		// script list
45		fd.add_ushort (1);   // number of items in script list
46		fd.add_tag ("DFLT"); // default script
47		fd.add_ushort (8);	 // offset to script table from script list
48
49		// script table
50		fd.add_ushort (4); // offset to default language system
51		fd.add_ushort (0); // number of languages
52
53		// LangSys table
54		fd.add_ushort (0); // reserved
55		fd.add_ushort (0); // required features (0xFFFF is none)
56		fd.add_ushort (1); // number of features
57		fd.add_ushort (0); // feature index
58
59		// feature table
60		fd.add_ushort (1); // number of features
61
62		fd.add_tag ("kern"); // feature tag
63		fd.add_ushort (8); // offset to feature
64
65		fd.add_ushort (0); // feature prameters (null)
66		fd.add_ushort (1); // number of lookups
67		fd.add_ushort (0); // lookup index
68
69		Gee.ArrayList<FontData> pair_set_data = new Gee.ArrayList<FontData>();
70		pairs.fetch_all_pairs ();
71
72		KernSplitter kern_splitter = new KernSplitter (pairs);
73
74		while (true) {
75			if (offset < 0) {
76				break;
77			}
78
79			KernList pairs_subset = kern_splitter.get_subset (offset);
80
81			if (pairs_subset.get_length () == 0) {
82				break;
83			}
84
85			FontData pairs = get_pair_pos_format1 (pairs_subset);
86			pair_set_data.add (pairs);
87
88			offset += pairs_subset.get_length ();
89		}
90
91		// lookup table
92		fd.add_ushort (1); // number of lookups
93		fd.add_ushort (4); // offset to lookup 1
94
95		int16 extention_pos = (int16) (pair_set_data.size * 2);
96
97		for (int16 index = 0; index < pair_set_data.size; index++) {
98			extention_pos += index * 8;
99		}
100
101		int64 pair_set_data_length = 0;
102		int64 extension_length = 8;
103
104		foreach (FontData pair_set in pair_set_data) {
105			pair_set_data_length += pair_set.length_with_padding ();
106			extension_length += 8;
107		}
108
109		if (pair_set_data.size > 0) {
110			FontData last_pair_set = pair_set_data.get (pair_set_data.size - 1);
111			uint64 last_externsion_offset = extension_length + pair_set_data_length;
112
113			last_externsion_offset -= 8;
114			last_externsion_offset -= last_pair_set.length ();
115
116			if (last_externsion_offset > uint32.MAX) {
117				warning ("Too manu kerning pairs for the extension positioning table."
118					+ @"last_externsion_offset: $last_externsion_offset > "
119					+ @"$(uint32.MAX)");
120				pair_set_data.clear ();
121			}
122		}
123
124		fd.add_ushort (9); // lookup type
125		fd.add_ushort (0); // lookup flags
126		fd.add_ushort ((uint16) pair_set_data.size); // number of subtables
127
128		if (pair_set_data.size > 0) {
129			for (int j = 0; j < pair_set_data.size; j++) {
130				uint16 lookup_size = (uint16) pair_set_data.size * 2;
131				uint16 offset_to_extension = (uint16) (6 + lookup_size + j * 8);
132				fd.add_ushort (offset_to_extension);  // array of offsets to subtables
133			}
134
135			// extension positioning table
136			int k = pair_set_data.size - 1;
137			while (extension_length > 0 && k >= 0) {
138				FontData pair_set = pair_set_data.get (k);
139				pair_set_data_length -= pair_set.length ();
140				extension_length -= 8;
141
142				uint64 externsion_offset = extension_length + pair_set_data_length;
143
144				if (externsion_offset > uint32.MAX) {
145					warning ("Too manu kerning pairs for extension positioning table.");
146				}
147
148				fd.add_ushort (1); // format
149				fd.add_ushort (2); // lookup type
150				fd.add_ulong ((uint32) externsion_offset); // extension offset
151				k--;
152			}
153
154			// MarkFilteringSet
155
156			foreach (FontData pair_set in pair_set_data) {
157				fd.append (pair_set);
158			}
159		}
160
161		fd.pad ();
162		this.font_data = fd;
163	}
164
165	// PairPosFormat1 subtable
166	FontData get_pair_pos_format1 (KernList pairs_subset) throws GLib.Error {
167		FontData fd = new FontData ();
168		uint coverage_offset;
169		uint16 num_pairs;
170		int i;
171		uint pair_set_offset;
172		uint written;
173		uint written_pairs;
174		uint last_gid_left;
175		uint last_gid_right;
176		uint16 pair_set_count;
177
178		num_pairs = (uint16) pairs_subset.get_length ();
179
180		pair_set_count = (uint16) pairs_subset.get_length_left (); // FIXME: boundaries
181
182		coverage_offset = 10 + pairs_offset_length (pairs_subset) + pairs_set_length (pairs_subset);
183
184		if (coverage_offset > uint16.MAX) {
185			warning (@"Invalid coverage offset." +
186				@"Total: $(pairs.get_length ()), " +
187				@"subset: $(pairs_subset.get_length ())" +
188				@"coverage_offset: $(coverage_offset) > 65535");
189
190			num_pairs = 0;
191			coverage_offset = 10;
192		}
193
194		fd.add_ushort (1); // position format
195		// offset to coverage table from beginning of kern pair table
196		fd.add_ushort ((uint16) coverage_offset);
197		fd.add_ushort (0x0004); // ValueFormat1 (0x0004 is x advance)
198		fd.add_ushort (0x0000); // ValueFormat2 (null, no value)
199		fd.add_ushort (pair_set_count); // n pairs
200
201		// pair offsets orderd by coverage index
202		pair_set_offset = 10 + pairs_offset_length (pairs_subset);
203
204		written = 0;
205		written_pairs = 0;
206		pairs_subset.all_pairs_format1 ((k) => {
207			try {
208				if (pair_set_offset > uint16.MAX) {
209					warning ("Invalid offset.");
210					return;
211				}
212
213				if (k.pairs.size == 0) {
214					warning ("No pairs.");
215				}
216
217				fd.add_ushort ((uint16) pair_set_offset);
218				pair_set_offset += 2;
219				pair_set_offset += 4 * k.pairs.size;
220				written += 2;
221			} catch (Error e) {
222				warning (e.message);
223			}
224		}, num_pairs);
225
226		if (unlikely (written != pairs_offset_length (pairs_subset))) {
227			warning (@"Bad pairs_offset_length () calculated: $(pairs_offset_length (pairs_subset)), real length $written");
228		}
229
230		// pair table
231		i = 0;
232		written = 0;
233		last_gid_left = 0;
234		last_gid_right = 0;
235		pairs_subset.all_pairs_format1 ((pn) => {
236			try {
237				PairFormat1 p = pn;
238				uint pairset_length = p.pairs.size;
239
240				if (pairset_length > uint16.MAX) {
241					warning ("Too many pairs");
242					pairset_length = uint16.MAX;
243				}
244
245				if (unlikely (p.left < last_gid_left)) {
246					warning (@"Kerning table is not sorted $(p.left) < $last_gid_left.");
247				}
248
249				last_gid_left = p.left;
250
251				fd.add_ushort ((uint16) pairset_length);
252				written += 2;
253				last_gid_right = 0;
254				written_pairs = 0;
255				foreach (Kern k in p.pairs) {
256
257					if (k.right == 0) {
258						warning (@"GID $(p.left) is kerned zero units to $(k.right).");
259					}
260
261					// pair value record
262					fd.add_ushort (k.right);     // gid of the second glyph
263					fd.add_short (k.kerning);    // value of ValueFormat1, horizontal adjustment for advance of the first glyph
264												 // value of ValueFormat2 (null)
265
266					if (unlikely (k.right < last_gid_right)) {
267						warning (@"Kerning table is not sorted $(k.right) < $last_gid_right).");
268					}
269
270					last_gid_right = k.right;
271
272					written += 4;
273					written_pairs++;
274				}
275
276				if (unlikely (written_pairs != p.pairs.size)) {
277					warning (@"written_pairs != p.pairs.length () $(written_pairs) != $(pairs.get_length ())   pairset_length: $pairset_length");
278				}
279
280				i++;
281			} catch (Error e) {
282				warning (e.message);
283			}
284		}, num_pairs);
285
286		if (unlikely (pairs_set_length (pairs_subset) != written)) {
287			warning (@"Bad pair set length: $(pairs_set_length (pairs_subset)), real length: $written");
288		}
289
290		if (unlikely (fd.length () != coverage_offset)) {
291			warning (@"Bad coverage offset, coverage_offset: $coverage_offset, real length: $(fd.length ())");
292			warning (@"pairs_offset_length: $(pairs_offset_length (pairs_subset)) pairs_set_length: $(pairs_set_length (pairs_subset))");
293		}
294
295		// coverage
296		fd.add_ushort (1); // format
297		fd.add_ushort (pair_set_count);
298
299		written = 0;
300		pairs_subset.all_pairs_format1 ((p) => {
301			try {
302				fd.add_ushort (p.left); // gid
303				written += 2;
304			} catch (Error e) {
305				warning (e.message);
306			}
307		}, num_pairs);
308
309		if (unlikely (written != 2 * pair_set_count)) {
310			warning (@"written != 2 * pair_set_count: $written != 2 * $(pair_set_count)");
311		}
312
313		return fd;
314	}
315
316	public static uint pairs_set_length (KernList kerning_list) {
317		uint len = 0;
318
319		kerning_list.all_pairs_format1 ((p) => {
320			len += 2 + 4 * p.pairs.size;
321		});
322
323		return len;
324	}
325
326	public static uint pairs_offset_length (KernList kerning_list) {
327		return 2 * kerning_list.pairs.size;
328	}
329}
330
331}
332