Font.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 Cairo;
using Xml;
namespace Supplement {
public class Font : GLib.Object {
GlyphTable glyph_cache = new GlyphTable ();
/** Glyphs that not are tied to a unichar value. */
GlyphTable unassigned_glyphs = new GlyphTable ();
List<string> glyph_names = new List<string> ();
/** Last unassigned index */
int next_unindexed = 0;
public List <string> background_images = new List <string> ();
public string background_scale = "1";
/** Top margin */
public double top_limit;
/** Height of upper case letters. */
public double top_position;
/** x-height upper bearing from origo. */
public double xheight_position;
/** Base line coordinate from origo. */
public double base_line;
/** Descender position */
public double bottom_position;
/** Bottom margon */
public double bottom_limit;
public string? backup_file = null;
public string? font_file = null;
bool modified = false;
private string name = "typeface";
bool ttf_export = true;
bool svg_export = true;
OpenFontFormatReader otf;
bool otf_font = false;
public List<string> grid_width = new List<string> ();
public Font () {
// positions in pixels at first zoom level
// default x-height should be 60 in 1:1
top_limit = -66 * Glyph.SCALE;
top_position = -54 * Glyph.SCALE;
xheight_position = -38 * Glyph.SCALE;
base_line = 18 * Glyph.SCALE;
bottom_position = 38 * Glyph.SCALE;
bottom_limit = 45 * Glyph.SCALE;
}
public void touch () {
modified = true;
}
public bool get_ttf_export () {
return ttf_export;
}
public bool get_svg_export () {
return svg_export;
}
public File get_backgrounds_folder () {
string fn = @"$(get_name ()) backgrounds";
File f = Supplement.get_settings_directory ().get_child (fn);
return f;
}
/** Retuns true if the current font has be modified */
public bool is_modified () {
return modified;
}
/** Full path to this font file. */
public string get_path () {
int i = 0;
if (font_file != null) {
return (!) font_file;
}
StringBuilder sb = new StringBuilder ();
sb.append (Environment.get_home_dir ());
sb.append (@"/$(get_name ()).ffi");
File f = File.new_for_path (sb.str);
while (f.query_exists ()) {
sb.erase ();
sb.append (Environment.get_home_dir ());
sb.append (@"/$(get_name ())$(++i).ffi");
f = File.new_for_path (sb.str);
}
return sb.str;
}
public string get_file_name () {
string p = get_path ();
int i = p.last_index_of ("/");
if (i == -1) {
i = p.last_index_of ("\\");
}
p = p.substring (i + 1);
return p;
}
public File get_folder () {
string p = get_path ();
int i = p.last_index_of ("/");
if (i == -1) {
i = p.last_index_of ("\\");
}
p = p.substring (0, i);
return File.new_for_path (p);
}
public double get_height () {
double r = base_line - top_position;
return (r > 0) ? r : -r;
}
public void set_name (string name) {
this.name = name;
}
public string get_name () {
return name;
}
public void print_all () {
stdout.printf ("Assigned:\n");
glyph_cache.for_each((g) => {
stdout.printf (@"$(g.get_name ())\n");
});
stdout.printf ("\n");
stdout.printf ("Unssigned:\n");
unassigned_glyphs.for_each ((g) => {
stdout.printf (@"$(g.get_name ())\n");
});
}
public bool has_glyph (string n) {
return get_glyph (n) != null;
}
public Glyph get_nonmarking_return () {
Glyph ret;
if (has_glyph ("nonmarkingreturn")) {
return (!) get_glyph ("nonmarkingreturn");
}
ret = new Glyph ("nonmarkingreturn", '\r');
ret.set_unassigned (false);
ret.left_limit = 0;
ret.right_limit = 0;
ret.remove_empty_paths ();
assert (ret.path_list.length () == 0);
return ret;
}
public Glyph get_null_character () {
Glyph n;
if (has_glyph ("null")) {
return (!) get_glyph ("null");
}
n = new Glyph ("null", '\0');
n.set_unassigned (false);
n.left_limit = 0;
n.right_limit = 0;
n.remove_empty_paths ();
assert (n.path_list.length () == 0);
return n;
}
public Glyph get_space () {
Glyph n;
if (has_glyph (" ")) {
return (!) get_glyph (" ");
}
if (has_glyph ("space")) {
return (!) get_glyph ("space");
}
n = new Glyph ("space", ' ');
n.set_unassigned (false);
n.left_limit = 0;
n.right_limit = 27;
n.remove_empty_paths ();
assert (n.path_list.length () == 0);
return n;
}
public Glyph get_not_def_character () {
Glyph g;
Path p;
Path i;
if (has_glyph (".notdef")) {
return (!) get_glyph (".notdef");
}
g = new Glyph (".notdef", 0);
p = new Path ();
i = new Path ();
g.set_unassigned (true);
g.left_limit = -33;
g.right_limit = 33;
p.add (-20, -top_position - 5);
p.add (20, -top_position - 5);
p.add (20, -base_line + 5);
p.add (-20, -base_line + 5);
p.close ();
i.add (-15, -top_position - 10);
i.add (15, -top_position - 10);
i.add (15, -base_line + 10);
i.add (-15, -base_line + 10);
i.reverse ();
i.close ();
g.add_path (i);
g.add_path (p);
return g;
}
public void add_glyph (Glyph glyph) {
GlyphCollection? gc = get_glyph_collection (glyph.get_name ());
if (gc == null) {
add_glyph_collection (new GlyphCollection (glyph));
}
}
public void add_glyph_collection (GlyphCollection glyph_collection) {
GlyphCollection? gc = get_cached_glyph_collection (glyph_collection.get_name ());
if (glyph_collection.get_name () == "") {
warning ("Refusing to insert glyph with name \"\", null character should be named null.");
return;
}
if (gc != null) {
warning ("glyph has already been added");
return;
}
if (glyph_collection.get_current ().is_unassigned ()) {
unassigned_glyphs.insert (glyph_collection);
} else {
glyph_cache.insert (glyph_collection);
}
if (!has_name (glyph_collection.get_name ())) {
glyph_names.append (glyph_collection.get_name ());
glyph_names.sort (strcmp);
}
}
public string get_name_for_character (unichar c) {
// if some glyph is already mapped to unichar, return it's name
uint i = 0;
Glyph? gl;
Glyph g;
StringBuilder sb;
while ((gl = get_glyph_indice (i++)) != null) {
g = (!) gl;
if (g.unichar_code == c) {
return g.name;
}
}
// otherwise return some default name, possibly from unicode database
if (c == 0) {
return ".null".dup ();
}
sb = new StringBuilder ();
sb.append_unichar (c);
return sb.str;
}
public bool has_name (string name) {
foreach (string n in glyph_names) {
if (n == name) {
return true;
}
}
return false;
}
public void delete_glyph (string glyph) {
glyph_names.remove_all (glyph);
glyph_cache.remove (glyph);
}
/** Obtain all versions and alterntes for this glyph. */
public GlyphCollection? get_glyph_collection (string glyph) {
GlyphCollection? gc = get_cached_glyph_collection (glyph);
Glyph? g;
if (gc == null && otf_font) {
// load it from otf file if we need to
g = otf.read_glyph (glyph);
if (g != null) {
add_glyph_callback ((!) g);
return get_cached_glyph_collection (glyph);
}
}
return gc;
}
public GlyphCollection? get_cached_glyph_collection (string glyph) {
GlyphCollection? gc = null;
gc = glyph_cache.get (glyph);
if (gc == null) {
gc = unassigned_glyphs.get (glyph);
}
return gc;
}
public Glyph? get_glyph (string glyph) {
GlyphCollection? gc = get_glyph_collection (glyph);
if (gc == null) {
return null;
}
return ((!)gc).get_current ();
}
public Glyph? get_glyph_indice (unichar glyph_indice) {
GlyphCollection? gc;
string n;
if (!(0 <= glyph_indice < glyph_names.length ())) {
return null;
}
n = glyph_names.nth (glyph_indice).data;
gc = get_glyph_collection (n);
if (gc != null) {
return ((!) gc).get_current ();
}
return null;
}
public void add_background_image (string file) {
background_images.append (file);
}
public double get_kerning (string a, string b) {
Glyph? gl = get_glyph (a);
Glyph g;
if (gl == null) {
warning (@"glyph \"$a\" does not exist cannot obtain kerning");
return 0;
}
g = (!) gl;
return g.get_kerning (b);
}
public void set_kerning (string a, string b, double val) {
Glyph? gl;
Glyph g;
gl = get_glyph (a);
if (gl == null) {
warning ("glyph is not parsed yet cannot add kerning");
return;
}
g = (!) gl;
g.add_kerning (b, val);
}
/** Returns path to backup file. */
public string save_backup () {
File dir = Supplement.get_backup_directory ();
File? temp_file = null;
int i = 0;
if (backup_file == null) {
temp_file = dir.get_child (@"current_font_$i.ffi");
while (((!) temp_file).query_exists ()) {
i++;
temp_file = dir.get_child (@"current_font_$i.ffi");
}
backup_file = ((!) temp_file).get_path ();
}
assert (backup_file != null);
write_font_file ((!) backup_file);
return (!) backup_file;
}
public bool save (string path) {
bool r = write_font_file (path);
if (r) {
font_file = path;
}
modified = false;
add_thumbnail ();
Preferences.add_recent_files (get_path ());
return r;
}
public bool write_font_file (string path) {
try {
File file = File.new_for_path (path);
if (file.query_file_type (0) == FileType.DIRECTORY) {
stderr.printf (@"Can not save font. $path is a directory.");
return false;
}
if (file.query_exists ()) {
file.delete ();
}
DataOutputStream os = new DataOutputStream(file.create(FileCreateFlags.REPLACE_DESTINATION));
os.put_string ("""<?xml version="1.0" encoding="utf-8" standalone="yes"?>""");
os.put_string ("\n");
os.put_string ("<font>\n");
os.put_string ("\n");
os.put_string (@"<name>$(get_name ())</name>\n");
os.put_string ("\n");
os.put_string (@"<ttf-export>$(ttf_export)</ttf-export>\n");
os.put_string ("\n");
os.put_string (@"<svg-export>$(svg_export)</svg-export>\n");
os.put_string ("\n");
os.put_string ("<lines>\n");
os.put_string (@"\t<top_limit>$top_limit</top_limit>\n");
os.put_string (@"\t<top_position>$top_position</top_position>\n");
os.put_string (@"\t<x-height>$xheight_position</x-height>\n");
os.put_string (@"\t<base_line>$base_line</base_line>\n");
os.put_string (@"\t<bottom_position>$bottom_position</bottom_position>\n");
os.put_string (@"\t<bottom_limit>$bottom_limit</bottom_limit>\n");
os.put_string ("</lines>\n\n");
foreach (string gv in grid_width) {
os.put_string (@"<grid width=\"$(gv)\"/>\n");
}
if (GridTool.sizes.length () > 0) {
os.put_string ("\n");
}
os.put_string (@"<background scale=\"$(background_scale)\" />\n");
os.put_string ("\n");
if (background_images.length () > 0) {
os.put_string (@"<images>\n");
foreach (string f in background_images) {
os.put_string (@"\t<img src=\"$f\"/>\n");
}
os.put_string (@"</images>\n");
os.put_string ("\n");
}
glyph_cache.for_each ((gc) => {
try {
bool selected;
foreach (Glyph g in gc.get_version_list ().glyphs) {
selected = (g == gc.get_current ());
write_glyph (g, selected, os);
}
} catch (GLib.Error ef) {
stderr.printf (@"Failed to save $path \n");
stderr.printf (@"$(ef.message) \n");
}
});
glyph_cache.for_each ((gc) => {
try {
Glyph glyph = gc.get_current ();
foreach (Kerning k in glyph.kerning) {
string l, r;
Glyph? gr = get_glyph (k.glyph_right);
Glyph glyph_right;
if (gr == null) {
warning ("kerning glyph that does not exist.");
}
glyph_right = (!) gr;
l = Font.to_hex_code (glyph.unichar_code);
r = Font.to_hex_code (glyph_right.unichar_code);
os.put_string (@"<hkern left=\"U+$l\" right=\"U+$r\" kerning=\"$(k.val)\"/>\n");
}
} catch (GLib.Error e) {
warning (e.message);
}
});
glyph_cache.for_each ((gc) => {
GlyphBackgroundImage bg;
try {
string data;
foreach (Glyph g in gc.get_version_list ().glyphs) {
if (g.get_background_image () != null) {
bg = (!) g.get_background_image ();
data = bg.get_png_base64 ();
if (!bg.is_valid ()) {
continue;
}
os.put_string (@"<background-image sha1=\"");
os.put_string (bg.get_sha1 ());
os.put_string ("\" ");
os.put_string (" data=\"");
os.put_string (data);
os.put_string ("");
os.put_string ("\" />\n");
}
}
} catch (GLib.Error ef) {
stderr.printf (@"Failed to save $path \n");
stderr.printf (@"$(ef.message) \n");
}
});
os.put_string ("</font>");
} catch (GLib.Error e) {
stderr.printf (@"Failed to save $path \n");
stderr.printf (@"$(e.message) \n");
return false;
} catch (GLib.IOError e) {
stderr.printf (@"Failed to save $path \n");
stderr.printf (@"$(e.message) \n");
return false;
}
return true;
}
private void write_glyph (Glyph g, bool selected, DataOutputStream os) throws GLib.Error {
os.put_string (@"<glyph unicode=\"$(to_hex (g.unichar_code))\" selected=\"$selected\" left=\"$(g.left_limit)\" right=\"$(g.right_limit)\">\n");
foreach (var p in g.path_list) {
if (p.points.length () == 0) {
continue;
}
os.put_string ("\t<object>");
foreach (var ep in p.points) {
os.put_string (@"<point x=\"$(ep.x)\" y=\"$(ep.y)\" ");
if (ep.right_handle.type == PointType.CURVE) {
os.put_string (@"right_angle=\"$(ep.right_handle.angle)\" ");
os.put_string (@"right_length=\"$(ep.right_handle.length)\" ");
}
if (ep.left_handle.type == PointType.CURVE) {
os.put_string (@"left_angle=\"$(ep.left_handle.angle)\" ");
os.put_string (@"left_length=\"$(ep.left_handle.length)\" ");
}
if (ep.right_handle.type == PointType.CURVE || ep.left_handle.type == PointType.CURVE) {
os.put_string (@"tie_handles=\"$(ep.tie_handles)\" ");
}
os.put_string ("/>");
}
os.put_string ("</object>\n");
}
GlyphBackgroundImage? bg = g.get_background_image ();
if (bg != null) {
GlyphBackgroundImage background_image = (!) bg;
double pos_x = background_image.img_x;
double pos_y = background_image.img_y;
double scale_x = background_image.img_scale_x;
double scale_y = background_image.img_scale_y;
double rotation = background_image.img_rotation;
if (background_image.is_valid ()) {
os.put_string (@"\t<background sha1=\"$(background_image.get_sha1 ())\" x=\"$pos_x\" y=\"$pos_y\" scale_x=\"$scale_x\" scale_y=\"$scale_y\" rotation=\"$rotation\"/>\n");
}
}
os.put_string ("</glyph>\n\n");
}
public void set_font_file (string path) {
font_file = path;
modified = false;
}
public uint length () {
return glyph_names.length ();
}
public bool is_empty () {
return (glyph_names.length () == 0);
}
public bool load (string path, bool recent = true) {
bool loaded = false;
try {
otf_font = false;
while (grid_width.length () > 0) {
grid_width.remove_link (grid_width.first ());
}
while (glyph_names.length () > 0) {
glyph_names.remove_link (glyph_names.first ());
}
glyph_cache.remove_all ();
unassigned_glyphs.remove_all ();
if (path.has_suffix (".ffi")) {
loaded = parse_file (path);
font_file = path;
}
if (Supplement.experimental) {
if (path.has_suffix (".ttf")) {
loaded = parse_otf_file (path);
font_file = path;
}
}
if (recent) {
add_thumbnail ();
Preferences.add_recent_files (get_path ());
}
} catch (GLib.Error e) {
warning (e.message);
return false;
}
return loaded;
}
private void add_thumbnail () {
File f = Supplement.get_thumbnail_directory ().get_child (@"$((!) get_file_name ()).png");
Glyph? gl = get_glyph ("a");
Glyph g;
ImageSurface img;
ImageSurface img_scale;
Context cr;
double scale;
if (gl == null) {
gl = get_glyph_indice (4);
}
if (gl == null) {
gl = get_not_def_character ();
}
g = (!) gl;
img = g.get_thumbnail ();
scale = 70.0 / img.get_width ();
if (scale > 70.0 / img.get_height ()) {
scale = 70.0 / img.get_height ();
}
if (scale > 1) {
scale = 1;
}
img_scale = new ImageSurface (Format.ARGB32, (int) (scale * img.get_width ()), (int) (scale * img.get_height ()));
cr = new Context (img_scale);
cr.scale (scale, scale);
cr.save ();
cr.set_source_surface (img, 0, 0);
cr.paint ();
cr.restore ();
img_scale.write_to_png ((!) f.get_path ());
}
/** Callback function for loading glyph in a separate thread. */
public void add_glyph_callback (Glyph g) {
GlyphCollection? gcl;
GlyphCollection gc;
gcl = get_cached_glyph_collection (g.get_name ());
if (gcl != null) {
warning (@"glyph \"$(g.get_name ())\" does already exist");
}
if (g.is_unassigned ()) {
gc = new GlyphCollection (g);
if (g.name == "") {
warning ("refusing to insert glyph without name");
g.name = @"($(++next_unindexed))";
return;
}
add_glyph_collection ((!) gc);
} else if (gcl == null) {
gc = new GlyphCollection (g);
add_glyph_collection ((!) gc);
} else {
stderr.printf (@"Glyph collection does already have an entry for $(g.get_name ()) char $((uint64) g.unichar_code).\n");
}
// take xheight from appropriate lower case letter
// xheight_position = estimate_xheight ();
}
public bool parse_otf_file (string path) throws GLib.Error {
otf = new OpenFontFormatReader ();
otf_font = true;
otf.parse_index (path);
foreach (string n in otf.get_all_names ()) {
glyph_names.append (n);
}
return true;
}
public bool parse_file (string path) throws GLib.Error {
Parser.init ();
Xml.Doc* doc;
Xml.Node* root;
Xml.Node* node;
// set this path as file for this font
font_file = path;
// empty cache and fill it with new glyphs from disk
glyph_cache.remove_all ();
unassigned_glyphs.remove_all ();
while (background_images.length () > 0) {
background_images.remove_link (background_images.first ());
}
create_background_files (path);
doc = Parser.parse_file (path);
root = doc->get_root_element ();
if (root == null) {
delete doc;
return false;
}
node = root;
for (Xml.Node* iter = node->children; iter != null; iter = iter->next) {
if (iter->name == "glyph") {
parse_glyph (iter);
}
if (iter->name == "lines") {
parse_font_boundries (iter);
}
if (iter->name == "grid") {
parse_grid (iter);
}
if (iter->name == "background") {
parse_background (iter);
}
if (iter->name == "images") {
parse_background_selection (iter);
}
if (iter->name == "name") {
name = iter->children->content;
}
if (iter->name == "hkern") {
parse_kerning (iter);
}
if (iter->name == "ttf-export") {
ttf_export = bool.parse (iter->children->content);
}
if (iter->name == "svg-export") {
svg_export = bool.parse (iter->children->content);
}
}
delete doc;
Parser.cleanup ();
return true;
}
private void create_background_files (string path) {
Xml.Doc* doc = Parser.parse_file (path);
Xml.Node* root;
Xml.Node* node;
root = doc->get_root_element ();
if (root == null) {
delete doc;
return;
}
node = root;
for (Xml.Node* iter = node->children; iter != null; iter = iter->next) {
if (iter->name == "name") {
name = iter->children->content;
}
if (iter->name == "background-image") {
parse_background_image (iter);
}
}
delete doc;
Parser.cleanup ();
}
private void parse_kerning (Xml.Node* node) {
string attr_name;
string attr_content;
string left = "";
string right = "";
string kern = "";
StringBuilder b;
for (Xml.Attr* prop = node->properties; prop != null; prop = prop->next) {
attr_name = prop->name;
attr_content = prop->children->content;
if (attr_name == "left") {
b = new StringBuilder ();
b.append_unichar (to_unichar (attr_content));
left = @"$(b.str)";
}
if (attr_name == "right") {
b = new StringBuilder ();
b.append_unichar (to_unichar (attr_content));
right = @"$(b.str)";
}
if (attr_name == "kerning") {
kern = attr_content;
}
}
set_kerning (left, right, double.parse (kern));
}
private void parse_background_image (Xml.Node* node)
requires (node != null)
{
string attr_name;
string attr_content;
string file = "";
string data = "";
File img_dir;
File img_file;
FileOutputStream file_stream;
DataOutputStream png_stream;
for (Xml.Attr* prop = node->properties; prop != null; prop = prop->next) {
return_if_fail (!is_null (prop->name));
return_if_fail (!is_null (prop->children));
return_if_fail (!is_null (prop->children->content));
attr_name = prop->name;
attr_content = prop->children->content;
if (attr_name == "sha1") {
file = attr_content;
}
if (attr_name == "data") {
data = attr_content;
}
}
img_dir = get_backgrounds_folder ().get_child ("parts");
if (!img_dir.query_exists ()) {
DirUtils.create ((!) img_dir.get_path (), 0xFFFFFF);
}
img_file = img_dir.get_child (@"$(file).png");
if (img_file.query_exists ()) {
return;
}
try {
file_stream = img_file.create (FileCreateFlags.REPLACE_DESTINATION);
png_stream = new DataOutputStream (file_stream);
png_stream.write (Base64.decode (data));
png_stream.close ();
} catch (GLib.Error e) {
warning (e.message);
}
}
private void parse_background (Xml.Node* node)
requires (node != null)
{
string attr_name;
string attr_content;
for (Xml.Attr* prop = node->properties; prop != null; prop = prop->next) {
return_if_fail (!is_null (prop->name));
return_if_fail (!is_null (prop->children));
return_if_fail (!is_null (prop->children->content));
attr_name = prop->name;
attr_content = prop->children->content;
if (attr_name == "scale") {
background_scale = attr_content;
}
}
}
private void parse_background_selection (Xml.Node* node) {
string attr_name = "";
string attr_content;
return_if_fail (node != null);
for (Xml.Node* iter = node->children; iter != null; iter = iter->next) {
if (iter->name == "img") {
for (Xml.Attr* prop = iter->properties; prop != null; prop = prop->next) {
attr_name = prop->name;
attr_content = prop->children->content;
if (attr_name == "src") {
background_images.append (attr_content);
}
}
}
}
}
private void parse_grid (Xml.Node* node) {
for (Xml.Attr* prop = node->properties; prop != null; prop = prop->next) {
string attr_name = prop->name;
string attr_content = prop->children->content;
if (attr_name == "width") {
grid_width.append (attr_content);
}
}
}
private void parse_font_boundries (Xml.Node* node) {
for (Xml.Node* iter = node->children; iter != null; iter = iter->next) {
if (iter->name == "top_limit") top_limit = parse_double_from_node (iter);
if (iter->name == "top_position") top_position = parse_double_from_node (iter);
if (iter->name == "x-height") xheight_position = parse_double_from_node (iter);
if (iter->name == "base_line") base_line = parse_double_from_node (iter);
if (iter->name == "bottom_position") bottom_position = parse_double_from_node (iter);
if (iter->name == "bottom_limit") bottom_limit = parse_double_from_node (iter);
}
}
private double parse_double_from_node (Xml.Node* iter) {
double d;
bool r = double.try_parse (iter->children->content, out d);
if (unlikely (!r)) {
string? s = iter->content;
if (s == null) warning (@"Content is null for node $(iter->name)\n");
else warning (@"Failed to parse double for \"$(iter->content)\"\n");
}
return (r) ? d : 0;
}
/** Parse one glyph. */
public void parse_glyph (Xml.Node* node) {
string name = "";
int left = 0;
int right = 0;
unichar uni = 0;
int version = 0;
bool selected = false;
Glyph g;
GlyphCollection? gc;
for (Xml.Attr* prop = node->properties; prop != null; prop = prop->next) {
string attr_name = prop->name;
string attr_content = prop->children->content;
StringBuilder b;
if (attr_name == "unicode") {
uni = to_unichar (attr_content);
b = new StringBuilder ();
b.append_unichar (uni);
name = b.str;
}
if (attr_name == "left") {
left = int.parse (attr_content);
}
if (attr_name == "right") {
right = int.parse (attr_content);
}
if (attr_name == "version") {
version = int.parse (attr_content);
}
if (attr_name == "selected") {
selected = bool.parse (attr_content);
}
}
g = new Glyph (name, uni);
g.left_limit = left;
g.right_limit = right;
parse_content (g, node);
// todo: use disk thread and idle add this:
gc = get_glyph_collection (g.get_name ());
if (g.get_name () == "") {
warning ("No name set for glyph.");
}
if (gc == null) {
gc = new GlyphCollection (g);
add_glyph_collection ((!) gc);
} else {
((!)gc).insert_glyph (g, selected);
}
}
/** Parse visual objects and paths */
private void parse_content (Glyph g, Xml.Node* node) {
Xml.Node* i;
return_if_fail (node != null);
for (Xml.Node* iter = node->children; iter != null; iter = iter->next) {
if (iter->name == "object") {
Path p = new Path ();
for (i = iter->children; i != null; i = i->next) {
if (i->name == "point") {
parse_point (p, i);
}
}
p.close ();
g.add_path (p);
}
if (iter->name == "background") {
parse_background_scale (g, iter);
}
}
}
private void parse_background_scale (Glyph g, Xml.Node* node) {
GlyphBackgroundImage img;
GlyphBackgroundImage? new_img = null;
string attr_name = "";
string attr_content;
File img_file = get_backgrounds_folder ().get_child ("parts");
for (Xml.Attr* prop = node->properties; prop != null; prop = prop->next) {
attr_name = prop->name;
attr_content = prop->children->content;
if (attr_name == "sha1") {
img_file = img_file.get_child (attr_content + ".png");
if (!img_file.query_exists ()) {
warning (@"Background file has not been created yet. $((!) img_file.get_path ())");
}
new_img = new GlyphBackgroundImage ((!) img_file.get_path ());
g.set_background_image ((!) new_img);
}
}
if (unlikely (new_img == null)) {
warning (@"No source for image found for $attr_name in $(g.name)");
return;
}
img = (!) new_img;
for (Xml.Attr* prop = node->properties; prop != null; prop = prop->next) {
attr_name = prop->name;
attr_content = prop->children->content;
if (attr_name == "x") {
img.img_x = double.parse (attr_content);
}
if (attr_name == "y") {
img.img_y = double.parse (attr_content);
}
if (attr_name == "scale_x") {
img.img_scale_x = double.parse (attr_content);
}
if (attr_name == "scale_y") {
img.img_scale_y = double.parse (attr_content);
}
if (attr_name == "rotation") {
img.img_rotation = double.parse (attr_content);
}
}
img.set_position(img.img_x, img.img_y);
}
private void parse_point (Path p, Xml.Node* iter) {
double x = 0;
double y = 0;
PointType type = PointType.LINE;
double angle_right = 0;
double angle_left = 0;
double length_right = 0;
double length_left = 0;
PointType type_right = PointType.LINE;
PointType type_left = PointType.LINE;
bool tie_handles = false;
EditPoint ep;
for (Xml.Attr* prop = iter->properties; prop != null; prop = prop->next) {
string attr_name = prop->name;
string attr_content = prop->children->content;
if (attr_name == "x") x = double.parse (attr_content);
if (attr_name == "y") y = double.parse (attr_content);
if (attr_name == "right_angle") {
type = PointType.CURVE; // FIXA: delete, maybe
}
if (attr_name == "right_angle") {
type_right = PointType.CURVE;
}
if (attr_name == "left_angle") {
type_left = PointType.CURVE;
}
if (attr_name == "right_angle") angle_right = double.parse (attr_content);
if (attr_name == "right_length") length_right = double.parse (attr_content);
if (attr_name == "left_angle") angle_left = double.parse (attr_content);
if (attr_name == "left_length") length_left = double.parse (attr_content);
if (attr_name == "tie_handles") tie_handles = bool.parse (attr_content);
}
ep = new EditPoint (x, y);
ep.type = type;
ep.right_handle.angle = angle_right;
ep.right_handle.length = length_right;
ep.right_handle.type = type_right;
ep.left_handle.angle = angle_left;
ep.left_handle.length = length_left;
ep.left_handle.type = type_left;
ep.tie_handles = tie_handles;
p.add_point (ep);
}
public bool restore_backup () {
string? b = present_backup_file ();
if (b == null) return false;
return parse_file ((!)b);
}
private string? present_backup_file () {
try {
File dir = Supplement.get_settings_directory ();
var files = dir.enumerate_children (FILE_ATTRIBUTE_STANDARD_NAME, 0);
// What if we have more than one backup file left?
FileInfo? file_info;
while ((file_info = files.next_file ()) != null) {
FileInfo fi = (!) file_info;
if (fi.get_name ().index_of ("current_font_") != -1) {
File f = dir.get_child (fi.get_name ());
backup_file = f.get_path ();
return backup_file;
}
}
} catch (GLib.Error e) {
stderr.printf (@"Failed to load backup\n");
stderr.printf (@"$(e.message) \n");
}
return null;
}
/** Delete temporary rescue files. */
public void delete_backup () {
if (backup_file == null) return;
try {
File f = File.new_for_path ((!) backup_file);
f.delete ();
} catch (GLib.Error e) {
stderr.printf (@"Failed to delete backup\n");
stderr.printf (@"$(e.message) \n");
}
}
public static string to_hex (unichar ch) {
StringBuilder s = new StringBuilder ();
s.append ("U+");
s.append (to_hex_code (ch));
return s.str;
}
public static string to_hex_code (unichar ch) {
StringBuilder s = new StringBuilder ();
uint8 a = (uint8)(ch & 0x00000F);
uint8 b = (uint8)((ch & 0x0000F0) >> 4 * 1);
uint8 c = (uint8)((ch & 0x000F00) >> 4 * 2);
uint8 d = (uint8)((ch & 0x00F000) >> 4 * 3);
uint8 e = (uint8)((ch & 0x0F0000) >> 4 * 4);
uint8 f = (uint8)((ch & 0xF00000) >> 4 * 5);
if (e != 0 || f != 0) {
s.append (oct_to_hex (f));
s.append (oct_to_hex (e));
}
if (c != 0 || d != 0) {
s.append (oct_to_hex (d));
s.append (oct_to_hex (c));
}
s.append (oct_to_hex (b));
s.append (oct_to_hex (a));
return s.str;
}
public static unichar to_unichar (string unicode) {
int index = 2;
int i = 0;
unichar c;
unichar rc = 0;
bool r;
if (unlikely (unicode.index_of ("U+") != 0)) {
warning ("All unicode values must begin with U+");
}
while (r = unicode.get_next_char (ref index, out c)) {
rc <<= 4;
rc += hex_to_oct (c);
return_val_if_fail (++i <= 6, '\0');
}
return rc;
}
private static string oct_to_hex (uint8 o) {
switch (o) {
case 10: return "a";
case 11: return "b";
case 12: return "c";
case 13: return "d";
case 14: return "e";
case 15: return "f";
}
return_val_if_fail (0 <= o <= 9, "-".dup ());
return o.to_string ();
}
private static uint8 hex_to_oct (unichar o) {
StringBuilder s = new StringBuilder ();
s.append_unichar (o);
switch (o) {
case 'a': return 10;
case 'b': return 11;
case 'c': return 12;
case 'd': return 13;
case 'e': return 14;
case 'f': return 15;
}
return_val_if_fail ('0' <= o <= '9', 0);
return (uint8) (o - '0');
}
}
}