pdf-writer 1.1.3 → 1.1.4
Sign up to get free protection for your applications and to get access to all the features.
- data/ChangeLog +1 -1
- data/LICENCE +9 -3
- data/README +1 -1
- data/bin/techbook +1 -1
- data/demo/chunkybacon.rb +1 -1
- data/demo/code.rb +1 -1
- data/demo/colornames.rb +1 -1
- data/demo/demo.rb +1 -1
- data/demo/gettysburg.rb +1 -1
- data/demo/hello.rb +1 -1
- data/demo/individual-i.rb +1 -1
- data/demo/pac.rb +1 -1
- data/demo/qr-language.rb +4 -4
- data/demo/qr-library.rb +1 -1
- data/lib/pdf/charts.rb +1 -1
- data/lib/pdf/charts/stddev.rb +2 -2
- data/lib/pdf/math.rb +1 -1
- data/lib/pdf/quickref.rb +17 -15
- data/lib/pdf/simpletable.rb +24 -23
- data/lib/pdf/techbook.rb +1 -1
- data/lib/pdf/writer.rb +21 -52
- data/lib/pdf/writer/arc4.rb +1 -1
- data/lib/pdf/writer/fontmetrics.rb +2 -1
- data/lib/pdf/writer/fonts/MustRead.html +19 -1
- data/lib/pdf/writer/graphics.rb +3 -3
- data/lib/pdf/writer/graphics/imageinfo.rb +365 -365
- data/lib/pdf/writer/lang.rb +1 -1
- data/lib/pdf/writer/lang/en.rb +1 -1
- data/lib/pdf/writer/object.rb +1 -1
- data/lib/pdf/writer/object/action.rb +1 -1
- data/lib/pdf/writer/object/annotation.rb +1 -1
- data/lib/pdf/writer/object/catalog.rb +1 -1
- data/lib/pdf/writer/object/contents.rb +1 -5
- data/lib/pdf/writer/object/destination.rb +1 -1
- data/lib/pdf/writer/object/encryption.rb +1 -1
- data/lib/pdf/writer/object/font.rb +1 -1
- data/lib/pdf/writer/object/fontdescriptor.rb +1 -1
- data/lib/pdf/writer/object/fontencoding.rb +1 -1
- data/lib/pdf/writer/object/image.rb +1 -5
- data/lib/pdf/writer/object/info.rb +3 -7
- data/lib/pdf/writer/object/outline.rb +1 -1
- data/lib/pdf/writer/object/outlines.rb +1 -1
- data/lib/pdf/writer/object/page.rb +1 -1
- data/lib/pdf/writer/object/pages.rb +1 -1
- data/lib/pdf/writer/object/procset.rb +1 -1
- data/lib/pdf/writer/object/viewerpreferences.rb +1 -1
- data/lib/pdf/writer/ohash.rb +1 -1
- data/lib/pdf/writer/oreader.rb +1 -1
- data/lib/pdf/writer/state.rb +1 -1
- data/lib/pdf/writer/strokestyle.rb +1 -1
- data/manual.pwd +1 -1
- metadata +115 -119
- data/demo/pagenumber.rb +0 -79
data/lib/pdf/writer/arc4.rb
CHANGED
@@ -6,7 +6,7 @@
|
|
6
6
|
# Licensed under a MIT-style licence. See LICENCE in the main distribution
|
7
7
|
# for full licensing information.
|
8
8
|
#
|
9
|
-
# $Id: fontmetrics.rb
|
9
|
+
# $Id: fontmetrics.rb 184 2007-12-10 03:18:48Z sandal $
|
10
10
|
#++
|
11
11
|
|
12
12
|
class PDF::Writer::FontMetrics
|
@@ -82,6 +82,7 @@ class PDF::Writer::FontMetrics
|
|
82
82
|
line.chomp!
|
83
83
|
line.strip!
|
84
84
|
key, *values = line.split
|
85
|
+
next if key.nil?
|
85
86
|
op = "#{key.downcase}=".to_sym
|
86
87
|
|
87
88
|
# I probably need to deal with MetricsSet. The default value is
|
@@ -1 +1,19 @@
|
|
1
|
-
<html>
|
1
|
+
<html>
|
2
|
+
|
3
|
+
<head>
|
4
|
+
<meta http-equiv="content-type" content="text/html;charset=iso-8859-1">
|
5
|
+
<meta name="generator" content="Adobe GoLive 4">
|
6
|
+
<title>Core 14 AFM Files - ReadMe</title>
|
7
|
+
</head>
|
8
|
+
|
9
|
+
<body bgcolor="white">
|
10
|
+
<font color="white">or</font>
|
11
|
+
<table border="0" cellpadding="0" cellspacing="2">
|
12
|
+
<tr>
|
13
|
+
<td width="40"></td>
|
14
|
+
<td width="300">This file and the 14 PostScript(R) AFM files it accompanies may be used, copied, and distributed for any purpose and without charge, with or without modification, provided that all copyright notices are retained; that the AFM files are not distributed without this file; that all modifications to this file or any of the AFM files are prominently noted in the modified file(s); and that this paragraph is not modified. Adobe Systems has no responsibility or obligation to support the use of the AFM files. <font color="white">Col</font></td>
|
15
|
+
</tr>
|
16
|
+
</table>
|
17
|
+
</body>
|
18
|
+
|
19
|
+
</html>
|
data/lib/pdf/writer/graphics.rb
CHANGED
@@ -6,7 +6,7 @@
|
|
6
6
|
# Licensed under a MIT-style licence. See LICENCE in the main distribution
|
7
7
|
# for full licensing information.
|
8
8
|
#
|
9
|
-
# $Id: graphics.rb
|
9
|
+
# $Id: graphics.rb 184 2007-12-10 03:18:48Z sandal $
|
10
10
|
#++
|
11
11
|
# Points for use in the drawing of polygons.
|
12
12
|
class PDF::Writer::PolygonPoint
|
@@ -518,7 +518,7 @@ module PDF::Writer::Graphics
|
|
518
518
|
end
|
519
519
|
|
520
520
|
# Add an image from a file to the current page at position <tt>(x,
|
521
|
-
# y)</tt> (the
|
521
|
+
# y)</tt> (the lower left-hand corner of the image). The image will be
|
522
522
|
# scaled to +width+ by +height+ units. The image may be a PNG or JPEG
|
523
523
|
# image.
|
524
524
|
#
|
@@ -550,7 +550,7 @@ module PDF::Writer::Graphics
|
|
550
550
|
end
|
551
551
|
|
552
552
|
# Add an image from a loaded image (JPEG or PNG) resource at position
|
553
|
-
# <tt>(x, y)</tt> (the
|
553
|
+
# <tt>(x, y)</tt> (the lower left-hand corner of the image) and scaled
|
554
554
|
# to +width+ by +height+ units. If provided, +image_info+ is a
|
555
555
|
# PDF::Writer::Graphics::ImageInfo object.
|
556
556
|
#
|
@@ -1,365 +1,365 @@
|
|
1
|
-
#--
|
2
|
-
# PDF::Writer for Ruby.
|
3
|
-
# http://rubyforge.org/projects/ruby-pdf/
|
4
|
-
# Copyright 2003 - 2005 Austin Ziegler.
|
5
|
-
#
|
6
|
-
# Licensed under a MIT-style licence. See LICENCE in the main distribution
|
7
|
-
# for full licensing information.
|
8
|
-
#
|
9
|
-
# This file is also licensed under standard Ruby licensing provisions: the
|
10
|
-
# Ruby licence and the GNU General Public Licence, version 2 or later.
|
11
|
-
#
|
12
|
-
# $Id: imageinfo.rb
|
13
|
-
#++
|
14
|
-
require 'pdf/writer/oreader'
|
15
|
-
|
16
|
-
# This is based on ImageSize, by Keisuke Minami <keisuke@rccn.com>. It can
|
17
|
-
# be found at http://www.rubycgi.org/tools/index.en.htm
|
18
|
-
#
|
19
|
-
# This has been integrated into PDF::Writer because as yet there has been
|
20
|
-
# no response to emails asking for my extensions to be integrated and a
|
21
|
-
# RubyGem package to be made available.
|
22
|
-
class PDF::Writer::Graphics::ImageInfo
|
23
|
-
# Image Format Constants
|
24
|
-
module Formats
|
25
|
-
OTHER = "OTHER"
|
26
|
-
GIF = "GIF" # CompuServe GIF87a and GIF89a images.
|
27
|
-
PNG = "PNG" # Portable Network Graphics, PNG
|
28
|
-
JPEG = "JPEG" # JPEG
|
29
|
-
BMP = "BMP" # Windows or OS/2 Bitmaps
|
30
|
-
PPM = "PPM" # PPM is like PBM, PGM, & XV
|
31
|
-
PBM = "PBM"
|
32
|
-
PGM = "PGM"
|
33
|
-
# XV = "XV" # Not supported
|
34
|
-
TIFF = "TIFF" # TIFF formats
|
35
|
-
XBM = "XBM" # X Bitmap
|
36
|
-
XPM = "XPM" # X Pixmap
|
37
|
-
PSD = "PSD" # PhotoShop
|
38
|
-
PCX = "PCX" # PCX Bitmap
|
39
|
-
SWF = "SWF" # Flash
|
40
|
-
end
|
41
|
-
Type = Formats
|
42
|
-
|
43
|
-
class << self
|
44
|
-
def formats
|
45
|
-
Formats.constants
|
46
|
-
end
|
47
|
-
alias :type_list :formats
|
48
|
-
end
|
49
|
-
|
50
|
-
JPEG_SOF_BLOCKS = %W(\xc0 \xc1 \xc2 \xc3 \xc5 \xc6 \xc7 \xc9 \xca \xcb \xcd \xce \xcf)
|
51
|
-
JPEG_APP_BLOCKS = %W(\xe0 \xe1 \xe2 \xe3 \xe4 \xe5 \xe6 \xe7 \xe8 \xe9 \xea \xeb \xec \xed \xee \xef)
|
52
|
-
|
53
|
-
# Receive image & make size. argument is image String or IO
|
54
|
-
def initialize(data, format = nil)
|
55
|
-
@data = data.dup rescue data
|
56
|
-
@info = {}
|
57
|
-
|
58
|
-
if @data.kind_of?(IO)
|
59
|
-
@top = @data.read(128)
|
60
|
-
@data.seek(0, 0)
|
61
|
-
# Define Singleton-method definition to IO (byte, offset)
|
62
|
-
def @data.read_o(length = 1, offset = nil)
|
63
|
-
self.seek(offset, 0) if offset
|
64
|
-
ret = self.read(length)
|
65
|
-
raise "cannot read!!" unless ret
|
66
|
-
ret
|
67
|
-
end
|
68
|
-
elsif @data.is_a?(String)
|
69
|
-
@top = @data[0, 128]
|
70
|
-
# Define Singleton-method definition to String (byte, offset)
|
71
|
-
@data.extend(PDF::Writer::OffsetReader)
|
72
|
-
else
|
73
|
-
raise "argument class error!! #{data.type}"
|
74
|
-
end
|
75
|
-
|
76
|
-
if format.nil?
|
77
|
-
@format = discover_format
|
78
|
-
else
|
79
|
-
match = false
|
80
|
-
Formats.constants.each { |t| match = true if format == t }
|
81
|
-
raise("format is failed. #{format}\n") unless match
|
82
|
-
@format = format
|
83
|
-
end
|
84
|
-
|
85
|
-
__send__("measure_#@format".intern) unless @format == Formats::OTHER
|
86
|
-
|
87
|
-
@data = data.dup
|
88
|
-
end
|
89
|
-
|
90
|
-
attr_reader :format
|
91
|
-
alias :get_type :format
|
92
|
-
attr_reader :height
|
93
|
-
alias :get_height :height
|
94
|
-
attr_reader :width
|
95
|
-
alias :get_width width
|
96
|
-
|
97
|
-
attr_reader :bits
|
98
|
-
attr_reader :channels
|
99
|
-
|
100
|
-
attr_reader :info
|
101
|
-
|
102
|
-
def discover_format
|
103
|
-
if @top =~ %r{^GIF8[79]a}
|
104
|
-
Formats::GIF
|
105
|
-
elsif @top[0, 3] == "\xff\xd8\xff"
|
106
|
-
Formats::JPEG
|
107
|
-
elsif @top[0, 8] == "\x89PNG\x0d\x0a\x1a\x0a"
|
108
|
-
Formats::PNG
|
109
|
-
elsif @top[0, 3] == "FWS"
|
110
|
-
Formats::SWF
|
111
|
-
elsif @top[0, 4] == "8BPS"
|
112
|
-
Formats::PSD
|
113
|
-
elsif @top[0, 2] == 'BM'
|
114
|
-
Formats::BMP
|
115
|
-
elsif @top[0, 4] == "MM\x00\x2a"
|
116
|
-
Formats::TIFF
|
117
|
-
elsif @top[0, 4] == "II\x2a\x00"
|
118
|
-
Formats::TIFF
|
119
|
-
elsif @top[0, 12] == "\x00\x00\x00\x0c\x6a\x50\x20\x20\x0d\x0a\x87\x0a"
|
120
|
-
Formats::JP2
|
121
|
-
elsif @top =~ %r{^P[1-7]}
|
122
|
-
Formats::PPM
|
123
|
-
elsif @top =~ %r{\#define\s+\S+\s+\d+}
|
124
|
-
Formats::XBM
|
125
|
-
elsif @top =~ %r{/\* XPM \*/}
|
126
|
-
Formats::XPM
|
127
|
-
elsif @top[0] == 10
|
128
|
-
Formats::PCX
|
129
|
-
else
|
130
|
-
Formats::OTHER # might be WBMP
|
131
|
-
end
|
132
|
-
end
|
133
|
-
private :discover_format
|
134
|
-
|
135
|
-
def measure_GIF
|
136
|
-
@data.read_o(6) # Skip GIF8.a
|
137
|
-
@width, @height, @bits = @data.read_o(5).unpack('vvC')
|
138
|
-
if @bits & 0x80 == 0x80
|
139
|
-
@bits = (@bits & 0x07) + 1
|
140
|
-
else
|
141
|
-
@bits = 0
|
142
|
-
end
|
143
|
-
@channels = 3
|
144
|
-
end
|
145
|
-
private :measure_GIF
|
146
|
-
|
147
|
-
def measure_PNG
|
148
|
-
@data.read_o(12)
|
149
|
-
raise "This file is not PNG." unless @data.read_o(4) == "IHDR"
|
150
|
-
# The file information is in the IHDR section.
|
151
|
-
# Offset Bytes Meaning
|
152
|
-
# 0 4 Width
|
153
|
-
# 5 4 Height
|
154
|
-
# 9 1 Bit Depth
|
155
|
-
# 10 1 Compression Method
|
156
|
-
# 11 1 Filter Method
|
157
|
-
# 12 1 Interlace Method
|
158
|
-
ihdr = @data.read_o(13).unpack("NNCCCCC")
|
159
|
-
@width = ihdr[0]
|
160
|
-
@height = ihdr[1]
|
161
|
-
@bits = ihdr[2]
|
162
|
-
@info[:color_type] = ihdr[3]
|
163
|
-
@info[:compression_method] = ihdr[4]
|
164
|
-
@info[:filter_method] = ihdr[5]
|
165
|
-
@info[:interlace_method] = ihdr[6]
|
166
|
-
|
167
|
-
|
168
|
-
end
|
169
|
-
private :measure_PNG
|
170
|
-
|
171
|
-
def measure_JPEG
|
172
|
-
c_marker = "\xff" # Section marker.
|
173
|
-
@data.read_o(2) # Skip the first two bytes of JPEG identifier.
|
174
|
-
loop do
|
175
|
-
marker, code, length = @data.read_o(4).unpack('aan')
|
176
|
-
raise "JPEG marker not found!" if marker != c_marker
|
177
|
-
|
178
|
-
if JPEG_SOF_BLOCKS.include?(code)
|
179
|
-
@bits, @height, @width, @channels = @data.read_o(6).unpack("CnnC")
|
180
|
-
break
|
181
|
-
end
|
182
|
-
|
183
|
-
buffer = @data.read_o(length - 2)
|
184
|
-
|
185
|
-
if JPEG_APP_BLOCKS.include?(code)
|
186
|
-
@info["APP#{code.to_i - 0xe0}"] = buffer
|
187
|
-
end
|
188
|
-
end
|
189
|
-
end
|
190
|
-
private :measure_JPEG
|
191
|
-
|
192
|
-
def measure_BMP
|
193
|
-
# Skip the first 14 bytes of the image.
|
194
|
-
@data.read_o(14)
|
195
|
-
# Up to the next 16 bytes will be used.
|
196
|
-
dim = @data.read_o(16)
|
197
|
-
|
198
|
-
# Get the "size" of the image from the next four bytes.
|
199
|
-
size = dim.unpack("V")
|
200
|
-
|
201
|
-
if size == 12
|
202
|
-
|
203
|
-
elsif size > 12 and (size <= 64 or size == 108)
|
204
|
-
|
205
|
-
end
|
206
|
-
end
|
207
|
-
private :measure_BMP
|
208
|
-
|
209
|
-
def measure_PPM
|
210
|
-
header = @data.read_o(1024)
|
211
|
-
header.gsub!(/^\#[^\n\r]*/m, "")
|
212
|
-
md = %r{^(P[1-6])\s+?(\d+)\s+?(\d+)}mo.match(header)
|
213
|
-
|
214
|
-
@width = md.captures[1]
|
215
|
-
@height = md.captures[2]
|
216
|
-
|
217
|
-
case md.captures[0]
|
218
|
-
when "P1", "P4"
|
219
|
-
@format = "PBM"
|
220
|
-
when "P2", "P5"
|
221
|
-
@format = "PGM"
|
222
|
-
when "P3", "P6"
|
223
|
-
@format = "PPM"
|
224
|
-
# when "P7"
|
225
|
-
# @format = "XV"
|
226
|
-
# header =~ /IMGINFO:(\d+)x(\d+)/m
|
227
|
-
# width = $1.to_i; height = $2.to_i
|
228
|
-
end
|
229
|
-
end
|
230
|
-
private :measure_PPM
|
231
|
-
|
232
|
-
alias :measure_PGM :measure_PPM
|
233
|
-
private :measure_PGM
|
234
|
-
alias :measure_PBM :measure_PPM
|
235
|
-
private :measure_PBM
|
236
|
-
|
237
|
-
XBM_DIMENSIONS_RE = %r{^\#define\s*\S*\s*(\d+)\s*\n\#define\s*\S*\s*(\d+)}mi
|
238
|
-
def measure_XBM
|
239
|
-
md = XBM_DIMENSIONS_RE.match(@data.read_o(1024))
|
240
|
-
|
241
|
-
@width = md.captures[0].to_i
|
242
|
-
@height = md.captures[1].to_i
|
243
|
-
end
|
244
|
-
private :measure_XBM
|
245
|
-
|
246
|
-
XPM_DIMENSIONS_RE = %r<"\s*(\d+)\s+(\d+)(\s+\d+\s+\d+){1,2}\s*">m
|
247
|
-
def measure_XPM
|
248
|
-
while line = @data.read_o(1024)
|
249
|
-
md = XPM_DIMENSIONS_RE.match(line)
|
250
|
-
if md
|
251
|
-
@width = md.captures[0].to_i
|
252
|
-
@height = md.captures[1].to_i
|
253
|
-
break
|
254
|
-
end
|
255
|
-
end
|
256
|
-
end
|
257
|
-
private :measure_XPM
|
258
|
-
|
259
|
-
def measure_PSD
|
260
|
-
@width, @height = @data.read_o(26).unpack("x14NN")
|
261
|
-
end
|
262
|
-
private :measure_PSD
|
263
|
-
|
264
|
-
def measure_PCX
|
265
|
-
header = @data.read_o(128)
|
266
|
-
head_part = header.unpack('C4S4')
|
267
|
-
@width = head_part[6] - head_part[4] + 1
|
268
|
-
@height = head_part[7] - head_part[5] + 1
|
269
|
-
end
|
270
|
-
private :measure_PCX
|
271
|
-
|
272
|
-
def measure_SWF
|
273
|
-
header = @data.read_o(9)
|
274
|
-
raise "This file is not SWF." unless header.unpack('a3')[0] == 'FWS'
|
275
|
-
|
276
|
-
bits = Integer("0b#{header.unpack('@8B5')}")
|
277
|
-
header << @data.read_o(bits * 4 / 8 + 1)
|
278
|
-
|
279
|
-
str = *(header.unpack("@8B#{5 + bits * 4}"))
|
280
|
-
last = 5
|
281
|
-
x_min = Integer("0b#{str[last, bits]}")
|
282
|
-
x_max = Integer("0b#{str[(last + bits), bits]}")
|
283
|
-
y_min = Integer("0b#{str[(last + (2 * bits)), bits]}")
|
284
|
-
y_max = Integer("0b#{str[(last + (3 * bits)), bits]}")
|
285
|
-
@width = (x_max - x_min) / 20
|
286
|
-
@height = (y_max - y_min) / 20
|
287
|
-
end
|
288
|
-
private :measure_SWF
|
289
|
-
|
290
|
-
# The same as SWF, except that the original data is compressed with
|
291
|
-
# Zlib. Disabled for now.
|
292
|
-
def measure_SWC
|
293
|
-
end
|
294
|
-
private :measure_SWC
|
295
|
-
|
296
|
-
def measure_TIFF
|
297
|
-
# 'v' little-endian
|
298
|
-
# 'n' default to big-endian
|
299
|
-
endian = (@data.read_o(4) =~ /II\x2a\x00/o) ? 'v' : 'n'
|
300
|
-
|
301
|
-
packspec = [
|
302
|
-
nil, # nothing (shouldn't happen)
|
303
|
-
'C', # BYTE (8-bit unsigned integer)
|
304
|
-
nil, # ASCII
|
305
|
-
endian, # SHORT (16-bit unsigned integer)
|
306
|
-
endian.upcase, # LONG (32-bit unsigned integer)
|
307
|
-
nil, # RATIONAL
|
308
|
-
'c', # SBYTE (8-bit signed integer)
|
309
|
-
nil, # UNDEFINED
|
310
|
-
endian, # SSHORT (16-bit unsigned integer)
|
311
|
-
endian.upcase, # SLONG (32-bit unsigned integer)
|
312
|
-
]
|
313
|
-
|
314
|
-
# Find the IFD location.
|
315
|
-
ifd_addr = *(@data.read_o(4).unpack(endian.upcase))
|
316
|
-
# Get the number of entries in the IFD.
|
317
|
-
ifd = @data.read_o(2, ifd_addr)
|
318
|
-
num_dirent = *(ifd.unpack(endian)) # Make it useful
|
319
|
-
ifd_addr += 2
|
320
|
-
num_dirent = ifd_addr + (num_dirent * 12) # Calc. maximum offset of IFD
|
321
|
-
|
322
|
-
loop do
|
323
|
-
break if @width and @height
|
324
|
-
|
325
|
-
ifd = @data.read_o(12, ifd_addr) # Get directory entry.
|
326
|
-
break if ifd.nil? or ifd_addr > num_dirent
|
327
|
-
ifd_addr += 12
|
328
|
-
|
329
|
-
tag = *(ifd.unpack(endian)) # ...decode its tag
|
330
|
-
type = *(ifd[2, 2].unpack(endian)) # ... and data type
|
331
|
-
|
332
|
-
# Check the type for sanity.
|
333
|
-
next if type > packspec.size or packspec[type].nil?
|
334
|
-
|
335
|
-
case tag
|
336
|
-
when 0x0100, 0xa002 # width
|
337
|
-
@width = *(ifd[8, 4].unpack(packspec[type]))
|
338
|
-
when 0x0101, 0xa003 # height
|
339
|
-
@height = *(ifd[8, 4].unpack(packspec[type]))
|
340
|
-
end
|
341
|
-
end
|
342
|
-
end
|
343
|
-
private :measure_TIFF
|
344
|
-
end
|
345
|
-
|
346
|
-
if __FILE__ == $0
|
347
|
-
require 'pdf/writer'
|
348
|
-
require 'pdf/writer/graphics'
|
349
|
-
|
350
|
-
print "Image Formats: #{PDF::Writer::Graphics::ImageInfo.formats.inspect}\n"
|
351
|
-
|
352
|
-
Dir.glob("*").each do |file|
|
353
|
-
print "#{file} (string)\n"
|
354
|
-
open(file, "rb") do |fh|
|
355
|
-
image = PDF::Writer::Graphics::ImageInfo.new(fh.read)
|
356
|
-
print <<-EOF
|
357
|
-
Format : #{image.format}
|
358
|
-
Width : #{image.width.inspect}
|
359
|
-
Height : #{image.height.inspect}
|
360
|
-
Bits : #{image.bits.inspect}
|
361
|
-
Channels: #{image.channels.inspect}
|
362
|
-
EOF
|
363
|
-
end
|
364
|
-
end
|
365
|
-
end
|
1
|
+
#--
|
2
|
+
# PDF::Writer for Ruby.
|
3
|
+
# http://rubyforge.org/projects/ruby-pdf/
|
4
|
+
# Copyright 2003 - 2005 Austin Ziegler.
|
5
|
+
#
|
6
|
+
# Licensed under a MIT-style licence. See LICENCE in the main distribution
|
7
|
+
# for full licensing information.
|
8
|
+
#
|
9
|
+
# This file is also licensed under standard Ruby licensing provisions: the
|
10
|
+
# Ruby licence and the GNU General Public Licence, version 2 or later.
|
11
|
+
#
|
12
|
+
# $Id: imageinfo.rb 184 2007-12-10 03:18:48Z sandal $
|
13
|
+
#++
|
14
|
+
require 'pdf/writer/oreader'
|
15
|
+
|
16
|
+
# This is based on ImageSize, by Keisuke Minami <keisuke@rccn.com>. It can
|
17
|
+
# be found at http://www.rubycgi.org/tools/index.en.htm
|
18
|
+
#
|
19
|
+
# This has been integrated into PDF::Writer because as yet there has been
|
20
|
+
# no response to emails asking for my extensions to be integrated and a
|
21
|
+
# RubyGem package to be made available.
|
22
|
+
class PDF::Writer::Graphics::ImageInfo
|
23
|
+
# Image Format Constants
|
24
|
+
module Formats
|
25
|
+
OTHER = "OTHER"
|
26
|
+
GIF = "GIF" # CompuServe GIF87a and GIF89a images.
|
27
|
+
PNG = "PNG" # Portable Network Graphics, PNG
|
28
|
+
JPEG = "JPEG" # JPEG
|
29
|
+
BMP = "BMP" # Windows or OS/2 Bitmaps
|
30
|
+
PPM = "PPM" # PPM is like PBM, PGM, & XV
|
31
|
+
PBM = "PBM"
|
32
|
+
PGM = "PGM"
|
33
|
+
# XV = "XV" # Not supported
|
34
|
+
TIFF = "TIFF" # TIFF formats
|
35
|
+
XBM = "XBM" # X Bitmap
|
36
|
+
XPM = "XPM" # X Pixmap
|
37
|
+
PSD = "PSD" # PhotoShop
|
38
|
+
PCX = "PCX" # PCX Bitmap
|
39
|
+
SWF = "SWF" # Flash
|
40
|
+
end
|
41
|
+
Type = Formats
|
42
|
+
|
43
|
+
class << self
|
44
|
+
def formats
|
45
|
+
Formats.constants
|
46
|
+
end
|
47
|
+
alias :type_list :formats
|
48
|
+
end
|
49
|
+
|
50
|
+
JPEG_SOF_BLOCKS = %W(\xc0 \xc1 \xc2 \xc3 \xc5 \xc6 \xc7 \xc9 \xca \xcb \xcd \xce \xcf)
|
51
|
+
JPEG_APP_BLOCKS = %W(\xe0 \xe1 \xe2 \xe3 \xe4 \xe5 \xe6 \xe7 \xe8 \xe9 \xea \xeb \xec \xed \xee \xef)
|
52
|
+
|
53
|
+
# Receive image & make size. argument is image String or IO
|
54
|
+
def initialize(data, format = nil)
|
55
|
+
@data = data.dup rescue data
|
56
|
+
@info = {}
|
57
|
+
|
58
|
+
if @data.kind_of?(IO)
|
59
|
+
@top = @data.read(128)
|
60
|
+
@data.seek(0, 0)
|
61
|
+
# Define Singleton-method definition to IO (byte, offset)
|
62
|
+
def @data.read_o(length = 1, offset = nil)
|
63
|
+
self.seek(offset, 0) if offset
|
64
|
+
ret = self.read(length)
|
65
|
+
raise "cannot read!!" unless ret
|
66
|
+
ret
|
67
|
+
end
|
68
|
+
elsif @data.is_a?(String)
|
69
|
+
@top = @data[0, 128]
|
70
|
+
# Define Singleton-method definition to String (byte, offset)
|
71
|
+
@data.extend(PDF::Writer::OffsetReader)
|
72
|
+
else
|
73
|
+
raise "argument class error!! #{data.type}"
|
74
|
+
end
|
75
|
+
|
76
|
+
if format.nil?
|
77
|
+
@format = discover_format
|
78
|
+
else
|
79
|
+
match = false
|
80
|
+
Formats.constants.each { |t| match = true if format == t }
|
81
|
+
raise("format is failed. #{format}\n") unless match
|
82
|
+
@format = format
|
83
|
+
end
|
84
|
+
|
85
|
+
__send__("measure_#@format".intern) unless @format == Formats::OTHER
|
86
|
+
|
87
|
+
@data = data.dup
|
88
|
+
end
|
89
|
+
|
90
|
+
attr_reader :format
|
91
|
+
alias :get_type :format
|
92
|
+
attr_reader :height
|
93
|
+
alias :get_height :height
|
94
|
+
attr_reader :width
|
95
|
+
alias :get_width width
|
96
|
+
|
97
|
+
attr_reader :bits
|
98
|
+
attr_reader :channels
|
99
|
+
|
100
|
+
attr_reader :info
|
101
|
+
|
102
|
+
def discover_format
|
103
|
+
if @top =~ %r{^GIF8[79]a}
|
104
|
+
Formats::GIF
|
105
|
+
elsif @top[0, 3] == "\xff\xd8\xff"
|
106
|
+
Formats::JPEG
|
107
|
+
elsif @top[0, 8] == "\x89PNG\x0d\x0a\x1a\x0a"
|
108
|
+
Formats::PNG
|
109
|
+
elsif @top[0, 3] == "FWS"
|
110
|
+
Formats::SWF
|
111
|
+
elsif @top[0, 4] == "8BPS"
|
112
|
+
Formats::PSD
|
113
|
+
elsif @top[0, 2] == 'BM'
|
114
|
+
Formats::BMP
|
115
|
+
elsif @top[0, 4] == "MM\x00\x2a"
|
116
|
+
Formats::TIFF
|
117
|
+
elsif @top[0, 4] == "II\x2a\x00"
|
118
|
+
Formats::TIFF
|
119
|
+
elsif @top[0, 12] == "\x00\x00\x00\x0c\x6a\x50\x20\x20\x0d\x0a\x87\x0a"
|
120
|
+
Formats::JP2
|
121
|
+
elsif @top =~ %r{^P[1-7]}
|
122
|
+
Formats::PPM
|
123
|
+
elsif @top =~ %r{\#define\s+\S+\s+\d+}
|
124
|
+
Formats::XBM
|
125
|
+
elsif @top =~ %r{/\* XPM \*/}
|
126
|
+
Formats::XPM
|
127
|
+
elsif @top[0] == 10
|
128
|
+
Formats::PCX
|
129
|
+
else
|
130
|
+
Formats::OTHER # might be WBMP
|
131
|
+
end
|
132
|
+
end
|
133
|
+
private :discover_format
|
134
|
+
|
135
|
+
def measure_GIF
|
136
|
+
@data.read_o(6) # Skip GIF8.a
|
137
|
+
@width, @height, @bits = @data.read_o(5).unpack('vvC')
|
138
|
+
if @bits & 0x80 == 0x80
|
139
|
+
@bits = (@bits & 0x07) + 1
|
140
|
+
else
|
141
|
+
@bits = 0
|
142
|
+
end
|
143
|
+
@channels = 3
|
144
|
+
end
|
145
|
+
private :measure_GIF
|
146
|
+
|
147
|
+
def measure_PNG
|
148
|
+
@data.read_o(12)
|
149
|
+
raise "This file is not PNG." unless @data.read_o(4) == "IHDR"
|
150
|
+
# The file information is in the IHDR section.
|
151
|
+
# Offset Bytes Meaning
|
152
|
+
# 0 4 Width
|
153
|
+
# 5 4 Height
|
154
|
+
# 9 1 Bit Depth
|
155
|
+
# 10 1 Compression Method
|
156
|
+
# 11 1 Filter Method
|
157
|
+
# 12 1 Interlace Method
|
158
|
+
ihdr = @data.read_o(13).unpack("NNCCCCC")
|
159
|
+
@width = ihdr[0]
|
160
|
+
@height = ihdr[1]
|
161
|
+
@bits = ihdr[2]
|
162
|
+
@info[:color_type] = ihdr[3]
|
163
|
+
@info[:compression_method] = ihdr[4]
|
164
|
+
@info[:filter_method] = ihdr[5]
|
165
|
+
@info[:interlace_method] = ihdr[6]
|
166
|
+
|
167
|
+
|
168
|
+
end
|
169
|
+
private :measure_PNG
|
170
|
+
|
171
|
+
def measure_JPEG
|
172
|
+
c_marker = "\xff" # Section marker.
|
173
|
+
@data.read_o(2) # Skip the first two bytes of JPEG identifier.
|
174
|
+
loop do
|
175
|
+
marker, code, length = @data.read_o(4).unpack('aan')
|
176
|
+
raise "JPEG marker not found!" if marker != c_marker
|
177
|
+
|
178
|
+
if JPEG_SOF_BLOCKS.include?(code)
|
179
|
+
@bits, @height, @width, @channels = @data.read_o(6).unpack("CnnC")
|
180
|
+
break
|
181
|
+
end
|
182
|
+
|
183
|
+
buffer = @data.read_o(length - 2)
|
184
|
+
|
185
|
+
if JPEG_APP_BLOCKS.include?(code)
|
186
|
+
@info["APP#{code.to_i - 0xe0}"] = buffer
|
187
|
+
end
|
188
|
+
end
|
189
|
+
end
|
190
|
+
private :measure_JPEG
|
191
|
+
|
192
|
+
def measure_BMP
|
193
|
+
# Skip the first 14 bytes of the image.
|
194
|
+
@data.read_o(14)
|
195
|
+
# Up to the next 16 bytes will be used.
|
196
|
+
dim = @data.read_o(16)
|
197
|
+
|
198
|
+
# Get the "size" of the image from the next four bytes.
|
199
|
+
size = dim.unpack("V").first # <- UNPACK RETURNS ARRAY, SO GET FIRST ELEMENT
|
200
|
+
|
201
|
+
if size == 12
|
202
|
+
@width, @height, @bits = dim.unpack("x4vvx3C")
|
203
|
+
elsif size > 12 and (size <= 64 or size == 108)
|
204
|
+
@width, @height, @bits = dim.unpack("x4VVv")
|
205
|
+
end
|
206
|
+
end
|
207
|
+
private :measure_BMP
|
208
|
+
|
209
|
+
def measure_PPM
|
210
|
+
header = @data.read_o(1024)
|
211
|
+
header.gsub!(/^\#[^\n\r]*/m, "")
|
212
|
+
md = %r{^(P[1-6])\s+?(\d+)\s+?(\d+)}mo.match(header)
|
213
|
+
|
214
|
+
@width = md.captures[1]
|
215
|
+
@height = md.captures[2]
|
216
|
+
|
217
|
+
case md.captures[0]
|
218
|
+
when "P1", "P4"
|
219
|
+
@format = "PBM"
|
220
|
+
when "P2", "P5"
|
221
|
+
@format = "PGM"
|
222
|
+
when "P3", "P6"
|
223
|
+
@format = "PPM"
|
224
|
+
# when "P7"
|
225
|
+
# @format = "XV"
|
226
|
+
# header =~ /IMGINFO:(\d+)x(\d+)/m
|
227
|
+
# width = $1.to_i; height = $2.to_i
|
228
|
+
end
|
229
|
+
end
|
230
|
+
private :measure_PPM
|
231
|
+
|
232
|
+
alias :measure_PGM :measure_PPM
|
233
|
+
private :measure_PGM
|
234
|
+
alias :measure_PBM :measure_PPM
|
235
|
+
private :measure_PBM
|
236
|
+
|
237
|
+
XBM_DIMENSIONS_RE = %r{^\#define\s*\S*\s*(\d+)\s*\n\#define\s*\S*\s*(\d+)}mi
|
238
|
+
def measure_XBM
|
239
|
+
md = XBM_DIMENSIONS_RE.match(@data.read_o(1024))
|
240
|
+
|
241
|
+
@width = md.captures[0].to_i
|
242
|
+
@height = md.captures[1].to_i
|
243
|
+
end
|
244
|
+
private :measure_XBM
|
245
|
+
|
246
|
+
XPM_DIMENSIONS_RE = %r<"\s*(\d+)\s+(\d+)(\s+\d+\s+\d+){1,2}\s*">m
|
247
|
+
def measure_XPM
|
248
|
+
while line = @data.read_o(1024)
|
249
|
+
md = XPM_DIMENSIONS_RE.match(line)
|
250
|
+
if md
|
251
|
+
@width = md.captures[0].to_i
|
252
|
+
@height = md.captures[1].to_i
|
253
|
+
break
|
254
|
+
end
|
255
|
+
end
|
256
|
+
end
|
257
|
+
private :measure_XPM
|
258
|
+
|
259
|
+
def measure_PSD
|
260
|
+
@width, @height = @data.read_o(26).unpack("x14NN")
|
261
|
+
end
|
262
|
+
private :measure_PSD
|
263
|
+
|
264
|
+
def measure_PCX
|
265
|
+
header = @data.read_o(128)
|
266
|
+
head_part = header.unpack('C4S4')
|
267
|
+
@width = head_part[6] - head_part[4] + 1
|
268
|
+
@height = head_part[7] - head_part[5] + 1
|
269
|
+
end
|
270
|
+
private :measure_PCX
|
271
|
+
|
272
|
+
def measure_SWF
|
273
|
+
header = @data.read_o(9)
|
274
|
+
raise "This file is not SWF." unless header.unpack('a3')[0] == 'FWS'
|
275
|
+
|
276
|
+
bits = Integer("0b#{header.unpack('@8B5')}")
|
277
|
+
header << @data.read_o(bits * 4 / 8 + 1)
|
278
|
+
|
279
|
+
str = *(header.unpack("@8B#{5 + bits * 4}"))
|
280
|
+
last = 5
|
281
|
+
x_min = Integer("0b#{str[last, bits]}")
|
282
|
+
x_max = Integer("0b#{str[(last + bits), bits]}")
|
283
|
+
y_min = Integer("0b#{str[(last + (2 * bits)), bits]}")
|
284
|
+
y_max = Integer("0b#{str[(last + (3 * bits)), bits]}")
|
285
|
+
@width = (x_max - x_min) / 20
|
286
|
+
@height = (y_max - y_min) / 20
|
287
|
+
end
|
288
|
+
private :measure_SWF
|
289
|
+
|
290
|
+
# The same as SWF, except that the original data is compressed with
|
291
|
+
# Zlib. Disabled for now.
|
292
|
+
def measure_SWC
|
293
|
+
end
|
294
|
+
private :measure_SWC
|
295
|
+
|
296
|
+
def measure_TIFF
|
297
|
+
# 'v' little-endian
|
298
|
+
# 'n' default to big-endian
|
299
|
+
endian = (@data.read_o(4) =~ /II\x2a\x00/o) ? 'v' : 'n'
|
300
|
+
|
301
|
+
packspec = [
|
302
|
+
nil, # nothing (shouldn't happen)
|
303
|
+
'C', # BYTE (8-bit unsigned integer)
|
304
|
+
nil, # ASCII
|
305
|
+
endian, # SHORT (16-bit unsigned integer)
|
306
|
+
endian.upcase, # LONG (32-bit unsigned integer)
|
307
|
+
nil, # RATIONAL
|
308
|
+
'c', # SBYTE (8-bit signed integer)
|
309
|
+
nil, # UNDEFINED
|
310
|
+
endian, # SSHORT (16-bit unsigned integer)
|
311
|
+
endian.upcase, # SLONG (32-bit unsigned integer)
|
312
|
+
]
|
313
|
+
|
314
|
+
# Find the IFD location.
|
315
|
+
ifd_addr = *(@data.read_o(4).unpack(endian.upcase))
|
316
|
+
# Get the number of entries in the IFD.
|
317
|
+
ifd = @data.read_o(2, ifd_addr)
|
318
|
+
num_dirent = *(ifd.unpack(endian)) # Make it useful
|
319
|
+
ifd_addr += 2
|
320
|
+
num_dirent = ifd_addr + (num_dirent * 12) # Calc. maximum offset of IFD
|
321
|
+
|
322
|
+
loop do
|
323
|
+
break if @width and @height
|
324
|
+
|
325
|
+
ifd = @data.read_o(12, ifd_addr) # Get directory entry.
|
326
|
+
break if ifd.nil? or ifd_addr > num_dirent
|
327
|
+
ifd_addr += 12
|
328
|
+
|
329
|
+
tag = *(ifd.unpack(endian)) # ...decode its tag
|
330
|
+
type = *(ifd[2, 2].unpack(endian)) # ... and data type
|
331
|
+
|
332
|
+
# Check the type for sanity.
|
333
|
+
next if type > packspec.size or packspec[type].nil?
|
334
|
+
|
335
|
+
case tag
|
336
|
+
when 0x0100, 0xa002 # width
|
337
|
+
@width = *(ifd[8, 4].unpack(packspec[type]))
|
338
|
+
when 0x0101, 0xa003 # height
|
339
|
+
@height = *(ifd[8, 4].unpack(packspec[type]))
|
340
|
+
end
|
341
|
+
end
|
342
|
+
end
|
343
|
+
private :measure_TIFF
|
344
|
+
end
|
345
|
+
|
346
|
+
if __FILE__ == $0
|
347
|
+
require 'pdf/writer'
|
348
|
+
require 'pdf/writer/graphics'
|
349
|
+
|
350
|
+
print "Image Formats: #{PDF::Writer::Graphics::ImageInfo.formats.inspect}\n"
|
351
|
+
|
352
|
+
Dir.glob("*").each do |file|
|
353
|
+
print "#{file} (string)\n"
|
354
|
+
open(file, "rb") do |fh|
|
355
|
+
image = PDF::Writer::Graphics::ImageInfo.new(fh.read)
|
356
|
+
print <<-EOF
|
357
|
+
Format : #{image.format}
|
358
|
+
Width : #{image.width.inspect}
|
359
|
+
Height : #{image.height.inspect}
|
360
|
+
Bits : #{image.bits.inspect}
|
361
|
+
Channels: #{image.channels.inspect}
|
362
|
+
EOF
|
363
|
+
end
|
364
|
+
end
|
365
|
+
end
|