jpeg2pdf 0.12

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.
@@ -0,0 +1,18 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = %q{jpeg2pdf}
3
+ s.version = "0.12"
4
+ s.date = Time.now
5
+ s.summary = %q{jpeg2pdf is a free program that converts a directory of JPEG files to a PDF file.}
6
+ s.author = %q{Koen Vervloesem}
7
+ s.email = %q{koen.vervloesem@myrealbox.com}
8
+ s.homepage = %q{http://koan.studentenweb.org/software/jpeg2pdf.html}
9
+ s.autorequire = %q{jpeg2pdf}
10
+ s.require_path = %q{lib}
11
+ s.default_executable = %q{jpeg2pdf}
12
+ s.has_rdoc = true
13
+ s.files = Dir.glob('**/*')
14
+ s.rdoc_options = ["--main", "README"]
15
+ s.extra_rdoc_files = ["README"]
16
+ s.executables = ["jpeg2pdf"]
17
+ s.bindir = %q{bin}
18
+ end
@@ -0,0 +1,291 @@
1
+ #!ruby
2
+
3
+ # This is an adapted version of the image_size library to support
4
+ # some extra information from the JPEG file format.
5
+ # Additions have a comment with the word 'koan'.
6
+
7
+ class ImageSize
8
+
9
+ # Image Type Constants
10
+ module Type
11
+ OTHER = "OTHER"
12
+ GIF = "GIF"
13
+ PNG = "PNG"
14
+ JPEG = "JPEG"
15
+ BMP = "BMP"
16
+ PPM = "PPM" # PPM is like PBM, PGM, & XV
17
+ PBM = "PBM"
18
+ PGM = "PGM"
19
+ # XV = "XV"
20
+ XBM = "XBM"
21
+ TIFF = "TIFF"
22
+ XPM = "XPM"
23
+ PSD = "PSD"
24
+ PCX = "PCX"
25
+ SWF = "SWF"
26
+ end
27
+
28
+ JpegCodeCheck = [
29
+ "\xc0", "\xc1", "\xc2", "\xc3",
30
+ "\xc5", "\xc6", "\xc7",
31
+ "\xc9", "\xca", "\xcb",
32
+ "\xcd", "\xce", "\xcf",
33
+ ]
34
+
35
+ # image type list
36
+ def ImageSize.type_list
37
+ Type.constants
38
+ end
39
+
40
+ # receive image & make size
41
+ # argument is image String or IO
42
+ def initialize(img_data, img_type = nil)
43
+ @img_data = img_data.dup
44
+ @img_wedth = nil
45
+ @img_height = nil
46
+
47
+ if @img_data.is_a?(IO)
48
+ @img_top = @img_data.read(128)
49
+ @img_data.seek(0, 0)
50
+ # define Singleton-method definition to IO (byte, offset)
51
+ def @img_data.read_o(length = 1, offset = nil)
52
+ self.seek(offset, 0) if offset
53
+ ret = self.read(length)
54
+ raise "cannot read!!" unless ret
55
+ ret
56
+ end
57
+ elsif @img_data.is_a?(String)
58
+ @img_top = @img_data[0, 128]
59
+ # define Singleton-method definition to String (byte, offset)
60
+ def @img_data.read_o(length = 1, offset = nil)
61
+ @img_offset = 0 if !(defined?(@img_offset))
62
+ @img_offset = offset if offset
63
+ ret = self[@img_offset, length]
64
+ @img_offset += length
65
+ ret
66
+ end
67
+ else
68
+ raise "argument class error!! #{img_data.type}"
69
+ end
70
+
71
+ if img_type.nil?
72
+ @img_type = check_type()
73
+ else
74
+ match = false
75
+ Type.constants.each do |t|
76
+ match = true if img_type == t
77
+ end
78
+ raise("img_type is failed. #{img_type}\n") if match == false
79
+ @img_type = img_type
80
+ end
81
+
82
+ eval("@img_width, @img_height = measure_" + @img_type + "()") if @img_type != Type::OTHER
83
+ end
84
+
85
+ # get parameter
86
+ def get_type; @img_type; end
87
+ def get_height
88
+ if @img_type == Type::OTHER then nil else @img_height end
89
+ end
90
+ def get_width
91
+ if @img_type == Type::OTHER then nil else @img_width end
92
+ end
93
+
94
+ # koan: additions to get the precision and number of components of a JPEG file
95
+ def get_bpc
96
+ if @img_type == Type::JPEG then @img_bpc else nil end
97
+ end
98
+ def get_components
99
+ if @img_type == Type::JPEG then @img_components else nil end
100
+ end
101
+
102
+ def check_type()
103
+ if @img_top =~ /^GIF8[7,9]a/ then Type::GIF
104
+ elsif @img_top[0, 8] == "\x89PNG\x0d\x0a\x1a\x0a" then Type::PNG
105
+ elsif @img_top[0, 2] == "\xFF\xD8" then Type::JPEG
106
+ elsif @img_top[0, 2] == 'BM' then Type::BMP
107
+ elsif @img_top =~ /^P[1-7]/ then Type::PPM
108
+ elsif @img_top =~ /\#define\s+\S+\s+\d+/ then Type::XBM
109
+ elsif @img_top[0, 4] == "MM\x00\x2a" then Type::TIFF
110
+ elsif @img_top[0, 4] == "II\x2a\x00" then Type::TIFF
111
+ elsif @img_top =~ /\/\* XPM \*\// then Type::XPM
112
+ elsif @img_top[0, 4] == "8BPS" then Type::PSD
113
+ elsif @img_top[0, 3] == "FWS" then Type::SWF
114
+ elsif @img_top[0] == 10 then Type::PCX
115
+ else Type::OTHER
116
+ end
117
+ end
118
+ private(:check_type)
119
+
120
+ def measure_GIF()
121
+ @img_data.read_o(6)
122
+ @img_data.read_o(4).unpack('vv')
123
+ end
124
+ private(:measure_GIF)
125
+
126
+ def measure_PNG()
127
+ @img_data.read_o(12)
128
+ raise unless @img_data.read_o(4) == "IHDR"
129
+ @img_data.read_o(8).unpack('NN')
130
+ end
131
+ private(:measure_PNG)
132
+
133
+ def measure_JPEG()
134
+ c_marker = "\xFF" # Section marker.
135
+ @img_data.read_o(2)
136
+ while(true)
137
+ marker, code, length = @img_data.read_o(4).unpack('aan')
138
+ raise "JPEG marker not found!" if marker != c_marker
139
+
140
+ if JpegCodeCheck.include?(code)
141
+ # koan: additions to extract the precision (bits per component) and number of components
142
+ @img_bpc, height, width, @img_components = @img_data.read_o(6).unpack('CnnC')
143
+ return([width, height])
144
+ end
145
+ @img_data.read_o(length - 2)
146
+ end
147
+ end
148
+ private(:measure_JPEG)
149
+
150
+ def measure_BMP()
151
+ @img_data.read_o(26).unpack("x18VV");
152
+ end
153
+ private(:measure_BMP)
154
+
155
+ def measure_PPM()
156
+ header = @img_data.read_o(1024)
157
+ header.gsub!(/^\#[^\n\r]*/m, "")
158
+ header =~ /^(P[1-6])\s+?(\d+)\s+?(\d+)/m
159
+ width = $2.to_i; height = $3.to_i
160
+ case $1
161
+ when "P1", "P4" then @img_type = "PBM"
162
+ when "P2", "P5" then @img_type = "PGM"
163
+ when "P3", "P6" then @img_type = "PPM"
164
+ # when "P7"
165
+ # @img_type = "XV"
166
+ # header =~ /IMGINFO:(\d+)x(\d+)/m
167
+ # width = $1.to_i; height = $2.to_i
168
+ end
169
+ [width, height]
170
+ end
171
+ private(:measure_PPM)
172
+
173
+ alias :measure_PGM :measure_PPM
174
+ private(:measure_PGM)
175
+ alias :measure_PBM :measure_PPM
176
+ private(:measure_PBM)
177
+
178
+ def measure_XBM()
179
+ @img_data.read_o(1024) =~ /^\#define\s*\S*\s*(\d+)\s*\n\#define\s*\S*\s*(\d+)/mi
180
+ [$1.to_i, $2.to_i]
181
+ end
182
+ private(:measure_XBM)
183
+
184
+ def measure_XPM()
185
+ width = height = nil
186
+ while(line = @img_data.read_o(1024))
187
+ if line =~ /"\s*(\d+)\s+(\d+)(\s+\d+\s+\d+){1,2}\s*"/m
188
+ width = $1.to_i; height = $2.to_i
189
+ break
190
+ end
191
+ end
192
+ [width, height]
193
+ end
194
+ private(:measure_XPM)
195
+
196
+ def measure_PSD()
197
+ @img_data.read_o(26).unpack("x14NN")
198
+ end
199
+ private(:measure_PSD)
200
+
201
+ def measure_TIFF()
202
+ endian = if (@img_data.read_o(4) =~ /II\x2a\x00/o) then 'v' else 'n' end
203
+ # 'v' little-endian 'n' default to big-endian
204
+
205
+ packspec = [
206
+ nil, # nothing (shouldn't happen)
207
+ 'C', # BYTE (8-bit unsigned integer)
208
+ nil, # ASCII
209
+ endian, # SHORT (16-bit unsigned integer)
210
+ endian.upcase, # LONG (32-bit unsigned integer)
211
+ nil, # RATIONAL
212
+ 'c', # SBYTE (8-bit signed integer)
213
+ nil, # UNDEFINED
214
+ endian, # SSHORT (16-bit unsigned integer)
215
+ endian.upcase, # SLONG (32-bit unsigned integer)
216
+ ]
217
+
218
+ offset = @img_data.read_o(4).unpack(endian.upcase)[0] # Get offset to IFD
219
+
220
+ ifd = @img_data.read_o(2, offset)
221
+ num_dirent = ifd.unpack(endian)[0] # Make it useful
222
+ offset += 2
223
+ num_dirent = offset + (num_dirent * 12); # Calc. maximum offset of IFD
224
+
225
+ ifd = width = height = nil
226
+ while(width.nil? || height.nil?)
227
+ ifd = @img_data.read_o(12, offset) # Get first directory entry
228
+ break if (ifd.nil? || (offset > num_dirent))
229
+ offset += 12
230
+ tag = ifd.unpack(endian)[0] # ...and decode its tag
231
+ type = ifd[2, 2].unpack(endian)[0] # ...and the data type
232
+
233
+ # Check the type for sanity.
234
+ next if (type > packspec.size + 0) || (packspec[type].nil?)
235
+ if tag == 0x0100 # Decode the value
236
+ width = ifd[8, 4].unpack(packspec[type])[0]
237
+ elsif tag == 0x0101 # Decode the value
238
+ height = ifd[8, 4].unpack(packspec[type])[0]
239
+ end
240
+ end
241
+
242
+ raise "#{if width.nil? then 'width not defined.' end} #{if height.nil? then 'height not defined.' end}" if width.nil? || height.nil?
243
+ [width, height]
244
+ end
245
+ private(:measure_TIFF)
246
+
247
+ def measure_PCX()
248
+ header = @img_data.read_o(128)
249
+ head_part = header.unpack('C4S4')
250
+ width = head_part[6] - head_part[4] + 1
251
+ height = head_part[7] - head_part[5] + 1
252
+ [width, height]
253
+ end
254
+ private(:measure_PCX)
255
+
256
+ def measure_SWF()
257
+ header = @img_data.read_o(9)
258
+ raise("This file is not SWF.") unless header.unpack('a3')[0] == 'FWS'
259
+
260
+ bit_length = Integer("0b#{header.unpack('@8B5')}")
261
+ header << @img_data.read_o(bit_length*4/8+1)
262
+ str = header.unpack("@8B#{5+bit_length*4}")[0]
263
+ last = 5
264
+ x_min = Integer("0b#{str[last,bit_length]}")
265
+ x_max = Integer("0b#{str[(last += bit_length),bit_length]}")
266
+ y_min = Integer("0b#{str[(last += bit_length),bit_length]}")
267
+ y_max = Integer("0b#{str[(last += bit_length),bit_length]}")
268
+ width = (x_max - x_min)/20
269
+ height = (y_max - y_min)/20
270
+ [width, height]
271
+ end
272
+ private(:measure_PCX)
273
+ end
274
+
275
+
276
+ if __FILE__ == $0
277
+ print "TypeList: #{ImageSize.type.inspect}\n"
278
+
279
+ Dir.glob("*").each do |file|
280
+ print "#{file} (string)\n"
281
+ open(file, "rb") do |fh|
282
+ img = ImageSize.new(fh.read)
283
+ print <<-EOF
284
+ type: #{img.get_type.inspect}
285
+ width: #{img.get_width.inspect}
286
+ height: #{img.get_height.inspect}
287
+ EOF
288
+ end
289
+ end
290
+ end
291
+
@@ -0,0 +1,84 @@
1
+ # License
2
+ #
3
+ # Copyright (c) 2004 Koen Vervloesem. All rights reserved.
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining
6
+ # a copy of this software and associated documentation files (the
7
+ # "Software"), to deal in the Software without restriction, including
8
+ # without limitation the rights to use, copy, modify, merge, publish,
9
+ # distribute, and/or sell copies of the Software, and to permit persons
10
+ # to whom the Software is furnished to do so, provided that the above
11
+ # copyright notice(s) and this permission notice appear in all copies of
12
+ # the Software and that both the above copyright notice(s) and this
13
+ # permission notice appear in supporting documentation.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
18
+ # OF THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
19
+ # HOLDERS INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY
20
+ # SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER
21
+ # RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF
22
+ # CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
23
+ # CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
24
+ #
25
+ # Except as contained in this notice, the name of a copyright holder
26
+ # shall not be used in advertising or otherwise to promote the sale, use
27
+ # or other dealings in this Software without prior written authorization
28
+ # of the copyright holder.
29
+
30
+ require 'rpdf'
31
+ require 'image_size'
32
+
33
+ class JPEG2PDF
34
+ include RPDF
35
+
36
+ def initialize(pdf)
37
+ @doc = Document.new(pdf)
38
+ @imagenumber = 0
39
+ end
40
+
41
+ def add_jpeg(filename)
42
+ jpegfile = File.open(filename)
43
+ imagedata = jpegfile.read
44
+ jpegfile.close
45
+ size = ImageSize.new(imagedata)
46
+ if(size.get_type == 'JPEG')
47
+ width = size.get_width
48
+ height = size.get_height
49
+ bpc = size.get_bpc
50
+ components = size.get_components
51
+ if components == 1
52
+ colorspace = :DeviceGray
53
+ elsif components == 3
54
+ colorspace = :DeviceRGB
55
+ elsif components == 4
56
+ colorspace = :DeviceCMYK
57
+ else
58
+ raise Exception.new("Unsupported colorspace in image #{filename}")
59
+ end
60
+
61
+ imagestream = PDFStream.new(imagedata, @doc)
62
+ imagestream.dict[:Filter] = [:DCTDecode]
63
+ imagestream.dict[:Name] = "Image#{@imagenumber}".intern
64
+
65
+ imagedictionary = ImageDictionary.new(width, height, colorspace, bpc)
66
+ imagestream.dict.update(imagedictionary)
67
+ imagestream.invalidate # write jpeg as soon as possible
68
+
69
+ page = Page.new(@doc, @doc.pagetree)
70
+ page[:MediaBox] = [0, 0, width, height]
71
+ page[:Resources] = { :XObject => { "Image#{@imagenumber}".intern => imagestream } }
72
+
73
+ contentstream = PDFStream.new("q\n#{width} 0 0 #{height} 0 0 cm\n/Image#{@imagenumber} Do\nQ", @doc)
74
+
75
+ page[:Contents] = contentstream
76
+ @imagenumber = @imagenumber + 1
77
+ end
78
+ end
79
+
80
+ def close
81
+ @doc.close_pdf
82
+ end
83
+
84
+ end
@@ -0,0 +1,48 @@
1
+ # License
2
+ #
3
+ # Copyright (c) 2004 Koen Vervloesem. All rights reserved.
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining
6
+ # a copy of this software and associated documentation files (the
7
+ # "Software"), to deal in the Software without restriction, including
8
+ # without limitation the rights to use, copy, modify, merge, publish,
9
+ # distribute, and/or sell copies of the Software, and to permit persons
10
+ # to whom the Software is furnished to do so, provided that the above
11
+ # copyright notice(s) and this permission notice appear in all copies of
12
+ # the Software and that both the above copyright notice(s) and this
13
+ # permission notice appear in supporting documentation.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
18
+ # OF THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
19
+ # HOLDERS INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY
20
+ # SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER
21
+ # RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF
22
+ # CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
23
+ # CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
24
+ #
25
+ # Except as contained in this notice, the name of a copyright holder
26
+ # shall not be used in advertising or otherwise to promote the sale, use
27
+ # or other dealings in this Software without prior written authorization
28
+ # of the copyright holder.
29
+
30
+ require 'rpdf/document'
31
+ require 'rpdf/documentcatalog'
32
+ require 'rpdf/imagedictionary'
33
+ require 'rpdf/page'
34
+ require 'rpdf/pagetree'
35
+ require 'rpdf/pdfarray'
36
+ require 'rpdf/pdfboolean'
37
+ require 'rpdf/pdfdictionary'
38
+ require 'rpdf/pdfinteger'
39
+ require 'rpdf/pdfname'
40
+ require 'rpdf/pdfnull'
41
+ require 'rpdf/pdfnumeric'
42
+ require 'rpdf/pdfobject'
43
+ require 'rpdf/pdfstream'
44
+ require 'rpdf/pdfstring'
45
+ require 'rpdf/rectangle'
46
+ require 'rpdf/trailer'
47
+ require 'rpdf/util'
48
+ require 'rpdf/version'
@@ -0,0 +1,33 @@
1
+ module RPDF
2
+
3
+ # Cross-Reference Table
4
+ # This implementation uses only one cross-reference section
5
+ # and no free entries.
6
+ # [PDFRef15 p69]
7
+ class CrossRefTable
8
+
9
+ def initialize
10
+ @hash = Hash.new
11
+ end
12
+
13
+ def addUsedEntry(position, objectnumber)
14
+ @hash[objectnumber] = position
15
+ end
16
+
17
+ def size
18
+ @hash.size + 1
19
+ end
20
+
21
+ def to_pdf
22
+ bytes = "xref\n"
23
+ bytes << "0 #{size}\n"
24
+ bytes << "0000000000 65535 f \n"
25
+ @hash.keys.sort.each { |objectnumber|
26
+ bytes << ("%010d" % @hash[objectnumber]) << " 00000 n \n"
27
+ }
28
+ bytes << "\n"
29
+ end
30
+
31
+ end
32
+
33
+ end