minicomic 0.0.1 → 0.2
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.
- 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:
|