Clone in a terminal:
LigatureCollection.vala
/*
	Copyright (C) 2014 Johan Mattsson

	This library is free software; you can redistribute it and/or modify 
	it under the terms of the GNU Lesser General Public License as 
	published by the Free Software Foundation; either version 3 of the 
	License, or (at your option) any later version.

	This library is distributed in the hope that it will be useful, but 
	WITHOUT ANY WARRANTY; without even the implied warranty of 
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 
	Lesser General Public License for more details.
*/

namespace BirdFont {

public class LigatureCollection : GLib.Object {

	public Gee.ArrayList<LigatureSet> ligature_sets;

	LigatureSet lig_set;
	LigatureSet last_set;

	public LigatureCollection.clig (GlyfTable glyf_table) {
		ligature_sets = new Gee.ArrayList<LigatureSet> ();	
		lig_set = new LigatureSet (glyf_table);
		last_set = new LigatureSet (glyf_table);
		
		add_clig_ligatures (glyf_table);
	}

	public LigatureCollection.contextual (GlyfTable glyf_table, ContextualLigature ligature) {
		ligature_sets = new Gee.ArrayList<LigatureSet> ();	
		lig_set = new LigatureSet (glyf_table);
		last_set = new LigatureSet (glyf_table);

		add_contextual_ligatures (glyf_table, ligature);
	}

	void add_clig_ligatures (GlyfTable glyf_table) {
		Font font = BirdFont.get_current_font ();
		Ligatures ligatures = font.get_ligatures ();	
		
		ligatures.get_ligatures ((parts, ligature) => {
			add_ligatures (glyf_table, parts, ligature);
		});	
	}

	void add_contextual_ligatures (GlyfTable glyf_table, ContextualLigature cl) {
		foreach (string l in cl.ligatures.strip ().split (" ")) {
			add_ligatures (glyf_table, cl.input, l);
		}
	}

	// multiple ligatures in non-contextual substitution
	public void add_ligatures (GlyfTable glyf_table, string characters, string ligatures) 
		requires (!is_null (lig_set) && !is_null (last_set)) {
			
		Font font = BirdFont.get_current_font ();
		string[] parts = characters.strip ().split (" ");
		string l = ligatures;
		bool has_set = false;

		if (l.has_prefix ("U+") || l.has_prefix ("u+")) {
			l = (!) Font.to_unichar (l).to_string ();
		}
		
		if (l == "space") {
			l = " ";
		}
		
		if (!font.has_glyph (l)) {
			warning (@"Ligature $l does not correspond to a glyph in this font.");
			return;
		}
		
		foreach (string p in parts) {		
			if (p.has_prefix ("U+") || p.has_prefix ("u+")) {
				p = (!) Font.to_unichar (p).to_string ();
			}

			if (p == "space") {
				p = " ";
			}
			
			if (!font.has_glyph (p)) {
				warning (@"Ligature substitution of $p is not possible, the character does have a glyph.");
				return;
			}
		}
		
		if (parts.length == 0) {
			warning ("No parts.");
			return;
		}
		
		foreach (LigatureSet s in ligature_sets) {
			if (s.starts_with (parts[0])) {
				has_set = true;
				last_set = s;
			}
		}
		
		if (has_set) {
			last_set.add (new Ligature (l, characters));
		} else {
			lig_set = new LigatureSet (glyf_table);
			lig_set.add (new Ligature (l, characters));
			ligature_sets.add (lig_set);
		}
		
		// make sure coverage table is sorted otherwise will substitution not work
		ligature_sets.sort ((a, b) => {
			LigatureSet la = (LigatureSet) a;
			LigatureSet lb = (LigatureSet) b;
			string ca, cb;
			
			if (la.get_coverage_char () == "space") {
				ca = " ";
			} else {
				ca = la.get_coverage_char ();
			}

			if (lb.get_coverage_char () == "space") {
				cb = " ";
			} else {
				cb = lb.get_coverage_char ();
			}

			return strcmp (ca, cb);
		});
	}

	public FontData get_font_data (GlyfTable glyf_table) throws GLib.Error {
		FontData set_data;
		uint16 ligature_pos;
		uint16 table_start;
		int coverage_offset;
		FontData fd;
		
		fd = new FontData ();

		// ligature substitution subtable
		table_start = (uint16) fd.length_with_padding ();

		fd.add_ushort (1); // format identifier
		
		coverage_offset = 6 + 2 * ligature_sets.size;
		fd.add_ushort ((uint16) coverage_offset); // offset to coverage
		fd.add_ushort ((uint16) ligature_sets.size); // number of ligature set tables

		// array of offsets to ligature sets
		uint16 size = 0;
		foreach (LigatureSet l in ligature_sets) {
			ligature_pos = 10 + (uint16) ligature_sets.size * 4 + size;
			fd.add_ushort (ligature_pos);
			size += (uint16) l.get_set_data ().length_with_padding ();
		}

		// coverage
		fd.add_ushort (1); // format
		fd.add_ushort ((uint16) ligature_sets.size);

		// coverage gid:
		foreach (LigatureSet l in ligature_sets) {
			fd.add_ushort ((uint16) glyf_table.get_gid (l.get_coverage_char ()));
		}
		
		foreach (LigatureSet l in ligature_sets) {
			set_data = l.get_set_data ();
			fd.append (set_data);
		}
		
		return fd;
	}
}

}

Contact

Email: johan.mattsson.m@gmail.com
Mastodon: typo.social/@birdfont
GitHub: johanmattssonm
LinkedIn: johan-mattsson-2ab324112