Clone in a terminal:
/*
	Copyright (C) 2013 2014 2015 2019 Johan Mattsson

	All rights reserved.
*/

#include "loadfont.h"

#include <assert.h>
#include <glib.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <ft2build.h>
#include FT_FREETYPE_H
#include FT_GLYPH_H
#include FT_OPENTYPE_VALIDATE_H
#include FT_TRUETYPE_TABLES_H
#include FT_SFNT_NAMES_H
#include <birdfont.h>

/** Error codes. */
#define OK 0

/** Point flags. */
#define QUADRATIC_OFF_CURVE 0
#define ON_CURVE 1
#define CUBIC_CURVE 2
#define DOUBLE_CURVE 4
#define HIDDEN_CURVE 8

extern GString* load_freetype_font (const gchar* file, gint* err);

/** Convert units per em in font file format to the BirdFont format. */
double get_units (double units_per_em) {
	return 100.0 / units_per_em;
}

/** @return a non zero value if the point is a part of two off curve 
 * points with a hidden on curve point in between.
 */
guint is_double_curve (char flag) {
	return flag & DOUBLE_CURVE;
}

/** @return a non zero value if the point is a cubic off curve point. */
guint is_cubic (char flag) {
	return flag & CUBIC_CURVE && (flag & ON_CURVE) == 0;
}

/** @return a non zero value if the point is a quadratic off curve point. */
guint is_quadratic (char flag) {
	return (flag & CUBIC_CURVE) == 0 && (flag & ON_CURVE) == 0;
}

/** @return a non zero value if the point is a on curve point */
guint is_line (char flag) {
	return flag & ON_CURVE;
}

/** @return a non zero value if the point is a hidden on curve point */
guint is_hidden (char flag) {
	return flag & HIDDEN_CURVE;
}

/** Convert every second hidden curve to a double curve and keep 
 * other points as on curve points.
 */ 
void set_double_curves (char* flag, int length) {
	int i;
	guint double_curve = FALSE;
	
	for (i = 1; i < length; i++) {
		if (is_line (flag[i])) {
			double_curve = FALSE;
		} else if (is_hidden (flag[i])) {
			if (!double_curve) {
				if (is_quadratic (flag[i - 1]) && is_quadratic (flag[i + 1])) {
					flag[i - 1] = DOUBLE_CURVE;
					flag[i] = HIDDEN_CURVE;
					flag[i + 1] = DOUBLE_CURVE;
					double_curve = TRUE;
				} else {
					flag[i] = ON_CURVE;
					double_curve = FALSE;
				}
			} else {
				flag[i] = ON_CURVE;
				double_curve = FALSE;
			} 
		}
		// ignore off curve points
	}
}

/** @return half the distance between two points. */
double half_way (double prev, double current) {
	return prev + ((current - prev) / 2.0);
} 

/** Remove hidden points.
 * @return length after removal
 */
int remove_hidden_points (FT_Vector* points, char* flags, guint length, guint capacity) {
	int k;
	int l;
	
	l = 0;
	for (k = 0; k < length; k++) {
		if (!is_hidden (flags[k])) {
			points[l] = points[k];
			flags[l] = flags[k];
			l++;
		}
	}
	
	for (k = l; k < capacity; k++) {
		points[l].x = 0;
		points[l].y = 0;
		flags[l] = 0;	
	}

	return l;
}	

/** Add extra point where two ore more off curve points follow each other. */
void create_contour (guint unicode, FT_Vector* points, char* flags, int* length, FT_Vector** new_points, char** new_flags, int* err) {
	// This function has been tested with many fonts. ElsieSwashCaps-Regular
	// is one of the more interesting fonts I have seen.
	int i;
	int j;
	guint prev_is_curve;
	guint first_is_curve = FALSE;
	//guint first_normal_off_curve = FALSE;
	double x = 0;
	double y = 0;
	FT_Vector* p;
	char* f;
	int len = *length;
	
	*new_points = malloc (4 * len * sizeof (FT_Vector));
	*new_flags = malloc (4 * len * sizeof (char));
	
	p = *new_points;
	f = *new_flags;
	
	for (i = 0; i < 4 * len; i++) {
		p[i].x = 0;
		p[i].y = 0;
		f[i] = 0;
	}
	
	if (len == 0) {
		return;
	}
	
	prev_is_curve = is_quadratic (flags[len - 1]);

	j = 0;
	i = 0;
	
	if (len > 2 && is_quadratic (flags[0]) && is_quadratic (flags[1])) {
		p[j].x = half_way (points[0].x, points[1].x);
		p[j].y = half_way (points[0].y, points[1].y);
						
		f[j] = ON_CURVE;
		prev_is_curve = FALSE;
		first_is_curve = TRUE;
		j++;
		i++;		
	} 
	
	// first point is off curve but it is not part of a double curve
	if (len > 2 && is_quadratic (flags[0]) && !is_quadratic (flags[1])) { 
		//first_normal_off_curve = TRUE;
	}
	
	while (i < len) {
		if (is_quadratic (flags[i])) {
			if (prev_is_curve && j != 0) {
				// add a new point if half way between two off curve points
				x = half_way (points[i].x, p[j - 1].x);
				y = half_way (points[i].y, p[j - 1].y);

				p[j].x = x;
				p[j].y = y;
				
				f[j] = HIDDEN_CURVE;

				j++;
			}
			
			if (i == 0) { // A fix for FiraCode Regular
				i++; 
				continue;
			}
			
			f[j] = QUADRATIC_OFF_CURVE;
			prev_is_curve = TRUE;
		} else if (is_line (flags[i])) {
			prev_is_curve = FALSE;
			f[j] = ON_CURVE;
		} else if (is_cubic (flags[i])) {
			prev_is_curve = FALSE;
			f[j] = CUBIC_CURVE;
		} else {
			g_warning ("invalid point flags: %d index: %d.\n", flags[i], i);
			prev_is_curve = FALSE;
			f[j] = ON_CURVE;
		}
		
		p[j] = points[i];
		j++;
		i++;
	}

	// last to first point
	if (first_is_curve && !prev_is_curve && is_quadratic (flags[i])) {
		p[j] = points[i];
		f[j] = QUADRATIC_OFF_CURVE;
		j++;
		i++;
		
		p[j].x = half_way (p[j - 1].x, points[0].x);
		p[j].y = half_way (p[j - 1].y, points[0].y);					
		f[j] = ON_CURVE; // FIXME
		prev_is_curve = FALSE;
		j++;
		i++;

		p[j] = points[0];
		f[j] = QUADRATIC_OFF_CURVE;
		j++;
		i++;
						
		p[j] = p[0];
		f[j] = f[0];
		j++;
	} else if (first_is_curve && !prev_is_curve && is_line (flags[i])) {
		p[j] = points[i];
		f[j] = ON_CURVE;
		j++;
		i++;	

		p[j] = points[0];
		f[j] = QUADRATIC_OFF_CURVE;
		j++;
		i++;
		
		p[j] = p[0];
		f[j] = f[0];
		j++;
	} else if (first_is_curve && prev_is_curve && is_quadratic (flags[i])) {
		x = half_way (p[j - 1].x, points[i].x);
		y = half_way (p[j - 1].y, points[i].y);
		p[j].x = x;
		p[j].y = y;
		f[j] = HIDDEN_CURVE;
		j++;

		p[j] = points[i];
		f[j] = flags[i];
		j++;
		i++;

		p[j].x = half_way (p[j - 1].x, points[0].x);
		p[j].y = half_way (p[j - 1].y, points[0].y);					
		f[j] = HIDDEN_CURVE;
		prev_is_curve = FALSE;
		
		j++;
		i++;

		p[j] = points[0];
		f[j] = QUADRATIC_OFF_CURVE;
		j++;
		
		p[j] = p[0];
		f[j] = ON_CURVE;
		j++;

		prev_is_curve = TRUE;
	} else if (prev_is_curve && (flags[0] & ON_CURVE) == 0) {
		if (is_quadratic (f[j - 1]) && is_quadratic (flags[i])) {
			x = half_way (p[j - 1].x, points[i].x); 
			y = half_way (p[j - 1].y, points[i].y);
			p[j].x = x;
			p[j].y = y;
			f[j] = HIDDEN_CURVE;
			j++;
		}

		p[j] = points[i];
		if (is_line (flags[i])) {
			f[j] = ON_CURVE;
		} else {
			f[j] = QUADRATIC_OFF_CURVE;
		}
		j++;		

		if (is_quadratic (f[0]) && is_quadratic (flags[0])) {
			x = half_way (p[j - 1].x, points[0].x);
			y = half_way (p[j - 1].y, points[0].y);
			p[j].x = x;
			p[j].y = y;
			f[j] = HIDDEN_CURVE;
			j++;
		}
		
		x = points[0].x;
		y = points[0].y;

		p[j].x = x;
		p[j].y = y;

		f[j] = QUADRATIC_OFF_CURVE;
		j++;		
	} else if (prev_is_curve && is_quadratic (flags[i])) {
		x = p[j - 1].x + ((points[i].x - p[j - 1].x) / 2.0);
		y = p[j - 1].y + ((points[i].y - p[j - 1].y) / 2.0);
		p[j].x = x;
		p[j].y = y;
		f[j] = HIDDEN_CURVE;
		j++;

		p[j] = points[i];
		f[j] = QUADRATIC_OFF_CURVE;
		j++;
		i++;

		if (is_quadratic (f[0])) {
			x = half_way (p[j - 1].x, points[i].x);
			y = half_way (p[j - 1].y, points[i].y);
			p[j].x = x;
			p[j].y = y;
			f[j] = HIDDEN_CURVE;
			j++;
			
			p[j] = p[0];
			f[j] = f[0];
			j++;
		} else { 
			p[j] = p[0];
			f[j] = f[0];
			j++;
		}
		
		prev_is_curve = TRUE;
	} else {
		p[j] = points[i];
		if (is_quadratic (flags[i])) 
			f[j] = QUADRATIC_OFF_CURVE;
		else 
			f[j] = ON_CURVE;
		j++;

		p[j] = p[0];
		if (is_quadratic (flags[i])) 
			f[j] = QUADRATIC_OFF_CURVE;
		else 
			f[j] = ON_CURVE;
		j++;
	}

	set_double_curves (f, j);
	*length = remove_hidden_points (p, f, j, 2 * len);
}

/** Get path data in .bf format. See BirdFontFile.get_point_data () for 
 * format specification.
 */
GString* get_bf_contour_data (guint unicode, FT_Vector* points, char* flags, int length, double units_per_em, int* err) {
	GString* bf = g_string_new ("");
	GString* contour;
	int i = 0;
	FT_Vector* new_points;
	char* new_flags;
	double x0, x1, x2;
	double y0, y1, y2;
	gchar coordinate_buffer[80];
	gchar* coordinate = (gchar*) &coordinate_buffer;
	double units = get_units (units_per_em);
	//guint prev_is_curve;
	//int points_length;
	
	if (length == 0) {
		return bf;
	}
	
	//points_length = length;

	//gboolean last_to_first = FALSE;
	
	//int last_x = 0;
	//int last_y = 0;
	
	create_contour (unicode, points, flags, &length, &new_points, &new_flags, err);
	
	if (length < 2) {
		g_warning ("No contour.");
		return bf;
	}
	
	x0 = new_points[0].x * units;
	y0 = new_points[0].y * units;
	
	g_string_printf (bf, "S ");
	
	g_ascii_formatd (coordinate, 80, "%f", x0);
	g_string_append (bf, coordinate);
	g_string_append (bf, ",");

	g_ascii_formatd (coordinate, 80, "%f", y0);
	g_string_append (bf, coordinate);

	i = 1;
	while (i < length) {
		contour = g_string_new ("");
		
		if (is_hidden (new_flags[i])) {
			// Skip it
			g_string_append (contour, "");
			i++;
		} else if (is_cubic (new_flags[i])) {
			x0 = new_points[i].x * units;
			y0 = new_points[i].y * units;
			x1 = new_points[i+1].x * units;
			y1 = new_points[i+1].y * units;
			x2 = new_points[i+2].x * units;
			y2 = new_points[i+2].y * units;
			
			g_string_printf (contour, " C ");
			
			g_ascii_formatd (coordinate, 80, "%f", x0);
			g_string_append (contour, coordinate);
			g_string_append (contour, ",");

			g_ascii_formatd (coordinate, 80, "%f", y0);
			g_string_append (contour, coordinate);
			g_string_append (contour, " ");

			g_ascii_formatd (coordinate, 80, "%f", x1);
			g_string_append (contour, coordinate);
			g_string_append (contour, ",");

			g_ascii_formatd (coordinate, 80, "%f", y1);
			g_string_append (contour, coordinate);
			g_string_append (contour, " ");

			g_ascii_formatd (coordinate, 80, "%f", x2);
			g_string_append (contour, coordinate);
			g_string_append (contour, ",");

			g_ascii_formatd (coordinate, 80, "%f", y2);
			g_string_append (contour, coordinate);
			
			//last_x = new_points[i+2].x;
			//last_y = new_points[i+2].y;
			
			i += 3;
		} else if (is_double_curve (new_flags[i])) {
			x0 = new_points[i].x * units;
			y0 = new_points[i].y * units;
			x1 = new_points[i+1].x * units;
			y1 = new_points[i+1].y * units;
			x2 = new_points[i+2].x * units;
			y2 = new_points[i+2].y * units;

			g_string_printf (contour, " D ");
			
			g_ascii_formatd (coordinate, 80, "%f", x0);
			g_string_append (contour, coordinate);
			g_string_append (contour, ",");

			g_ascii_formatd (coordinate, 80, "%f", y0);
			g_string_append (contour, coordinate);
			g_string_append (contour, " ");

			g_ascii_formatd (coordinate, 80, "%f", x1);
			g_string_append (contour, coordinate);
			g_string_append (contour, ",");

			g_ascii_formatd (coordinate, 80, "%f", y1);
			g_string_append (contour, coordinate);
			g_string_append (contour, " ");

			g_ascii_formatd (coordinate, 80, "%f", x2);
			g_string_append (contour, coordinate);
			g_string_append (contour, ",");

			g_ascii_formatd (coordinate, 80, "%f", y2);
			g_string_append (contour, coordinate);

			//last_x = new_points[i+2].x;
			//last_y = new_points[i+2].y;
			
			i += 3;
		} else if (is_quadratic (new_flags[i])) {
			x0 = new_points[i].x * units;
			y0 = new_points[i].y * units;
			x1 = new_points[i+1].x * units;
			y1 = new_points[i+1].y * units;

			g_string_printf (contour, " Q ");
			
			g_ascii_formatd (coordinate, 80, "%f", x0);
			g_string_append (contour, coordinate);
			g_string_append (contour, ",");

			g_ascii_formatd (coordinate, 80, "%f", y0);
			g_string_append (contour, coordinate);
			g_string_append (contour, " ");

			g_ascii_formatd (coordinate, 80, "%f", x1);
			g_string_append (contour, coordinate);
			g_string_append (contour, ",");

			g_ascii_formatd (coordinate, 80, "%f", y1);
			g_string_append (contour, coordinate);
			
			//last_x = new_points[i + 1].x;
			//last_y = new_points[i + 1].y;
			
			i += 2;
		} else if (is_line (new_flags[i])) {
			x0 = new_points[i].x * units;
			y0 = new_points[i].y * units;

			g_string_printf (contour, " L ");
			
			g_ascii_formatd (coordinate, 80, "%f", x0);
			g_string_append (contour, coordinate);
			g_string_append (contour, ",");

			g_ascii_formatd (coordinate, 80, "%f", y0);
			g_string_append (contour, coordinate);
			
			i += 1;
			
			//last_x = new_points[i].x;
			//last_y = new_points[i].y;
		} else {
			contour = g_string_new ("");
			g_warning ("Can't parse outline.\n");
			*err = 1;
			i++;
		}
		
		g_string_append (bf, contour->str);
		g_string_free (contour, TRUE);
	}
	
	free (new_points);
	free (new_flags);
	
	return bf;
}

/** Get path element in .bf format. */
GString* get_bf_path (guint unicode, FT_Face face, double units_per_em, int* err) {
	GString* bf = g_string_new ("");
	GString* contour;
	//FT_Error error;
	int i;
	int start;
	int end;

	if (face->glyph->outline.n_points == 0) {
		return bf;			
	}

	start = 0;
	for (i = 0; i < face->glyph->outline.n_contours; i++) {
		end = face->glyph->outline.contours [i];
		contour = get_bf_contour_data (unicode, face->glyph->outline.points + start, face->glyph->outline.tags + start, end - start, units_per_em, err);
		g_string_append_printf (bf, "\t\t<path data=\"%s\" />\n", contour->str);
		g_string_free (contour, TRUE);
		start = face->glyph->outline.contours [i] + 1;
	}

	return bf;
}

FreeTypeFontFace* open_font (const gchar* file) {
	FT_Library library = NULL;
	FT_Face face = NULL;
	int error;
	FreeTypeFontFace* font;
	
	error = FT_Init_FreeType (&library);
	if (error != OK) {
		g_warning ("Freetype init error %d.\n", error);
		return NULL;
	}

	gchar* short_path = (gchar *)file;

	error = FT_New_Face (library, short_path, 0, &face);
	
	if (error) {
		if (FT_Done_FreeType (library) != 0) {
			g_warning ("Can't close freetype.");
		}
		
		g_warning ("Freetype font face error %d in (open_font)", error);
		g_warning ("Can't open file %s",  file);
		g_warning ("Short path: %s",  short_path);
		
		return NULL;
		
	}
	
	font = malloc (sizeof (FreeTypeFontFace));
	font->face = face;
	font->library = library;
	
	error = FT_Select_Charmap (face , FT_ENCODING_UNICODE);
	if (error) {
		g_warning ("Freetype can not use Unicode, error: %d\n", error);
		close_ft_font (font);
		return NULL;
	}
	
	return font;
}

void close_ft_font (FreeTypeFontFace* font) {
	if (font != NULL) {
		if (font->face != NULL) {
			if (FT_Done_Face (font->face) != 0) {
				g_warning ("Can't close font.");
			}
			font->face = NULL;
		}
		
		if (font->library != NULL) {
			if (FT_Done_FreeType (font->library) != 0) {
				g_warning ("Can't close freetype.");
			}
			font->library = NULL;
		}
		
		free (font);
	}
}

GString* load_glyph (FreeTypeFontFace* font, guint unicode) {
	GString* glyph;
	GString* paths;
	int err = OK;
	int gid;
	FT_ULong charcode = (FT_ULong) unicode;
	double units;
	gchar line_position_buffer[80];
	gchar* line_position = (gchar*) &line_position_buffer;
	FT_Error error;
	
	if (font == NULL || font->face == NULL || font->library == NULL) {
		g_warning ("No font in load_glyph");
		return NULL;
	}
	
	gid = FT_Get_Char_Index (font->face, charcode);
	
	if (gid == 0) {
		return NULL;
	}
	
	units = get_units (font->face->units_per_EM);
	
	glyph = g_string_new ("<font>");

	g_string_append_printf (glyph, "<horizontal>\n");

	g_ascii_formatd (line_position, 80, "%f", font->face->ascender * units);
	g_string_append_printf (glyph, "\t<top_limit>%s</top_limit>\n", line_position);

	g_string_append_printf (glyph, "\t<base_line>0</base_line>\n");
	
	g_ascii_formatd (line_position, 80, "%f", font->face->descender * units);
	g_string_append_printf (glyph, "\t<bottom_limit>%s</bottom_limit>\n", line_position);		
	
	g_string_append_printf (glyph, "</horizontal>\n");

	error = FT_Load_Glyph(font->face, gid, FT_LOAD_DEFAULT | FT_LOAD_NO_SCALE);
	
	if (error != 0) {
		g_warning ("Failed to load glyph.");
		g_string_free (glyph, TRUE);
		return NULL;
	}
	
	paths = get_bf_path (unicode, font->face, font->face->units_per_EM, &err);
	
	if (err != OK) {
		g_warning ("Can't load glyph.");
		g_string_free (glyph, TRUE);
		g_string_free (paths, TRUE);
		return NULL;
	}

	g_string_append_printf (glyph, "<collection unicode=\"U+%x\">\n", (guint)charcode);
	g_string_append_printf (glyph, "\t<selected id=\"0\" />\n");
	g_string_append_printf (glyph, "\t<glyph id=\"0\" left=\"%f\" right=\"%f\">\n", 
		0.0, font->face->glyph->metrics.horiAdvance * units);
		
	g_string_append_printf (glyph, "%s", paths->str);
	g_string_append_printf (glyph, "%s", "\t</glyph>");
	g_string_append_printf (glyph, "%s", "\t</collection>");
	g_string_append_printf (glyph, "%s", "</font>");
	
	g_string_free (paths, TRUE);
	
	if (err != OK) {
		g_warning ("Can't load glyph data.");
	}
	
	return glyph;
}

/** Get all character codes for a glyph.
 * @gid glyph id
 * @return array of character codes
 */
FT_ULong* get_charcodes (FT_Face face, FT_UInt gid) {
	FT_ULong charcode = 0;
	FT_UInt gindex;
	const int MAX = 255;
	FT_ULong* result = malloc (sizeof (FT_ULong) * (MAX + 1));
	guint results = 0;
	// TODO: find the lookup function in freetype
    
	charcode = FT_Get_First_Char (face, &gindex);

	while (gindex != 0) {

		if (results >= MAX) {
			g_warning ("Too many code points in font for one GID");
			break;
		}
		
		charcode = FT_Get_Next_Char (face, charcode, &gindex);
		if (gindex == gid && charcode != 0) {
			result[results] = charcode;
			results++;
		}
	}
	
	if (results == 0) {
		g_warning ("Can not find unicode value for gid %d.", gid);
	}
	
	result[results] = 0;
	results++;	
		
	return result;
}

/** Height of letter. */
int get_height (FT_Face face, guint unichar) {
	int error;
	FT_ULong charcode = (FT_ULong) unichar;
	FT_UInt index = FT_Get_Char_Index (face, charcode);

	error = FT_Load_Glyph (face, index, FT_LOAD_DEFAULT | FT_LOAD_NO_SCALE);
	if (error) {
		g_warning ("Failed to obtain height. (%d)\n", error);
		return 0;
	}
		
	return (int) face->glyph->metrics.height;	
}

/** Height of lower case letters. */
int get_xheight (FT_Face face) {
	return get_height (face, 'x');
}

/** Height of upper case letters. */
int get_top (FT_Face face) {
	return get_height (face, 'X');
}
/** Obtain descender. */
int get_descender (FT_Face face) {
	int error;
	FT_BBox bbox;
	FT_ULong charcode = (FT_ULong) 'g';
	FT_UInt index = FT_Get_Char_Index (face, charcode);
	FT_Glyph glyph;
	
	error = FT_Load_Glyph (face, index, FT_LOAD_DEFAULT | FT_LOAD_NO_SCALE);
	if (error) {
		g_warning ("Failed to obtain descender. (%d)\n", error);
		return 0;
	}
	
	FT_Get_Glyph (face->glyph, &glyph); 
	FT_Glyph_Get_CBox (glyph, FT_GLYPH_BBOX_UNSCALED, &bbox);
	
	return (int) bbox.yMin;	
}

/** Append name table data to a gstring. */
void append_description (GString* str, FT_SfntName* name_table_data) {
	gchar* utf8_str;
	gsize read, written;
	GError* error = NULL;	
	
	if (name_table_data->encoding_id == 0) { // mac roman
		utf8_str = g_convert ((const gchar *)name_table_data->string, name_table_data->string_len, "utf-8", "macintosh", &read, &written, &error);

		if (error == NULL) {
			g_string_append (str, g_markup_escape_text (utf8_str, -1));
			g_free (utf8_str);
		} else {
			g_warning ("Error in append_description: %s\n", error->message);
			g_error_free (error);
		}
	} else if (name_table_data->encoding_id == 1) { // windows unicode
		utf8_str = g_convert ((const gchar *)name_table_data->string, name_table_data->string_len, "utf-8", "ucs-2be", &read, &written, &error);

		if (error == NULL) {
			g_string_append (str, g_markup_escape_text (utf8_str, -1));
			g_free (utf8_str);
		} else {
			g_warning ("Error in append_description: %s\n", error->message);
			g_error_free (error);
		}
	} else {
		g_warning ("Encoding %u is not supported for platform %d.\n", name_table_data->encoding_id, name_table_data->platform_id);
	}
}

int getIndexForNameIdEncoding (FT_Face face, int id, int encoding) {
	FT_SfntName name_table_data;
	FT_Error error;
	int c = FT_Get_Sfnt_Name_Count (face);
	int i = 0;
	while (i < c){
		error = FT_Get_Sfnt_Name (face, i, &name_table_data);
		
		if (error == 0
			&& name_table_data.name_id == id 
			&& name_table_data.encoding_id == encoding) {
			return i;
		}
		
		i++;
	}
	
	return -1;
}

int getIndexForNameId (FT_Face face, int id) {
	int i = getIndexForNameIdEncoding (face, id, 1);
	
	if (i != -1) {
		return i;
		
	}
	
	return getIndexForNameIdEncoding (face, id, 0);
}

/** Convert font to bf format.
 * @param face freetype font face
 * @param err error code
 * @return xml representation of a bf font
 */
GString* get_bf_font (FT_Face face, const char* file, int* err) {
	GString* bf = g_string_new ("");
	GString* bf_data;
	//gchar* kerning;
	GString* glyph;
	FT_Error error;
	FT_Long i;
	FT_ULong charcode;
	FT_UInt gid;
	FT_SfntName name_table_data;
	double units_per_em;
	double units;
	gchar line_position_buffer[80];
	gchar* line_position = (gchar*) &line_position_buffer;
	int name_index;
	double gap;
	
	*err = OK;

	units_per_em = face->units_per_EM;
	units = get_units (units_per_em);
	
	if (face == NULL) {
		return  bf;
	}
	
	g_string_append (bf, "<?xml version=\"1.0\" encoding=\"utf-8\" standalone=\"yes\"?>\n");
	g_string_append (bf, "<font>\n");

	g_string_append_printf (bf, "<postscript_name>%s</postscript_name>\n", g_markup_escape_text (FT_Get_Postscript_Name(face), -1));
	g_string_append_printf (bf, "<name>%s</name>\n", g_markup_escape_text (face->family_name, -1));
	
	if (face->style_name != NULL) {
		g_string_append_printf (bf, "<subfamily>%s</subfamily>\n", g_markup_escape_text (face->style_name, -1));
	} else {
		g_string_append_printf (bf, "<subfamily></subfamily>\n");
	}
	
	// full name
	name_index = getIndexForNameId (face, 4);
	if (name_index != -1 && FT_Get_Sfnt_Name (face, name_index, &name_table_data) == 0) { 
		g_string_append (bf, "<full_name>");
		append_description (bf, &name_table_data);
		g_string_append (bf, "</full_name>\n");
	} else {
		g_string_append (bf, "<full_name></full_name>\n");
	}

	// unique identifier
	name_index = getIndexForNameId (face, 3);
	if (name_index != -1 && FT_Get_Sfnt_Name (face, name_index, &name_table_data) == 0) { 
		g_string_append (bf, "<unique_identifier>");
		append_description (bf, &name_table_data);
		g_string_append (bf, "</unique_identifier>\n");
	} else {
		g_string_append (bf, "<unique_identifier></unique_identifier>\n");
	}

	// version
	name_index = getIndexForNameId (face, 5);
	if (name_index != -1 && FT_Get_Sfnt_Name (face, name_index, &name_table_data) == 0) {
		g_string_append (bf, "<version>");
		append_description (bf, &name_table_data);
		g_string_append (bf, "</version>\n");
	} else {
		g_string_append (bf, "<version></version>\n");
	}

	name_index = getIndexForNameId (face, 10);
	if (name_index != -1 && FT_Get_Sfnt_Name (face, name_index, &name_table_data) == 0) { // description
		g_string_append (bf, "<description>");
		append_description (bf, &name_table_data);
		g_string_append (bf, "</description>\n");
	} else {
		g_string_append (bf, "<description></description>\n");
	}
	
	// copyright
	name_index = getIndexForNameId (face, 0);
	if (name_index != -1 && FT_Get_Sfnt_Name (face, name_index, &name_table_data) == 0) { 
		g_string_append (bf, "<copyright>");
		append_description (bf, &name_table_data);
		g_string_append (bf, "</copyright>\n");
	} else {
		g_string_append (bf, "<copyright></copyright>\n");
	}

	name_index = getIndexForNameId (face, 7);
	if (name_index != -1 && FT_Get_Sfnt_Name (face, name_index, &name_table_data) == 0) {
		g_string_append (bf, "<trademark>");
		append_description (bf, &name_table_data);
		g_string_append (bf, "</trademark>\n");
	} else {
		g_string_append (bf, "<trademark></trademark>\n");
	}

	name_index = getIndexForNameId (face, 8);
	if (name_index != -1 && FT_Get_Sfnt_Name (face, 8, &name_table_data) == 0) {
		g_string_append (bf, "<manefacturer>");
		append_description (bf, &name_table_data);
		g_string_append (bf, "</manefacturer>\n");
	} else {
		g_string_append (bf, "<manefacturer></manefacturer>\n");
	}

	name_index = getIndexForNameId (face, 9);
	if (name_index != -1 && FT_Get_Sfnt_Name (face, name_index, &name_table_data) == 0) {
		g_string_append (bf, "<designer>");
		append_description (bf, &name_table_data);
		g_string_append (bf, "</designer>\n");
	} else {
		g_string_append (bf, "<designer></designer>\n");
	}

	name_index = getIndexForNameId (face, 11);
	if (name_index != -1 && FT_Get_Sfnt_Name (face, name_index, &name_table_data) == 0) {
		g_string_append (bf, "<vendor_url>");
		append_description (bf, &name_table_data);
		g_string_append (bf, "</vendor_url>\n");
	} else {
		g_string_append (bf, "<vendor_url></vendor_url>\n");
	}

	name_index = getIndexForNameId (face, 12);
	if (name_index != -1 && FT_Get_Sfnt_Name (face, name_index, &name_table_data) == 0) {
		g_string_append (bf, "<designer_url>");
		append_description (bf, &name_table_data);
		g_string_append (bf, "</designer_url>\n");
	} else {
		g_string_append (bf, "<designer_url></designer_url>\n");
	}

	name_index = getIndexForNameId (face, 13);
	if (name_index != -1 && FT_Get_Sfnt_Name (face, name_index, &name_table_data) == 0) {
		g_string_append (bf, "<license>");
		append_description (bf, &name_table_data);
		g_string_append (bf, "</license>\n");
	} else {
		g_string_append (bf, "<license></license>\n");
	}
	
	name_index = getIndexForNameId (face, 14);
	if (name_index != -1 && FT_Get_Sfnt_Name (face, name_index, &name_table_data) == 0) {
		g_string_append (bf, "<license_url>");
		append_description (bf, &name_table_data);
		g_string_append (bf, "</license_url>\n");
	} else {
		g_string_append (bf, "<license_url></license_url>\n");
	}
		
	g_string_append_printf (bf, "<backup>%s</backup>\n", g_markup_escape_text (file, -1));


	g_string_append_printf (bf, "<horizontal>\n");
	
	g_ascii_formatd (line_position, 80, "%f", face->ascender * units);
	g_string_append_printf (bf, "\t<top_limit>%s</top_limit>\n", line_position);
	
	g_ascii_formatd (line_position, 80, "%f", get_top (face) * units);
	g_string_append_printf (bf, "\t<top_position>%s</top_position>\n", line_position);
	
	g_ascii_formatd (line_position, 80, "%f", get_xheight (face) * units);
	g_string_append_printf (bf, "\t<x-height>%s</x-height>\n", line_position);
	
	g_string_append_printf (bf, "\t<base_line>0</base_line>\n");
	
	g_ascii_formatd (line_position, 80, "%f", get_descender (face) * units);
	g_string_append_printf (bf, "\t<bottom_position>%s</bottom_position>\n", line_position);
	
	g_ascii_formatd (line_position, 80, "%f", face->descender * units);
	g_string_append_printf (bf, "\t<bottom_limit>%s</bottom_limit>\n", line_position);		
	
	gap = (face->height - (face->ascender - face->descender)) * units;
	g_ascii_formatd (line_position, 80, "%f", gap);
	g_string_append_printf (bf, "\t<line_gap>%s</line_gap>\n", line_position);		
	
	g_string_append_printf (bf, "</horizontal>\n");

	// space character
	gid = FT_Get_Char_Index (face, ' ');
	if (gid != 0) {
		FT_Load_Glyph(face, gid, FT_LOAD_DEFAULT | FT_LOAD_NO_SCALE);
		g_string_append_printf (bf, "<collection unicode=\"U+20\">\n");
		g_string_append_printf (bf, "\t<glyph left=\"%f\" right=\"%f\" selected=\"true\">\n", 0.0, face->glyph->metrics.horiAdvance * units);
		
		//bf_data = get_bf_path (charcode, face, units_per_em, err);
		bf_data = get_bf_path (' ', face, units_per_em, err);
		g_string_append (bf, bf_data->str);	
					
		g_string_append (bf, "\t</glyph>\n");
		g_string_append_printf (bf, "</collection>\n");
	}

	// glyph outlines
	for (i = 0; i < face->num_glyphs; i++) {
		error = FT_Load_Glyph (face, i, FT_LOAD_DEFAULT | FT_LOAD_NO_SCALE);
		if (error) {
			g_warning ("Freetype failed to load glyph %d.\n", (int)i);
			g_warning ("FT_Load_Glyph error %d\n", error);
			*err = 1;
			return bf;
		}

		if (face->glyph->format != ft_glyph_format_outline) {
			g_warning ("Freetype error no outline found in glyph.\n");
			*err = 1;
			return bf;
		}

		FT_ULong* charcodes = get_charcodes (face, i);
		int charcode_index = 0;
		gboolean has_unicode_value;
		guint unassigned_index = 0;
		
		while (TRUE) {	
			charcode = charcodes[charcode_index];
			
			if (charcode == 0) {
				has_unicode_value = FALSE;
			} else {
				has_unicode_value = TRUE;
			}
			
			if (charcode < 32) { // control character
				g_warning ("Control character found, %d.", (guint)charcode);
				has_unicode_value = FALSE;
			}
			
			glyph = g_string_new ("");
			
			if (charcode == 32) { // import space later
				break;
			}
			
			if (!has_unicode_value) {
				g_warning ("Ignoring glyph without a Unicode value, %ld.  (condition: %d)",
					i, has_unicode_value);
				break;
			}
			
			g_string_append_printf (glyph, "<collection ");
			
			if (has_unicode_value) {
				g_string_append_printf (glyph, "unicode=\"U+%x\"", (guint)charcode);
			} else {
				if (i == 0) {
					g_string_append_printf (glyph, "name=\".notdef\"");
				} else {
					unassigned_index++;
					g_string_append_printf (glyph, "name=\"unassigned_character_%d\"", unassigned_index);
				}
			}
			
			g_string_append_printf (glyph, ">\n");
			
			g_string_append_printf (glyph, "\t<glyph left=\"%f\" right=\"%f\" selected=\"true\">\n", 0.0, face->glyph->metrics.horiAdvance * units);
			
			bf_data = get_bf_path (charcode, face, units_per_em, err);
			g_string_append (glyph, bf_data->str);	
						
			g_string_append (glyph, "\t</glyph>\n");
			g_string_append_printf (glyph, "</collection>\n");

			g_string_append (bf, glyph->str);
			g_string_free (glyph, TRUE);
			
			charcode_index++;
			
			if (!has_unicode_value) {
				break;
			}
		}
	}

	bird_font_open_font_format_reader_append_kerning (bf, file);

	g_string_append (bf, "</font>\n");

	
	return bf;
}

/** Load typeface with freetype2 and return the result as a bf font. 
 *  Parameter err will be set to non zero vaule if an error occurs.
 */
GString* load_freetype_font (const gchar* file, gint* err) {
	GString* bf = g_string_new ("");
	GString* bf_data = g_string_new ("");

	FT_Library library;
	FT_Face face;
	int error;
	//FT_Glyph glyph;
	//FT_UInt glyph_index;

	error = FT_Init_FreeType (&library);
	if (error != OK) {
		g_warning ("Freetype init error %d.\n", error);
		*err = error;
		return bf;
	}
	
	if (file == NULL) {
		g_warning ("No file provided for freetype.");
		*err = 1;
		return bf;
	}
	
	gchar* short_path = calloc ((2048 + 1), sizeof(gchar));
	short_path = (gchar *)file;

	error = FT_New_Face (library, short_path, 0, &face);
	if (error) {
		g_warning ("Freetype font face error %d in (load_freetype_font)", error);
		g_warning ("Can't open file %s",  file);
		g_warning ("Short path:     %s",  short_path);
		
		*err = error;
		return bf;
	}

	error = FT_Select_Charmap (face , FT_ENCODING_UNICODE);
	if (error) {
		g_warning ("Freetype can not use Unicode, error: %d\n", error);
		*err = error;
		return bf;
	}

	error = FT_Set_Char_Size (face, 0, 800, 300, 300);
	if (error) {
		g_warning ("Freetype FT_Set_Char_Size failed, error: %d.\n", error);
		*err = error;
		return bf;
	}

	bf_data = get_bf_font (face, file, &error);
	
	if (error != OK) {
		g_warning ("Failed to parse font.\n");
		*err = error;
		return bf;	
	}

	FT_Done_Face (face);
	FT_Done_FreeType (library);
	
	*err = OK;
	g_string_append (bf, bf_data->str);
	
	return bf;
}

gboolean freetype_has_glyph (FreeTypeFontFace* font, guint unicode) {
	if (font == NULL || font->face == NULL || font->library == NULL) {
		g_warning ("No font in freetype_has_glyph");
		return FALSE;
	}

	int gid = FT_Get_Char_Index (font->face, unicode);
	
	return gid != 0;
}

gboolean get_freetype_font_is_regular (const char* file) {
	FT_Library library = NULL;
	FT_Face face = NULL;
	int error;
	FreeTypeFontFace* font = NULL;
	
	error = FT_Init_FreeType (&library);
	
	if (error != OK) {
		g_warning ("Freetype init error %d.\n", error);
		return FALSE;
	}

	gchar* short_path = calloc ((2048 + 1), sizeof(gchar));
	short_path = g_strdup (file);

	error = FT_New_Face (library, short_path, 0, &face);
	
	if (error) {
		if (FT_Done_FreeType (library) != 0) {
			g_warning ("Can't close freetype.");
		}
		
		g_warning ("Freetype font face error %d in (open_font)", error);
		g_warning ("Can't open file %s",  file);
		g_warning ("Short path: %s",  short_path);
		
		return FALSE;
	}
	
	if (font == NULL || font->face == NULL || font->library == NULL) {
		g_warning ("No font in get_freetype_font_is_regular");
		return FALSE;
	}
	
	return !g_str_match_string ("Bold", face->style_name, FALSE)
		&& !g_str_match_string ("Italic", face->style_name, FALSE);
}

gulong* get_all_unicode_points_in_font (const gchar* file) {
	//FT_ULong* charcodes = NULL;
	FT_Library library;
	FT_Face face;

	int error = FT_Init_FreeType (&library);
	
	if (error != OK) {
		g_warning ("Freetype init error %d.\n", error);
		return NULL;
	}
	
	if (file == NULL) {
		g_warning ("No file provided for freetype.");
		return NULL;
	}
	
	gchar* short_path = g_strdup (file);
	
	error = FT_New_Face (library, short_path, 0, &face);
	if (error) {
		g_warning ("Freetype font face error %d in (load_freetype_font)", error);
		g_warning ("Can't open file %s",  file);
		g_warning ("Short path:     %s",  short_path);
		return NULL;
	}

	error = FT_Select_Charmap (face , FT_ENCODING_UNICODE);
	if (error) {
		g_warning ("Freetype can not use Unicode, error: %d\n", error);
		return NULL;
	}

	if (face == NULL) {
		g_warning("No font face in get_all_unicode_points_in_font");
		return NULL;
	}
	
	if (face->num_glyphs == 0) {
		return NULL;
	}
	
	gboolean error_in_result = FALSE;
	FT_UInt gindex = 0;
	FT_ULong charcode;
	gint num_glyphs = 0;
	
	// cound how big the result buffer needs to be
	charcode = FT_Get_First_Char (face, &gindex);
	
	for (int i = 0; i < face->num_glyphs && !error; i++) {
		while (gindex != 0) {
			charcode = FT_Get_Next_Char (face, charcode, &gindex);
			num_glyphs++;	
		}
	}

	// Fill the result buffer
	
	gulong* result = malloc((num_glyphs + 1) * sizeof (gulong));
	int result_index = 0;
	
	if (result == NULL) {
		g_warning ("cant malloc result buffer of size %d ", num_glyphs);
		return NULL;
	}
	
	charcode = FT_Get_First_Char (face, &gindex);
	
	for (int i = 0; i < num_glyphs && !error_in_result && gindex > 0; i++) {
		while (gindex != 0) {
			charcode = FT_Get_Next_Char (face, charcode, &gindex);
			
			if (charcode != 0) {
				if (result_index < 0 || result_index > num_glyphs) {
					error_in_result = TRUE;
					g_warning ("result_index out of bounds %d", result_index);
					break;
				}
				
				result[result_index] = charcode;
				result_index++;
			}
		}
	}

	result[result_index] = 0;
	
	FT_Done_Face (face);
	FT_Done_FreeType (library);
	
	return result;
}

Contact

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