combine_pdf 0.0.6 → 0.0.7
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/combine_pdf.rb +40 -13
- data/lib/combine_pdf/combine_pdf_basic_writer.rb +174 -133
- data/lib/combine_pdf/combine_pdf_decrypt.rb +29 -19
- data/lib/combine_pdf/combine_pdf_filter.rb +16 -3
- data/lib/combine_pdf/combine_pdf_operations.rb +13 -12
- data/lib/combine_pdf/combine_pdf_parser.rb +13 -5
- data/lib/combine_pdf/combine_pdf_pdf.rb +136 -15
- data/lib/combine_pdf/font_metrics/metrics_dictionary.rb +23 -21
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6e1bf0dc605e123de2a5e4e809c079c64da812a5
|
4
|
+
data.tar.gz: a3f744fbab605cb9abd85332566219ab9735e421
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 73845fbdabd424d02f5c1494df97a2e790d38c0c8575ee78214ab2279ba359bd41533ba9a493e755059904a18bd8ef39e78d574f3eae2ccd60c411733735f942
|
7
|
+
data.tar.gz: 7bd08ed3ea3131a1170f583e1a2c17eae17b406af1f4cec8d19b6c8cbbc59c36dd651d1e70a848b13be7d0767bad127686f9eae288037eba881517c492505c27
|
data/lib/combine_pdf.rb
CHANGED
@@ -34,23 +34,32 @@ require "combine_pdf/font_metrics/metrics_dictionary.rb"
|
|
34
34
|
# In the future, this library will also allow stamping and watermarking PDFs (it allows this now, only with some issues).
|
35
35
|
#
|
36
36
|
# PDF objects can be used to combine or to inject data.
|
37
|
-
# == Combine
|
37
|
+
# == Combine/Merge PDF files or Pages
|
38
38
|
# To combine PDF files (or data):
|
39
39
|
# pdf = CombinePDF.new
|
40
40
|
# pdf << CombinePDF.new("file1.pdf") # one way to combine, very fast.
|
41
|
-
# CombinePDF.new("file2.pdf")
|
41
|
+
# pdf << CombinePDF.new("file2.pdf")
|
42
42
|
# pdf.save "combined.pdf"
|
43
|
-
#
|
44
|
-
#
|
45
|
-
#
|
46
|
-
#
|
47
|
-
#
|
48
|
-
#
|
49
|
-
#
|
50
|
-
#
|
51
|
-
#
|
52
|
-
# pdf
|
53
|
-
#
|
43
|
+
# or even a one liner:
|
44
|
+
# (CombinePDF.new("file1.pdf") << CombinePDF.new("file2.pdf") << CombinePDF.new("file3.pdf")).save("combined.pdf")
|
45
|
+
# you can also add just odd or even pages:
|
46
|
+
# pdf = CombinePDF.new
|
47
|
+
# i = 0
|
48
|
+
# CombinePDF.new("file.pdf").pages.each do |page
|
49
|
+
# i += 1
|
50
|
+
# pdf << page if i.even?
|
51
|
+
# end
|
52
|
+
# pdf.save "even_pages.pdf"
|
53
|
+
# notice that adding all the pages one by one is slower then adding the whole file.
|
54
|
+
# == Add content to existing pages (Stamp / Watermark)
|
55
|
+
# To add content to existing PDF pages, first import the new content from an existing PDF file.
|
56
|
+
# after that, add the content to each of the pages in your existing PDF.
|
57
|
+
#
|
58
|
+
# in this example, we will add a company logo to each page:
|
59
|
+
# company_logo = CombinePDF.new("company_logo.pdf").pages[0]
|
60
|
+
# pdf = CombinePDF.new "content_file.pdf"
|
61
|
+
# pdf.pages.each {|page| page << company_logo} # notice the << operator is on a page and not a PDF object.
|
62
|
+
# pdf.save "content_with_logo.pdf"
|
54
63
|
# Notice the << operator is on a page and not a PDF object. The << operator acts differently on PDF objects and on Pages.
|
55
64
|
#
|
56
65
|
# The << operator defaults to secure injection by renaming references to avoid conflics. For overlaying pages using compressed data that might not be editable (due to limited filter support), you can use:
|
@@ -59,6 +68,24 @@ require "combine_pdf/font_metrics/metrics_dictionary.rb"
|
|
59
68
|
#
|
60
69
|
# Notice that page objects are Hash class objects and the << operator was added to the Page instances without altering the class.
|
61
70
|
#
|
71
|
+
# == Page Numbering
|
72
|
+
# adding page numbers to a PDF object or file is as simple as can be:
|
73
|
+
# pdf = CombinePDF.new "file_to_number.pdf"
|
74
|
+
# pdf.number_pages
|
75
|
+
# pdf.save "file_with_numbering.pdf"
|
76
|
+
#
|
77
|
+
# numbering can be done with many different options, with different formating, with or without a box object, and even with opacity values.
|
78
|
+
#
|
79
|
+
# == Loading PDF data
|
80
|
+
# Loading PDF data can be done from file system or directly from the memory.
|
81
|
+
#
|
82
|
+
# Loading data from a file is easy:
|
83
|
+
# pdf = CombinePDF.new("file.pdf")
|
84
|
+
# you can also parse PDF files from memory:
|
85
|
+
# pdf_data = IO.read 'file.pdf' # for this demo, load a file to memory
|
86
|
+
# pdf = CombinePDF.parse(pdf_data)
|
87
|
+
# Loading from the memory is especially effective for importing PDF data recieved through the internet or from a different authoring library such as Prawn.
|
88
|
+
#
|
62
89
|
# == Decryption & Filters
|
63
90
|
#
|
64
91
|
# Some PDF files are encrypted and some are compressed (the use of filters)...
|
@@ -10,12 +10,11 @@
|
|
10
10
|
|
11
11
|
module CombinePDF
|
12
12
|
|
13
|
-
#@private
|
14
13
|
#:nodoc: all
|
15
|
-
|
16
|
-
# <b>
|
14
|
+
|
15
|
+
# <b>not fully tested!</b>
|
17
16
|
#
|
18
|
-
#
|
17
|
+
# NO UNICODE SUPPORT!
|
19
18
|
#
|
20
19
|
# in the future I wish to make a simple PDF page writer, that has only one functions - the text box.
|
21
20
|
# Once the simple writer is ready (creates a text box in a self contained Page element),
|
@@ -24,9 +23,9 @@ module CombinePDF
|
|
24
23
|
#
|
25
24
|
# The PDFWriter class is a subclass of Hash and represents a PDF Page object.
|
26
25
|
#
|
27
|
-
# Writing on this Page is done using the
|
26
|
+
# Writing on this Page is done using the textbox function.
|
28
27
|
#
|
29
|
-
# Setting the page dimentions can be either at the new or using the
|
28
|
+
# Setting the page dimentions can be either at the new or using the mediabox method.
|
30
29
|
#
|
31
30
|
# the rest of the methods are for internal use.
|
32
31
|
#
|
@@ -36,32 +35,32 @@ module CombinePDF
|
|
36
35
|
# We can either insert the PDFWriter as a new page:
|
37
36
|
# pdf = CombinePDF.new
|
38
37
|
# new_page = PDFWriter.new
|
39
|
-
# new_page.
|
38
|
+
# new_page.textbox "some text"
|
40
39
|
# pdf << new_page
|
41
40
|
# pdf.save "file_with_new_page.pdf"
|
42
41
|
# Or we can insert the PDFWriter as an overlay (stamp / watermark) over existing pages:
|
43
42
|
# pdf = CombinePDF.new
|
44
43
|
# new_page = PDFWriter.new "some_file.pdf"
|
45
|
-
# new_page.
|
44
|
+
# new_page.textbox "some text"
|
46
45
|
# pdf.pages.each {|page| page << new_page }
|
47
46
|
# pdf.save "stamped_file.pdf"
|
48
47
|
class PDFWriter < Hash
|
49
48
|
|
50
|
-
def initialize(
|
49
|
+
def initialize(mediabox = [0.0, 0.0, 612.0, 792.0])
|
51
50
|
# indirect_reference_id, :indirect_generation_number
|
52
51
|
self[:Type] = :Page
|
53
52
|
self[:indirect_reference_id] = 0
|
54
53
|
self[:Resources] = {}
|
55
54
|
self[:Contents] = { is_reference_only: true , referenced_object: {indirect_reference_id: 0, raw_stream_content: ""} }
|
56
|
-
self[:MediaBox] =
|
55
|
+
self[:MediaBox] = mediabox
|
57
56
|
end
|
58
57
|
# accessor (getter) for the :MediaBox element of the page
|
59
|
-
def
|
58
|
+
def mediabox
|
60
59
|
self[:MediaBox]
|
61
60
|
end
|
62
61
|
# accessor (setter) for the :MediaBox element of the page
|
63
62
|
# dimentions:: an Array consisting of four numbers (can be floats) setting the size of the media box.
|
64
|
-
def
|
63
|
+
def mediabox=(dimentions = [0.0, 0.0, 612.0, 792.0])
|
65
64
|
self[:MediaBox] = dimentions
|
66
65
|
end
|
67
66
|
|
@@ -76,78 +75,191 @@ module CombinePDF
|
|
76
75
|
# y:: the BUTTOM position of the box.
|
77
76
|
# length:: the length of the box.
|
78
77
|
# height:: the height of the box.
|
79
|
-
#
|
78
|
+
# text_align:: symbol for horizontal text alignment, can be ":center" (default), ":right", ":left"
|
79
|
+
# text_valign:: symbol for vertical text alignment, can be ":center" (default), ":top", ":buttom"
|
80
|
+
# font_name:: a Symbol representing one of the 14 standard fonts. defaults to ":Helvetica". the options are:
|
81
|
+
# - :"Times-Roman"
|
82
|
+
# - :"Times-Bold"
|
83
|
+
# - :"Times-Italic"
|
84
|
+
# - :"Times-BoldItalic"
|
85
|
+
# - :Helvetica
|
86
|
+
# - :"Helvetica-Bold"
|
87
|
+
# - :"Helvetica-BoldOblique"
|
88
|
+
# - :"Helvetica- Oblique"
|
89
|
+
# - :Courier
|
90
|
+
# - :"Courier-Bold"
|
91
|
+
# - :"Courier-Oblique"
|
92
|
+
# - :"Courier-BoldOblique"
|
93
|
+
# - :Symbol
|
94
|
+
# - :ZapfDingbats
|
80
95
|
# font_size:: a Fixnum for the font size, or :fit_text to fit the text in the box. defaults to ":fit_text"
|
81
|
-
#
|
82
|
-
|
96
|
+
# max_font_size:: if font_size is set to :fit_text, this will be the maximum font size. defaults to nil (no maximum)
|
97
|
+
# font_color:: text color in [R, G, B], an array with three floats, each in a value between 0 to 1 (gray will be "[0.5, 0.5, 0.5]"). defaults to black.
|
98
|
+
# stroke_color:: text stroke color in [R, G, B], an array with three floats, each in a value between 0 to 1 (gray will be "[0.5, 0.5, 0.5]"). defounlts to nil (no stroke).
|
99
|
+
# stroke_width:: text stroke width in PDF units. defaults to 0 (none).
|
100
|
+
# box_color:: box fill color in [R, G, B], an array with three floats, each in a value between 0 to 1 (gray will be "[0.5, 0.5, 0.5]"). defaults to nil (none).
|
101
|
+
# border_color:: box border color in [R, G, B], an array with three floats, each in a value between 0 to 1 (gray will be "[0.5, 0.5, 0.5]"). defaults to nil (none).
|
102
|
+
# border_width:: border width in PDF units. defaults to nil (none).
|
103
|
+
# box_radius:: border radius in PDF units. defaults to 0 (no corner rounding).
|
104
|
+
# opacity:: textbox opacity, a float between 0 (transparent) and 1 (opaque)
|
105
|
+
# <b>now on testing mode, defaults are different! box defaults to gray with border and rounding.</b>
|
106
|
+
def textbox(text, properties = {})
|
83
107
|
options = {
|
84
|
-
text_alignment: :center,
|
85
|
-
text_color: [0,0,0],
|
86
|
-
text_stroke_color: nil,
|
87
|
-
text_stroke_width: 0,
|
88
|
-
font_name: :Helvetica,
|
89
|
-
font_size: :fit_text,
|
90
|
-
border_color: [0.5,0.5,0.5],
|
91
|
-
border_width: 2,
|
92
|
-
border_radius: 0,
|
93
|
-
background_color: [0.7,0.7,0.7],
|
94
|
-
opacity: 1,
|
95
108
|
x: 0,
|
96
109
|
y: 0,
|
97
110
|
length: -1,
|
98
111
|
height: -1,
|
112
|
+
text_align: :center,
|
113
|
+
text_valign: :center,
|
114
|
+
font_name: :Helvetica,
|
115
|
+
font_size: :fit_text,
|
116
|
+
max_font_size: nil,
|
117
|
+
font_color: [0,0,0],
|
118
|
+
stroke_color: nil,
|
119
|
+
stroke_width: 0,
|
120
|
+
box_color: nil,
|
121
|
+
border_color: nil,
|
122
|
+
border_width: 0,
|
123
|
+
box_radius: 0,
|
124
|
+
opacity: 1
|
99
125
|
}
|
100
126
|
options.update properties
|
101
127
|
# reset the length and height to meaningful values, if negative
|
102
|
-
options[:length] =
|
103
|
-
options[:height] =
|
128
|
+
options[:length] = mediabox[2] - options[:x] if options[:length] < 0
|
129
|
+
options[:height] = mediabox[3] - options[:y] if options[:height] < 0
|
104
130
|
# fit text in box, if requested
|
131
|
+
font_size = options[:font_size]
|
105
132
|
if options[:font_size] == :fit_text
|
106
|
-
|
133
|
+
font_size = self.fit_text text, options[:font_name], options[:length], options[:height]
|
134
|
+
font_size = options[:max_font_size] if options[:max_font_size] && font_size > options[:max_font_size]
|
107
135
|
end
|
108
136
|
|
109
137
|
|
110
138
|
# create box stream
|
139
|
+
box_stream = ""
|
140
|
+
# set graphic state for box
|
141
|
+
if options[:box_color] || (options[:border_width].to_i > 0 && options[:border_color])
|
142
|
+
# compute x and y position for text
|
143
|
+
x = options[:x]
|
144
|
+
y = options[:y]
|
145
|
+
|
146
|
+
# set graphic state for the box
|
147
|
+
box_stream << "q\nq\nq\n"
|
148
|
+
box_graphic_state = { ca: options[:opacity], CA: options[:opacity], LW: options[:border_width], LC: 0, LJ: 0, LD: 0 }
|
149
|
+
if options[:box_radius] != 0 # if the text box has rounded corners
|
150
|
+
box_graphic_state[:LC], box_graphic_state[:LJ] = 2, 1
|
151
|
+
end
|
152
|
+
box_graphic_state = graphic_state box_graphic_state # adds the graphic state to Resources and gets the reference
|
153
|
+
box_stream << "#{PDFOperations._object_to_pdf box_graphic_state} gs\n"
|
154
|
+
box_stream << "DeviceRGB CS\nDeviceRGB cs\n"
|
155
|
+
if options[:box_color]
|
156
|
+
box_stream << "#{options[:box_color].join(' ')} scn\n"
|
157
|
+
end
|
158
|
+
if options[:border_width].to_i > 0 && options[:border_color]
|
159
|
+
box_stream << "#{options[:border_color].join(' ')} SCN\n"
|
160
|
+
end
|
161
|
+
# create the path
|
162
|
+
radius = options[:box_radius]
|
163
|
+
half_radius = radius.to_f / 2
|
164
|
+
## set starting point
|
165
|
+
box_stream << "#{options[:x] + radius} #{options[:y]} m\n"
|
166
|
+
## buttom and right corner - first line and first corner
|
167
|
+
box_stream << "#{options[:x] + options[:length] - radius} #{options[:y]} l\n" #buttom
|
168
|
+
if options[:box_radius] != 0 # make first corner, if not straight.
|
169
|
+
box_stream << "#{options[:x] + options[:length] - half_radius} #{options[:y]} "
|
170
|
+
box_stream << "#{options[:x] + options[:length]} #{options[:y] + half_radius} "
|
171
|
+
box_stream << "#{options[:x] + options[:length]} #{options[:y] + radius} c\n"
|
172
|
+
end
|
173
|
+
## right and top-right corner
|
174
|
+
box_stream << "#{options[:x] + options[:length]} #{options[:y] + options[:height] - radius} l\n"
|
175
|
+
if options[:box_radius] != 0
|
176
|
+
box_stream << "#{options[:x] + options[:length]} #{options[:y] + options[:height] - half_radius} "
|
177
|
+
box_stream << "#{options[:x] + options[:length] - half_radius} #{options[:y] + options[:height]} "
|
178
|
+
box_stream << "#{options[:x] + options[:length] - radius} #{options[:y] + options[:height]} c\n"
|
179
|
+
end
|
180
|
+
## top and top-left corner
|
181
|
+
box_stream << "#{options[:x] + radius} #{options[:y] + options[:height]} l\n"
|
182
|
+
if options[:box_radius] != 0
|
183
|
+
box_stream << "#{options[:x] + half_radius} #{options[:y] + options[:height]} "
|
184
|
+
box_stream << "#{options[:x]} #{options[:y] + options[:height] - half_radius} "
|
185
|
+
box_stream << "#{options[:x]} #{options[:y] + options[:height] - radius} c\n"
|
186
|
+
end
|
187
|
+
## left and buttom-left corner
|
188
|
+
box_stream << "#{options[:x]} #{options[:y] + radius} l\n"
|
189
|
+
if options[:box_radius] != 0
|
190
|
+
box_stream << "#{options[:x]} #{options[:y] + half_radius} "
|
191
|
+
box_stream << "#{options[:x] + half_radius} #{options[:y]} "
|
192
|
+
box_stream << "#{options[:x] + radius} #{options[:y]} c\n"
|
193
|
+
end
|
194
|
+
# fill / stroke path
|
195
|
+
box_stream << "h\n"
|
196
|
+
if options[:box_color] && options[:border_width].to_i > 0 && options[:border_color]
|
197
|
+
box_stream << "B\n"
|
198
|
+
elsif options[:box_color] # fill if fill color is set
|
199
|
+
box_stream << "f\n"
|
200
|
+
elsif options[:border_width].to_i > 0 && options[:border_color] # stroke if border is set
|
201
|
+
box_stream << "S\n"
|
202
|
+
end
|
203
|
+
|
204
|
+
# exit graphic state for the box
|
205
|
+
box_stream << "Q\nQ\nQ\n"
|
206
|
+
end
|
207
|
+
contents << box_stream
|
111
208
|
|
112
209
|
# reset x,y by text alignment - x,y are calculated from the buttom left
|
113
210
|
# each unit (1) is 1/72 Inch
|
114
|
-
x = options[:x]
|
115
|
-
y = options[:y]
|
116
211
|
# create text stream
|
117
212
|
text_stream = ""
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
213
|
+
if text.to_s != "" && font_size != 0 && (options[:font_color] || options[:stroke_color])
|
214
|
+
# compute x and y position for text
|
215
|
+
x = options[:x]
|
216
|
+
y = options[:y]
|
217
|
+
|
218
|
+
text_size = dimentions_of text, options[:font_name], font_size
|
219
|
+
if options[:text_align] == :center
|
220
|
+
x = (options[:length] - text_size[0])/2 + x
|
221
|
+
elsif options[:text_align] == :right
|
222
|
+
x = (options[:length] - text_size[0]) + x
|
223
|
+
end
|
224
|
+
if options[:text_valign] == :center
|
225
|
+
y = (options[:height] - text_size[1])/2 + y
|
226
|
+
elsif options[:text_valign] == :top
|
227
|
+
y = (options[:height] - text_size[1]) + y
|
228
|
+
end
|
229
|
+
# set graphic state for text
|
230
|
+
text_stream << "q\nq\nq\n"
|
231
|
+
text_graphic_state = graphic_state({ca: options[:opacity], CA: options[:opacity], LW: options[:stroke_width].to_f, LC: 2, LJ: 1, LD: 0})
|
232
|
+
text_stream << "#{PDFOperations._object_to_pdf text_graphic_state} gs\n"
|
233
|
+
text_stream << "DeviceRGB CS\nDeviceRGB cs\n"
|
234
|
+
# set text render mode
|
235
|
+
if options[:font_color]
|
236
|
+
text_stream << "#{options[:font_color].join(' ')} scn\n"
|
237
|
+
end
|
238
|
+
if options[:stroke_width].to_i > 0 && options[:stroke_color]
|
239
|
+
text_stream << "#{options[:stroke_color].join(' ')} SCN\n"
|
240
|
+
if options[:font_color]
|
241
|
+
text_stream << "2 Tr\n"
|
242
|
+
else
|
243
|
+
final_stream << "1 Tr\n"
|
244
|
+
end
|
245
|
+
elsif options[:font_color]
|
246
|
+
text_stream << "0 Tr\n"
|
247
|
+
else
|
248
|
+
text_stream << "3 Tr\n"
|
249
|
+
end
|
250
|
+
# format text object
|
251
|
+
text_stream << "BT\n" # the Begine Text marker
|
252
|
+
text_stream << PDFOperations._format_name_to_pdf(font options[:font_name]) # Set font name
|
253
|
+
text_stream << " #{font_size} Tf\n" # set font size and add font operator
|
254
|
+
text_stream << "#{options[:font_color].join(' ')} rg\n" # sets the color state
|
255
|
+
text_stream << "#{x} #{y} Td\n" # set location for text object
|
256
|
+
text_stream << PDFOperations._format_string_to_pdf(text) # insert the string in PDF format
|
257
|
+
text_stream << " Tj\n ET\n" # the Text object operator and the End Text marker
|
258
|
+
# exit graphic state for text
|
259
|
+
text_stream << "Q\nQ\nQ\n"
|
144
260
|
end
|
261
|
+
contents << text_stream
|
145
262
|
|
146
|
-
# clear graphic states
|
147
|
-
final_stream << "Q\nQ\nQ\n"
|
148
|
-
final_stream << "Q\nQ\nQ\n"
|
149
|
-
|
150
|
-
contents << final_stream
|
151
263
|
self
|
152
264
|
end
|
153
265
|
|
@@ -236,75 +348,4 @@ end
|
|
236
348
|
|
237
349
|
|
238
350
|
|
239
|
-
# # text_box output example
|
240
|
-
# q
|
241
|
-
# q
|
242
|
-
# /GraphiStateName gs
|
243
|
-
# /DeviceRGB cs
|
244
|
-
# 0.867 0.867 0.867 scn
|
245
|
-
# 293.328 747.000 m
|
246
|
-
# 318.672 747.000 l
|
247
|
-
# 323.090 747.000 326.672 743.418 326.672 739.000 c
|
248
|
-
# 326.672 735.800 l
|
249
|
-
# 326.672 731.382 323.090 727.800 318.672 727.800 c
|
250
|
-
# 293.328 727.800 l
|
251
|
-
# 288.910 727.800 285.328 731.382 285.328 735.800 c
|
252
|
-
# 285.328 739.000 l
|
253
|
-
# 285.328 743.418 288.910 747.000 293.328 747.000 c
|
254
|
-
# h
|
255
|
-
# 293.328 64.200 m
|
256
|
-
# 318.672 64.200 l
|
257
|
-
# 323.090 64.200 326.672 60.618 326.672 56.200 c
|
258
|
-
# 326.672 53.000 l
|
259
|
-
# 326.672 48.582 323.090 45.000 318.672 45.000 c
|
260
|
-
# 293.328 45.000 l
|
261
|
-
# 288.910 45.000 285.328 48.582 285.328 53.000 c
|
262
|
-
# 285.328 56.200 l
|
263
|
-
# 285.328 60.618 288.910 64.200 293.328 64.200 c
|
264
|
-
# h
|
265
|
-
# f
|
266
|
-
# 0.000 0.000 0.000 scn
|
267
|
-
# /DeviceRGB CS
|
268
|
-
# 1.000 1.000 1.000 SCN
|
269
|
-
|
270
|
-
# 2 Tr
|
271
|
-
# 0.000 0.000 0.000 scn
|
272
|
-
# 0.000 0.000 0.000 SCN
|
273
|
-
# 1.000 1.000 1.000 SCN
|
274
|
-
# 0.000 0.000 0.000 scn
|
275
|
-
# 0.000 0.000 0.000 scn
|
276
|
-
# 0.000 0.000 0.000 SCN
|
277
|
-
|
278
|
-
# BT
|
279
|
-
# 291.776 733.3119999999999 Td
|
280
|
-
# /FontName 16 Tf
|
281
|
-
# [<2d2032202d>] TJ
|
282
|
-
# ET
|
283
|
-
|
284
|
-
# 1.000 1.000 1.000 SCN
|
285
|
-
# 0.000 0.000 0.000 scn
|
286
|
-
# 0.000 0.000 0.000 scn
|
287
|
-
# 0.000 0.000 0.000 SCN
|
288
|
-
# 1.000 1.000 1.000 SCN
|
289
|
-
# 0.000 0.000 0.000 scn
|
290
|
-
# 0.000 0.000 0.000 scn
|
291
|
-
# 0.000 0.000 0.000 SCN
|
292
|
-
|
293
|
-
# BT
|
294
|
-
# 291.776 50.512 Td
|
295
|
-
# /FontName 16 Tf
|
296
|
-
# [<2d2032202d>] TJ
|
297
|
-
# ET
|
298
|
-
|
299
|
-
# 1.000 1.000 1.000 SCN
|
300
|
-
# 0.000 0.000 0.000 scn
|
301
|
-
|
302
|
-
# 0 Tr
|
303
|
-
# Q
|
304
|
-
# Q
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
351
|
|
@@ -8,15 +8,20 @@
|
|
8
8
|
|
9
9
|
|
10
10
|
module CombinePDF
|
11
|
-
#@private
|
12
11
|
#:nodoc: all
|
12
|
+
|
13
|
+
# @private
|
14
|
+
# This is an internal class. you don't need it.
|
13
15
|
class PDFDecrypt
|
14
16
|
|
15
|
-
|
17
|
+
# make a new Decrypt object. requires:
|
18
|
+
# objects:: an array containing the encrypted objects.
|
19
|
+
# root_dictionary:: the root PDF dictionary, containing the Encrypt dictionary.
|
20
|
+
def initialize objects=[], root_dictionary = {}
|
16
21
|
@objects = objects
|
17
|
-
@encryption_dictionary =
|
22
|
+
@encryption_dictionary = root_dictionary[:Encrypt]
|
18
23
|
raise "Cannot decrypt an encrypted file without an encryption dictionary!" unless @encryption_dictionary
|
19
|
-
@
|
24
|
+
@root_dictionary = root_dictionary
|
20
25
|
@padding_key = [ 0x28, 0xBF, 0x4E, 0x5E, 0x4E, 0x75, 0x8A, 0x41,
|
21
26
|
0x64, 0x00, 0x4E, 0x56, 0xFF, 0xFA, 0x01, 0x08,
|
22
27
|
0x2E, 0x2E, 0x00, 0xB6, 0xD0, 0x68, 0x3E, 0x80,
|
@@ -25,6 +30,25 @@ module CombinePDF
|
|
25
30
|
@encryption_iv = nil
|
26
31
|
PDFOperations.change_references_to_actual_values @objects, @encryption_dictionary
|
27
32
|
end
|
33
|
+
|
34
|
+
# call this to start the decryption.
|
35
|
+
def decrypt
|
36
|
+
raise_encrypted_error @encryption_dictionary unless @encryption_dictionary[:Filter] == :Standard
|
37
|
+
@key = set_general_key
|
38
|
+
case @encryption_dictionary[:V]
|
39
|
+
when 1,2
|
40
|
+
warn "trying to decrypt with RC4."
|
41
|
+
# raise_encrypted_error
|
42
|
+
_perform_decrypt_proc_ @objects, self.method(:decrypt_RC4)
|
43
|
+
else
|
44
|
+
raise_encrypted_error
|
45
|
+
end
|
46
|
+
#rebuild stream lengths?
|
47
|
+
@objects
|
48
|
+
end
|
49
|
+
|
50
|
+
protected
|
51
|
+
|
28
52
|
def set_general_key(password = "")
|
29
53
|
# 1) make sure the initial key is 32 byte long (if no password, uses padding).
|
30
54
|
key = (password.bytes[0..32] + @padding_key)[0..31].pack('C*').force_encoding(Encoding::ASCII_8BIT)
|
@@ -35,7 +59,7 @@ module CombinePDF
|
|
35
59
|
key << [@encryption_dictionary[:P]].pack('i')
|
36
60
|
# 4) Pass the first element of the file’s file identifier array
|
37
61
|
# (the value of the ID entry in the document’s trailer dictionary
|
38
|
-
key << @
|
62
|
+
key << @root_dictionary[:ID][0]
|
39
63
|
# # 4(a) (Security handlers of revision 4 or greater)
|
40
64
|
# # if document metadata is not being encrypted, add 4 bytes with the value 0xFFFFFFFF.
|
41
65
|
if @encryption_dictionary[:R] >= 4
|
@@ -68,20 +92,6 @@ module CombinePDF
|
|
68
92
|
end
|
69
93
|
@key
|
70
94
|
end
|
71
|
-
def decrypt
|
72
|
-
raise_encrypted_error @encryption_dictionary unless @encryption_dictionary[:Filter] == :Standard
|
73
|
-
@key = set_general_key
|
74
|
-
case @encryption_dictionary[:V]
|
75
|
-
when 1,2
|
76
|
-
warn "trying to decrypt with RC4."
|
77
|
-
# raise_encrypted_error
|
78
|
-
_perform_decrypt_proc_ @objects, self.method(:decrypt_RC4)
|
79
|
-
else
|
80
|
-
raise_encrypted_error
|
81
|
-
end
|
82
|
-
#rebuild stream lengths?
|
83
|
-
@objects
|
84
|
-
end
|
85
95
|
def decrypt_none(encrypted, encrypted_id, encrypted_generation, encrypted_filter)
|
86
96
|
"encrypted"
|
87
97
|
end
|
@@ -8,17 +8,27 @@
|
|
8
8
|
|
9
9
|
|
10
10
|
module CombinePDF
|
11
|
-
|
12
11
|
#@private
|
13
12
|
#:nodoc: all
|
13
|
+
|
14
|
+
# This is an internal class. you don't need it.
|
14
15
|
module PDFFilter
|
15
16
|
module_function
|
16
17
|
|
17
|
-
|
18
|
+
# deflate / compress an object.
|
19
|
+
#
|
20
|
+
# <b>isn't supported yet!</b>
|
21
|
+
#
|
22
|
+
# object:: object to compress.
|
23
|
+
# filter:: filter to use.
|
24
|
+
def deflate_object object = nil, filter = :none
|
18
25
|
false
|
19
26
|
end
|
20
27
|
|
21
|
-
|
28
|
+
# inflate / decompress an object
|
29
|
+
#
|
30
|
+
# object:: object to decompress.
|
31
|
+
def inflate_object object = nil
|
22
32
|
filter_array = object[:Filter]
|
23
33
|
if filter_array.is_a?(Hash) && filter_array[:is_reference_only]
|
24
34
|
filter_array = filter_array[:referenced_object]
|
@@ -71,6 +81,9 @@ module CombinePDF
|
|
71
81
|
object.delete(:Filter)
|
72
82
|
true
|
73
83
|
end
|
84
|
+
|
85
|
+
protected
|
86
|
+
|
74
87
|
def raise_unsupported_error (object = {})
|
75
88
|
raise "Filter #{object} unsupported. couldn't deflate object"
|
76
89
|
end
|
@@ -5,21 +5,22 @@ module CombinePDF
|
|
5
5
|
## These are common functions, used within the different classes
|
6
6
|
## These functions aren't open to the public.
|
7
7
|
################################################################
|
8
|
+
|
8
9
|
#@private
|
10
|
+
# lists the Hash keys used for PDF objects
|
11
|
+
#
|
12
|
+
# the CombinePDF library doesn't use special classes for its objects (PDFPage class, PDFStream class or anything like that).
|
13
|
+
#
|
14
|
+
# there is only one PDF class which represents the whole of the PDF file.
|
15
|
+
#
|
16
|
+
# this Hash lists the private Hash keys that the CombinePDF library uses to
|
17
|
+
# differentiate between complex PDF objects.
|
9
18
|
PRIVATE_HASH_KEYS = [:indirect_reference_id, :indirect_generation_number, :raw_stream_content, :is_reference_only, :referenced_object, :indirect_without_dictionary]
|
10
19
|
#@private
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
98 => 8, #b
|
16
|
-
102 => 255, #f
|
17
|
-
40 => 40, #(
|
18
|
-
41 => 41, #)
|
19
|
-
92 => 92 #\
|
20
|
-
}
|
21
|
-
#@private
|
22
|
-
#:nodoc: all
|
20
|
+
#:nodoc: all
|
21
|
+
|
22
|
+
|
23
|
+
# This is an internal class. you don't need it.
|
23
24
|
module PDFOperations
|
24
25
|
module_function
|
25
26
|
def inject_to_page page = {Type: :Page, MediaBox: [0,0,612.0,792.0], Resources: {}, Contents: []}, stream = nil, top = true
|
@@ -11,15 +11,16 @@
|
|
11
11
|
module CombinePDF
|
12
12
|
|
13
13
|
|
14
|
-
#######################################################
|
15
14
|
#@private
|
16
15
|
#:nodoc: all
|
16
|
+
|
17
17
|
# This is the Parser class.
|
18
18
|
#
|
19
19
|
# It takes PDF data and parses it.
|
20
20
|
#
|
21
21
|
# The information is then used to initialize a PDF object.
|
22
|
-
|
22
|
+
#
|
23
|
+
# This is an internal class. you don't need it.
|
23
24
|
class PDFParser
|
24
25
|
|
25
26
|
# the array containing all the parsed data (PDF Objects)
|
@@ -30,6 +31,12 @@ module CombinePDF
|
|
30
31
|
#
|
31
32
|
# they are mainly to used to know if the file is (was) encrypted and to get more details.
|
32
33
|
attr_reader :info_object, :root_object
|
34
|
+
|
35
|
+
# when creating a parser, it is important to set the data (String) we wish to parse.
|
36
|
+
#
|
37
|
+
# <b>the data is required and it is not possible to set the data at a later stage</b>
|
38
|
+
#
|
39
|
+
# string:: the data to be parsed, as a String object.
|
33
40
|
def initialize (string)
|
34
41
|
raise TypeError, "couldn't parse and data, expecting type String" unless string.is_a? String
|
35
42
|
@string_to_parse = string.force_encoding(Encoding::ASCII_8BIT)
|
@@ -43,7 +50,7 @@ module CombinePDF
|
|
43
50
|
@scanner = nil
|
44
51
|
end
|
45
52
|
|
46
|
-
# parse the data in the parser (set
|
53
|
+
# parse the data in the new parser (the data already set through the initialize / new method)
|
47
54
|
def parse
|
48
55
|
return @parsed unless @parsed.empty?
|
49
56
|
@scanner = StringScanner.new @string_to_parse
|
@@ -114,8 +121,9 @@ module CombinePDF
|
|
114
121
|
@parsed
|
115
122
|
end
|
116
123
|
|
117
|
-
|
118
|
-
|
124
|
+
# the actual recoursive parsing is done here.
|
125
|
+
#
|
126
|
+
# this is an internal function, but it was left exposed for posible future features.
|
119
127
|
def _parse_
|
120
128
|
out = []
|
121
129
|
str = ''
|
@@ -11,26 +11,63 @@
|
|
11
11
|
|
12
12
|
|
13
13
|
module CombinePDF
|
14
|
-
|
14
|
+
|
15
15
|
# PDF class is the PDF object that can save itself to
|
16
16
|
# a file and that can be used as a container for a full
|
17
|
-
# PDF file data, including version etc'.
|
17
|
+
# PDF file data, including version, information etc'.
|
18
18
|
#
|
19
19
|
# PDF objects can be used to combine or to inject data.
|
20
|
-
# == Combine
|
20
|
+
# == Combine/Merge PDF files or Pages
|
21
21
|
# To combine PDF files (or data):
|
22
22
|
# pdf = CombinePDF.new
|
23
|
-
# pdf << CombinePDF.new
|
24
|
-
# CombinePDF.new("file2.pdf")
|
23
|
+
# pdf << CombinePDF.new("file1.pdf") # one way to combine, very fast.
|
24
|
+
# pdf << CombinePDF.new("file2.pdf")
|
25
25
|
# pdf.save "combined.pdf"
|
26
|
-
#
|
27
|
-
#
|
28
|
-
#
|
29
|
-
#
|
30
|
-
#
|
31
|
-
#
|
32
|
-
#
|
33
|
-
|
26
|
+
# or even a one liner:
|
27
|
+
# (CombinePDF.new("file1.pdf") << CombinePDF.new("file2.pdf") << CombinePDF.new("file3.pdf")).save("combined.pdf")
|
28
|
+
# you can also add just odd or even pages:
|
29
|
+
# pdf = CombinePDF.new
|
30
|
+
# i = 0
|
31
|
+
# CombinePDF.new("file.pdf").pages.each do |page
|
32
|
+
# i += 1
|
33
|
+
# pdf << page if i.even?
|
34
|
+
# end
|
35
|
+
# pdf.save "even_pages.pdf"
|
36
|
+
# notice that adding all the pages one by one is slower then adding the whole file.
|
37
|
+
# == Add content to existing pages (Stamp / Watermark)
|
38
|
+
# To add content to existing PDF pages, first import the new content from an existing PDF file.
|
39
|
+
# after that, add the content to each of the pages in your existing PDF.
|
40
|
+
#
|
41
|
+
# in this example, we will add a company logo to each page:
|
42
|
+
# company_logo = CombinePDF.new("company_logo.pdf").pages[0]
|
43
|
+
# pdf = CombinePDF.new "content_file.pdf"
|
44
|
+
# pdf.pages.each {|page| page << company_logo} # notice the << operator is on a page and not a PDF object.
|
45
|
+
# pdf.save "content_with_logo.pdf"
|
46
|
+
# Notice the << operator is on a page and not a PDF object. The << operator acts differently on PDF objects and on Pages.
|
47
|
+
#
|
48
|
+
# The << operator defaults to secure injection by renaming references to avoid conflics. For overlaying pages using compressed data that might not be editable (due to limited filter support), you can use:
|
49
|
+
# pdf.pages(nil, false).each {|page| page << stamp_page}
|
50
|
+
#
|
51
|
+
#
|
52
|
+
# Notice that page objects are Hash class objects and the << operator was added to the Page instances without altering the class.
|
53
|
+
#
|
54
|
+
# == Page Numbering
|
55
|
+
# adding page numbers to a PDF object or file is as simple as can be:
|
56
|
+
# pdf = CombinePDF.new "file_to_number.pdf"
|
57
|
+
# pdf.number_pages
|
58
|
+
# pdf.save "file_with_numbering.pdf"
|
59
|
+
#
|
60
|
+
# numbering can be done with many different options, with different formating, with or without a box object, and even with opacity values.
|
61
|
+
#
|
62
|
+
# == Loading PDF data
|
63
|
+
# Loading PDF data can be done from file system or directly from the memory.
|
64
|
+
#
|
65
|
+
# Loading data from a file is easy:
|
66
|
+
# pdf = CombinePDF.new("file.pdf")
|
67
|
+
# you can also parse PDF files from memory:
|
68
|
+
# pdf_data = IO.read 'file.pdf' # for this demo, load a file to memory
|
69
|
+
# pdf = CombinePDF.parse(pdf_data)
|
70
|
+
# Loading from the memory is especially effective for importing PDF data recieved through the internet or from a different authoring library such as Prawn.
|
34
71
|
class PDF
|
35
72
|
# the objects attribute is an Array containing all the PDF sub-objects for te class.
|
36
73
|
attr_reader :objects
|
@@ -144,7 +181,8 @@ module CombinePDF
|
|
144
181
|
# the content added is compressed using unsupported filters or options.
|
145
182
|
#
|
146
183
|
# the default is for the << operator to attempt a secure copy, by attempting to rename the content references and avoiding conflicts.
|
147
|
-
# because
|
184
|
+
# because not all PDF files are created equal (some might have formating errors or variations),
|
185
|
+
# it is imposiible to learn if the attempt was successful.
|
148
186
|
#
|
149
187
|
# (page objects are Hash class objects. the << operator is added to the specific instances without changing the class)
|
150
188
|
#
|
@@ -240,6 +278,83 @@ module CombinePDF
|
|
240
278
|
return self #return self object for injection chaining (pdf << page << page << page)
|
241
279
|
end
|
242
280
|
|
281
|
+
# and page numbers to the PDF
|
282
|
+
# options:: a Hash of options setting the behavior and format of the page numbers:
|
283
|
+
# - :number_format a string representing the format for page number. defaults to ' - %d - '.
|
284
|
+
# - :number_location an Array containing the location for the page numbers, can be :top, :buttom, :top_left, :top_right, :bottom_left, :bottom_right. defaults to [:top, :buttom].
|
285
|
+
# - :start_at a Fixnum that sets the number for first page number. defaults to 1.
|
286
|
+
# - :margin_from_height a number (PDF points) for the top and buttom margins. defaults to 45.
|
287
|
+
# - :margin_from_side a number (PDF points) for the left and right margins. defaults to 15.
|
288
|
+
# also take all the options for PDFWriter.textbox.
|
289
|
+
# defaults to font_name: :Helvetica, font_size: 12 and no box (:border_width => 0, :box_color => nil).
|
290
|
+
def number_pages(options = {})
|
291
|
+
opt = {
|
292
|
+
number_format: ' - %d - ',
|
293
|
+
number_location: [:top, :bottom],
|
294
|
+
start_at: 1,
|
295
|
+
font_size: 12,
|
296
|
+
font_name: :Helvetica,
|
297
|
+
margin_from_height: 45,
|
298
|
+
margin_from_side: 15
|
299
|
+
}
|
300
|
+
opt.update options
|
301
|
+
page_number = opt[:start_at]
|
302
|
+
pages.each do |page|
|
303
|
+
# create a "stamp" PDF page with the same size as the target page
|
304
|
+
mediabox = page[:MediaBox]
|
305
|
+
stamp = PDFWriter.new mediabox
|
306
|
+
# set the visible dimentions to the CropBox, if it exists.
|
307
|
+
cropbox = page[:CropBox]
|
308
|
+
mediabox = cropbox if cropbox
|
309
|
+
# set stamp text
|
310
|
+
text = opt[:number_format] % page_number
|
311
|
+
# compute locations for text boxes
|
312
|
+
text_dimantions = stamp.dimentions_of( text, opt[:font_name], opt[:font_size] )
|
313
|
+
box_width = text_dimantions[0] * 1.2
|
314
|
+
box_height = text_dimantions[1] * 2
|
315
|
+
opt[:length] ||= box_width
|
316
|
+
opt[:height] ||= box_height
|
317
|
+
from_height = 45
|
318
|
+
from_side = 15
|
319
|
+
page_width = mediabox[2]
|
320
|
+
page_height = mediabox[3]
|
321
|
+
center_position = (page_width - box_width)/2
|
322
|
+
left_position = from_side
|
323
|
+
right_position = page_width - from_side - box_width
|
324
|
+
top_position = page_height - from_height
|
325
|
+
buttom_position = from_height + box_height
|
326
|
+
x = center_position
|
327
|
+
y = top_position
|
328
|
+
if opt[:number_location].include? :top
|
329
|
+
stamp.textbox text, {x: x, y: y }.merge(opt)
|
330
|
+
end
|
331
|
+
y = buttom_position #bottom position
|
332
|
+
if opt[:number_location].include? :bottom
|
333
|
+
stamp.textbox text, {x: x, y: y }.merge(opt)
|
334
|
+
end
|
335
|
+
y = top_position #top position
|
336
|
+
x = left_position # left posotion
|
337
|
+
if opt[:number_location].include? :top_left
|
338
|
+
stamp.textbox text, {x: x, y: y }.merge(opt)
|
339
|
+
end
|
340
|
+
y = buttom_position #bottom position
|
341
|
+
if opt[:number_location].include? :bottom_left
|
342
|
+
stamp.textbox text, {x: x, y: y }.merge(opt)
|
343
|
+
end
|
344
|
+
x = right_position # right posotion
|
345
|
+
y = top_position #top position
|
346
|
+
if opt[:number_location].include? :top_right
|
347
|
+
stamp.textbox text, {x: x, y: y }.merge(opt)
|
348
|
+
end
|
349
|
+
y = buttom_position #bottom position
|
350
|
+
if opt[:number_location].include? :bottom_right
|
351
|
+
stamp.textbox text, {x: x, y: y }.merge(opt)
|
352
|
+
end
|
353
|
+
page << stamp
|
354
|
+
page_number += 1
|
355
|
+
end
|
356
|
+
end
|
357
|
+
|
243
358
|
# get the title for the pdf
|
244
359
|
# The title is stored in the information dictionary and isn't required
|
245
360
|
def title
|
@@ -264,7 +379,11 @@ module CombinePDF
|
|
264
379
|
@info[:Author] = new_author
|
265
380
|
end
|
266
381
|
end
|
267
|
-
|
382
|
+
|
383
|
+
#:nodoc: all
|
384
|
+
|
385
|
+
|
386
|
+
class PDF
|
268
387
|
# @private
|
269
388
|
# Some PDF objects contain references to other PDF objects.
|
270
389
|
#
|
@@ -296,7 +415,9 @@ module CombinePDF
|
|
296
415
|
def each_object(&block)
|
297
416
|
PDFOperations._each_object(@objects, &block)
|
298
417
|
end
|
418
|
+
|
299
419
|
protected
|
420
|
+
|
300
421
|
# @private
|
301
422
|
# this function returns all the Page objects - regardless of order and even if not cataloged
|
302
423
|
# could be used for finding "lost" pages... but actually rather useless.
|
@@ -1,26 +1,5 @@
|
|
1
1
|
module CombinePDF
|
2
2
|
class PDFWriter < Hash
|
3
|
-
protected
|
4
|
-
METRICS_DICTIONARY = {
|
5
|
-
:"Times-Roman" => TIMES_ROMAN_METRICS,
|
6
|
-
:"Times-Bold" => TIMES_BOLD_METRICS,
|
7
|
-
:"Times-Italic" => TIMES_ITALIC_METRICS,
|
8
|
-
:"Times-BoldItalic" => TIMES_BOLDITALIC_METRICS,
|
9
|
-
:Helvetica => HELVETICA_METRICS,
|
10
|
-
:"Helvetica-Bold" => HELVETICA_BOLD_METRICS,
|
11
|
-
:"Helvetica-BoldOblique"=> HELVETICA_BOLDOBLIQUE_METRICS,
|
12
|
-
:"Helvetica-Oblique" => HELVETICA_OBLIQUE_METRICS,
|
13
|
-
:Courier => COURIER_METRICS,
|
14
|
-
:"Courier-Bold" => COURIER_BOLD_METRICS,
|
15
|
-
:"Courier-Oblique" => COURIER_OBLIQUE_METRICS,
|
16
|
-
:"Courier-BoldOblique" => COURIER_BOLDOBLIQUE_METRICS,
|
17
|
-
:Symbol => SYMBOL_METRICS,
|
18
|
-
:ZapfDingbats => ZAPFDINGBATS_METRICS
|
19
|
-
}
|
20
|
-
def self.get_metrics(font_name)
|
21
|
-
METRICS_DICTIONARY[font_name]
|
22
|
-
end
|
23
|
-
|
24
3
|
# This function calculates the dimentions of a string in a PDF.
|
25
4
|
#
|
26
5
|
# UNICODE SUPPORT IS MISSING!
|
@@ -46,6 +25,29 @@ module CombinePDF
|
|
46
25
|
end
|
47
26
|
[width.to_f/1000*size, height.to_f/1000*size]
|
48
27
|
end
|
28
|
+
|
29
|
+
protected
|
30
|
+
|
31
|
+
METRICS_DICTIONARY = {
|
32
|
+
:"Times-Roman" => TIMES_ROMAN_METRICS,
|
33
|
+
:"Times-Bold" => TIMES_BOLD_METRICS,
|
34
|
+
:"Times-Italic" => TIMES_ITALIC_METRICS,
|
35
|
+
:"Times-BoldItalic" => TIMES_BOLDITALIC_METRICS,
|
36
|
+
:Helvetica => HELVETICA_METRICS,
|
37
|
+
:"Helvetica-Bold" => HELVETICA_BOLD_METRICS,
|
38
|
+
:"Helvetica-BoldOblique"=> HELVETICA_BOLDOBLIQUE_METRICS,
|
39
|
+
:"Helvetica-Oblique" => HELVETICA_OBLIQUE_METRICS,
|
40
|
+
:Courier => COURIER_METRICS,
|
41
|
+
:"Courier-Bold" => COURIER_BOLD_METRICS,
|
42
|
+
:"Courier-Oblique" => COURIER_OBLIQUE_METRICS,
|
43
|
+
:"Courier-BoldOblique" => COURIER_BOLDOBLIQUE_METRICS,
|
44
|
+
:Symbol => SYMBOL_METRICS,
|
45
|
+
:ZapfDingbats => ZAPFDINGBATS_METRICS
|
46
|
+
}
|
47
|
+
def self.get_metrics(font_name)
|
48
|
+
METRICS_DICTIONARY[font_name]
|
49
|
+
end
|
50
|
+
|
49
51
|
# this method returns the size for which the text fits the requested metrices
|
50
52
|
# the size is type Float and is rather exact
|
51
53
|
# if the text cannot fit such a small place, returns zero (0).
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: combine_pdf
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.7
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Boaz Segev
|
@@ -26,7 +26,8 @@ dependencies:
|
|
26
26
|
- !ruby/object:Gem::Version
|
27
27
|
version: 0.1.5
|
28
28
|
description: A nifty gem, in pure Ruby, to parse PDF files and combine (merge) them
|
29
|
-
with other PDF files, watermark them or stamp them (all using
|
29
|
+
with other PDF files, number the pages, watermark them or stamp them (all using
|
30
|
+
the PDF file format).
|
30
31
|
email: bsegev@gmail.com
|
31
32
|
executables: []
|
32
33
|
extensions: []
|