minicomic 0.0.1 → 0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (4) hide show
  1. data/README +117 -40
  2. data/Rakefile +2 -1
  3. data/lib/minicomic.rb +257 -103
  4. metadata +12 -4
data/README CHANGED
@@ -3,8 +3,29 @@
3
3
  +minicomic+ is a library providing a set of rake rules for building
4
4
  print and web-ready files for minicomics from a set of SVG files.
5
5
 
6
- Currently, it is hard-coded for generating 5.5x8.5in format black-and-white
7
- comics of the sort most suitable for inexpensive photocopying.
6
+ Originally designed for generating black-and-white folio minicomics of the sort
7
+ most suited for inexpensive photocopying, the most recent versions produce
8
+ color output by default. If you are generating classic black-and-white
9
+ minicomics, the options:
10
+
11
+ :color => :monochrome, :rasterize => false
12
+
13
+ are recommended for getting optimum quality and file sizes.
14
+
15
+ == getting minicomic
16
+
17
+ +minicomic+ is available from Rubyforge:
18
+
19
+ http://www.rubyforge.org/projects/minicomic
20
+
21
+ Or via RubyGems:
22
+
23
+ sudo gem install minicomic
24
+
25
+ Development versions are available via git from repo.or.cz:
26
+
27
+ git://repo.or.cz/minicomic.git
28
+ http://repo.or.cz/r/minicomic.git
8
29
 
9
30
  == requirements
10
31
 
@@ -19,7 +40,7 @@ shell's path:
19
40
  * pngcrush
20
41
 
21
42
  If you're on Ubuntu, these correspond to the +inkscape+, +psutils+,
22
- +gs-gpl+, +imagemagick+ and +pngcrush+ packages, respectively.
43
+ <tt>gs-gpl</tt>, +imagemagick+ and +pngcrush+ packages, respectively.
23
44
 
24
45
  == usage
25
46
 
@@ -29,26 +50,36 @@ The simplest way to use +minicomic+ is to create a +Rakefile+ as follows:
29
50
 
30
51
  minicomic '.'
31
52
 
32
- +minicomic+ will look for pages in a +pages/+ folder in the given directory
33
- (in this particular case, '.' meaning the directory where the +Rakefile+ lives).
53
+ +minicomic+ will look for pages in a <tt>pages/</tt> folder in the given
54
+ directory (in this particular case, '.' meaning the directory where the
55
+ +Rakefile+ lives). Options can be included after the directory name, for
56
+ instance:
34
57
 
35
- +minicomic+ looks for SVG files in +pages/+ named according to the following
36
- conventions:
58
+ minicomic '.', :color => false
37
59
 
38
- +page-NN.svg+:: page NN
39
- +front-cover.svg+:: the front cover of the comic (optional)
40
- +back-cover.svg+:: the back cover of the comic (optional)
41
- +inside-front.svg+:: the inside front cover of the comic (optional)
42
- +inside-back.svg+:: the inside back cover of the comic (optional)
60
+ (specific options will be discussed later)
43
61
 
44
- It is also possible to have two-page spreads in single files:
62
+ In the pages directory, +minicomic+ looks for SVG files named according to the
63
+ following conventions:
45
64
 
46
- +pages-NN-MM.svg+:: the spread spanning pages NN-MM
47
- +cover.svg+:: the cover (back and front together in one file; optional)
65
+ <tt>page-NN.svg</tt>:: page +NN+
66
+ <tt>front-cover.svg</tt>:: the front cover of the comic (optional)
67
+ <tt>back-cover.svg</tt>:: the back cover of the comic (optional)
68
+ <tt>inside-front.svg</tt>:: the inside front cover of the comic (optional)
69
+ <tt>inside-back.svg</tt>:: the inside back cover of the comic (optional)
48
70
 
49
71
  Page numbers start at 1 (page 1 is a right-handed page, and the first
50
- interior page). Page documents should be 5.5x8.5in or smaller for single
51
- pages, and 11x8.5in for two-page spreads.
72
+ interior page excluding the inside cover). The exact size of page documents
73
+ is not important; they will be scaled to fit the selected output format
74
+ (preserving aspect ratio).
75
+
76
+ It is also possible to have two-page spreads in single files:
77
+
78
+ <tt>pages-NN-MM.svg</tt>:: the spread spanning pages <tt>NN</tt>-<tt>MM</tt>
79
+ <tt>cover.svg</tt>:: the cover (back and front together in one file; optional)
80
+
81
+ A two-page spread will be cut in half and each half placed on the appropriate
82
+ page in the print output.
52
83
 
53
84
  == print output
54
85
 
@@ -57,50 +88,96 @@ interior pages up to the next multiple of four, padding with blank pages
57
88
  as needed. The page graphics will be scaled down slightly from their full
58
89
  size, and smaller graphics will be centered.
59
90
 
91
+ === print-related options
92
+
93
+ There are several options which control how minicomic's print output is
94
+ generated:
95
+
96
+ <tt>:format</tt>:: the format for the folded booklet; options are <tt>:half_letter</tt>, <tt>:a5</tt>, <tt>:b5</tt>, or a two-element array with dimensions in postscript points (default: <tt>:half_letter</tt>)
97
+ <tt>:rasterize</tt>:: whether print output should be rasterized rather than being exported as PostScript -- PostScript will usually result in smaller files, but cannot support many Inkscape features like blur or transparency (default: +true+)
98
+ <tt>:margin</tt>:: a nominal horizontal margin in PostScript points; the page images will be scaled down slightly to accomodate this margin (default: +13.5+, which is 3/16 of an inch)
99
+
100
+ The required paper format will be the double the booklet's format, so
101
+ <tt>:half_letter</tt> requires US letter paper, <tt>:a5</tt>
102
+ requires A4 paper, and <tt>:b5</tt> requires B4 paper.
103
+
104
+ If the pages are rasterized, then the following options also apply to print:
105
+
106
+ <tt>:color</tt>:: <tt>true</tt>, <tt>false</tt>, or <tt>:monochrome</tt> -- whether the rasterized images should be full-color, greyscale, or monochrome (24-bit color, 8-bit greyscale, or 1-bit bitmap) (default: <tt>true</tt>)
107
+ <tt>:dpi</tt>:: the target DPI for the rasterized page images (taking into account any scaling to accomodate the margins) (default: +200+)
108
+
109
+ === proof output
110
+
60
111
  To generate a "proof" PDF that you can examine to see what spreads will
61
- look like in the assembled comic, use:
112
+ look like in the assembled comic (i.e. "reader spreads"), use:
62
113
 
63
- +rake proof+
114
+ rake proof
64
115
 
65
- The PDF will be created as +print/proof.pdf+. Since I rarely use the
66
- inside covers for anything, +minicomic+ places the front and back covers
67
- opposite the first and last interior pages respectively.
116
+ The PDF will be created as <tt>print/proof.pdf</tt>. Since I rarely use the
117
+ inside covers for anything, +minicomic+ currently places the front and back
118
+ covers opposite the first and last interior pages respectively.
68
119
 
69
- To generate a set of PDFs suitable for printing and assembly, use:
120
+ === single-sided printing
70
121
 
71
- +rake print+
122
+ To generate a set of PDFs suitable for single-sided printing and assembly, use:
72
123
 
73
- This will generate a set of three PDFs:
124
+ rake single
74
125
 
75
- +print/duplex.pdf+:: suitable for duplex printing
76
- +print/front.pdf+ and +print/back.pdf+:: front and back sides for single-sided printing
126
+ This will generate a set of two PDFs:
77
127
 
78
- If you are using a duplex printer, if you're lucky it will deposit its
79
- output pages face-up and they will be ready for assembly (this is the norm).
80
- Otherwise if it deposits its pages face-down, you will have reverse them before
81
- you can assemble the comic.
128
+ <tt>print/front.pdf</tt>:: front side of all pages
129
+ <tt>print/back.pdf</tt>:: back side of all pages
82
130
 
83
131
  When using the single-sided PDFs, _you will need to experiment_ to find the
84
132
  correct order to use them in, and the correct way to flip the paper. For my
85
- printer, I print +back.pdf+ first, then flip the stack the long way before
86
- printing +front.pdf+ on it. Other printers will differ depending on how the
87
- paper is loaded in the tray, and how it is stacked on output.
133
+ printer, I print <tt>back.pdf</tt> first, then flip the stack the long way
134
+ before printing <tt>front.pdf</tt> on it. Other printers will differ depending
135
+ on how the paper is loaded in the tray, and how it is stacked on output.
136
+
137
+ === duplex printing
138
+
139
+ To generate a set of PDFs suitable for duplex printing and assembly, use:
140
+
141
+ rake duplex
142
+
143
+ This will generate a single PDF, <tt>print/duplex.pdf</tt>.
144
+
145
+ When printing this PDF, if you're lucky, your printer it will deposit its
146
+ output pages face-down and they will be ready for assembly (this is the norm).
147
+ Otherwise if it deposits its pages face-up, you will have reverse their order
148
+ before you can assemble the comic.
88
149
 
89
150
  == web output
90
151
 
91
152
  You can generate files for web upload via:
92
153
 
93
- +rake web+
154
+ rake web
155
+
156
+ === web-related options
157
+
158
+ The following options apply to web output:
94
159
 
95
- When generating output for the web, +minicomic+ will generate a set of 4-bit
96
- greyscale PNGs, and a corresponding set of JPEG thumbnails:
160
+ <tt>:web_height</tt>:: the height, in pixels, of images for web output (width will be determined according to the page's aspect ratio) (default: 680)
161
+ <tt>:thumbnail_height</tt>:: the height, in pixels, of thumbnail images (default: 96)
162
+ <tt>:color</tt>:: whether the generated images should be 24-bit color (+true+), 8-bit greyscale (+false+), or 4-bit greyscale (<tt>:monochrome</tt>) (default: <tt>true</tt>)
97
163
 
98
- +web/page-NN.png+:: page NN
99
- +web/thumbnail-NN.jpeg:: thumbnail of page NN
164
+ === output files
165
+
166
+ When generating output for the web, +minicomic+ will generate a set of PNGs
167
+ and a corresponding set of JPEG thumbnails:
168
+
169
+ <tt>web/page-NN.png</tt>:: page +NN+
170
+ <tt>web/thumbnail-NN.jpeg</tt>:: thumbnail of page +NN+
171
+
172
+ For spreads, the filenames are similar, except instead of a single page number
173
+ +NN+, a page range will be indicated as <tt>NN-MM</tt>.
100
174
 
101
175
  == cleanup
102
176
 
103
177
  You can easily get rid of the temporary and output files with:
104
178
 
105
- +rake clean+
179
+ rake clean
180
+
181
+ This is often a good idea if you've changed any of minicomic's options, since
182
+ files generated with the previous options may still be lingering.
106
183
 
data/Rakefile CHANGED
@@ -18,7 +18,7 @@ end
18
18
  gemspec = Gem::Specification.new do |gemspec|
19
19
  gemspec.platform = Gem::Platform::RUBY
20
20
  gemspec.name = "minicomic"
21
- gemspec.version = "0.0.1"
21
+ gemspec.version = "0.2"
22
22
  gemspec.author = "MenTaLguY <mental@rydia.net>"
23
23
  gemspec.summary = "Rake rules for minicomic impressions"
24
24
  gemspec.test_file = 'test/test_all.rb'
@@ -27,6 +27,7 @@ gemspec = Gem::Specification.new do |gemspec|
27
27
  gemspec.has_rdoc = true
28
28
  gemspec.extra_rdoc_files = %w(README)
29
29
  gemspec.rdoc_options = %w(--main README)
30
+ gemspec.add_dependency 'rake', '>= 0.7.0'
30
31
  end
31
32
 
32
33
  task :package => [ :test ]
@@ -9,44 +9,114 @@
9
9
  require 'set'
10
10
  require 'rake'
11
11
 
12
- module Minicomic
13
- extend self
12
+ class Minicomic
14
13
 
15
- private
14
+ DEFAULT_OPTIONS = {
15
+ :rasterize => true,
16
+
17
+ :margin => 13.5,
18
+ :dpi => 200,
19
+
20
+ :color => true,
21
+
22
+ :format => :half_letter,
23
+
24
+ # in pixels
25
+ :web_height => 680,
26
+ :thumbnail_height => 96
27
+ }
16
28
 
17
- # in PostScript points
18
- FORMAT_WIDTH = ( 5.5 * 72 ).to_i
19
- FORMAT_HEIGHT = ( 8.5 * 72 ).to_i
20
- PAGE_SCALE = 0.932
29
+ private
21
30
 
22
- # other constants
31
+ # constants
23
32
  LEFT = 0
24
33
  RIGHT = 1
25
34
  PHI = ( 1 + Math.sqrt( 5 ) ) / 2
26
35
 
36
+ module Common
37
+ def get_ps_bbox( filename )
38
+ result = nil
39
+ File.open( filename, "r" ) do |stream|
40
+ magic = stream.gets.chomp
41
+ break unless magic =~ /^%!PS-Adobe/
42
+ stream.each_line do |line|
43
+ case line
44
+ when /^%%BoundingBox: (\d+) (\d+) (\d+) (\d+)/
45
+ result = [ ($1.to_f)..($3.to_f), ($2.to_f)..($4.to_f) ]
46
+ break
47
+ when /^%%EndComments/
48
+ break
49
+ end
50
+ end
51
+ end
52
+ raise RuntimeError, "unable to get bbox for #{ filename }" unless result
53
+ result
54
+ end
55
+
56
+ def get_generic_bbox( filename )
57
+ IO.popen("-", "r") do |stream|
58
+ if stream
59
+ line = stream.gets
60
+ if line
61
+ line.chomp.split.map { |n| 0.0..(n.to_f) }
62
+ else
63
+ raise RuntimeError, "unable to get bbox for #{ filename }"
64
+ end
65
+ else
66
+ begin
67
+ sh "identify", "-format", "%w %h", filename
68
+ rescue Exception => e
69
+ e.display
70
+ end
71
+ exit! 127
72
+ end
73
+ end
74
+ end
75
+
76
+ def get_bbox( filename )
77
+ case filename
78
+ when /\.e?ps$/i
79
+ get_ps_bbox( filename )
80
+ else
81
+ get_generic_bbox( filename )
82
+ end
83
+ end
84
+
85
+ def scale_print_page( width, height )
86
+ width_scale = @options[:format_width].to_f / width
87
+ height_scale = @options[:format_height].to_f / height
88
+ if width * height_scale > @options[:format_width]
89
+ width_scale
90
+ else
91
+ height_scale
92
+ end * @options[:scale]
93
+ end
94
+ end
95
+ include Common
96
+
27
97
  def eps_from_svg( eps_file, svg_file )
28
98
  file eps_file => [ svg_file ] do
29
99
  sh 'inkscape', '-T', '-B', '-E', eps_file, svg_file
30
100
  end
31
101
  end
32
102
 
33
- def back_eps( out_file, in_file )
103
+ def back_ps( out_file, in_file )
34
104
  file out_file => [ in_file ] do
35
105
  sh 'psselect', '-e', in_file, out_file
36
106
  end
37
107
  end
38
108
 
39
- def front_eps( out_file, in_file )
109
+ def front_ps( out_file, in_file )
40
110
  file out_file => [ in_file ] do
41
111
  sh 'psselect', '-o', in_file, out_file
42
112
  end
43
113
  end
44
114
 
45
115
  def make2up( in_file, out_file )
46
- sh 'psnup', "-W#{ FORMAT_WIDTH }", "-H#{ FORMAT_HEIGHT }", "-h#{ FORMAT_WIDTH * 2 }", "-w#{ FORMAT_HEIGHT }", '-2', in_file, out_file
116
+ sh 'psnup', "-W#{ @options[:format_width].round }", "-H#{ @options[:format_height].round }", "-h#{ ( @options[:format_width] * 2 ).round }", "-w#{ @options[:format_height].round }", '-2', in_file, out_file
47
117
  end
48
118
 
49
- def duplex_eps( out_file, in_file )
119
+ def duplex_ps( out_file, in_file )
50
120
  temp_file = "#{ out_file }.temp"
51
121
  file out_file => [ in_file ] do
52
122
  sh 'psbook', in_file, temp_file
@@ -58,7 +128,7 @@ def duplex_eps( out_file, in_file )
58
128
  end
59
129
  end
60
130
 
61
- def proof_eps( out_file, in_file )
131
+ def proof_ps( out_file, in_file )
62
132
  temp_file = "#{ out_file }.temp"
63
133
  file out_file => [ in_file ] do
64
134
  sh 'psselect', '-p1,3-_3,_1', in_file, temp_file
@@ -70,32 +140,64 @@ def proof_eps( out_file, in_file )
70
140
  end
71
141
  end
72
142
 
73
- def pdf_from_eps( pdf_file, eps_file )
74
- file pdf_file => [ eps_file ] do
75
- sh 'ps2pdf', '-sPAPERSIZE=letter', eps_file, pdf_file
143
+ def pdf_from_ps( pdf_file, ps_file )
144
+ file pdf_file => [ ps_file ] do
145
+ sh 'gs', '-q', '-dSAFER', '-dNOPAUSE', '-dBATCH', '-sDEVICE=pdfwrite', "-sOutputFile=#{ pdf_file }", '-dAutoRotatePages=/None', '-c', "<< /PageSize [#{ @options[:format_height].round } #{ ( @options[:format_width] * 2 ).round }] /Orientation 3 >> setpagedevice", '-c', '.setpdfwrite', '-f', ps_file
76
146
  end
77
147
  end
78
148
 
79
- def png_from_svg( png_file, svg_file )
149
+ def png_from_svg( png_file, svg_file, high_res, &height_calc )
80
150
  temp_png_file = "#{ png_file }.temp"
81
151
  file png_file => [ svg_file ] do
82
- sh 'inkscape', '-d', '80', '-C', '-y', '1.0', '-e', temp_png_file, svg_file
152
+ height = height_calc.call svg_file
153
+ sh 'inkscape', '-h', height.to_s, '-C', '-y', '1.0', '-e', temp_png_file, svg_file
83
154
  begin
84
- sh 'pngcrush', '-force', '-bit_depth', '4', '-c', '0', '-q', temp_png_file, png_file
155
+ args = '-q', '-rem', 'allb'
156
+ case @options[:color]
157
+ when :color, true
158
+ args.push '-c', '2'
159
+ when :greyscale, :grayscale, false
160
+ args.push '-c', '0', '-bit_depth', '8'
161
+ when :monochrome
162
+ args.push '-c', '0', '-bit_depth', ( high_res ? '1' : '4' )
163
+ end
164
+ args.push temp_png_file, png_file
165
+ sh 'pngcrush', *args
166
+ rescue Exception
167
+ rm png_file if File.exist? png_file
168
+ raise
85
169
  ensure
86
170
  rm temp_png_file
87
171
  end
88
172
  end
89
173
  end
90
174
 
175
+ def web_png_from_svg( png_file, svg_file )
176
+ png_from_svg( png_file, svg_file, false ) { @options[:web_height] }
177
+ end
178
+
179
+ def print_png_from_svg( png_file, splits, svg_file )
180
+ png_from_svg( png_file, svg_file, @options[:dpi] > 150 ) do |filename|
181
+ dims = get_bbox( filename ).map { |r| r.end - r.begin }
182
+ scale = scale_print_page( dims[0] / splits, dims[1] )
183
+ ( dims[1].to_f * scale * @options[:dpi] / 72 ).round
184
+ end
185
+ end
186
+
91
187
  def thumbnail_jpeg_from_image( jpeg_file, image_file )
92
188
  file jpeg_file => [ image_file ] do
93
- sh 'convert', '-filter', 'sinc', '-resize', "x96", image_file, jpeg_file
189
+ sh 'convert', '-filter', 'sinc', '-resize', "x#{ @options[:thumbnail_height] }", image_file, jpeg_file
94
190
  end
95
191
  end
96
192
 
193
+ ImageSlice = Struct.new :n, :total, :filename
194
+ PageSpec = Struct.new :bbox, :scale, :dims, :filename
195
+
97
196
  class BookletLayout
98
- def initialize( stream, *pages )
197
+ include Common
198
+
199
+ def initialize( options, stream, *pages )
200
+ @options = options
99
201
  @bboxes = {}
100
202
  @stream = stream
101
203
 
@@ -122,37 +224,35 @@ class BookletLayout
122
224
  def format_pages( is_cover, *pages )
123
225
  spread = pages.map do |page|
124
226
  if page
125
- [ bbox(*page), page[2] ]
126
- else
127
- nil
128
- end
129
- end.map do |page|
130
- if page
131
- bbox, document = page
132
- dims = (0..1).map { |d| ( bbox[d].end - bbox[d].begin ) * PAGE_SCALE }
133
- [ bbox, dims, document ]
227
+ bounds = bbox( page )
228
+ dims = (0..1).map { |d| bounds[d].end - bounds[d].begin }
229
+ scale = scale_print_page( *dims )
230
+ PageSpec[ bounds, scale, dims.map { |n| n * scale }, page.filename ]
134
231
  else
135
232
  nil
136
233
  end
137
234
  end
138
235
 
139
236
  if spread.all? # i.e. both?
140
- spread_dims = spread.map { |page| page[1] }
141
- left_document = spread[LEFT][2]
142
- right_document = spread[RIGHT][2]
143
- if left_document == right_document
237
+ spread_dims = spread.map { |page| page.dims }
238
+
239
+ if spread[LEFT].filename == spread[RIGHT].filename
144
240
  tx = translate_spread( *spread_dims )
145
241
  elsif is_cover
146
242
  tx = spread_dims.map { |dims| translate_single( dims ) }
147
243
  else
148
244
  tx = translate_facing( *spread_dims )
149
245
  end
150
- [ [ spread[LEFT][0], tx[LEFT], left_document ],
151
- [ spread[RIGHT][0], tx[RIGHT], right_document ] ]
246
+
247
+ [ LEFT, RIGHT ].map do |side|
248
+ [ spread[side].bbox, spread[side].scale, tx[side],
249
+ spread[side].filename ]
250
+ end
152
251
  else
153
252
  spread.map do |page|
154
253
  if page
155
- [ page[0], translate_single( page[1] ), page[2] ]
254
+ [ page.bbox, page.scale, translate_single( page.dims ),
255
+ page.filename ]
156
256
  else
157
257
  nil
158
258
  end
@@ -160,32 +260,18 @@ class BookletLayout
160
260
  end
161
261
  end
162
262
 
163
- def bbox( i, n, document )
164
- rx, ry = @bboxes[document] ||= (
165
- rx, ry = 0.0..(FORMAT_WIDTH.to_f * n), 0.0..(FORMAT_HEIGHT.to_f)
166
- File.open( document, "r" ) do |stream|
167
- stream.each_line do |line|
168
- case line
169
- when /^%%BoundingBox: (\d+) (\d+) (\d+) (\d+)/
170
- rx, ry = ($1.to_f)..($3.to_f), ($2.to_f)..($4.to_f)
171
- break
172
- when /^%%EndComments/
173
- break
174
- end
175
- end
176
- end
177
- [ rx, ry ]
178
- )
263
+ def bbox( page )
264
+ rx, ry = @bboxes[page.filename] ||= get_bbox( page.filename )
179
265
  width = rx.end - rx.begin
180
- [ ( rx.begin + width * i / n )..( rx.begin + width * ( i + 1 ) / n ), ry ]
266
+ [ ( rx.begin + width * page.n / page.total )..( rx.begin + width * ( page.n + 1 ) / page.total ), ry ]
181
267
  end
182
268
 
183
269
  def bottom_margin( height )
184
- ( FORMAT_HEIGHT.to_f - height ) / PHI
270
+ ( @options[:format_height].to_f - height ) / PHI
185
271
  end
186
272
 
187
273
  def horizontal_margin( width )
188
- ( FORMAT_WIDTH.to_f - width ) / 2
274
+ ( @options[:format_width].to_f - width ) / 2
189
275
  end
190
276
 
191
277
  def translate_single( dims )
@@ -201,7 +287,7 @@ class BookletLayout
201
287
  end
202
288
 
203
289
  def translate_spread( left, right )
204
- [ [ FORMAT_WIDTH.to_f - left[0], bottom_margin( left[1] ) ],
290
+ [ [ @options[:format_width].to_f - left[0], bottom_margin( left[1] ) ],
205
291
  [ 0, bottom_margin( right[1] ) ] ]
206
292
  end
207
293
 
@@ -218,7 +304,7 @@ class BookletLayout
218
304
  emit_dsc 'Creator', 'minicomic'
219
305
  emit_dsc 'Pages', pages.size
220
306
  #emit_dsc 'Orientation', 'Portrait'
221
- #bbox = "0 0 #{ FORMAT_WIDTH } #{ FORMAT_HEIGHT }"
307
+ #bbox = "0 0 #{ @options[:format_width].round } #{ @options[:format_height].round }"
222
308
  #emit_dsc 'BoundingBox', bbox
223
309
  #emit_dsc 'HiResBoundingBox', bbox
224
310
  emit_dsc 'EndComments'
@@ -241,21 +327,37 @@ class BookletLayout
241
327
  @stream.puts "closepath eoclip newpath"
242
328
  end
243
329
 
244
- def emit_page( n, bbox, translate, document )
330
+ def emit_page( n, bbox, scale, translate, document )
245
331
  emit_dsc 'Page', "#{ n } #{ n }"
246
332
 
247
333
  @stream.puts "save"
248
-
249
- emit_clip_rect( 0, 0, FORMAT_WIDTH, FORMAT_HEIGHT )
334
+ @stream.puts "/showpage {} def"
335
+ emit_clip_rect( 0, 0, @options[:format_width].round, @options[:format_height].round )
250
336
 
251
337
  @stream.puts <<EOS
252
- #{ bbox.map { |r| -r.begin }.join( ' ' ) } translate
253
- #{ PAGE_SCALE } #{ PAGE_SCALE } scale
254
338
  #{ translate.join( ' ' ) } translate
339
+ #{ scale } #{ scale } scale
255
340
  EOS
256
341
 
257
- emit_clip_rect( bbox[0].begin, bbox[1].begin, bbox[0].end, bbox[1].end )
342
+ emit_embedded_page( bbox, document )
343
+
344
+ @stream.puts "restore"
345
+ @stream.puts "showpage"
346
+ emit_dsc 'PageTrailer'
347
+ end
348
+
349
+ def emit_embedded_page( bbox, document )
350
+ case document
351
+ when /\.e?ps$/i
352
+ emit_embedded_page_eps( bbox, document )
353
+ else
354
+ emit_embedded_page_generic( bbox, document )
355
+ end
356
+ end
258
357
 
358
+ def emit_embedded_page_eps( bbox, document )
359
+ @stream.puts "#{ bbox.map { |r| -r.begin }.join( ' ' ) } translate"
360
+ emit_clip_rect( bbox[0].begin, bbox[1].begin, bbox[0].end, bbox[1].end )
259
361
  emit_dsc 'BeginDocument', File.basename( document )
260
362
  File.open( document, 'r' ) do |input|
261
363
  input.each_line do |line|
@@ -263,8 +365,25 @@ EOS
263
365
  end
264
366
  end
265
367
  emit_dsc 'EndDocument'
266
- emit_dsc 'PageTrailer'
267
- @stream.puts "restore"
368
+ end
369
+
370
+ def emit_embedded_page_generic( bbox, document )
371
+ emit_dsc 'BeginDocument', File.basename( document )
372
+ IO.popen( "-", "r" ) do |input|
373
+ if input
374
+ input.each_line do |line|
375
+ @stream.puts line
376
+ end
377
+ else
378
+ begin
379
+ sh "convert", "-extract", "#{ ( bbox[0].end - bbox[0].begin ).round }x#{ ( bbox[1].end - bbox[1].begin ).round }+#{ bbox[0].begin.round }+#{ bbox[1].begin.round }", document, "eps:-"
380
+ rescue Exception => e
381
+ e.display
382
+ end
383
+ exit! 127
384
+ end
385
+ end
386
+ emit_dsc 'EndDocument'
268
387
  end
269
388
 
270
389
  def emit_empty_page( n )
@@ -274,10 +393,10 @@ EOS
274
393
  end
275
394
 
276
395
  def layout_booklet( layout_ps, *pages )
277
- file layout_ps => pages.compact.map { |i, n, file| file } do
396
+ file layout_ps => pages.compact.map { |page| page.filename } do
278
397
  begin
279
398
  File.open( layout_ps, 'w' ) do |stream|
280
- BookletLayout.new( stream, *pages )
399
+ BookletLayout.new( @options, stream, *pages )
281
400
  end
282
401
  rescue
283
402
  rm layout_ps
@@ -286,7 +405,8 @@ def layout_booklet( layout_ps, *pages )
286
405
  end
287
406
  end
288
407
 
289
- def special_name_re( name )
408
+ SPECIAL_NAMES = %w(front-cover back-cover cover inside-front inside-back)
409
+ SPECIAL_NAME_RES = SPECIAL_NAMES.map do |name|
290
410
  Regexp.new( Regexp.quote( name ).gsub( /-/, "\\W*" ), "i" )
291
411
  end
292
412
 
@@ -297,12 +417,28 @@ def normalize_special_name( name )
297
417
  name
298
418
  end
299
419
 
300
- SPECIAL_NAMES = %w(front-cover back-cover cover inside-front inside-back)
301
- SPECIAL_NAME_RES = SPECIAL_NAMES.map { |n| special_name_re( n ) }
420
+ def initialize( dir, options={} )
421
+ @options = DEFAULT_OPTIONS.merge options
422
+
423
+ case @options[:format]
424
+ when :half_letter
425
+ @options[:format_width] = 5.5 * 72
426
+ @options[:format_height] = 8.5 * 72
427
+ when :a5
428
+ @options[:format_width] = 148 * 72 / 25.4
429
+ @options[:format_height] = 210 * 72 / 25.4
430
+ when :b5
431
+ @options[:format_width] = 176 * 72 / 25.4
432
+ @options[:format_height] = 250 * 72 / 25.4
433
+ when Array
434
+ @options[:format_width] = @options[:format][0].to_f
435
+ @options[:format_height] = @options[:format][1].to_f
436
+ else
437
+ raise ArgumentError, "Unknown booklet size #{ @options[:format] }"
438
+ end
302
439
 
303
- public
440
+ @options[:scale] = ( @options[:format_width] - ( @options[:margin] * 2.0 ) ) / @options[:format_width]
304
441
 
305
- def minicomic( dir )
306
442
  pages_dir = File.join( dir, 'pages' )
307
443
 
308
444
  scratch_dir = File.join( dir, 'scratch' )
@@ -319,6 +455,12 @@ def minicomic( dir )
319
455
  specials = Set.new
320
456
  pages = []
321
457
 
458
+ if @options[:rasterize]
459
+ print_ext = 'png'
460
+ else
461
+ print_ext = 'eps'
462
+ end
463
+
322
464
  FileList[File.join( pages_dir, '*.svg' )].each do |page_svg|
323
465
  name = File.basename( page_svg )
324
466
  name.sub!( /\.svg$/, '' )
@@ -352,21 +494,30 @@ def minicomic( dir )
352
494
  next
353
495
  end
354
496
 
355
- page_eps = File.join( scratch_dir, "#{ print_name }.eps" )
497
+ print_file = File.join( scratch_dir, "#{ print_name }.#{ print_ext }" )
356
498
 
357
- if min and min > 0
499
+ if min
358
500
  size = max - min + 1
359
- (0...size).each do |n|
360
- pages[n+min-1] = [ n, size, page_eps ]
501
+ if min > 0
502
+ (0...size).each do |n|
503
+ pages[n+min-1] = ImageSlice[ n, size, print_file ]
504
+ end
361
505
  end
506
+ else
507
+ size = 1
362
508
  end
363
509
 
364
- eps_from_svg( page_eps, page_svg )
365
- task page_eps => [ scratch_dir ]
510
+ case print_ext
511
+ when 'png'
512
+ print_png_from_svg( print_file, size, page_svg )
513
+ when 'eps'
514
+ eps_from_svg( print_file, page_svg )
515
+ end
516
+ task print_file => [ scratch_dir ]
366
517
 
367
518
  if web_name
368
519
  page_png = File.join( web_dir, "#{ web_name }.png" )
369
- png_from_svg( page_png, page_svg )
520
+ web_png_from_svg( page_png, page_svg )
370
521
  task page_png => [ web_dir ]
371
522
 
372
523
  thumbnail_name = web_name.sub( /^page/, 'thumbnail' )
@@ -378,49 +529,54 @@ def minicomic( dir )
378
529
  end
379
530
  end
380
531
 
381
- pages = [ nil, nil ] + pages + [ nil ] * ( pages.size % 4 ) + [ nil, nil ]
532
+ pages = [ nil, nil ] +
533
+ pages + [ nil ] * ( ( 4 - pages.size % 4 ) % 4 ) +
534
+ [ nil, nil ]
382
535
 
383
536
  specials.each do |name|
384
- filename = File.join( scratch_dir, "#{ name }.eps" )
537
+ filename = File.join( scratch_dir, "#{ name }.#{ print_ext }" )
385
538
  case name
386
539
  when 'front-cover'
387
- pages[0] ||= [ 0, 1, filename ] # 'cover' takes precedence
540
+ pages[0] ||= ImageSlice[ 0, 1, filename ] # 'cover' takes precedence
388
541
  when 'back-cover'
389
- pages[-1] ||= [ 0, 1, filename ] # 'cover' takes precedence
542
+ pages[-1] ||= ImageSlice[ 0, 1, filename ] # 'cover' takes precedence
390
543
  when 'cover'
391
- pages[-1] = [ 0, 2, filename ]
392
- pages[0] = [ 1, 2, filename ]
544
+ pages[-1] = ImageSlice[ 0, 2, filename ]
545
+ pages[0] = ImageSlice[ 1, 2, filename ]
393
546
  when 'inside-front'
394
- pages[1] = [ 0, 1, filename ]
547
+ pages[1] = ImageSlice[ 0, 1, filename ]
395
548
  when 'inside-back'
396
- pages[-2] = [ 0, 1, filename ]
549
+ pages[-2] = ImageSlice[ 0, 1, filename ]
397
550
  end
398
551
  end
399
552
 
400
553
  layout_booklet( layout_ps, *pages )
401
554
 
402
- duplex_eps = File.join( scratch_dir, "duplex.eps" )
555
+ duplex_ps = File.join( scratch_dir, "duplex.ps" )
403
556
 
404
557
  %w(proof back front duplex).each do |kind|
405
558
  case kind
406
559
  when 'duplex', 'proof'
407
560
  source = layout_ps
408
561
  else
409
- source = duplex_eps
562
+ source = duplex_ps
410
563
  end
411
- kind_eps = File.join( scratch_dir, "#{ kind }.eps" )
412
- send( "#{ kind }_eps", kind_eps, source )
413
- task kind_eps => [ scratch_dir ]
564
+ kind_ps = File.join( scratch_dir, "#{ kind }.ps" )
565
+ send( "#{ kind }_ps", kind_ps, source )
566
+ task kind_ps => [ scratch_dir ]
414
567
  kind_pdf = File.join( print_dir, "#{ kind }.pdf" )
415
- pdf_from_eps( kind_pdf, kind_eps )
568
+ pdf_from_ps( kind_pdf, kind_ps )
416
569
  task kind_pdf => [ print_dir ]
417
570
  task kind => [ kind_pdf ]
418
571
  end
419
572
 
420
- desc "Generate PDF files for printing"
421
- task :print => [ :duplex, :front, :back ]
573
+ desc "Generate PDF file for duplex printing"
574
+ task :duplex
422
575
 
423
- desc "Generate a proof PDF for review"
576
+ desc "Generate PDF files for single-sided printing"
577
+ task :single => [ :front, :back ]
578
+
579
+ desc "Generate PDF file for review of reader spreads"
424
580
  task :proof
425
581
 
426
582
  desc "Remove all output and intermediate files"
@@ -433,15 +589,13 @@ def minicomic( dir )
433
589
  desc "Generate graphics for the web"
434
590
  task :web
435
591
 
436
- desc "Generate web and print output"
437
- task :both => [ :print, :web ]
438
-
439
- task :default => [ :both ]
592
+ desc "All print and web output"
593
+ task :all => [ :proof, :duplex, :single, :web ]
440
594
  end
441
595
 
442
596
  end
443
597
 
444
- def minicomic( dir )
445
- Minicomic.minicomic( dir )
598
+ def minicomic( dir, options={} )
599
+ Minicomic.new( dir, options )
446
600
  end
447
601
 
metadata CHANGED
@@ -3,8 +3,8 @@ rubygems_version: 0.8.11
3
3
  specification_version: 1
4
4
  name: minicomic
5
5
  version: !ruby/object:Gem::Version
6
- version: 0.0.1
7
- date: 2007-03-29 00:00:00 -04:00
6
+ version: "0.2"
7
+ date: 2007-04-21 00:00:00 -04:00
8
8
  summary: Rake rules for minicomic impressions
9
9
  require_paths:
10
10
  - lib
@@ -45,5 +45,13 @@ extensions: []
45
45
 
46
46
  requirements: []
47
47
 
48
- dependencies: []
49
-
48
+ dependencies:
49
+ - !ruby/object:Gem::Dependency
50
+ name: rake
51
+ version_requirement:
52
+ version_requirements: !ruby/object:Gem::Version::Requirement
53
+ requirements:
54
+ - - ">="
55
+ - !ruby/object:Gem::Version
56
+ version: 0.7.0
57
+ version: