minicomic 0.0.1 → 0.2
Sign up to get free protection for your applications and to get access to all the features.
- data/README +117 -40
- data/Rakefile +2 -1
- data/lib/minicomic.rb +257 -103
- 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
|
-
|
7
|
-
|
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
|
-
|
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
|
33
|
-
(in this particular case, '.' meaning the directory where the
|
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
|
-
|
36
|
-
conventions:
|
58
|
+
minicomic '.', :color => false
|
37
59
|
|
38
|
-
|
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
|
-
|
62
|
+
In the pages directory, +minicomic+ looks for SVG files named according to the
|
63
|
+
following conventions:
|
45
64
|
|
46
|
-
|
47
|
-
|
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
|
51
|
-
|
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
|
-
|
114
|
+
rake proof
|
64
115
|
|
65
|
-
The PDF will be created as
|
66
|
-
inside covers for anything, +minicomic+ places the front and back
|
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
|
-
|
120
|
+
=== single-sided printing
|
70
121
|
|
71
|
-
|
122
|
+
To generate a set of PDFs suitable for single-sided printing and assembly, use:
|
72
123
|
|
73
|
-
|
124
|
+
rake single
|
74
125
|
|
75
|
-
|
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
|
-
|
79
|
-
|
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
|
86
|
-
printing
|
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
|
-
|
154
|
+
rake web
|
155
|
+
|
156
|
+
=== web-related options
|
157
|
+
|
158
|
+
The following options apply to web output:
|
94
159
|
|
95
|
-
|
96
|
-
|
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
|
-
|
99
|
-
|
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
|
-
|
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.
|
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 ]
|
data/lib/minicomic.rb
CHANGED
@@ -9,44 +9,114 @@
|
|
9
9
|
require 'set'
|
10
10
|
require 'rake'
|
11
11
|
|
12
|
-
|
13
|
-
extend self
|
12
|
+
class Minicomic
|
14
13
|
|
15
|
-
|
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
|
-
|
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
|
-
#
|
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
|
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
|
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#{
|
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
|
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
|
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
|
74
|
-
file pdf_file => [
|
75
|
-
sh '
|
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
|
-
|
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
|
-
|
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', "
|
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
|
-
|
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
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
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
|
141
|
-
|
142
|
-
|
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
|
-
|
151
|
-
|
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
|
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(
|
164
|
-
rx, ry = @bboxes[
|
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 *
|
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
|
-
(
|
270
|
+
( @options[:format_height].to_f - height ) / PHI
|
185
271
|
end
|
186
272
|
|
187
273
|
def horizontal_margin( width )
|
188
|
-
(
|
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
|
-
[ [
|
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 #{
|
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,
|
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
|
-
|
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
|
-
|
267
|
-
|
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 { |
|
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
|
-
|
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
|
-
|
301
|
-
|
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
|
-
|
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
|
-
|
497
|
+
print_file = File.join( scratch_dir, "#{ print_name }.#{ print_ext }" )
|
356
498
|
|
357
|
-
if min
|
499
|
+
if min
|
358
500
|
size = max - min + 1
|
359
|
-
|
360
|
-
|
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
|
-
|
365
|
-
|
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
|
-
|
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 ] +
|
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 }
|
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
|
-
|
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 =
|
562
|
+
source = duplex_ps
|
410
563
|
end
|
411
|
-
|
412
|
-
send( "#{ kind }
|
413
|
-
task
|
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
|
-
|
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
|
421
|
-
task :
|
573
|
+
desc "Generate PDF file for duplex printing"
|
574
|
+
task :duplex
|
422
575
|
|
423
|
-
desc "Generate
|
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 "
|
437
|
-
task :
|
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.
|
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.
|
7
|
-
date: 2007-
|
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:
|