combine_pdf 0.0.6 → 0.0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +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: []
|