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, 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, EmbeddedSvg embedded, 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 string svg_data; 133 134 svg_data = embedded.get_transformed_svg_data (); 135 font = OpenFontFormatWriter.get_current_font (); 136 137 layer_content = new Gee.ArrayList<Tag> (); 138 svg_tags = new Gee.ArrayList<Tag> (); 139 meta = new Gee.ArrayList<Tag> (); 140 xml = new XmlParser (svg_data); 141 142 if (!xml.validate ()) { 143 warning("Invalid SVG data in TTF table."); 144 return; 145 } 146 147 svg_root_tag = xml.get_root_tag (); 148 149 foreach (Tag tag in svg_root_tag) { 150 string name = tag.get_name(); 151 152 if (name == "defs") { 153 svg_tags.add (tag); 154 } else if (name == "style") { 155 svg_tags.add (tag); 156 } else if (name == "metadata") { 157 meta.add (tag); 158 } else if (name == "sodipodi") { 159 meta.add (tag); 160 } else { 161 layer_content.add (tag); 162 } 163 } 164 165 svg.append ("<"); 166 svg.append (svg_root_tag.get_name ()); 167 svg.append (" "); 168 append_tag_attributes (svg, svg_root_tag); 169 svg.append (">"); 170 171 foreach (Tag tag in svg_tags) { 172 append_tag (svg, tag); 173 } 174 175 foreach (Tag tag in layer_content) { 176 append_tag (svg, tag); 177 } 178 179 svg.append ("</"); 180 svg.append (svg_root_tag.get_name ()); 181 svg.append (">\n"); 182 } 183 184 public void append_tag (StringBuilder svg, Tag tag) { 185 string content = tag.get_content (); 186 187 svg.append ("<"); 188 svg.append (tag.get_name ()); 189 190 svg.append (" "); 191 append_tag_attributes (svg, tag); 192 193 if (content == "") { 194 svg.append (" /"); 195 } 196 197 svg.append (">"); 198 199 if (content != "") { 200 svg.append (content); 201 svg.append ("</"); 202 svg.append (tag.get_name ()); 203 svg.append (">"); 204 } 205 } 206 207 public void append_tag_attributes (StringBuilder svg, Tag tag) { 208 bool first = true; 209 210 foreach (Attribute attribute in tag.get_attributes ()) { 211 string ns = attribute.get_namespace (); 212 213 if (!first) { 214 svg.append (" "); 215 } 216 217 if (ns != "") { 218 svg.append (ns); 219 svg.append (":"); 220 } 221 222 svg.append (attribute.get_name ()); 223 svg.append ("="); 224 svg.append ("\""); 225 svg.append (attribute.get_content ()); 226 svg.append ("\""); 227 228 first = false; 229 } 230 } 231 232 public void process_svg_data () throws GLib.Error { 233 FontData fd = new FontData (); 234 235 int32 svg_index_offset = 10; 236 237 fd.add_ushort (0); // version 238 fd.add_ulong (svg_index_offset); 239 fd.add_ulong (0); // reserved 240 241 uint32 document_offset = 2 + 12 * entries.size; 242 243 // SVG Documents Index 244 fd.add_ushort ((uint16) entries.size); 245 246 foreach (SvgTableEntry entry in entries) { 247 fd.add_ushort (entry.glyph_id); // start 248 fd.add_ushort (entry.glyph_id); // end 249 fd.add_ulong (document_offset); // offset 250 fd.add_ulong (entry.data.length_with_padding ()); // length 251 document_offset += entry.data.length_with_padding (); 252 } 253 254 foreach (SvgTableEntry entry in entries) { 255 fd.append (entry.data); 256 } 257 258 fd.pad (); 259 260 this.font_data = fd; 261 } 262 263 public EmbeddedSvg generate_svg (PathList paths, GlyphCollection glyphs) { 264 StringBuilder svg = new StringBuilder (); 265 266 svg.append ("<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n"); 267 svg.append ("""<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">"""); 268 svg.append ("\n"); 269 270 Glyph g = glyphs.get_current (); 271 Font font = OpenFontFormatWriter.get_current_font (); 272 svg.append (@"<g transform=\"translate(0, $(font.get_height ()))\">\n"); 273 svg.append (@"<g transform=\"translate($(-g.left_limit), $(-font.base_line))\">\n"); 274 275 svg.append ("""<path style="fill:#000000;fill-opacity:1;" """); 276 svg.append ("d=\""); 277 278 foreach (Path p in paths.paths) { 279 p.add_hidden_double_points (); 280 281 int i = 0; 282 EditPoint? n = null; 283 EditPoint m; 284 285 if (p.points.size < 2) { 286 return new EmbeddedSvg (new SvgDrawing ()); 287 } 288 289 p.create_list (); 290 291 foreach (var e in p.points) { 292 if (i == 0) { 293 add_abs_start (e, svg); 294 i++; 295 n = e; 296 continue; 297 } 298 299 m = (!) n; 300 301 add_abs_next (m, e, svg); 302 303 n = e; 304 i++; 305 } 306 307 if (!p.is_open ()) { 308 m = p.get_first_point (); 309 add_abs_next ((!) n, m, svg); 310 close_path (svg); 311 } 312 } 313 svg.append ("\" />\n"); 314 315 svg.append ("</g>\n"); 316 svg.append ("</g>\n"); 317 svg.append ("</svg>\n"); 318 319 EmbeddedSvg embedded = new EmbeddedSvg (new SvgDrawing ()); 320 embedded.x = g.left_limit; 321 embedded.y = font.get_height (); 322 embedded.svg_data = svg.str; 323 324 return embedded; 325 } 326 327 public static void add_abs_next (EditPoint start, EditPoint end, StringBuilder svg) { 328 if (start.right_handle.type == PointType.LINE_QUADRATIC) { 329 add_abs_line_to (start, end, svg); 330 } else if (start.right_handle.type == PointType.LINE_CUBIC && end.left_handle.type == PointType.LINE_CUBIC) { 331 add_abs_line_to (start, end, svg); 332 } else if (end.left_handle.type == PointType.QUADRATIC || start.right_handle.type == PointType.QUADRATIC) { 333 add_quadratic_abs_path (start, end, svg); 334 } else if (end.left_handle.type == PointType.DOUBLE_CURVE || start.right_handle.type == PointType.DOUBLE_CURVE) { 335 add_double_quadratic_abs_path (start, end, svg); 336 } else { 337 add_cubic_abs_path (start, end, svg); 338 } 339 } 340 341 private static void add_abs_start (EditPoint ep, StringBuilder svg) { 342 svg.append_printf ("M"); 343 svg.append_printf ("%s ", round (ep.x)); 344 svg.append_printf ("%s ", round (-ep.y)); 345 } 346 347 public static void close_path (StringBuilder svg) { 348 svg.append ("z"); 349 } 350 351 private static void add_abs_line_to (EditPoint start, EditPoint stop, StringBuilder svg) { 352 svg.append ("L"); 353 svg.append_printf ("%s ", round (stop.x)); 354 svg.append_printf ("%s ", round (-stop.y)); 355 } 356 357 private static void add_double_quadratic_abs_path (EditPoint start, EditPoint end, StringBuilder svg) { 358 EditPoint middle; 359 double x, y; 360 361 x = start.get_right_handle ().x + (end.get_left_handle ().x - start.get_right_handle ().x) / 2; 362 y = start.get_right_handle ().y + (end.get_left_handle ().y - start.get_right_handle ().y) / 2; 363 364 middle = new EditPoint (x, y, PointType.QUADRATIC); 365 middle.right_handle = end.get_left_handle ().copy (); 366 367 add_quadratic_abs_path (start, middle, svg); 368 add_quadratic_abs_path (middle, end, svg); 369 } 370 371 private static void add_quadratic_abs_path (EditPoint start, EditPoint stop, StringBuilder svg) { 372 // cubic path 373 svg.append_printf ("Q"); 374 375 svg.append_printf ("%s ", round (start.get_right_handle ().x)); 376 svg.append_printf ("%s ", round (-start.get_right_handle ().y)); 377 378 svg.append_printf ("%s ", round (stop.x)); 379 svg.append_printf ("%s ", round (-stop.y)); 380 } 381 382 private static void add_cubic_abs_path (EditPoint start, EditPoint stop, StringBuilder svg) { 383 // cubic path 384 svg.append_printf ("C"); 385 386 svg.append_printf ("%s ", round (start.get_right_handle ().x)); 387 svg.append_printf ("%s ", round (-start.get_right_handle ().y)); 388 389 svg.append_printf ("%s ", round (stop.get_left_handle ().x)); 390 svg.append_printf ("%s ", round (-stop.get_left_handle ().y)); 391 392 svg.append_printf ("%s ", round (stop.x)); 393 svg.append_printf ("%s ", round (-stop.y)); 394 } 395 396 } 397 398 } 399