inkmake 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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: []