pixel_raster 0.0.2 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +5 -5
- data/README.md +1 -1
- data/VERSION +1 -0
- data/bin/img2pixel +6 -0
- data/lib/img2pixel.rb +154 -0
- data/lib/pixel_raster.rb +174 -33
- metadata +37 -8
- data/bin/xpm2tikz +0 -8
- data/lib/xpm2tikz.rb +0 -45
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: dd6246ac2fb2fc015e61ec280b5be406f2eade547be1dce4ced45dbd6120a208
|
4
|
+
data.tar.gz: 5a2adb8d02a378a963b52304556e6d5636625da5fb4599695c6bf7167c06b0d9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9665873f9a4bfa9525f02a17bd0e2a910a07e26304e90d7ff7580be387a871f197ffa553eaecba4021a299c296264100dad4f59d0e9fd399568b700c07b0d6ae
|
7
|
+
data.tar.gz: c3f8038be85df22b0583f2ee63da7cb7589174ee2ae44506fdbefe47b51c7d76aaf68d5ef3d4e47b60f8803cd9f6a2d519b4452929ec6469aec1bf2b3a623453
|
data/README.md
CHANGED
@@ -21,6 +21,6 @@ paint-yourself raster versions (see [images.pdf](images.pdf)).
|
|
21
21
|
## xpm2tikz
|
22
22
|
|
23
23
|
The script takes xpm images from the command line as input and
|
24
|
-
outputs [tikz](https://ctan.org/pkg/pgf)
|
24
|
+
outputs [tikz](https://ctan.org/pkg/pgf) code to stdout.
|
25
25
|
|
26
26
|
See `xpm2tikz -h`.
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
1.0.0
|
data/bin/img2pixel
ADDED
data/lib/img2pixel.rb
ADDED
@@ -0,0 +1,154 @@
|
|
1
|
+
#! /usr/bin/env ruby
|
2
|
+
# -*- coding: utf-8 -*-
|
3
|
+
|
4
|
+
require 'pixel_raster'
|
5
|
+
require 'optparse'
|
6
|
+
|
7
|
+
# Base module
|
8
|
+
module PixelRaster
|
9
|
+
class MagickConverter
|
10
|
+
|
11
|
+
# Parse command line arguments (+ARGV+) and execute accordingly
|
12
|
+
#
|
13
|
+
# See +img2pixel -h+ for a list of options.
|
14
|
+
def self.command_line(argv=ARGV, stdout=$stdout, stderr=$stderr)
|
15
|
+
outfile = nil
|
16
|
+
outdir = nil
|
17
|
+
outmode = :both
|
18
|
+
empty_suffix = '-empty'
|
19
|
+
filled_suffix = '-filled'
|
20
|
+
params = {
|
21
|
+
type: :svg,
|
22
|
+
bg_color: 'white'
|
23
|
+
}
|
24
|
+
|
25
|
+
if argv.length == 0 # no argument given
|
26
|
+
argv << '-h' # give the help text
|
27
|
+
end
|
28
|
+
|
29
|
+
begin
|
30
|
+
OptionParser.new do |p|
|
31
|
+
p.accept(Symbol) { |s| s.downcase.to_sym }
|
32
|
+
|
33
|
+
p.banner = "Convert image(s) to a pixel representation."
|
34
|
+
|
35
|
+
p.separator ""
|
36
|
+
p.separator "Usage: #{$0} [options] INFILE [INFILE…]"
|
37
|
+
p.separator ""
|
38
|
+
p.separator "For each INFILE two output files are written for the empty and filled version"
|
39
|
+
p.separator "(but see --mode)."
|
40
|
+
p.separator ""
|
41
|
+
p.separator "With “-O OUTFILE” all images are output to OUTFILE."
|
42
|
+
p.separator "Caveat: having several <svg>-elements concatenated in one file"
|
43
|
+
p.separator "may not be supported by your viewer."
|
44
|
+
p.separator ""
|
45
|
+
p.separator "Options:"
|
46
|
+
p.on_tail("-h", "--help", "Show this help message and exit.") do
|
47
|
+
stdout.puts p
|
48
|
+
exit
|
49
|
+
end
|
50
|
+
|
51
|
+
p.on("-V", "--version", "Show version information and exit.") do
|
52
|
+
stdout.puts File.read(File.join(File.dirname(__FILE__),'..','VERSION'))
|
53
|
+
exit
|
54
|
+
end
|
55
|
+
|
56
|
+
p.on('-O', '--output FILENAME',
|
57
|
+
'Write output to FILENAME (“-” for STDOUT).',
|
58
|
+
'If this option is given, all images',
|
59
|
+
'are written to the same file.',
|
60
|
+
'If this is given, “-d” is ignored.') do |filename|
|
61
|
+
outfile = filename
|
62
|
+
end
|
63
|
+
|
64
|
+
p.on("-#", "--nr-of-colors N", Numeric,
|
65
|
+
"Number of colors for the final image.") do |n|
|
66
|
+
params[:nr_of_colors] = n
|
67
|
+
end
|
68
|
+
|
69
|
+
p.on('-c', '--bg-color COLOR',
|
70
|
+
"Background-color for empty version.",
|
71
|
+
"SVG color name. May be “none”.",
|
72
|
+
"Ignored for --type tikz.",
|
73
|
+
"Default: #{params[:bg_color]}") do |color|
|
74
|
+
params[:bg_color] = color
|
75
|
+
end
|
76
|
+
|
77
|
+
p.on('-t', '--type TYPE', Symbol,
|
78
|
+
'The output type (svg or tikz).',
|
79
|
+
"Default: #{params[:type]}") do |type|
|
80
|
+
[:svg,:tikz].include?(type) or
|
81
|
+
p.abort("unknown output type #{type}")
|
82
|
+
params[:type] = type
|
83
|
+
end
|
84
|
+
|
85
|
+
p.on('-m', '--mode MODE', Symbol,
|
86
|
+
'Output mode (both, empty, filled).',
|
87
|
+
"Default: #{outmode}") do |mode|
|
88
|
+
[:both,:empty,:filled].include?(mode) or
|
89
|
+
p.abort("unknown mode #{mode}")
|
90
|
+
outmode = mode
|
91
|
+
end
|
92
|
+
|
93
|
+
p.on('-l','--light2dark',
|
94
|
+
'color numbering direction.',
|
95
|
+
'Default: light2dark (unless 2 color imgs)') do
|
96
|
+
params[:light2dark] = true
|
97
|
+
end
|
98
|
+
|
99
|
+
p.on('-L','--dark2light',
|
100
|
+
'color numbering direction.',
|
101
|
+
'Default: light2dark (unless 2 color imgs)') do
|
102
|
+
params[:light2dark] = false
|
103
|
+
end
|
104
|
+
|
105
|
+
p.on('-d', '--dir DIR',
|
106
|
+
'directory for output files.',
|
107
|
+
'Must exist and be writeable.',
|
108
|
+
"Default: same as INFILE") do |dir|
|
109
|
+
outdir = dir
|
110
|
+
end
|
111
|
+
end.parse!(argv)
|
112
|
+
rescue OptionParser::ParseError => pe
|
113
|
+
stderr.puts pe
|
114
|
+
exit 22 # Errno::EINVAL::Errno (but may not exist on all platforms)
|
115
|
+
end
|
116
|
+
|
117
|
+
|
118
|
+
MagickConverter.new(**params) do |c|
|
119
|
+
if outfile == '-' # Write everything to standard out
|
120
|
+
argv.each do |infile|
|
121
|
+
stdout << c.convert(infile, mode: :empty) unless outmode==:filled
|
122
|
+
stdout << c.convert(infile, mode: :filled) unless outmode==:empty
|
123
|
+
end
|
124
|
+
elsif outfile # an outfile is given, so write everything in this file
|
125
|
+
File.open(outfile, 'w') do |out|
|
126
|
+
argv.each do |infile|
|
127
|
+
out << c.convert(infile, mode: :empty)
|
128
|
+
out << c.convert(infile, mode: :filled)
|
129
|
+
end
|
130
|
+
end
|
131
|
+
else
|
132
|
+
argv.each do |infile|
|
133
|
+
# if no outfile is given we generate the filename from the infile
|
134
|
+
base_out = File.join(outdir || File.dirname(infile),
|
135
|
+
File.basename(infile, '.*'))
|
136
|
+
|
137
|
+
empty_out = base_out + empty_suffix + '.' + params[:type].to_s
|
138
|
+
filled_out = base_out + filled_suffix + '.' + params[:type].to_s
|
139
|
+
unless outmode==:filled
|
140
|
+
File.open(empty_out,"w") do |out|
|
141
|
+
out << c.convert(infile, mode: :empty)
|
142
|
+
end
|
143
|
+
end
|
144
|
+
unless outmode==:empty
|
145
|
+
File.open(filled_out,"w") do |out|
|
146
|
+
out << c.convert(infile, mode: :filled)
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
data/lib/pixel_raster.rb
CHANGED
@@ -1,44 +1,185 @@
|
|
1
1
|
#! /usr/bin/env ruby
|
2
|
+
# -*- coding: utf-8 -*-
|
3
|
+
|
4
|
+
require 'rmagick'
|
2
5
|
|
3
6
|
module PixelRaster
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
colors
|
7
|
+
class MagickConverter
|
8
|
+
|
9
|
+
attr_reader :nr_of_colors, :bg_color, :resize, :light2dark, :type
|
10
|
+
|
11
|
+
# The converter is initialized with the constraints
|
12
|
+
# for the images to be processed.
|
13
|
+
#
|
14
|
+
# Any image must have a color index (palette), this is enforced.
|
15
|
+
# It is not that useful to have more than 10 colors or more than 30x30
|
16
|
+
# pixels, but this is only enforced if the corresponding parameters
|
17
|
+
# are given.
|
18
|
+
# @see #read_image
|
19
|
+
#
|
20
|
+
# @param nr_of_colors maximum number of colors for the color index
|
21
|
+
# @param bg_color background color for empty pixel image (svg)
|
22
|
+
# @param resize [String] resize string (see ImageMagick resize option)
|
23
|
+
# @param light2dark [Boolean] if true 0 is the lightest color
|
24
|
+
# @param type [:svg|:tikz]
|
25
|
+
def initialize(nr_of_colors: nil, bg_color: 'white',
|
26
|
+
resize: nil, light2dark: nil,
|
27
|
+
type: :svg)
|
28
|
+
@nr_of_colors = nr_of_colors
|
29
|
+
@bg_color = bg_color
|
30
|
+
@resize = resize
|
31
|
+
@light2dark = light2dark
|
32
|
+
@type = type
|
33
|
+
yield(self) if block_given?
|
34
|
+
end
|
18
35
|
|
19
|
-
|
20
|
-
|
21
|
-
|
36
|
+
# Reads an image using RMagick and adjusts size and palette.
|
37
|
+
# As we need an indexed image we check and create one if neccessary.
|
38
|
+
#
|
39
|
+
# @see #initialize
|
40
|
+
# @param filename read this file
|
41
|
+
# @return [Magick::Image] the (processed) image
|
42
|
+
def read_image(filename)
|
43
|
+
# We assume each file includes exactly one image
|
44
|
+
image = Magick::Image.read(filename).first
|
45
|
+
|
46
|
+
# on demand we resize the image
|
47
|
+
# the API is kinda complicated here:
|
48
|
+
# change_geometry computes the cols and rows from the given resize string
|
49
|
+
# and yields the given block with these values
|
50
|
+
if @resize
|
51
|
+
image.change_geometry(@resize) do |cols, rows, i|
|
52
|
+
image = i.resize!(cols, rows)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
# on demand we reduce the number of colors
|
57
|
+
if @nr_of_colors
|
58
|
+
image = image.quantize(@nr_of_colors)
|
59
|
+
else
|
60
|
+
# If the image does not have a palette/color index (and no number of
|
61
|
+
# columns was given) we create one with 2 colors.
|
62
|
+
unless image.palette?
|
63
|
+
image = image.quantize(2)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
# finally we remove duplicate colors.
|
67
|
+
image.compress_colormap!
|
68
|
+
|
69
|
+
return image
|
70
|
+
end
|
71
|
+
|
72
|
+
# Wrapper for the converter methods.
|
73
|
+
#
|
74
|
+
# @param mode [:empty|:filled] the output mode
|
75
|
+
# @param type [:svg|:tikz] the output type
|
76
|
+
# @see #image2tikz
|
77
|
+
# @see #image2svg
|
78
|
+
def convert(filename, mode:, type: @type)
|
79
|
+
img = read_image(filename)
|
80
|
+
case type
|
81
|
+
when :tikz
|
82
|
+
image2tikz(img, prefix: mode==:empty ? 'n' : 'y')
|
83
|
+
when :svg
|
84
|
+
image2svg(img, mode: mode)
|
85
|
+
else
|
86
|
+
raise "unknown output type #{type}"
|
87
|
+
end
|
22
88
|
end
|
23
89
|
|
24
|
-
|
25
|
-
|
90
|
+
|
91
|
+
# create a tikz pixel representation of an image.
|
92
|
+
#
|
93
|
+
# @param img [Magick::Image] the image
|
94
|
+
# @param prefix [String] node class prefix (to distinguish empty and filled mode)
|
95
|
+
def image2tikz(img, prefix: "n")
|
96
|
+
|
97
|
+
colormap = compute_colormap(img)
|
98
|
+
|
99
|
+
tikz = " \\begin{tikzpicture}[yscale=-1]
|
100
|
+
\\draw[step=1,help lines] (0,0) grid (#{img.columns},#{img.rows});
|
101
|
+
"
|
102
|
+
img.each_pixel do |p, c, r|
|
103
|
+
cc = colormap[p]
|
104
|
+
tikz << " \\node[#{prefix}#{cc}] at (#{c+0.5},#{r+0.5}) {#{cc}};\n"
|
105
|
+
end
|
106
|
+
tikz << "
|
107
|
+
\\end{tikzpicture}
|
108
|
+
|
109
|
+
"
|
110
|
+
tikz
|
111
|
+
end
|
112
|
+
|
113
|
+
# create a svg pixel representation of an image.
|
114
|
+
#
|
115
|
+
# @param img [Magick::Image] the image
|
116
|
+
# @param mode [:empty|:filled] fill mode
|
117
|
+
def image2svg(img, mode: :empty)
|
118
|
+
|
119
|
+
colormap = compute_colormap(img)
|
120
|
+
|
121
|
+
# ToDo: make these flexible:
|
122
|
+
xw = 20
|
123
|
+
yh = 20
|
124
|
+
xwt = 10
|
125
|
+
yht = 15
|
126
|
+
|
127
|
+
# We create the SVG directly by string
|
128
|
+
svg = %Q'<svg xmlns="http://www.w3.org/2000/svg">
|
129
|
+
<style>
|
130
|
+
.pixel {
|
131
|
+
stroke : black;
|
132
|
+
stroke-width : 1;
|
133
|
+
'
|
134
|
+
if mode == :empty
|
135
|
+
svg << "
|
136
|
+
fill: #{@bg_color};
|
137
|
+
"
|
138
|
+
end
|
139
|
+
svg << '
|
140
|
+
}
|
141
|
+
'
|
142
|
+
if mode == :filled
|
143
|
+
colormap.each do |k,v|
|
144
|
+
color = k.to_color(Magick::AllCompliance,false, 8, true)
|
145
|
+
textcolor = k.to_hsla[2] > 50 ? 'black' : 'white'
|
146
|
+
svg << %Q{ .p#{v} { fill: #{color}; }\n}
|
147
|
+
|
148
|
+
svg << %Q{ .t#{v} { fill: #{textcolor}; }\n}
|
149
|
+
end
|
150
|
+
end
|
151
|
+
svg << %Q{ </style>\n}
|
152
|
+
img.each_pixel do |p, c, r|
|
153
|
+
cc = colormap[p]
|
154
|
+
svg << %Q{ <rect x="#{c*xw}" y="#{r*yh}" width="#{xw}" height="#{yh}" class="pixel p#{cc}" />\n}
|
155
|
+
svg << %Q{ <text x="#{c*xw+xwt}" y="#{r*yh+yht}" text-anchor="middle" class="t#{cc}">#{cc}</text>\n}
|
156
|
+
end
|
157
|
+
svg << "</svg>\n"
|
158
|
+
svg
|
159
|
+
end
|
160
|
+
|
161
|
+
# compute an ordered colormap for the image
|
162
|
+
# @param img [Magick::Image] the image
|
163
|
+
# @param light2dark [Boolean] whether to invert the color map
|
164
|
+
def compute_colormap(img, light2dark: @light2dark)
|
165
|
+
# we create a colormap sorted from dark to light
|
166
|
+
colormap= {};
|
167
|
+
colors = img.color_histogram.map(&:first).sort_by(&:intensity)
|
168
|
+
# at least for a black and white image having 0 as white and
|
169
|
+
# 1 as black is more intuitive as you paint the pixels in black
|
170
|
+
# which are set. This is different from the common internal
|
171
|
+
# representation of images!
|
172
|
+
# We therefore assume light2dark _unless_ it's a 2-color image
|
173
|
+
# if it is not explicitely set:
|
174
|
+
light2dark = colors.length != 2 if light2dark.nil?
|
175
|
+
colors.reverse! if light2dark
|
176
|
+
colors.each_with_index do |p,i|
|
177
|
+
colormap[p]=i
|
178
|
+
end
|
179
|
+
return colormap
|
26
180
|
end
|
27
|
-
end
|
28
|
-
|
29
|
-
def xpm2tikz(filename, prefix="n")
|
30
|
-
xpm = read_xpm(filename)
|
31
|
-
"\\begin{tikzpicture}[yscale=-1]
|
32
|
-
\\draw[step=1,help lines] (0,0) grid (#{xpm.first.length},#{xpm.length});
|
33
|
-
" + xpm.each_with_index.collect { |row,r|
|
34
|
-
row.each_with_index.collect { |color,c|
|
35
|
-
cc = color=='#FFFFFF' ? '0' : '1'
|
36
|
-
"\\node[#{prefix+cc}] at (#{c+0.5},#{r+0.5}) {#{cc}};"
|
37
|
-
}.join("\n")
|
38
|
-
}.join("\n") + "
|
39
|
-
\\end{tikzpicture}
|
40
|
-
"
|
41
181
|
end
|
42
182
|
end
|
43
183
|
|
184
|
+
|
44
185
|
|
metadata
CHANGED
@@ -1,28 +1,57 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: pixel_raster
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 1.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Klaus Stein
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2019-
|
12
|
-
dependencies:
|
11
|
+
date: 2019-08-05 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rmagick
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '4'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '4'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: mititest-rg
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
13
41
|
description: Create a pixel raster representation of an image (for educational purposes).
|
14
42
|
email: ruby@istik.de
|
15
43
|
executables:
|
16
|
-
-
|
44
|
+
- img2pixel
|
17
45
|
extensions: []
|
18
46
|
extra_rdoc_files: []
|
19
47
|
files:
|
20
48
|
- Beispiel.svg
|
21
49
|
- LICENSE
|
22
50
|
- README.md
|
23
|
-
-
|
51
|
+
- VERSION
|
52
|
+
- bin/img2pixel
|
53
|
+
- lib/img2pixel.rb
|
24
54
|
- lib/pixel_raster.rb
|
25
|
-
- lib/xpm2tikz.rb
|
26
55
|
homepage: https://github.com/Lapizistik/pixel_raster
|
27
56
|
licenses:
|
28
57
|
- GPL-3.0
|
@@ -43,8 +72,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
43
72
|
version: '0'
|
44
73
|
requirements: []
|
45
74
|
rubyforge_project:
|
46
|
-
rubygems_version: 2.
|
75
|
+
rubygems_version: 2.7.6.2
|
47
76
|
signing_key:
|
48
77
|
specification_version: 4
|
49
|
-
summary: Pixel image
|
78
|
+
summary: Pixel image raster visualization
|
50
79
|
test_files: []
|
data/bin/xpm2tikz
DELETED
data/lib/xpm2tikz.rb
DELETED
@@ -1,45 +0,0 @@
|
|
1
|
-
#! /usr/bin/env ruby
|
2
|
-
# -*- coding: utf-8 -*-
|
3
|
-
|
4
|
-
require 'pixel_raster'
|
5
|
-
require 'optparse'
|
6
|
-
|
7
|
-
module PixelRaster
|
8
|
-
|
9
|
-
def command_line
|
10
|
-
out = $stdout
|
11
|
-
|
12
|
-
OptionParser.new do |parser|
|
13
|
-
parser.banner = "\nConvert xpm image(s) to tikz. \n\nUsage: $0 [-O OUTFILE] INFILE [INFILE…]"
|
14
|
-
|
15
|
-
parser.on("-h", "--help", "Show this help message") do
|
16
|
-
puts parser
|
17
|
-
exit
|
18
|
-
end
|
19
|
-
|
20
|
-
parser.on('-O', '--output FILENAME',
|
21
|
-
'write output to FILENAME (default: stdout)') do |filename|
|
22
|
-
begin
|
23
|
-
# We open the outfile here without ensuring it will be closed.
|
24
|
-
# This is ok as we need it throughout the whole script runtime
|
25
|
-
# and it will be closed on exit automatically.
|
26
|
-
out = File.open(filename, 'w')
|
27
|
-
|
28
|
-
# We could be more specific about catching errors, but we
|
29
|
-
# exit the script anyway, and this may be different on different OSes.
|
30
|
-
rescue StandardError => e
|
31
|
-
warn "Could not open “#{filename}” for writing."
|
32
|
-
warn e
|
33
|
-
exit e.errno
|
34
|
-
end
|
35
|
-
end
|
36
|
-
end.parse!
|
37
|
-
|
38
|
-
ARGV.each do |filename|
|
39
|
-
out << xpm2tikz(filename)
|
40
|
-
out << "\n\\vfill\n\n"
|
41
|
-
out << xpm2tikz(filename,'y')
|
42
|
-
out << "\n\\vfill\n\n"
|
43
|
-
end
|
44
|
-
end
|
45
|
-
end
|