combine_pdf 0.1.7 → 0.1.8
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +6 -0
- data/lib/combine_pdf.rb +3 -179
- data/lib/combine_pdf/combine_pdf_basic_writer.rb +4 -3
- data/lib/combine_pdf/combine_pdf_decrypt.rb +13 -0
- data/lib/combine_pdf/combine_pdf_methods.rb +187 -0
- data/lib/combine_pdf/combine_pdf_operations.rb +22 -1
- data/lib/combine_pdf/combine_pdf_pdf.rb +1 -1
- data/lib/combine_pdf/version.rb +1 -1
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 01bc76f1d54a27d2c7eb98b55a37655d48edd496
|
4
|
+
data.tar.gz: 809d1607c8142ce61b31b078b3f3b24ff2716a05
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ce3db7d462887c3ef9e38e675efae4d8ddd5f4a18636566f695ccb3e5fa357e722c463f5d383889bcd67e5f15da65838549fe3d034aa8fd0adf22f3a9b2deca8
|
7
|
+
data.tar.gz: 0744547ed7fcdbb415925248ffa0b9e766269b90c236c30904a396dbfdd121dfe6ad3ac8f8fac72ea371dbf2ed47cb29fcdc9f9b8d956e6a03fff1354dbfe668
|
data/CHANGELOG.md
CHANGED
@@ -2,6 +2,12 @@
|
|
2
2
|
|
3
3
|
***
|
4
4
|
|
5
|
+
Change log v.0.1.8
|
6
|
+
|
7
|
+
**fix**: Fixed an issue reported by Saba, where PDF files that were written using bad practices (namely, without wrapping their content streams correctly) would not be stamped correctly due to changes in the space matrix (CTM). Fixed by wrapping all existing streams.
|
8
|
+
|
9
|
+
***
|
10
|
+
|
5
11
|
Change log v.0.1.7
|
6
12
|
|
7
13
|
**fix**: PDF `insert` had a typo in the code that would cause failure when unsupported object insertion was attempted - fixed by Nathan Keyes (nkeyes).
|
data/lib/combine_pdf.rb
CHANGED
@@ -17,6 +17,8 @@ load "combine_pdf/combine_pdf_parser.rb"
|
|
17
17
|
load "combine_pdf/combine_pdf_pdf.rb"
|
18
18
|
require "combine_pdf/version"
|
19
19
|
|
20
|
+
load "combine_pdf/combine_pdf_methods.rb"
|
21
|
+
|
20
22
|
|
21
23
|
# This is a pure ruby library to combine/merge, stmap/overlay and number PDF files - as well as to create tables (ment for indexing combined files).
|
22
24
|
#
|
@@ -103,185 +105,7 @@ require "combine_pdf/version"
|
|
103
105
|
#
|
104
106
|
# MIT
|
105
107
|
module CombinePDF
|
106
|
-
module_function
|
107
|
-
|
108
|
-
# Create an empty PDF object or create a PDF object from a file (parsing the file).
|
109
|
-
# file_name:: is the name of a file to be parsed.
|
110
|
-
def load(file_name = "")
|
111
|
-
raise TypeError, "couldn't parse and data, expecting type String" unless file_name.is_a?(String) || file_name.is_a?(Pathname)
|
112
|
-
return PDF.new() if file_name == ''
|
113
|
-
PDF.new( PDFParser.new( IO.read(file_name).force_encoding(Encoding::ASCII_8BIT) ) )
|
114
|
-
end
|
115
|
-
def new(file_name = "")
|
116
|
-
load(file_name)
|
117
|
-
end
|
118
|
-
|
119
|
-
# Create a PDF object from a raw PDF data (parsing the data).
|
120
|
-
# data:: is a string that represents the content of a PDF file.
|
121
|
-
def parse(data)
|
122
|
-
raise TypeError, "couldn't parse and data, expecting type String" unless data.is_a? String
|
123
|
-
PDF.new( PDFParser.new(data) )
|
124
|
-
end
|
125
|
-
# makes a PDFWriter object
|
126
|
-
#
|
127
|
-
# PDFWriter objects reresent an empty page and have the method "textbox"
|
128
|
-
# that adds content to that page.
|
129
|
-
#
|
130
|
-
# PDFWriter objects are used internally for numbering pages (by creating a PDF page
|
131
|
-
# with the page number and "stamping" it over the existing page).
|
132
|
-
#
|
133
|
-
# ::mediabox an Array representing the size of the PDF document. defaults to: [0.0, 0.0, 612.0, 792.0]
|
134
|
-
#
|
135
|
-
# if the page is PDFWriter object as a stamp, the final size will be that of the original page.
|
136
|
-
def create_page(mediabox = [0, 0, 595.3, 841.9])
|
137
|
-
PDFWriter.new mediabox
|
138
|
-
end
|
139
|
-
|
140
|
-
# makes a PDF object containing a table
|
141
|
-
#
|
142
|
-
# all the pages in this PDF object are PDFWriter objects and are
|
143
|
-
# writable using the texbox function (should you wish to add a title, or more info)
|
144
|
-
#
|
145
|
-
# the main intended use of this method is to create indexes (a table of contents) for merged data.
|
146
|
-
#
|
147
|
-
# example:
|
148
|
-
# pdf = CombinePDF.create_table headers: ["header 1", "another header"], table_data: [ ["this is one row", "with two columns"] , ["this is another row", "also two columns", "the third will be ignored"] ]
|
149
|
-
# pdf.save "table_file.pdf"
|
150
|
-
#
|
151
|
-
# accepts a Hash with any of the following keys as well as any of the PDFWriter#textbox options:
|
152
|
-
# headers:: an Array of strings with the headers (will be repeated every page).
|
153
|
-
# table_data:: as Array of Arrays, each containing a string for each column. the first row sets the number of columns. extra columns will be ignored.
|
154
|
-
# font:: a registered or standard font name (see PDFWriter). defaults to nil (:Helvetica).
|
155
|
-
# header_font:: a registered or standard font name for the headers (see PDFWriter). defaults to nil (the font for all the table rows).
|
156
|
-
# max_font_size:: the maximum font size. if the string doesn't fit, it will be resized. defaults to 14.
|
157
|
-
# column_widths:: an array of relative column widths ([1,2] will display only the first two columns, the second twice as big as the first). defaults to nil (even widths).
|
158
|
-
# header_color:: the header color. defaults to [0.8, 0.8, 0.8] (light gray).
|
159
|
-
# main_color:: main row color. defaults to nil (transparent / white).
|
160
|
-
# alternate_color:: alternate row color. defaults to [0.95, 0.95, 0.95] (very light gray).
|
161
|
-
# font_color:: font color. defaults to [0,0,0] (black).
|
162
|
-
# border_color:: border color. defaults to [0,0,0] (black).
|
163
|
-
# border_width:: border width in PDF units. defaults to 1.
|
164
|
-
# header_align:: the header text alignment within each column (:right, :left, :center). defaults to :center.
|
165
|
-
# row_align:: the row text alignment within each column. defaults to :left (:right for RTL table).
|
166
|
-
# direction:: the table's writing direction (:ltr or :rtl). this reffers to the direction of the columns and doesn't effect text (rtl text is automatically recognized). defaults to :ltr.
|
167
|
-
# rows_per_page:: the number of rows per page, INCLUDING the header row. deafults to 25.
|
168
|
-
# page_size:: the size of the page in PDF points. defaults to [0, 0, 595.3, 841.9] (A4).
|
169
|
-
def create_table(options = {})
|
170
|
-
defaults = {
|
171
|
-
headers: nil,
|
172
|
-
table_data: [[]],
|
173
|
-
font: nil,
|
174
|
-
header_font: nil,
|
175
|
-
max_font_size: 14,
|
176
|
-
column_widths: nil,
|
177
|
-
header_color: [0.8, 0.8, 0.8],
|
178
|
-
main_color: nil,
|
179
|
-
alternate_color: [0.95, 0.95, 0.95],
|
180
|
-
font_color: [0,0,0],
|
181
|
-
border_color: [0,0,0],
|
182
|
-
border_width: 1,
|
183
|
-
header_align: :center,
|
184
|
-
row_align: nil,
|
185
|
-
direction: :ltr,
|
186
|
-
rows_per_page: 25,
|
187
|
-
page_size: [0, 0, 595.3, 841.9] #A4
|
188
|
-
}
|
189
|
-
options = defaults.merge options
|
190
|
-
options[:header_font] = options[:font] unless options[:header_font]
|
191
|
-
options[:row_align] ||= ( (options[:direction] == :rtl) ? :right : :left )
|
192
|
-
# assert table_data is an array of arrays
|
193
|
-
return false unless (options[:table_data].select {|r| !r.is_a?(Array) }).empty?
|
194
|
-
# compute sizes
|
195
|
-
page_size = options[:page_size]
|
196
|
-
top = page_size[3] * 0.9
|
197
|
-
height = page_size[3] * 0.8 / options[:rows_per_page]
|
198
|
-
from_side = page_size[2] * 0.1
|
199
|
-
width = page_size[2] * 0.8
|
200
|
-
columns = options[:table_data][0].length
|
201
|
-
column_widths = []
|
202
|
-
columns.times {|i| column_widths << (width/columns) }
|
203
|
-
if options[:column_widths]
|
204
|
-
scale = 0
|
205
|
-
options[:column_widths].each {|w| scale += w}
|
206
|
-
column_widths = []
|
207
|
-
options[:column_widths].each { |w| column_widths << (width*w/scale) }
|
208
|
-
end
|
209
|
-
column_widths = column_widths.reverse if options[:direction] == :rtl
|
210
|
-
# set pdf object and start writing the data
|
211
|
-
table = PDF.new()
|
212
|
-
page = nil
|
213
|
-
rows_per_page = options[:rows_per_page]
|
214
|
-
row_number = rows_per_page + 1
|
215
|
-
|
216
|
-
options[:table_data].each do |row_data|
|
217
|
-
if row_number > rows_per_page
|
218
|
-
page = create_page page_size
|
219
|
-
table << page
|
220
|
-
row_number = 1
|
221
|
-
# add headers
|
222
|
-
if options[:headers]
|
223
|
-
x = from_side
|
224
|
-
headers = options[:headers]
|
225
|
-
headers = headers.reverse if options[:direction] == :rtl
|
226
|
-
column_widths.each_index do |i|
|
227
|
-
text = headers[i].to_s
|
228
|
-
page.textbox text, {x: x, y: (top - (height*row_number)), width: column_widths[i], height: height, box_color: options[:header_color], text_align: options[:header_align] }.merge(options).merge({font: options[:header_font]})
|
229
|
-
x += column_widths[i]
|
230
|
-
end
|
231
|
-
row_number += 1
|
232
|
-
end
|
233
|
-
end
|
234
|
-
x = from_side
|
235
|
-
row_data = row_data.reverse if options[:direction] == :rtl
|
236
|
-
column_widths.each_index do |i|
|
237
|
-
text = row_data[i].to_s
|
238
|
-
box_color = options[:main_color]
|
239
|
-
box_color = options[:alternate_color] if options[:alternate_color] && row_number.odd?
|
240
|
-
page.textbox text, {x: x, y: (top - (height*row_number)), width: column_widths[i], height: height, box_color: box_color, text_align: options[:row_align]}.merge(options)
|
241
|
-
x += column_widths[i]
|
242
|
-
end
|
243
|
-
row_number += 1
|
244
|
-
end
|
245
|
-
table
|
246
|
-
end
|
247
|
-
def new_table(options = {})
|
248
|
-
create_table options
|
249
|
-
end
|
250
|
-
|
251
|
-
# adds a correctly formatted font object to the font library.
|
252
|
-
#
|
253
|
-
# registered fonts will remain in the library and will only be embeded in
|
254
|
-
# PDF objects when they are used by PDFWriter objects (for example, for numbering pages).
|
255
|
-
#
|
256
|
-
# this function enables plug-ins to expend the font functionality of CombinePDF.
|
257
|
-
#
|
258
|
-
# font_name:: a Symbol with the name of the font. if the fonts exists in the library, it will be overwritten!
|
259
|
-
# font_metrics:: a Hash of font metrics, of the format char => {wx: char_width, boundingbox: [left_x, buttom_y, right_x, top_y]} where char == character itself (i.e. " " for space). The Hash should contain a special value :missing for the metrics of missing characters. an optional :wy might be supported in the future, for up to down fonts.
|
260
|
-
# font_pdf_object:: a Hash in the internal format recognized by CombinePDF, that represents the font object.
|
261
|
-
# font_cmap:: a CMap dictionary Hash) which maps unicode characters to the hex CID for the font (i.e. {"a" => "61", "z" => "7a" }).
|
262
|
-
def register_font(font_name, font_metrics, font_pdf_object, font_cmap = nil)
|
263
|
-
Fonts.register_font font_name, font_metrics, font_pdf_object, font_cmap
|
264
|
-
end
|
265
108
|
|
266
|
-
# adds an existing font (from any PDF Object) to the font library.
|
267
|
-
#
|
268
|
-
# returns the font on success or false on failure.
|
269
|
-
#
|
270
|
-
# example:
|
271
|
-
# fonts = CombinePDF.new("japanese_fonts.pdf").fonts(true)
|
272
|
-
# CombinePDF.register_font_from_pdf_object :david, fonts[0]
|
273
|
-
#
|
274
|
-
# VERY LIMITTED SUPPORT:
|
275
|
-
# - at the moment it only imports Type0 fonts.
|
276
|
-
# - also, to extract the Hash of the actual font object you were looking for, is not a trivial matter. I do it on the console.
|
277
|
-
# font_name:: a Symbol with the name of the font registry. if the fonts exists in the library, it will be overwritten!
|
278
|
-
# font_object:: a Hash in the internal format recognized by CombinePDF, that represents the font object.
|
279
|
-
def register_existing_font font_name, font_object
|
280
|
-
Fonts.register_font_from_pdf_object font_name, font_object
|
281
|
-
end
|
282
|
-
def register_font_from_pdf_object font_name, font_object
|
283
|
-
register_existing_font font_name, font_object
|
284
|
-
end
|
285
109
|
end
|
286
110
|
|
287
111
|
|
@@ -301,7 +125,7 @@ end
|
|
301
125
|
## test performance with:
|
302
126
|
## puts Benchmark.measure { pdf = CombinePDF.new(file); pdf.save "test.pdf" } # PDFEditor.new_pdf
|
303
127
|
## demo: file_name = "~/Ruby/pdfs/encrypted.pdf"; pdf=0; puts Benchmark.measure { pdf = CombinePDF.new(file_name); pdf.save "test.pdf" }
|
304
|
-
## at the moment... my code
|
128
|
+
## at the moment... my code is terribly slow for larger files... :(
|
305
129
|
## The file saving is solved (I hope)... but file loading is an issue.
|
306
130
|
## pdf.each_object {|obj| puts "Stream length: #{obj[:raw_stream_content].length} was registered as #{obj[:Length].is_a?(Hash)? obj[:Length][:referenced_object][:indirect_without_dictionary] : obj[:Length]}" if obj[:raw_stream_content] }
|
307
131
|
## pdf.objects.each {|obj| puts "#{obj.class.name}: #{obj[:indirect_reference_id]}, #{obj[:indirect_generation_number]} is: #{obj[:Type] || obj[:indirect_without_dictionary]}" }
|
@@ -114,7 +114,8 @@ module CombinePDF
|
|
114
114
|
border_color: nil,
|
115
115
|
border_width: 0,
|
116
116
|
box_radius: 0,
|
117
|
-
opacity: 1
|
117
|
+
opacity: 1,
|
118
|
+
ctm: [1,0,0,1,0,0]
|
118
119
|
}
|
119
120
|
options.update properties
|
120
121
|
# reset the length and height to meaningful values, if negative
|
@@ -134,7 +135,7 @@ module CombinePDF
|
|
134
135
|
|
135
136
|
# set graphic state for the box
|
136
137
|
box_stream << "q\n"
|
137
|
-
box_graphic_state = { ca: options[:opacity], CA: options[:opacity], LW: options[:border_width], LC: 0, LJ: 0, LD: 0 }
|
138
|
+
box_graphic_state = { ca: options[:opacity], CA: options[:opacity], LW: options[:border_width], LC: 0, LJ: 0, LD: 0, CTM: options[:ctm]}
|
138
139
|
if options[:box_radius] != 0 # if the text box has rounded corners
|
139
140
|
box_graphic_state[:LC], box_graphic_state[:LJ] = 2, 1
|
140
141
|
end
|
@@ -228,7 +229,7 @@ module CombinePDF
|
|
228
229
|
|
229
230
|
# set graphic state for text
|
230
231
|
text_stream << "q\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_graphic_state = graphic_state({ca: options[:opacity], CA: options[:opacity], LW: options[:stroke_width].to_f, LC: 2, LJ: 1, LD: 0, CTM: options[:ctm]})
|
232
233
|
text_stream << "#{PDFOperations._object_to_pdf text_graphic_state} gs\n"
|
233
234
|
text_stream << "DeviceRGB CS\nDeviceRGB cs\n"
|
234
235
|
# set text render mode
|
@@ -45,6 +45,19 @@ module CombinePDF
|
|
45
45
|
when 1,2
|
46
46
|
# raise_encrypted_error
|
47
47
|
_perform_decrypt_proc_ @objects, self.method(:decrypt_RC4)
|
48
|
+
when 4
|
49
|
+
# raise unsupported error for now
|
50
|
+
raise_encrypted_error
|
51
|
+
# make sure CF is a Hash (as required by the PDF standard for this type of encryption).
|
52
|
+
raise_encrypted_error unless @encryption_dictionary[:CF].is_a?(Hash)
|
53
|
+
|
54
|
+
# do nothing if there is no data to decrypt except embeded files...?
|
55
|
+
return true unless (@encryption_dictionary[:CF].values.select { |v| !v[:AuthEvent] || v[:AuthEvent] == :DocOpen } ).empty?
|
56
|
+
|
57
|
+
# attempt to decrypt all strings?
|
58
|
+
# attempt to decrypy all streams
|
59
|
+
# attempt to decrypt all embeded files?
|
60
|
+
|
48
61
|
else
|
49
62
|
raise_encrypted_error
|
50
63
|
end
|
@@ -0,0 +1,187 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
|
3
|
+
|
4
|
+
|
5
|
+
|
6
|
+
|
7
|
+
module CombinePDF
|
8
|
+
module_function
|
9
|
+
|
10
|
+
# Create an empty PDF object or create a PDF object from a file (parsing the file).
|
11
|
+
# file_name:: is the name of a file to be parsed.
|
12
|
+
def load(file_name = "")
|
13
|
+
raise TypeError, "couldn't parse and data, expecting type String" unless file_name.is_a?(String) || file_name.is_a?(Pathname)
|
14
|
+
return PDF.new() if file_name == ''
|
15
|
+
PDF.new( PDFParser.new( IO.read(file_name).force_encoding(Encoding::ASCII_8BIT) ) )
|
16
|
+
end
|
17
|
+
def new(file_name = "")
|
18
|
+
load(file_name)
|
19
|
+
end
|
20
|
+
|
21
|
+
# Create a PDF object from a raw PDF data (parsing the data).
|
22
|
+
# data:: is a string that represents the content of a PDF file.
|
23
|
+
def parse(data)
|
24
|
+
raise TypeError, "couldn't parse and data, expecting type String" unless data.is_a? String
|
25
|
+
PDF.new( PDFParser.new(data) )
|
26
|
+
end
|
27
|
+
# makes a PDFWriter object
|
28
|
+
#
|
29
|
+
# PDFWriter objects reresent an empty page and have the method "textbox"
|
30
|
+
# that adds content to that page.
|
31
|
+
#
|
32
|
+
# PDFWriter objects are used internally for numbering pages (by creating a PDF page
|
33
|
+
# with the page number and "stamping" it over the existing page).
|
34
|
+
#
|
35
|
+
# ::mediabox an Array representing the size of the PDF document. defaults to: [0.0, 0.0, 612.0, 792.0]
|
36
|
+
#
|
37
|
+
# if the page is PDFWriter object as a stamp, the final size will be that of the original page.
|
38
|
+
def create_page(mediabox = [0, 0, 595.3, 841.9])
|
39
|
+
PDFWriter.new mediabox
|
40
|
+
end
|
41
|
+
|
42
|
+
# makes a PDF object containing a table
|
43
|
+
#
|
44
|
+
# all the pages in this PDF object are PDFWriter objects and are
|
45
|
+
# writable using the texbox function (should you wish to add a title, or more info)
|
46
|
+
#
|
47
|
+
# the main intended use of this method is to create indexes (a table of contents) for merged data.
|
48
|
+
#
|
49
|
+
# example:
|
50
|
+
# pdf = CombinePDF.create_table headers: ["header 1", "another header"], table_data: [ ["this is one row", "with two columns"] , ["this is another row", "also two columns", "the third will be ignored"] ]
|
51
|
+
# pdf.save "table_file.pdf"
|
52
|
+
#
|
53
|
+
# accepts a Hash with any of the following keys as well as any of the PDFWriter#textbox options:
|
54
|
+
# headers:: an Array of strings with the headers (will be repeated every page).
|
55
|
+
# table_data:: as Array of Arrays, each containing a string for each column. the first row sets the number of columns. extra columns will be ignored.
|
56
|
+
# font:: a registered or standard font name (see PDFWriter). defaults to nil (:Helvetica).
|
57
|
+
# header_font:: a registered or standard font name for the headers (see PDFWriter). defaults to nil (the font for all the table rows).
|
58
|
+
# max_font_size:: the maximum font size. if the string doesn't fit, it will be resized. defaults to 14.
|
59
|
+
# column_widths:: an array of relative column widths ([1,2] will display only the first two columns, the second twice as big as the first). defaults to nil (even widths).
|
60
|
+
# header_color:: the header color. defaults to [0.8, 0.8, 0.8] (light gray).
|
61
|
+
# main_color:: main row color. defaults to nil (transparent / white).
|
62
|
+
# alternate_color:: alternate row color. defaults to [0.95, 0.95, 0.95] (very light gray).
|
63
|
+
# font_color:: font color. defaults to [0,0,0] (black).
|
64
|
+
# border_color:: border color. defaults to [0,0,0] (black).
|
65
|
+
# border_width:: border width in PDF units. defaults to 1.
|
66
|
+
# header_align:: the header text alignment within each column (:right, :left, :center). defaults to :center.
|
67
|
+
# row_align:: the row text alignment within each column. defaults to :left (:right for RTL table).
|
68
|
+
# direction:: the table's writing direction (:ltr or :rtl). this reffers to the direction of the columns and doesn't effect text (rtl text is automatically recognized). defaults to :ltr.
|
69
|
+
# rows_per_page:: the number of rows per page, INCLUDING the header row. deafults to 25.
|
70
|
+
# page_size:: the size of the page in PDF points. defaults to [0, 0, 595.3, 841.9] (A4).
|
71
|
+
def create_table(options = {})
|
72
|
+
defaults = {
|
73
|
+
headers: nil,
|
74
|
+
table_data: [[]],
|
75
|
+
font: nil,
|
76
|
+
header_font: nil,
|
77
|
+
max_font_size: 14,
|
78
|
+
column_widths: nil,
|
79
|
+
header_color: [0.8, 0.8, 0.8],
|
80
|
+
main_color: nil,
|
81
|
+
alternate_color: [0.95, 0.95, 0.95],
|
82
|
+
font_color: [0,0,0],
|
83
|
+
border_color: [0,0,0],
|
84
|
+
border_width: 1,
|
85
|
+
header_align: :center,
|
86
|
+
row_align: nil,
|
87
|
+
direction: :ltr,
|
88
|
+
rows_per_page: 25,
|
89
|
+
page_size: [0, 0, 595.3, 841.9] #A4
|
90
|
+
}
|
91
|
+
options = defaults.merge options
|
92
|
+
options[:header_font] = options[:font] unless options[:header_font]
|
93
|
+
options[:row_align] ||= ( (options[:direction] == :rtl) ? :right : :left )
|
94
|
+
# assert table_data is an array of arrays
|
95
|
+
return false unless (options[:table_data].select {|r| !r.is_a?(Array) }).empty?
|
96
|
+
# compute sizes
|
97
|
+
page_size = options[:page_size]
|
98
|
+
top = page_size[3] * 0.9
|
99
|
+
height = page_size[3] * 0.8 / options[:rows_per_page]
|
100
|
+
from_side = page_size[2] * 0.1
|
101
|
+
width = page_size[2] * 0.8
|
102
|
+
columns = options[:table_data][0].length
|
103
|
+
column_widths = []
|
104
|
+
columns.times {|i| column_widths << (width/columns) }
|
105
|
+
if options[:column_widths]
|
106
|
+
scale = 0
|
107
|
+
options[:column_widths].each {|w| scale += w}
|
108
|
+
column_widths = []
|
109
|
+
options[:column_widths].each { |w| column_widths << (width*w/scale) }
|
110
|
+
end
|
111
|
+
column_widths = column_widths.reverse if options[:direction] == :rtl
|
112
|
+
# set pdf object and start writing the data
|
113
|
+
table = PDF.new()
|
114
|
+
page = nil
|
115
|
+
rows_per_page = options[:rows_per_page]
|
116
|
+
row_number = rows_per_page + 1
|
117
|
+
|
118
|
+
options[:table_data].each do |row_data|
|
119
|
+
if row_number > rows_per_page
|
120
|
+
page = create_page page_size
|
121
|
+
table << page
|
122
|
+
row_number = 1
|
123
|
+
# add headers
|
124
|
+
if options[:headers]
|
125
|
+
x = from_side
|
126
|
+
headers = options[:headers]
|
127
|
+
headers = headers.reverse if options[:direction] == :rtl
|
128
|
+
column_widths.each_index do |i|
|
129
|
+
text = headers[i].to_s
|
130
|
+
page.textbox text, {x: x, y: (top - (height*row_number)), width: column_widths[i], height: height, box_color: options[:header_color], text_align: options[:header_align] }.merge(options).merge({font: options[:header_font]})
|
131
|
+
x += column_widths[i]
|
132
|
+
end
|
133
|
+
row_number += 1
|
134
|
+
end
|
135
|
+
end
|
136
|
+
x = from_side
|
137
|
+
row_data = row_data.reverse if options[:direction] == :rtl
|
138
|
+
column_widths.each_index do |i|
|
139
|
+
text = row_data[i].to_s
|
140
|
+
box_color = options[:main_color]
|
141
|
+
box_color = options[:alternate_color] if options[:alternate_color] && row_number.odd?
|
142
|
+
page.textbox text, {x: x, y: (top - (height*row_number)), width: column_widths[i], height: height, box_color: box_color, text_align: options[:row_align]}.merge(options)
|
143
|
+
x += column_widths[i]
|
144
|
+
end
|
145
|
+
row_number += 1
|
146
|
+
end
|
147
|
+
table
|
148
|
+
end
|
149
|
+
def new_table(options = {})
|
150
|
+
create_table options
|
151
|
+
end
|
152
|
+
|
153
|
+
# adds a correctly formatted font object to the font library.
|
154
|
+
#
|
155
|
+
# registered fonts will remain in the library and will only be embeded in
|
156
|
+
# PDF objects when they are used by PDFWriter objects (for example, for numbering pages).
|
157
|
+
#
|
158
|
+
# this function enables plug-ins to expend the font functionality of CombinePDF.
|
159
|
+
#
|
160
|
+
# font_name:: a Symbol with the name of the font. if the fonts exists in the library, it will be overwritten!
|
161
|
+
# font_metrics:: a Hash of font metrics, of the format char => {wx: char_width, boundingbox: [left_x, buttom_y, right_x, top_y]} where char == character itself (i.e. " " for space). The Hash should contain a special value :missing for the metrics of missing characters. an optional :wy might be supported in the future, for up to down fonts.
|
162
|
+
# font_pdf_object:: a Hash in the internal format recognized by CombinePDF, that represents the font object.
|
163
|
+
# font_cmap:: a CMap dictionary Hash) which maps unicode characters to the hex CID for the font (i.e. {"a" => "61", "z" => "7a" }).
|
164
|
+
def register_font(font_name, font_metrics, font_pdf_object, font_cmap = nil)
|
165
|
+
Fonts.register_font font_name, font_metrics, font_pdf_object, font_cmap
|
166
|
+
end
|
167
|
+
|
168
|
+
# adds an existing font (from any PDF Object) to the font library.
|
169
|
+
#
|
170
|
+
# returns the font on success or false on failure.
|
171
|
+
#
|
172
|
+
# example:
|
173
|
+
# fonts = CombinePDF.new("japanese_fonts.pdf").fonts(true)
|
174
|
+
# CombinePDF.register_font_from_pdf_object :david, fonts[0]
|
175
|
+
#
|
176
|
+
# VERY LIMITTED SUPPORT:
|
177
|
+
# - at the moment it only imports Type0 fonts.
|
178
|
+
# - also, to extract the Hash of the actual font object you were looking for, is not a trivial matter. I do it on the console.
|
179
|
+
# font_name:: a Symbol with the name of the font registry. if the fonts exists in the library, it will be overwritten!
|
180
|
+
# font_object:: a Hash in the internal format recognized by CombinePDF, that represents the font object.
|
181
|
+
def register_existing_font font_name, font_object
|
182
|
+
Fonts.register_font_from_pdf_object font_name, font_object
|
183
|
+
end
|
184
|
+
def register_font_from_pdf_object font_name, font_object
|
185
|
+
register_existing_font font_name, font_object
|
186
|
+
end
|
187
|
+
end
|
@@ -16,6 +16,13 @@ module CombinePDF
|
|
16
16
|
# differentiate between complex PDF objects.
|
17
17
|
PRIVATE_HASH_KEYS = [:indirect_reference_id, :indirect_generation_number, :raw_stream_content, :is_reference_only, :referenced_object, :indirect_without_dictionary]
|
18
18
|
|
19
|
+
# holds a simple content stream that starts a PDF graphic state container - used for wrapping malformed PDF content streams.
|
20
|
+
CONTENT_CONTAINER_START = { is_reference_only: true , referenced_object: {indirect_reference_id: 0, raw_stream_content: 'q'} }
|
21
|
+
# holds a simple content stream that ends a PDF graphic state container - used for wrapping malformed PDF content streams.
|
22
|
+
CONTENT_CONTAINER_MIDDLE = { is_reference_only: true , referenced_object: {indirect_reference_id: 0, raw_stream_content: "Q\nq"} }
|
23
|
+
# holds a simple content stream that ends a PDF graphic state container - used for wrapping malformed PDF content streams.
|
24
|
+
CONTENT_CONTAINER_END = { is_reference_only: true , referenced_object: {indirect_reference_id: 0, raw_stream_content: 'Q'} }
|
25
|
+
|
19
26
|
# @private
|
20
27
|
# @!visibility private
|
21
28
|
#:nodoc: all
|
@@ -75,10 +82,16 @@ module CombinePDF
|
|
75
82
|
|
76
83
|
if top # if this is a stamp (overlay)
|
77
84
|
page[:Contents] = original_contents
|
85
|
+
page[:Contents].unshift CONTENT_CONTAINER_START.dup
|
86
|
+
page[:Contents].push CONTENT_CONTAINER_MIDDLE.dup
|
78
87
|
page[:Contents].push *stream_contents
|
88
|
+
page[:Contents].push CONTENT_CONTAINER_END.dup
|
79
89
|
else #if this was a watermark (underlay? would be lost if the page was scanned, as white might not be transparent)
|
80
90
|
page[:Contents] = stream_contents
|
91
|
+
page[:Contents].unshift CONTENT_CONTAINER_START.dup
|
92
|
+
page[:Contents].push CONTENT_CONTAINER_MIDDLE.dup
|
81
93
|
page[:Contents].push *original_contents
|
94
|
+
page[:Contents].push CONTENT_CONTAINER_END.dup
|
82
95
|
end
|
83
96
|
|
84
97
|
page
|
@@ -139,7 +152,8 @@ module CombinePDF
|
|
139
152
|
stream[:raw_stream_content].gsub! _object_to_pdf(old_key), _object_to_pdf(new_key) ##### PRAY(!) that the parsed datawill be correctly reproduced!
|
140
153
|
end
|
141
154
|
# patch back to PDF defaults, for OCRed PDF files.
|
142
|
-
stream[:raw_stream_content] = "q\nq\nq\nDeviceRGB CS\nDeviceRGB cs\n0 0 0 rg\n0 0 0 RG\n0 Tr\n%s\nQ\nQ\nQ\n" % stream[:raw_stream_content]
|
155
|
+
# stream[:raw_stream_content] = "q\nq\nq\nDeviceRGB CS\nDeviceRGB cs\n0 0 0 rg\n0 0 0 RG\n0 Tr\n%s\nQ\nQ\nQ\n" % stream[:raw_stream_content]
|
156
|
+
stream[:raw_stream_content] = "q\nq\nq\nDeviceRGB CS\nDeviceRGB cs\n0 0 0 rg\n0 0 0 RG\n0 Tr\n1 0 0 1 0 0 cm\n%s\nQ\nQ\nQ\n" % stream[:raw_stream_content]
|
143
157
|
end
|
144
158
|
|
145
159
|
new_page
|
@@ -158,6 +172,13 @@ module CombinePDF
|
|
158
172
|
end
|
159
173
|
end
|
160
174
|
|
175
|
+
# returns the PDF Object Hash holding the acutal data (if exists) or the original hash (if it wasn't a reference)
|
176
|
+
#
|
177
|
+
# works only AFTER references have been connected.
|
178
|
+
def get_referenced object
|
179
|
+
object[:referenced_object] || object
|
180
|
+
end
|
181
|
+
|
161
182
|
|
162
183
|
# Ruby normally assigns pointes.
|
163
184
|
# noramlly:
|
@@ -531,7 +531,7 @@ module CombinePDF
|
|
531
531
|
each_object do |obj|
|
532
532
|
if obj[:is_reference_only]
|
533
533
|
obj[:referenced_object] = objects_reference_hash[ [obj[:indirect_reference_id], obj[:indirect_generation_number] ] ]
|
534
|
-
warn "couldn't connect a reference!!! could be a null object, Silent error!!!" unless obj[:referenced_object]
|
534
|
+
warn "couldn't connect a reference!!! could be a null or removed (empty) object, Silent error!!!" unless obj[:referenced_object]
|
535
535
|
end
|
536
536
|
end
|
537
537
|
|
data/lib/combine_pdf/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: combine_pdf
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.8
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Boaz Segev
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-
|
11
|
+
date: 2014-12-18 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: ruby-rc4
|
@@ -73,6 +73,7 @@ files:
|
|
73
73
|
- lib/combine_pdf/combine_pdf_decrypt.rb
|
74
74
|
- lib/combine_pdf/combine_pdf_filter.rb
|
75
75
|
- lib/combine_pdf/combine_pdf_fonts.rb
|
76
|
+
- lib/combine_pdf/combine_pdf_methods.rb
|
76
77
|
- lib/combine_pdf/combine_pdf_operations.rb
|
77
78
|
- lib/combine_pdf/combine_pdf_parser.rb
|
78
79
|
- lib/combine_pdf/combine_pdf_pdf.rb
|
@@ -102,4 +103,3 @@ signing_key:
|
|
102
103
|
specification_version: 4
|
103
104
|
summary: Combine, stamp and watermark PDF files in pure Ruby.
|
104
105
|
test_files: []
|
105
|
-
has_rdoc:
|