.
1 /*
2 Copyright (C) 2013 2015 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 */
14 namespace BirdFont {
15
16 public class GlyphSequence : GLib.Object {
17
18 /** A list of all glyphs */
19 public Gee.ArrayList<Glyph?> glyph;
20
21 /** A list of corresponding glyph ranges if applicable. */
22 public Gee.ArrayList<GlyphRange?> ranges;
23
24 OtfTags otf_tags;
25
26 public GlyphSequence () {
27 glyph = new Gee.ArrayList<Glyph?> ();
28 ranges = new Gee.ArrayList<GlyphRange?> ();
29 otf_tags = new OtfTags ();
30 }
31
32 public void set_otf_tags (OtfTags tags) {
33 this.otf_tags = tags;
34 }
35
36 public int length () {
37 return glyph.size;
38 }
39
40 public void add (Glyph? g) {
41 glyph.add (g);
42 ranges.add (null);
43 }
44
45 public void append (GlyphSequence c) {
46 foreach (Glyph? g in c.glyph) {
47 glyph.add (g);
48 }
49
50 foreach (GlyphRange? r in c.ranges) {
51 ranges.add (r);
52 }
53 }
54
55 /** Perform glyph substitution.
56 * @param tags enable otf features
57 * @return a new sequence with ligatures
58 */
59 public GlyphSequence process_ligatures (Font font) {
60 // FIXME add range to ligature
61 GlyphSequence ligature_sequence = new GlyphSequence ();
62 bool has_range = false;
63 Ligatures ligatures;
64
65 foreach (Glyph? g in glyph) {
66 ligature_sequence.glyph.add (g);
67 }
68
69 foreach (GlyphRange? r in ranges) {
70 ligature_sequence.ranges.add (r);
71 if (r != null) {
72 has_range = true;
73 }
74 }
75
76 // skip ligature substitution if this sequence contains ranges
77 if (has_range) {
78 return ligature_sequence;
79 }
80
81 ligatures = font.get_ligatures ();
82
83 foreach (ContextualLigature c in ligatures.contextual_ligatures) {
84 if (c.is_valid ()) {
85 ligature_sequence.replace_contextual (c.get_backtrack (),
86 c.get_input (),
87 c.get_lookahead (),
88 c.get_ligature_sequence ());
89 }
90 }
91
92 ligatures.get_single_substitution_ligatures ((substitute, ligature) => {
93 ligature_sequence.replace (substitute, ligature);
94 });
95
96 // salt and similar tags
97 foreach (string tag in otf_tags.elements) {
98 Gee.ArrayList<Alternate> alternates;
99 alternates = font.alternates.get_alt (tag);
100
101 foreach (Alternate a in alternates) {
102 GlyphSequence old = new GlyphSequence ();
103 Glyph? g = font.get_glyph_by_name (a.glyph_name);
104
105 if (g != null) {
106 old.add (g);
107
108 if (a.alternates.size > 0) {
109 // FIXME: pick one of several alternates
110 string alt_name = a.alternates.get (0);
111 Glyph? alt = font.get_glyph_by_name (alt_name);
112
113 if (alt != null) {
114 GlyphSequence replacement = new GlyphSequence ();
115 replacement.add (alt);
116 ligature_sequence.replace (old, replacement);
117 } else {
118 warning (@"Alternate does not exist: $(alt_name)");
119 }
120 }
121 } else {
122 warning (@"Alternative for a missing glyph: $(a.glyph_name)");
123 }
124 }
125 }
126
127 ligature_sequence.ranges.clear ();
128 for (int i = 0; i < ligature_sequence.glyph.size; i++) {
129 ligature_sequence.ranges.add (null);
130 }
131
132 return ligature_sequence;
133 }
134
135 void replace (GlyphSequence old, GlyphSequence replacement) {
136 int i = 0;
137 while (i < glyph.size) {
138 if (starts_with (old, i)) {
139 glyph = substitute (i, old.glyph.size, replacement);
140 i += replacement.length ();
141 } else {
142 i++;
143 }
144 }
145 }
146
147 void replace_contextual (GlyphSequence backtrack, GlyphSequence input, GlyphSequence lookahead,
148 GlyphSequence replacement) {
149
150 bool start, middle, end;
151 int i = 0;
152 int advance = 0;
153
154 while (i < glyph.size) {
155 start = starts_with (backtrack, i);
156 middle = starts_with (input, i + backtrack.length ());
157 end = starts_with (lookahead, i + backtrack.length () + input.length ());
158
159 if (start && middle && end) {
160 glyph = substitute (i + backtrack.length (), input.length (), replacement);
161
162 advance = backtrack.length () + replacement.length ();
163 i += advance + 1;
164
165 if (advance <= 0) {
166 warning ("No advancement.");
167 return;
168 }
169 } else {
170 i++;
171 }
172 }
173 }
174
175 bool starts_with (GlyphSequence old, uint index) {
176 Glyph? gl;
177
178 foreach (Glyph? g in old.glyph) {
179 if (index >= glyph.size) {
180 return false;
181 }
182
183 gl = glyph.get ((int) index);
184
185 if (g != gl) {
186 return false;
187 }
188
189 index++;
190 }
191
192 return true;
193 }
194
195 Gee.ArrayList<Glyph?> substitute (uint index, uint length, GlyphSequence substitute) {
196 Gee.ArrayList<Glyph?> new_list = new Gee.ArrayList<Glyph?> ();
197 int i = 0;
198
199 foreach (Glyph? g in glyph) {
200 if (i == index) {
201 foreach (Glyph? gn in substitute.glyph) {
202 new_list.add (gn);
203 }
204 }
205
206 if (!(i >= index && i < index + length)) {
207 new_list.add (g);
208 }
209
210 i++;
211 }
212
213 return new_list;
214 }
215 }
216
217 }
218