inkmake 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (4) hide show
  1. checksums.yaml +7 -0
  2. data/bin/inkmake +3 -0
  3. data/lib/inkmake.rb +729 -0
  4. metadata +47 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 6e87e04d632d6a75ebce65acc01aa293b578f9d5
4
+ data.tar.gz: f72bbe50c0df69548ae1782fe445759926f444b4
5
+ SHA512:
6
+ metadata.gz: 03b1fbe3d36f59e06b93a28d1eb23f086d78345ceaaa95c0984f200c7b3469752f01a8984c7bcb6391cc6957c9c509a22d42212fb49268b83915570e93d5f522
7
+ data.tar.gz: a1dbb31c526e51f0561fdd2fda974f75788d2e05771801caa2032adce2d70f14a9a57d1e2d85ac9d1da6804fdbc6f58d801fd5acdecfcd2145be778b89e90c8d
data/bin/inkmake ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env ruby
2
+ require "inkmake"
3
+ Inkmake.run(ARGV)
data/lib/inkmake.rb ADDED
@@ -0,0 +1,729 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # inkmake - Makefile inspired export from SVG files using Inkscape as backend
4
+ # with some added smartness.
5
+ #
6
+ # Copyright (c) 2015 <mattias.wadman@gmail.com>
7
+ #
8
+ # MIT License:
9
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
10
+ # of this software and associated documentation files (the "Software"), to deal
11
+ # in the Software without restriction, including without limitation the rights
12
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13
+ # copies of the Software, and to permit persons to whom the Software is
14
+ # furnished to do so, subject to the following conditions:
15
+ #
16
+ # The above copyright notice and this permission notice shall be included in
17
+ # all copies or substantial portions of the Software.
18
+ #
19
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25
+ # THE SOFTWARE.
26
+ #
27
+ # Try to stay campatible with Ruby 1.8.7 as its the default ruby
28
+ # version included in Mac OS X (at least Lion).
29
+ #
30
+ # NOTE: Rotation is done using a temporary SVG file that translate and rotate
31
+ # a double resolution bitmap and export as a bitmap to the correct resolution.
32
+ # This hack is done to get around that Inkscape cant set bitmap oversampling
33
+ # mode per file or from command line, default is 2x2 oversampling.
34
+ #
35
+
36
+ require "csv"
37
+ require "rexml/document"
38
+ require "rexml/xpath"
39
+ require "open3"
40
+ require "optparse"
41
+ require "fileutils"
42
+ require "tempfile"
43
+ require "uri"
44
+ require "pathname"
45
+
46
+ class Inkmake
47
+ @verbose = false
48
+ @inkscape_path = nil
49
+ class << self
50
+ attr :verbose, :inkscape_path
51
+ end
52
+
53
+ class InkscapeUnit
54
+ # 90dpi as reference
55
+ Units = {
56
+ "pt" => 1.25,
57
+ "pc" => 15,
58
+ "mm" => 3.543307,
59
+ "cm" => 35.43307,
60
+ "dm" => 354.3307,
61
+ "m" => 3543.307,
62
+ "in" => 90,
63
+ "ft" => 1080,
64
+ "uu" => 1 # user unit, 90 dpi
65
+ }
66
+
67
+ attr_reader :value, :unit
68
+
69
+ def initialize(value, unit="uu")
70
+ case value
71
+ when /^(\d+(?:\.\d+)?)(\w+)?$/ then
72
+ @value = $1.to_f
73
+ @unit = $2
74
+ @unit ||= unit
75
+ @unit = (@unit == "px" or Units.has_key?(@unit)) ? @unit : "uu"
76
+ else
77
+ @value = value.kind_of?(String) ? value.to_f: value
78
+ @unit = unit
79
+ end
80
+ end
81
+
82
+ def to_pixels(dpi=90.0)
83
+ return @value.round if @unit == "px"
84
+ ((dpi / 90.0) * Units[@unit] * @value).round
85
+ end
86
+
87
+ def to_s
88
+ "%g#{@unit}" % @value
89
+ end
90
+
91
+ def scale(f)
92
+ return self if @unit == "px"
93
+ InkscapeUnit.new(@value * f, @unit)
94
+ end
95
+ end
96
+
97
+ class InkscapeResolution
98
+ attr_reader :width, :height
99
+
100
+ def initialize(width, height, unit="uu")
101
+ @width = width.kind_of?(InkscapeUnit) ? width : InkscapeUnit.new(width, unit)
102
+ @height = height.kind_of?(InkscapeUnit) ? height : InkscapeUnit.new(height, unit)
103
+ end
104
+
105
+ def scale(f)
106
+ InkscapeResolution.new(@width.scale(f), @height.scale(f))
107
+ end
108
+
109
+ def to_s
110
+ "#{@width.to_s}x#{@height.to_s}"
111
+ end
112
+ end
113
+
114
+ class InkscapeRemote
115
+ def initialize
116
+ open
117
+ probe_decimal_symbol
118
+ yield self
119
+ ensure
120
+ quit
121
+ end
122
+
123
+ def open
124
+ @in, @out, @err = Open3.popen3(*[self.class.path, "--shell"])
125
+ loop do
126
+ case response
127
+ when :prompt then break
128
+ end
129
+ end
130
+ end
131
+
132
+ def command(args)
133
+ c = args.collect do |key, value|
134
+ if value
135
+ "\"#{key}=#{self.class.escape value.to_s}\""
136
+ else
137
+ key
138
+ end
139
+ end.join(" ")
140
+ puts "> #{c}" if Inkmake.verbose
141
+ @in.write "#{c}\n"
142
+ @in.flush
143
+ end
144
+
145
+ def response
146
+ o = @out.read(1)
147
+ if o == ">"
148
+ puts "< #{o}" if Inkmake.verbose
149
+ return :prompt;
150
+ end
151
+ o = o + @out.readline
152
+ puts "< #{o}" if Inkmake.verbose
153
+ o
154
+ end
155
+
156
+ # this is weird but is the least weird and most protable way i could come up with
157
+ # to figuring out what decimal symbol to use.
158
+ # forcing LC_NUMERIC=C seems hard to do in a portable way
159
+ # trying to use env inkmake is running in is also not so portable (windows?)
160
+ def probe_decimal_symbol
161
+ svg =
162
+ "<?xml version=\"1.0\"?>" +
163
+ "<svg xmlns:xlink=\"http://www.w3.org/1999/xlink\" width=\"1\" height=\"1\">" +
164
+ "</svg>"
165
+ f = Tempfile.new("inkmake")
166
+ f.write(svg)
167
+ f.flush
168
+ begin
169
+ command({
170
+ "--file" => f.path,
171
+ "--export-png" => Tempfile.new("inkmake").path,
172
+ "--export-area" => "0.0:0.0:1.0:1.0",
173
+ })
174
+ response
175
+ @decimal_symbol = "."
176
+ rescue EOFError
177
+ @decimal_symbol = ","
178
+ # restart inkscape
179
+ open
180
+ end
181
+ end
182
+
183
+ def export(opts)
184
+ c = {
185
+ "--file" => opts[:svg_path],
186
+ "--export-#{opts[:format]}" => opts[:out_path]
187
+ }
188
+ if opts[:res]
189
+ s = opts[:rotate_scale_hack] ? 2 : 1
190
+ c["--export-width"] = opts[:res].width.to_pixels(opts[:dpi] || 90) * s
191
+ c["--export-height"] = opts[:res].height.to_pixels(opts[:dpi] || 90) * s
192
+ end
193
+ if opts[:dpi]
194
+ c["--export-dpi"] = opts[:dpi]
195
+ end
196
+ if opts[:area].kind_of? Array
197
+ c["--export-area"] = ("%f:%f:%f:%f" % opts[:area]).gsub(".", @decimal_symbol)
198
+ elsif opts[:area] == :drawing
199
+ c["--export-area-drawing"] = nil
200
+ elsif opts[:area].kind_of? String
201
+ c["--export-id"] = opts[:area]
202
+ end
203
+ command(c)
204
+ width, height = [0, 0]
205
+ out = nil
206
+ loop do
207
+ case response
208
+ when /^Bitmap saved as: (.*)$/ then
209
+ out = $1
210
+ when /^Area .* exported to (\d+) x (\d+) pixels.*$/ then
211
+ width = $1
212
+ height = $2
213
+ when :prompt then break
214
+ end
215
+ end
216
+
217
+ [width, height]
218
+ end
219
+
220
+ def query_all(file)
221
+ ids = []
222
+ command({
223
+ "--file" => file,
224
+ "--query-all" => nil,
225
+ })
226
+ loop do
227
+ case response
228
+ when /^(.*),(.*),(.*),(.*),(.*)$/ then ids << [$1, $2.to_f, $3.to_f, $4.to_f, $5.to_f]
229
+ when :prompt then break
230
+ end
231
+ end
232
+ ids
233
+ end
234
+
235
+ def ids(file)
236
+ Hash[query_all(file).map {|l| [l[0], l[1..-1]]}]
237
+ end
238
+
239
+ def drawing_area(file)
240
+ query_all(file).first[1..-1]
241
+ end
242
+
243
+ def quit
244
+ command({"quit" => nil})
245
+ @out.read
246
+ nil
247
+ end
248
+
249
+ def self.escape(s)
250
+ s.gsub(/(["'])/, '\\\\\1')
251
+ end
252
+
253
+ def self.path
254
+ return Inkmake.inkscape_path if Inkmake.inkscape_path
255
+
256
+ # try to figure out inkscape path
257
+ p = (
258
+ (["/Applications/Inkscape.app/Contents/Resources/bin/inkscape",
259
+ 'c:\Program Files\Inkscape\inkscape.exe',
260
+ 'c:\Program Files (x86)\Inkscape\inkscape.exe'] +
261
+ (ENV['PATH'].split(':').map {|p| File.join(p, "inkscape")}))
262
+ .select do |path|
263
+ File.exists? path
264
+ end)
265
+ .first
266
+ if p
267
+ p
268
+ else
269
+ begin
270
+ require "osx/cocoa"
271
+ "#{OSX::NSWorkspace.sharedWorkspace.fullPathForApplication:"Inkscape"}/Contents/Resources/bin/inkscape"
272
+ rescue NameError, LoadError
273
+ nil
274
+ end
275
+ end
276
+ end
277
+ end
278
+
279
+ class InkFile
280
+ attr_reader :svg_path, :out_path
281
+ DefaultVariants = {
282
+ "@2x" => {:scale => 2.0}
283
+ }
284
+ Rotations = {
285
+ "right" => 90,
286
+ "left" => -90,
287
+ "upsidedown" => 180
288
+ }
289
+ # 123x123, 12.3cm*12.3cm
290
+ RES_RE = /^(\d+(?:\.\d+)?(?:px|pt|pc|mm|cm|dm|m|in|ft|uu)?)[x*](\d+(?:\.\d+)?(?:px|pt|pc|mm|cm|dm|m|in|ft|uu)?)$/
291
+ # *123, *1.23
292
+ SCALE_RE = /^\*(\d+(?:\.\d+)?)$/
293
+ # 180dpi
294
+ DPI_RE = /^(\d+(?:\.\d+)?)dpi$/i
295
+ # (prefix)[(...)](suffix)
296
+ DEST_RE = /^([^\[]*)(?:\[(.*)\])?(.*)$/
297
+ # test.svg, test.SVG
298
+ SVG_RE = /\.svg$/i
299
+ # ext to format, supported inkscape output formats
300
+ EXT_RE = /\.(png|pdf|ps|eps)$/i
301
+ # supported inkscape output formats
302
+ FORMAT_RE = /^(png|pdf|ps|eps)$/i
303
+ # @name
304
+ AREA_NAME_RE = /^@(.*)$/
305
+ # @x:y:w:h
306
+ AREA_SPEC_RE = /^@(\d+(?:\.\d+)?):(\d+(?:\.\d+)?):(\d+(?:\.\d+)?):(\d+(?:\.\d+)?)$/
307
+ # right, left, upsidedown
308
+ ROTATE_RE = /^(right|left|upsidedown)$/
309
+ # show/hide layer or id, "+Layer 1", +#id, -*
310
+ SHOWHIDE_RE = /^([+-])(.+)$/
311
+
312
+ class SyntaxError < StandardError
313
+ end
314
+
315
+ class ProcessError < StandardError
316
+ end
317
+
318
+ def initialize(file, opts)
319
+ @file = file
320
+ @images = []
321
+ @force = opts[:force]
322
+
323
+ svg_path = nil
324
+ out_path = nil
325
+ File.read(file).lines.each_with_index do |line, index|
326
+ line.strip!
327
+ next if line.empty? or line.start_with? "#"
328
+ begin
329
+ case line
330
+ when /^svg:(.*)/i then svg_path = File.expand_path($1.strip, File.dirname(file))
331
+ when /^out:(.*)/i then out_path = File.expand_path($1.strip, File.dirname(file))
332
+ else
333
+ @images << InkImage.new(self, parse_line(line))
334
+ end
335
+ rescue SyntaxError => e
336
+ puts "#{file}:#{index+1}: #{e.message}"
337
+ exit
338
+ end
339
+ end
340
+
341
+ # order is: argument, config in inkfile, inkfile directory
342
+ @svg_path = opts[:svg_path] || svg_path || File.dirname(file)
343
+ @out_path = opts[:out_path] || out_path || File.dirname(file)
344
+ end
345
+
346
+ def parse_split_line(line)
347
+ # changed CSV API in ruby 1.9
348
+ if RUBY_VERSION.start_with? "1.8"
349
+ CSV::parse_line(line, fs = " ")
350
+ else
351
+ CSV::parse_line(line, {:col_sep => " "})
352
+ end
353
+ end
354
+
355
+ def parse_line(line)
356
+ cols = nil
357
+ begin
358
+ cols = parse_split_line(line)
359
+ rescue CSV::MalformedCSVError => e
360
+ raise SyntaxError, e.message
361
+ end
362
+ raise SyntaxError, "Invalid number of columns" if cols.count < 1
363
+
364
+ if not DEST_RE.match(cols[0])
365
+ raise SyntaxError, "Invalid destination format \"#{cols[0]}\""
366
+ end
367
+
368
+ opts = {}
369
+ opts[:prefix] = $1
370
+ variants = $2
371
+ opts[:suffix] = $3
372
+ opts[:format] = $1.downcase if EXT_RE.match(opts[:prefix] + opts[:suffix])
373
+
374
+ cols[1..-1].each do |col|
375
+ case col
376
+ when RES_RE then opts[:res] = InkscapeResolution.new($1, $2, "px")
377
+ when SVG_RE then opts[:svg] = col
378
+ when AREA_SPEC_RE then opts[:area] = [$1.to_f, $2.to_f, $3.to_f, $4.to_f]
379
+ when AREA_NAME_RE then opts[:area] = $1
380
+ when /^drawing$/ then opts[:area] = :drawing
381
+ when FORMAT_RE then opts[:format] = $1.downcase
382
+ when ROTATE_RE then opts[:rotate] = Rotations[$1]
383
+ when SCALE_RE then opts[:scale] = $1.to_f
384
+ when DPI_RE then opts[:dpi] = $1.to_f
385
+ when SHOWHIDE_RE
386
+ op = $1 == "+" ? :show : :hide
387
+ if $2.start_with? "#"
388
+ type = :id
389
+ name= $2[1..-1]
390
+ else
391
+ type = :layer
392
+ name = $2 == "*" ? :all : $2
393
+ end
394
+ (opts[:showhide] ||= []).push({:op => op, :type => type, :name => name})
395
+ else
396
+ raise SyntaxError, "Unknown column \"#{col}\""
397
+ end
398
+ end
399
+
400
+ if not opts[:format]
401
+ raise SyntaxError, "Unknown or no output format could be determined"
402
+ end
403
+
404
+ variants = (variants.split("|") if variants) || []
405
+ opts[:variants] = variants.collect do |variant|
406
+ name, options = variant.split("=", 2)
407
+ if options
408
+ options = Hash[
409
+ options.split(",").map do |option|
410
+ case option
411
+ when ROTATE_RE then [:rotate, Rotations[$1]]
412
+ when RES_RE then [:res, InkscapeResolution.new($1, $2, "px")]
413
+ when SCALE_RE then [:scale, $1.to_f]
414
+ when DPI_RE then [:dpi, $1.to_f]
415
+ else
416
+ raise SyntaxError, "Invalid variant option \"#{option}\""
417
+ end
418
+ end
419
+ ]
420
+ else
421
+ options = DefaultVariants[name]
422
+ raise SyntaxError, "Invalid default variant \"#{name}\"" if not options
423
+ end
424
+
425
+ [name, options]
426
+ end
427
+
428
+ opts
429
+ end
430
+
431
+ def variants_to_generate
432
+ l = []
433
+ @images.each do |image|
434
+ image.variants.each do |variant|
435
+ next if not @force and
436
+ File.exists? variant.out_path and
437
+ File.mtime(variant.out_path) > File.mtime(image.svg_path) and
438
+ File.mtime(variant.out_path) > File.mtime(@file)
439
+ if variant.out_path == image.svg_path
440
+ raise ProcessError, "Avoiding overwriting source SVG file #{image.svg_path}"
441
+ end
442
+
443
+ l << variant
444
+ end
445
+ end
446
+
447
+ l
448
+ end
449
+
450
+ def temp_rotate_svg(path, degrees, width, height)
451
+ if degrees != 180
452
+ out_width, out_height = height, width
453
+ else
454
+ out_width, out_height = width, height
455
+ end
456
+ svg =
457
+ "<?xml version=\"1.0\"?>" +
458
+ "<svg xmlns:xlink=\"http://www.w3.org/1999/xlink\" width=\"#{out_width}\" height=\"#{out_height}\">" +
459
+ "<g>" +
460
+ "<image transform=\"translate(#{out_width/2} #{out_height/2}) rotate(#{degrees})\"" +
461
+ " width=\"#{width}\" height=\"#{height}\" x=\"#{-width/2}\" y=\"#{-height/2}\"" +
462
+ " xlink:href=\"file:///#{URI.escape(path)}\" />" +
463
+ "</g>" +
464
+ "</svg>"
465
+ f = Tempfile.new("inkmake")
466
+ f.write(svg)
467
+ f.flush
468
+ f.seek(0)
469
+ [f, out_width, out_height]
470
+ end
471
+
472
+ def process
473
+ variants = variants_to_generate
474
+ if variants.empty?
475
+ return false
476
+ end
477
+
478
+ idfilemap = {}
479
+ InkscapeRemote.new do |inkscape|
480
+ variants.each do |variant|
481
+ if not File.exists? variant.image.svg_path
482
+ raise ProcessError, "Source SVG file #{variant.image.svg_path} does not exist"
483
+ end
484
+
485
+ out_res = nil
486
+ # order: 200x200, @id/area, svg res
487
+ if variant.image.res
488
+ out_res = variant.image.res
489
+ elsif variant.image.area == :drawing
490
+ res = inkscape.drawing_area(variant.image.svg_path)
491
+ out_res = InkscapeResolution.new(res[2], res[3], "uu")
492
+ elsif variant.image.area
493
+ if variant.image.area.kind_of? String
494
+ if not idfilemap.has_key? variant.image.svg_path
495
+ idfilemap[variant.image.svg_path] = inkscape.ids(variant.image.svg_path)
496
+ end
497
+
498
+ if not idfilemap[variant.image.svg_path].has_key? variant.image.area
499
+ raise ProcessError, "Unknown id \"#{variant.image.area}\" in file #{variant.image.svg_path} when exporting #{variant.out_path}"
500
+ end
501
+
502
+ res = idfilemap[variant.image.svg_path][variant.image.area]
503
+ out_res = InkscapeResolution.new(res[2], res[3], "uu")
504
+ else
505
+ a = variant.image.area
506
+ # x0:y0:x1:y1
507
+ out_res = InkscapeResolution.new(a[2]-a[0], a[3]-a[1], "uu")
508
+ end
509
+ else
510
+ out_res = variant.image.svg_res
511
+ end
512
+
513
+ scale = variant.options[:scale]
514
+ if scale
515
+ out_res = out_res.scale(scale)
516
+ end
517
+
518
+ out_res = variant.options[:res] if variant.options[:res]
519
+
520
+ rotate = (variant.image.format == "png" and variant.options[:rotate])
521
+
522
+ FileUtils.mkdir_p File.dirname(variant.out_path)
523
+
524
+ svg_path = variant.image.svg_path
525
+ if variant.image.showhide
526
+ svg_path = variant.image.svg_showhide_file.path
527
+ end
528
+
529
+ res = inkscape.export({
530
+ :svg_path => svg_path,
531
+ :out_path => variant.out_path,
532
+ :res => out_res,
533
+ :dpi => variant.options[:dpi],
534
+ :format => variant.image.format,
535
+ :area => variant.image.area,
536
+ :rotate_scale_hack => rotate
537
+ })
538
+
539
+ if rotate
540
+ tmp, width, height = temp_rotate_svg(variant.out_path, rotate, res[0].to_i, res[1].to_i)
541
+ res = inkscape.export({
542
+ :svg_path => tmp.path,
543
+ :out_path => variant.out_path,
544
+ :res => InkscapeResolution.new(width / 2, height / 2, "px"),
545
+ :format => variant.image.format
546
+ })
547
+ tmp.close!
548
+ end
549
+
550
+ rel_path = Pathname.new(variant.out_path).relative_path_from(Pathname.new(Dir.pwd))
551
+ if variant.image.format == "png"
552
+ puts "#{rel_path} #{res[0]}x#{res[1]}"
553
+ else
554
+ puts rel_path
555
+ end
556
+ end
557
+ end
558
+
559
+ return true
560
+ end
561
+ end
562
+
563
+ class InkImage
564
+ attr_reader :inkfile, :prefix, :variants, :suffix, :res, :format, :area, :showhide
565
+
566
+ def initialize(inkfile, opts)
567
+ @inkfile = inkfile
568
+ @prefix = opts[:prefix]
569
+ variant_opts = {
570
+ :rotate => opts[:rotate],
571
+ :scale => opts[:scale],
572
+ :dpi => opts[:dpi]
573
+ }
574
+ @variants = [InkVariant.new(self, "", variant_opts)]
575
+ opts[:variants].each do |name, options|
576
+ @variants << InkVariant.new(self, name, options)
577
+ end
578
+ @suffix = opts[:suffix]
579
+ @res = opts[:res]
580
+ @svg = opts[:svg]
581
+ @format = opts[:format]
582
+ @area = opts[:area]
583
+ @showhide = opts[:showhide]
584
+ end
585
+
586
+ def svg_path
587
+ File.expand_path(@svg || File.basename(@prefix + @suffix, ".*") + ".svg", inkfile.svg_path)
588
+ end
589
+
590
+ def svg_res
591
+ @svg_res ||=
592
+ begin
593
+ doc = REXML::Document.new File.read(svg_path)
594
+ svgattr = doc.elements.to_a("//svg")[0].attributes
595
+ if svgattr["width"] and svgattr["height"]
596
+ InkscapeResolution.new(svgattr["width"], svgattr["height"], "uu")
597
+ else
598
+ nil
599
+ end
600
+ end
601
+ end
602
+
603
+ def svg_showhide_file
604
+ @svg_showhide_file ||=
605
+ begin
606
+ doc = REXML::Document.new File.read(svg_path)
607
+
608
+ layers = {}
609
+ REXML::XPath.each(doc, "//svg:g[@inkscape:groupmode='layer']").each do |e|
610
+ label = e.attributes["label"]
611
+ next if not label
612
+ layers[label] = e
613
+ end
614
+
615
+ ids = {}
616
+ REXML::XPath.each(doc, "//svg:*[@id]").each do |e|
617
+ id = e.attributes["id"]
618
+ next if not id
619
+ ids[id] = e
620
+ end
621
+
622
+ @showhide.each do |sh|
623
+ elms = nil
624
+ if sh[:type] == :layer
625
+ if sh[:name] == :all
626
+ elms = layers.values
627
+ else
628
+ e = layers[sh[:name]]
629
+ if not e
630
+ raise InkFile::ProcessError, "Layer \"#{sh[:name]}\" not found in #{svg_path}"
631
+ end
632
+ elms = [e]
633
+ end
634
+ else
635
+ e = ids[sh[:name]]
636
+ if not e
637
+ raise InkFile::ProcessError, "Id \"#{sh[:name]}\" not found in #{svg_path}"
638
+ end
639
+ elms = [e]
640
+ end
641
+
642
+ elms.each do |e|
643
+ # NOTE: should be visibility for #ids to not affect flow etc?
644
+ e.delete_attribute("display")
645
+ # also remove display inside style attributes
646
+ if e.attributes["style"]
647
+ style_declarations = e.attributes["style"].split(";")
648
+ style_declarations_to_keep = []
649
+ style_declarations.each do | sd |
650
+ property, value = sd.split(":", 2)
651
+ if value && property == "display"
652
+ # throw it out
653
+ else
654
+ style_declarations_to_keep.push(sd)
655
+ end
656
+ end
657
+ e.attributes["style"] = style_declarations_to_keep.join(";")
658
+ end
659
+ if sh[:op] == :hide
660
+ e.add_attribute("display", "none")
661
+ else
662
+ # show is a nop
663
+ end
664
+ end
665
+ end
666
+
667
+ f = Tempfile.new("inkmake")
668
+ doc.write(:output => f)
669
+ f.flush
670
+ f
671
+ end
672
+ end
673
+ end
674
+
675
+ class InkVariant
676
+ attr_reader :image, :name, :options
677
+
678
+ def initialize(image, name, options)
679
+ @image = image
680
+ @name = name
681
+ @options = options
682
+ end
683
+
684
+ def out_path
685
+ File.expand_path(
686
+ "#{@image.prefix}#{@name}#{@image.suffix}",
687
+ @image.inkfile.out_path)
688
+ end
689
+ end
690
+
691
+ def self.run(argv)
692
+ inkfile_path = nil
693
+ inkfile_opts = {}
694
+ OptionParser.new do |o|
695
+ o.banner = "Usage: #{$0} [options] [Inkfile]"
696
+ o.on("-v", "--verbose", "Verbose output") { @verbose = true }
697
+ o.on("-s", "--svg PATH", "SVG source base path") { |v| inkfile_opts[:svg_path] = v }
698
+ o.on("-o", "--out PATH", "Output base path") { |v| inkfile_opts[:out_path] = v }
699
+ o.on("-f", "--force", "Force regenerate (skip time check)") { |v| inkfile_opts[:force] = true }
700
+ o.on("-i", "--inkscape PATH", "Inkscape binary path", "Default: #{InkscapeRemote.path || "not found"}") { |v| @inkscape_path = v }
701
+ o.on("-h", "--help", "Display help") { puts o; exit }
702
+ begin
703
+ inkfile_path = o.parse!(argv).first
704
+ rescue OptionParser::InvalidOption => e
705
+ puts e.message
706
+ exit 1
707
+ end
708
+ end
709
+
710
+ inkfile_path = File.expand_path(inkfile_path || "Inkfile", Dir.pwd)
711
+
712
+ begin
713
+ raise "Could not find Inkscape binary (maybe try --inkscape?)" if not InkscapeRemote.path
714
+ raise "Inkscape binary #{InkscapeRemote.path} does not exist or is not executable" if not InkscapeRemote.path or not File.executable? InkscapeRemote.path
715
+ rescue StandardError => e
716
+ puts e.message
717
+ exit 1
718
+ end
719
+
720
+ begin
721
+ if not InkFile.new(inkfile_path, inkfile_opts).process
722
+ puts "Everything seems to be up to date"
723
+ end
724
+ rescue InkFile::ProcessError, SystemCallError => e
725
+ puts e.message
726
+ exit 1
727
+ end
728
+ end
729
+ end
metadata ADDED
@@ -0,0 +1,47 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: inkmake
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Mattias Wadman
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-05-01 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description:
14
+ email: mattias.wadman@gmail.com
15
+ executables:
16
+ - inkmake
17
+ extensions: []
18
+ extra_rdoc_files: []
19
+ files:
20
+ - bin/inkmake
21
+ - lib/inkmake.rb
22
+ homepage: https://github.com/wader/inkmake
23
+ licenses:
24
+ - MIT
25
+ metadata: {}
26
+ post_install_message:
27
+ rdoc_options: []
28
+ require_paths:
29
+ - lib
30
+ required_ruby_version: !ruby/object:Gem::Requirement
31
+ requirements:
32
+ - - ">="
33
+ - !ruby/object:Gem::Version
34
+ version: '0'
35
+ required_rubygems_version: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: '0'
40
+ requirements: []
41
+ rubyforge_project:
42
+ rubygems_version: 2.2.2
43
+ signing_key:
44
+ specification_version: 4
45
+ summary: Makefile inspired export from SVG files using Inkscape as backend with some
46
+ added smartness
47
+ test_files: []