The Birdfont Source Code


All Repositories / birdfont.git / blob – RSS feed

SvgTable.vala in libbirdfont/OpenFontFormat

This file is a part of the Birdfont project.

Contributing

Send patches or pull requests to johan.mattsson.m@gmail.com.
Clone this repository: git clone https://github.com/johanmattssonm/birdfont.git

Revisions

View the latest version of libbirdfont/OpenFontFormat/SvgTable.vala.
Merge ../birdfont-2.x
1 /* 2 Copyright (C) 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 15 using B; 16 using SvgBird; 17 18 namespace BirdFont { 19 20 public class SvgTable : OtfTable { 21 22 int glyphs_in_table = 0; 23 Gee.ArrayList<SvgTableEntry> entries; 24 25 public SvgTable () { 26 id = "SVG "; 27 entries = new Gee.ArrayList<SvgTableEntry> (); 28 } 29 30 public bool has_glyphs () { 31 return glyphs_in_table > 0; 32 } 33 34 public void process (GlyfTable glyf_table) throws GLib.Error { 35 Font font = OpenFontFormatWriter.get_current_font (); 36 GlyphCollection? glyph_collection; 37 GlyphCollection glyphs; 38 int gid; 39 Gee.ArrayList<EmbeddedSvg> embedded_svg; 40 41 for (int index = 0; index < font.length (); index++) { 42 glyph_collection = font.get_glyph_collection_index (index); 43 44 if (glyph_collection != null) { 45 glyphs = (!) glyph_collection; 46 embedded_svg = get_embedded_svg (glyphs); 47 48 if (embedded_svg.size > 0) { 49 gid = glyf_table.get_gid (glyphs.get_name ()); 50 51 StringBuilder svg = new StringBuilder (); 52 svg.append ("<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n"); 53 54 svg.append ("""<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">"""); 55 svg.append ("\n\n"); 56 57 svg.append ("<g id="); 58 svg.append ("\""); 59 svg.append ("glyph"); 60 svg.append (@"$gid"); 61 svg.append ("\" >"); 62 63 foreach (EmbeddedSvg embedded in embedded_svg) { 64 svg.append ("<g "); 65 66 // scale the internal coordinates from 100 units per em to the 67 // number of units per em in this font and move the glyph 68 // in to the em box 69 Glyph glyph = glyphs.get_current (); 70 double scale = HeadTable.UNITS; 71 double x = embedded.x - glyph.left_limit; 72 double y = font.base_line - embedded.y; 73 svg.append (@"transform=\"scale($scale) translate($x, $y)\""); 74 75 svg.append (">"); 76 svg.append ("\n\n"); 77 78 append_svg_glyph (svg, embedded.svg_data, glyphs); 79 80 svg.append ("</g>\n"); 81 svg.append ("\n\n"); 82 } 83 84 svg.append ("</g>\n"); 85 svg.append ("</svg>"); 86 87 SvgTableEntry entry; 88 entry = new SvgTableEntry ((uint16) gid, svg.str); 89 entries.add (entry); 90 91 glyphs_in_table++; 92 } 93 } 94 } 95 96 process_svg_data (); 97 } 98 99 Gee.ArrayList<EmbeddedSvg> get_embedded_svg (GlyphCollection glyphs) { 100 Gee.ArrayList<EmbeddedSvg> svg = new Gee.ArrayList<EmbeddedSvg> (); 101 PathList path_list = new PathList (); 102 103 foreach (SvgBird.Object object in glyphs.get_current ().get_visible_objects ()) { 104 if (object is EmbeddedSvg) { 105 svg.add ((EmbeddedSvg) object); 106 } else if (object is PathObject) { 107 Path path = ((PathObject) object).get_path (); 108 109 if (path.stroke > 0) { 110 path_list.append (path.get_completed_stroke ()); 111 } else { 112 path_list.add (path); 113 } 114 } 115 } 116 117 if (path_list.paths.size > 0) { 118 EmbeddedSvg svg_data = generate_svg (path_list, glyphs); 119 svg.add (svg_data); 120 } 121 122 return svg; 123 } 124 125 void append_svg_glyph (StringBuilder svg, string svg_data, GlyphCollection glyphs) { 126 Gee.ArrayList<Tag> layer_content; 127 Gee.ArrayList<Tag> svg_tags; 128 Gee.ArrayList<Tag> meta; 129 XmlParser xml; 130 Tag svg_root_tag; 131 Font font; 132 133 font = OpenFontFormatWriter.get_current_font (); 134 135 layer_content = new Gee.ArrayList<Tag> (); 136 svg_tags = new Gee.ArrayList<Tag> (); 137 meta = new Gee.ArrayList<Tag> (); 138 xml = new XmlParser (svg_data); 139 140 if (!xml.validate ()) { 141 warning("Invalid SVG data in TTF table."); 142 return; 143 } 144 145 svg_root_tag = xml.get_root_tag (); 146 147 foreach (Tag tag in svg_root_tag) { 148 string name = tag.get_name(); 149 150 if (name == "defs") { 151 svg_tags.add (tag); 152 } else if (name == "style") { 153 svg_tags.add (tag); 154 } else if (name == "metadata") { 155 meta.add (tag); 156 } else if (name == "sodipodi") { 157 meta.add (tag); 158 } else { 159 layer_content.add (tag); 160 } 161 } 162 163 svg.append ("<"); 164 svg.append (svg_root_tag.get_name ()); 165 svg.append (" "); 166 append_tag_attributes (svg, svg_root_tag); 167 svg.append (">"); 168 169 foreach (Tag tag in svg_tags) { 170 append_tag (svg, tag); 171 } 172 173 foreach (Tag tag in layer_content) { 174 append_tag (svg, tag); 175 } 176 177 svg.append ("</"); 178 svg.append (svg_root_tag.get_name ()); 179 svg.append (">\n"); 180 } 181 182 public void append_tag (StringBuilder svg, Tag tag) { 183 string content = tag.get_content (); 184 185 svg.append ("<"); 186 svg.append (tag.get_name ()); 187 188 svg.append (" "); 189 append_tag_attributes (svg, tag); 190 191 if (content == "") { 192 svg.append (" /"); 193 } 194 195 svg.append (">"); 196 197 if (content != "") { 198 svg.append (content); 199 svg.append ("</"); 200 svg.append (tag.get_name ()); 201 svg.append (">"); 202 } 203 } 204 205 public void append_tag_attributes (StringBuilder svg, Tag tag) { 206 bool first = true; 207 208 foreach (Attribute attribute in tag.get_attributes ()) { 209 string ns = attribute.get_namespace (); 210 211 if (!first) { 212 svg.append (" "); 213 } 214 215 if (ns != "") { 216 svg.append (ns); 217 svg.append (":"); 218 } 219 220 svg.append (attribute.get_name ()); 221 svg.append ("="); 222 svg.append ("\""); 223 svg.append (attribute.get_content ()); 224 svg.append ("\""); 225 226 first = false; 227 } 228 } 229 230 public void process_svg_data () throws GLib.Error { 231 FontData fd = new FontData (); 232 233 int32 svg_index_offset = 10; 234 235 fd.add_ushort (0); // version 236 fd.add_ulong (svg_index_offset); 237 fd.add_ulong (0); // reserved 238 239 uint32 document_offset = 2 + 12 * entries.size; 240 241 // SVG Documents Index 242 fd.add_ushort ((uint16) entries.size); 243 244 foreach (SvgTableEntry entry in entries) { 245 fd.add_ushort (entry.glyph_id); // start 246 fd.add_ushort (entry.glyph_id); // end 247 fd.add_ulong (document_offset); // offset 248 fd.add_ulong (entry.data.length_with_padding ()); // length 249 document_offset += entry.data.length_with_padding (); 250 } 251 252 foreach (SvgTableEntry entry in entries) { 253 fd.append (entry.data); 254 } 255 256 fd.pad (); 257 258 this.font_data = fd; 259 } 260 261 public EmbeddedSvg generate_svg (PathList paths, GlyphCollection glyphs) { 262 StringBuilder svg = new StringBuilder (); 263 264 svg.append ("<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n"); 265 svg.append ("""<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">"""); 266 267 Glyph g = glyphs.get_current (); 268 Font font = OpenFontFormatWriter.get_current_font (); 269 svg.append (@"<g transform=\"translate(0, $(font.get_height ()))\">\n"); 270 svg.append (@"<g transform=\"translate($(-g.left_limit), $(-font.base_line))\">\n"); 271 272 svg.append ("""<path style="fill:#000000;fill-opacity:1;" """); 273 svg.append ("d=\""); 274 275 foreach (Path p in paths.paths) { 276 p.add_hidden_double_points (); 277 278 279 int i = 0; 280 EditPoint? n = null; 281 EditPoint m; 282 283 if (p.points.size < 2) { 284 return new EmbeddedSvg (new SvgDrawing ()); 285 } 286 287 p.create_list (); 288 289 foreach (var e in p.points) { 290 if (i == 0) { 291 add_abs_start (e, svg); 292 i++; 293 n = e; 294 continue; 295 } 296 297 m = (!) n; 298 299 add_abs_next (m, e, svg); 300 301 n = e; 302 i++; 303 } 304 305 if (!p.is_open ()) { 306 m = p.get_first_point (); 307 add_abs_next ((!) n, m, svg); 308 close_path (svg); 309 } 310 } 311 svg.append ("\" />\n"); 312 313 svg.append ("</g>\n"); 314 svg.append ("</g>\n"); 315 svg.append ("</svg>\n"); 316 317 EmbeddedSvg embedded = new EmbeddedSvg (new SvgDrawing ()); 318 embedded.x = g.left_limit; 319 embedded.y = font.get_height (); 320 embedded.svg_data = svg.str; 321 322 return embedded; 323 } 324 325 public static void add_abs_next (EditPoint start, EditPoint end, StringBuilder svg) { 326 if (start.right_handle.type == PointType.LINE_QUADRATIC) { 327 add_abs_line_to (start, end, svg); 328 } else if (start.right_handle.type == PointType.LINE_CUBIC && end.left_handle.type == PointType.LINE_CUBIC) { 329 add_abs_line_to (start, end, svg); 330 } else if (end.left_handle.type == PointType.QUADRATIC || start.right_handle.type == PointType.QUADRATIC) { 331 add_quadratic_abs_path (start, end, svg); 332 } else if (end.left_handle.type == PointType.DOUBLE_CURVE || start.right_handle.type == PointType.DOUBLE_CURVE) { 333 add_double_quadratic_abs_path (start, end, svg); 334 } else { 335 add_cubic_abs_path (start, end, svg); 336 } 337 } 338 339 private static void add_abs_start (EditPoint ep, StringBuilder svg) { 340 svg.append_printf ("M"); 341 svg.append_printf ("%s ", round (ep.x)); 342 svg.append_printf ("%s ", round (-ep.y)); 343 } 344 345 public static void close_path (StringBuilder svg) { 346 svg.append ("z"); 347 } 348 349 private static void add_abs_line_to (EditPoint start, EditPoint stop, StringBuilder svg) { 350 svg.append ("L"); 351 svg.append_printf ("%s ", round (stop.x)); 352 svg.append_printf ("%s ", round (-stop.y)); 353 } 354 355 private static void add_double_quadratic_abs_path (EditPoint start, EditPoint end, StringBuilder svg) { 356 EditPoint middle; 357 double x, y; 358 359 x = start.get_right_handle ().x + (end.get_left_handle ().x - start.get_right_handle ().x) / 2; 360 y = start.get_right_handle ().y + (end.get_left_handle ().y - start.get_right_handle ().y) / 2; 361 362 middle = new EditPoint (x, y, PointType.QUADRATIC); 363 middle.right_handle = end.get_left_handle ().copy (); 364 365 add_quadratic_abs_path (start, middle, svg); 366 add_quadratic_abs_path (middle, end, svg); 367 } 368 369 private static void add_quadratic_abs_path (EditPoint start, EditPoint stop, StringBuilder svg) { 370 // cubic path 371 svg.append_printf ("Q"); 372 373 svg.append_printf ("%s ", round (start.get_right_handle ().x)); 374 svg.append_printf ("%s ", round (-start.get_right_handle ().y)); 375 376 svg.append_printf ("%s ", round (stop.x)); 377 svg.append_printf ("%s ", round (-stop.y)); 378 } 379 380 private static void add_cubic_abs_path (EditPoint start, EditPoint stop, StringBuilder svg) { 381 // cubic path 382 svg.append_printf ("C"); 383 384 svg.append_printf ("%s ", round (start.get_right_handle ().x)); 385 svg.append_printf ("%s ", round (-start.get_right_handle ().y)); 386 387 svg.append_printf ("%s ", round (stop.get_left_handle ().x)); 388 svg.append_printf ("%s ", round (-stop.get_left_handle ().y)); 389 390 svg.append_printf ("%s ", round (stop.x)); 391 svg.append_printf ("%s ", round (-stop.y)); 392 } 393 394 } 395 396 } 397