jpeg2pdf 0.12

Sign up to get free protection for your applications and to get access to all the features.
@@ -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