combine_pdf 0.0.11 → 0.0.12
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 +92 -14
- data/lib/combine_pdf/combine_pdf_basic_writer.rb +112 -43
- data/lib/combine_pdf/combine_pdf_fonts.rb +32 -33
- data/lib/combine_pdf/combine_pdf_pdf.rb +2 -2
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a8c0bc3044a3d626227cd92230f5469ab88daac5
|
4
|
+
data.tar.gz: 1fbf45c126ddf79754f2660f9802d489d8583031
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 39080aa09fef9475249937e92d0eb5c425a840c6663762ef2c896ad6326fc717e35bfcc1f698f9aa882cae6e05d47bb5caad8c0073b2e96043a49a9856a7f044
|
7
|
+
data.tar.gz: 95cd39ba719bd65323c84da9f5d86195faa9e68874158c40e4c872c060d328bc0a3b212d1b79841efc3240b8d09b6c505a28b57dd62aad7c2cc306b2c105b9df
|
data/lib/combine_pdf.rb
CHANGED
@@ -33,18 +33,35 @@ load "combine_pdf/combine_pdf_pdf.rb"
|
|
33
33
|
|
34
34
|
|
35
35
|
# This is a pure ruby library to merge PDF files.
|
36
|
+
#
|
36
37
|
# In the future, this library will also allow stamping and watermarking PDFs (it allows this now, only with some issues).
|
37
38
|
#
|
38
39
|
# PDF objects can be used to combine or to inject data.
|
40
|
+
#
|
41
|
+
# here is the most basic application for the library, a one-liner that combines the PDF files and saves them:
|
42
|
+
# (CombinePDF.new("file1.pdf") << CombinePDF.new("file2.pdf") << CombinePDF.new("file3.pdf")).save("combined.pdf")
|
43
|
+
#
|
44
|
+
# == Loading PDF data
|
45
|
+
# Loading PDF data can be done from file system or directly from the memory.
|
46
|
+
#
|
47
|
+
# Loading data from a file is easy:
|
48
|
+
# pdf = CombinePDF.new("file.pdf")
|
49
|
+
# you can also parse PDF files from memory:
|
50
|
+
# pdf_data = IO.read 'file.pdf' # for this demo, load a file to memory
|
51
|
+
# pdf = CombinePDF.parse(pdf_data)
|
52
|
+
# Loading from the memory is especially effective for importing PDF data recieved through the internet or from a different authoring library such as Prawn.
|
53
|
+
#
|
39
54
|
# == Combine/Merge PDF files or Pages
|
40
55
|
# To combine PDF files (or data):
|
41
56
|
# pdf = CombinePDF.new
|
42
|
-
# pdf << CombinePDF.new("file1.pdf")
|
57
|
+
# pdf << CombinePDF.new("file1.pdf")
|
43
58
|
# pdf << CombinePDF.new("file2.pdf")
|
44
59
|
# pdf.save "combined.pdf"
|
45
|
-
#
|
46
|
-
#
|
47
|
-
# you can also
|
60
|
+
# as demonstrated above, these can be chained for into a one-liner.
|
61
|
+
#
|
62
|
+
# you can also only selected pages.
|
63
|
+
#
|
64
|
+
# in this example, only even pages will be added:
|
48
65
|
# pdf = CombinePDF.new
|
49
66
|
# i = 0
|
50
67
|
# CombinePDF.new("file.pdf").pages.each do |page|
|
@@ -52,12 +69,12 @@ load "combine_pdf/combine_pdf_pdf.rb"
|
|
52
69
|
# pdf << page if i.even?
|
53
70
|
# end
|
54
71
|
# pdf.save "even_pages.pdf"
|
55
|
-
# notice that adding
|
72
|
+
# notice that adding the whole file is faster then adding each page seperately.
|
56
73
|
# == Add content to existing pages (Stamp / Watermark)
|
57
74
|
# To add content to existing PDF pages, first import the new content from an existing PDF file.
|
58
75
|
# after that, add the content to each of the pages in your existing PDF.
|
59
76
|
#
|
60
|
-
# in this example,
|
77
|
+
# in this example, a company logo will be stamped over each page:
|
61
78
|
# company_logo = CombinePDF.new("company_logo.pdf").pages[0]
|
62
79
|
# pdf = CombinePDF.new "content_file.pdf"
|
63
80
|
# pdf.pages.each {|page| page << company_logo} # notice the << operator is on a page and not a PDF object.
|
@@ -77,16 +94,40 @@ load "combine_pdf/combine_pdf_pdf.rb"
|
|
77
94
|
# pdf.save "file_with_numbering.pdf"
|
78
95
|
#
|
79
96
|
# numbering can be done with many different options, with different formating, with or without a box object, and even with opacity values.
|
97
|
+
# == Writing Content
|
98
|
+
# page numbering actually adds content using the PDFWriter object (a very basic writer).
|
99
|
+
#
|
100
|
+
# in this example, all the PDF pages will be stamped, along the top, with a red box, with blue text, stating "Draft, page #".
|
101
|
+
# here is the easy way (we can even use "number_pages" without page numbers, if we wish):
|
102
|
+
# pdf = CombinePDF.new "file_to_stamp.pdf"
|
103
|
+
# pdf.number_pages number_format: " - Draft, page %d - ", number_location: [:top], font_color: [0,0,1], box_color: [0.4,0,0], opacity: 0.75, font_size:16
|
104
|
+
# pdf.save "draft.pdf"
|
105
|
+
#
|
106
|
+
# for demntration, it will now be coded the hard way, just so we can play more directly with some of the data.
|
107
|
+
#
|
108
|
+
# pdf = CombinePDF.new "file_to_stamp.pdf"
|
109
|
+
# ipage_number = 1
|
110
|
+
# pdf.pages.each do |page|
|
111
|
+
# # create a "stamp" PDF page with the same size as the target page
|
112
|
+
# # we will do this because we will use this to center the box in the page
|
113
|
+
# mediabox = page[:MediaBox]
|
114
|
+
# # CombinePDF is pointer based...
|
115
|
+
# # so you can add the stamp to the page and still continue to edit it's content!
|
116
|
+
# stamp = PDFWriter.new mediabox
|
117
|
+
# page << stamp
|
118
|
+
# # set the visible dimensions to the CropBox, if it exists.
|
119
|
+
# cropbox = page[:CropBox]
|
120
|
+
# mediabox = cropbox if cropbox
|
121
|
+
# # set stamp text
|
122
|
+
# text = " Draft (page %d) " % page_number
|
123
|
+
# # write the textbox
|
124
|
+
# stamp.textbox text, x: mediabox[0]+30, y: mediabox[1]+30, width: mediabox[2]-mediabox[0]-60, height: mediabox[3]-mediabox[1]-60, font_color: [0,0,1], font_size: :fit_text, box_color: [0.4,0,0], opacity: 0.5
|
125
|
+
# end
|
126
|
+
# pdf.save "draft.pdf"
|
80
127
|
#
|
81
|
-
# == Loading PDF data
|
82
|
-
# Loading PDF data can be done from file system or directly from the memory.
|
83
128
|
#
|
84
|
-
#
|
85
|
-
#
|
86
|
-
# you can also parse PDF files from memory:
|
87
|
-
# pdf_data = IO.read 'file.pdf' # for this demo, load a file to memory
|
88
|
-
# pdf = CombinePDF.parse(pdf_data)
|
89
|
-
# Loading from the memory is especially effective for importing PDF data recieved through the internet or from a different authoring library such as Prawn.
|
129
|
+
# font support for the writer is still in the works and is extreamly limited.
|
130
|
+
# at the moment it is best to limit the fonts to the 14 standard latin fonts (no unicode).
|
90
131
|
#
|
91
132
|
# == Decryption & Filters
|
92
133
|
#
|
@@ -132,10 +173,47 @@ module CombinePDF
|
|
132
173
|
PDF.new( PDFParser.new(data) )
|
133
174
|
end
|
134
175
|
# makes a PDFWriter object
|
176
|
+
#
|
177
|
+
# PDFWriter objects reresent an empty page and have the method "textbox"
|
178
|
+
# that adds content to that page.
|
179
|
+
#
|
180
|
+
# PDFWriter objects are used internally for numbering pages (by creating a PDF page
|
181
|
+
# with the page number and "stamping" it over the existing page).
|
182
|
+
#
|
135
183
|
# ::mediabox an Array representing the size of the PDF document. defaults to: [0.0, 0.0, 612.0, 792.0]
|
184
|
+
#
|
185
|
+
# if the page is PDFWriter object as a stamp, the final size will be that of the original page.
|
136
186
|
def create_page(mediabox = [0.0, 0.0, 612.0, 792.0])
|
137
187
|
PDFWriter.new mediabox
|
138
188
|
end
|
189
|
+
|
190
|
+
# adds a correctly formatted font object to the font library.
|
191
|
+
#
|
192
|
+
# registered fonts will remain in the library and will only be embeded in
|
193
|
+
# PDF objects when they are used by PDFWriter objects (for example, for numbering pages).
|
194
|
+
#
|
195
|
+
# this function enables plug-ins to expend the font functionality of CombinePDF.
|
196
|
+
#
|
197
|
+
# font_name:: a Symbol with the name of the font. if the fonts exists in the library, it will be overwritten!
|
198
|
+
# 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.
|
199
|
+
# font_pdf_object:: a Hash in the internal format recognized by CombinePDF, that represents the font object.
|
200
|
+
# font_cmap:: a CMap dictionary Hash) which maps unicode characters to the hex CID for the font (i.e. {"a" => "61", "z" => "7a" }).
|
201
|
+
def register_font(font_name, font_metrics, font_pdf_object, font_cmap = nil)
|
202
|
+
Fonts.register_font font_name, font_metrics, font_pdf_object, font_cmap
|
203
|
+
end
|
204
|
+
|
205
|
+
# adds an existing font (from any PDF Object) to the font library.
|
206
|
+
#
|
207
|
+
# returns the font on success or false on failure.
|
208
|
+
#
|
209
|
+
# VERY LIMITTED SUPPORT:
|
210
|
+
# - at the moment it only imports Type0 fonts.
|
211
|
+
# - 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.
|
212
|
+
# font_name:: a Symbol with the name of the font registry. if the fonts exists in the library, it will be overwritten!
|
213
|
+
# font_object:: a Hash in the internal format recognized by CombinePDF, that represents the font object.
|
214
|
+
def register_font_from_pdf_object font_name, font_object
|
215
|
+
Fonts.register_font_from_pdf_object font_name, font_object
|
216
|
+
end
|
139
217
|
end
|
140
218
|
|
141
219
|
|
@@ -44,6 +44,7 @@ module CombinePDF
|
|
44
44
|
def initialize(mediabox = [0.0, 0.0, 612.0, 792.0])
|
45
45
|
# indirect_reference_id, :indirect_generation_number
|
46
46
|
@contents = ""
|
47
|
+
@base_font_name = "Writer" + SecureRandom.urlsafe_base64(7) + "PDF"
|
47
48
|
self[:Type] = :Page
|
48
49
|
self[:indirect_reference_id] = 0
|
49
50
|
self[:Resources] = {}
|
@@ -67,11 +68,11 @@ module CombinePDF
|
|
67
68
|
# the symbols and values in the properties Hash could be any or all of the following:
|
68
69
|
# x:: the left position of the box.
|
69
70
|
# y:: the BUTTOM position of the box.
|
70
|
-
#
|
71
|
+
# width:: the width/length of the box. negative values will be computed from edge of page. defaults to 0 (end of page).
|
71
72
|
# height:: the height of the box. negative values will be computed from edge of page. defaults to 0 (end of page).
|
72
73
|
# text_align:: symbol for horizontal text alignment, can be ":center" (default), ":right", ":left"
|
73
74
|
# text_valign:: symbol for vertical text alignment, can be ":center" (default), ":top", ":buttom"
|
74
|
-
# font:: a
|
75
|
+
# font:: a registered font name or an Array of names. defaults to ":Helvetica". The 14 standard fonts names are:
|
75
76
|
# - :"Times-Roman"
|
76
77
|
# - :"Times-Bold"
|
77
78
|
# - :"Times-Italic"
|
@@ -100,11 +101,11 @@ module CombinePDF
|
|
100
101
|
options = {
|
101
102
|
x: 0,
|
102
103
|
y: 0,
|
103
|
-
|
104
|
+
width: 0,
|
104
105
|
height: -1,
|
105
106
|
text_align: :center,
|
106
107
|
text_valign: :center,
|
107
|
-
font:
|
108
|
+
font: nil,
|
108
109
|
font_size: :fit_text,
|
109
110
|
max_font_size: nil,
|
110
111
|
font_color: [0,0,0],
|
@@ -118,15 +119,8 @@ module CombinePDF
|
|
118
119
|
}
|
119
120
|
options.update properties
|
120
121
|
# reset the length and height to meaningful values, if negative
|
121
|
-
options[:
|
122
|
+
options[:width] = mediabox[2] - options[:x] + options[:width] if options[:width] <= 0
|
122
123
|
options[:height] = mediabox[3] - options[:y] + options[:height] if options[:height] <= 0
|
123
|
-
# fit text in box, if requested
|
124
|
-
font_size = options[:font_size]
|
125
|
-
if options[:font_size] == :fit_text
|
126
|
-
font_size = self.fit_text text, options[:font], options[:length], options[:height]
|
127
|
-
font_size = options[:max_font_size] if options[:max_font_size] && font_size > options[:max_font_size]
|
128
|
-
end
|
129
|
-
|
130
124
|
|
131
125
|
# create box stream
|
132
126
|
box_stream = ""
|
@@ -146,29 +140,29 @@ module CombinePDF
|
|
146
140
|
box_stream << "#{PDFOperations._object_to_pdf box_graphic_state} gs\n"
|
147
141
|
box_stream << "DeviceRGB CS\nDeviceRGB cs\n"
|
148
142
|
if options[:box_color]
|
149
|
-
box_stream << "#{options[:box_color].join(' ')}
|
143
|
+
box_stream << "#{options[:box_color].join(' ')} rg\n"
|
150
144
|
end
|
151
145
|
if options[:border_width].to_i > 0 && options[:border_color]
|
152
|
-
box_stream << "#{options[:border_color].join(' ')}
|
146
|
+
box_stream << "#{options[:border_color].join(' ')} RG\n"
|
153
147
|
end
|
154
148
|
# create the path
|
155
149
|
radius = options[:box_radius]
|
156
|
-
half_radius = radius.to_f / 2
|
150
|
+
half_radius = (radius.to_f / 2).round 4
|
157
151
|
## set starting point
|
158
152
|
box_stream << "#{options[:x] + radius} #{options[:y]} m\n"
|
159
153
|
## buttom and right corner - first line and first corner
|
160
|
-
box_stream << "#{options[:x] + options[:
|
154
|
+
box_stream << "#{options[:x] + options[:width] - radius} #{options[:y]} l\n" #buttom
|
161
155
|
if options[:box_radius] != 0 # make first corner, if not straight.
|
162
|
-
box_stream << "#{options[:x] + options[:
|
163
|
-
box_stream << "#{options[:x] + options[:
|
164
|
-
box_stream << "#{options[:x] + options[:
|
156
|
+
box_stream << "#{options[:x] + options[:width] - half_radius} #{options[:y]} "
|
157
|
+
box_stream << "#{options[:x] + options[:width]} #{options[:y] + half_radius} "
|
158
|
+
box_stream << "#{options[:x] + options[:width]} #{options[:y] + radius} c\n"
|
165
159
|
end
|
166
160
|
## right and top-right corner
|
167
|
-
box_stream << "#{options[:x] + options[:
|
161
|
+
box_stream << "#{options[:x] + options[:width]} #{options[:y] + options[:height] - radius} l\n"
|
168
162
|
if options[:box_radius] != 0
|
169
|
-
box_stream << "#{options[:x] + options[:
|
170
|
-
box_stream << "#{options[:x] + options[:
|
171
|
-
box_stream << "#{options[:x] + options[:
|
163
|
+
box_stream << "#{options[:x] + options[:width]} #{options[:y] + options[:height] - half_radius} "
|
164
|
+
box_stream << "#{options[:x] + options[:width] - half_radius} #{options[:y] + options[:height]} "
|
165
|
+
box_stream << "#{options[:x] + options[:width] - radius} #{options[:y] + options[:height]} c\n"
|
172
166
|
end
|
173
167
|
## top and top-left corner
|
174
168
|
box_stream << "#{options[:x] + radius} #{options[:y] + options[:height]} l\n"
|
@@ -203,19 +197,26 @@ module CombinePDF
|
|
203
197
|
# each unit (1) is 1/72 Inch
|
204
198
|
# create text stream
|
205
199
|
text_stream = ""
|
206
|
-
if text.to_s != "" && font_size != 0 && (options[:font_color] || options[:stroke_color])
|
200
|
+
if text.to_s != "" && options[:font_size] != 0 && (options[:font_color] || options[:stroke_color])
|
207
201
|
# compute x and y position for text
|
208
202
|
x = options[:x]
|
209
203
|
y = options[:y]
|
210
204
|
|
211
|
-
|
205
|
+
# set the fonts (fonts array, with :Helvetica as fallback).
|
206
|
+
fonts = [*options[:font], :Helvetica]
|
207
|
+
# fit text in box, if requested
|
208
|
+
font_size = options[:font_size]
|
209
|
+
if options[:font_size] == :fit_text
|
210
|
+
font_size = self.fit_text text, fonts, options[:width], options[:height]
|
211
|
+
font_size = options[:max_font_size] if options[:max_font_size] && font_size > options[:max_font_size]
|
212
|
+
end
|
212
213
|
|
213
|
-
text_size =
|
214
|
+
text_size = dimensions_of text, fonts, font_size
|
214
215
|
|
215
216
|
if options[:text_align] == :center
|
216
|
-
x = (options[:
|
217
|
+
x = (options[:width] - text_size[0])/2 + x
|
217
218
|
elsif options[:text_align] == :right
|
218
|
-
x = (options[:
|
219
|
+
x = (options[:width] - text_size[0]) + x
|
219
220
|
end
|
220
221
|
if options[:text_valign] == :center
|
221
222
|
y = (options[:height] - text_size[1])/2 + y
|
@@ -230,10 +231,10 @@ module CombinePDF
|
|
230
231
|
text_stream << "DeviceRGB CS\nDeviceRGB cs\n"
|
231
232
|
# set text render mode
|
232
233
|
if options[:font_color]
|
233
|
-
text_stream << "#{options[:font_color].join(' ')}
|
234
|
+
text_stream << "#{options[:font_color].join(' ')} rg\n"
|
234
235
|
end
|
235
236
|
if options[:stroke_width].to_i > 0 && options[:stroke_color]
|
236
|
-
text_stream << "#{options[:stroke_color].join(' ')}
|
237
|
+
text_stream << "#{options[:stroke_color].join(' ')} RG\n"
|
237
238
|
if options[:font_color]
|
238
239
|
text_stream << "2 Tr\n"
|
239
240
|
else
|
@@ -244,14 +245,19 @@ module CombinePDF
|
|
244
245
|
else
|
245
246
|
text_stream << "3 Tr\n"
|
246
247
|
end
|
247
|
-
# format text object
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
248
|
+
# format text object(s)
|
249
|
+
# text_stream << "#{options[:font_color].join(' ')} rg\n" # sets the color state
|
250
|
+
encode(text, fonts).each do |encoded|
|
251
|
+
text_stream << "BT\n" # the Begine Text marker
|
252
|
+
text_stream << PDFOperations._format_name_to_pdf(set_font encoded[0]) # Set font name
|
253
|
+
text_stream << " #{font_size} Tf\n" # set font size and add font operator
|
254
|
+
text_stream << "#{x.round 4} #{y.round 4} Td\n" # set location for text object
|
255
|
+
text_stream << ( encoded[1] ) # insert the encoded string to the stream
|
256
|
+
text_stream << " Tj\n" # the Text object operator and the End Text marker
|
257
|
+
text_stream << "ET\n" # the Text object operator and the End Text marker
|
258
|
+
x += encoded[2]/1000*font_size #update text starting point
|
259
|
+
y -= encoded[3]/1000*font_size #update text starting point
|
260
|
+
end
|
255
261
|
# exit graphic state for text
|
256
262
|
text_stream << "Q\nQ\nQ\n"
|
257
263
|
end
|
@@ -259,8 +265,13 @@ module CombinePDF
|
|
259
265
|
|
260
266
|
self
|
261
267
|
end
|
262
|
-
|
263
|
-
|
268
|
+
# gets the dimentions (width and height) of the text, as it will be printed in the PDF.
|
269
|
+
#
|
270
|
+
# text:: the text to measure
|
271
|
+
# font:: a font name or an Array of font names. Font names should be registered fonts. The 14 standard fonts are pre regitered with the font library.
|
272
|
+
# size:: the size of the font (defaults to 1000 points).
|
273
|
+
def dimensions_of(text, fonts, size = 1000)
|
274
|
+
Fonts.dimensions_of text, fonts, size
|
264
275
|
end
|
265
276
|
# this method returns the size for which the text fits the requested metrices
|
266
277
|
# the size is type Float and is rather exact
|
@@ -273,7 +284,7 @@ module CombinePDF
|
|
273
284
|
def fit_text(text, font, length, height = 10000000)
|
274
285
|
size = 100000
|
275
286
|
size_array = [size]
|
276
|
-
metrics = Fonts.
|
287
|
+
metrics = Fonts.dimensions_of text, font, size
|
277
288
|
if metrics[0] > length
|
278
289
|
size_array << size * length/metrics[0]
|
279
290
|
end
|
@@ -282,6 +293,8 @@ module CombinePDF
|
|
282
293
|
end
|
283
294
|
size_array.min
|
284
295
|
end
|
296
|
+
|
297
|
+
|
285
298
|
protected
|
286
299
|
|
287
300
|
# accessor (getter) for the :Resources element of the page
|
@@ -295,7 +308,7 @@ module CombinePDF
|
|
295
308
|
end
|
296
309
|
# creates a font object and adds the font to the resources dictionary
|
297
310
|
# returns the name of the font for the content stream.
|
298
|
-
# font:: a Symbol of one of the
|
311
|
+
# font:: a Symbol of one of the fonts registered in the library, or:
|
299
312
|
# - :"Times-Roman"
|
300
313
|
# - :"Times-Bold"
|
301
314
|
# - :"Times-Italic"
|
@@ -319,7 +332,7 @@ module CombinePDF
|
|
319
332
|
end
|
320
333
|
end
|
321
334
|
# set a secure name for the font
|
322
|
-
name = (
|
335
|
+
name = (@base_font_name + (resources[:Font].length + 1).to_s).to_sym
|
323
336
|
# get font object
|
324
337
|
font_object = Fonts.get_font(font)
|
325
338
|
# return false if the font wan't found in the library.
|
@@ -329,6 +342,8 @@ module CombinePDF
|
|
329
342
|
#return name
|
330
343
|
name
|
331
344
|
end
|
345
|
+
# register or get a registered graphoc state dictionary.
|
346
|
+
# the method returns the name of the graphos state, for use in a content stream.
|
332
347
|
def graphic_state(graphic_state_dictionary = {})
|
333
348
|
# if the graphic state exists, return it's name
|
334
349
|
resources[:ExtGState] ||= {}
|
@@ -346,6 +361,60 @@ module CombinePDF
|
|
346
361
|
#return name
|
347
362
|
name
|
348
363
|
end
|
364
|
+
|
365
|
+
# encodes the text in an array of [:font_name, <PDFHexString>] for use in textbox
|
366
|
+
def encode text, fonts
|
367
|
+
# text must be a unicode string and fonts must be an array.
|
368
|
+
# this is an internal method, don't perform tests.
|
369
|
+
fonts_array = []
|
370
|
+
fonts.each do |name|
|
371
|
+
f = Fonts.get_font name
|
372
|
+
fonts_array << f if f
|
373
|
+
end
|
374
|
+
|
375
|
+
# before starting, we should reorder any RTL content in the string
|
376
|
+
text = reorder_rtl_content text
|
377
|
+
|
378
|
+
out = []
|
379
|
+
text.chars.each do |c|
|
380
|
+
fonts_array.each_index do |i|
|
381
|
+
if fonts_array[i].cmap.nil? || (fonts_array[i].cmap && fonts_array[i].cmap[c])
|
382
|
+
#add to array
|
383
|
+
if out.last.nil? || out.last[0] != fonts[i]
|
384
|
+
out.last[1] << ">" unless out.last.nil?
|
385
|
+
out << [fonts[i], "<" , 0, 0]
|
386
|
+
end
|
387
|
+
out.last[1] << ( fonts_array[i].cmap.nil? ? ( c.unpack("H*")[0] ) : (fonts_array[i].cmap[c]) )
|
388
|
+
if fonts_array[i].metrics[c]
|
389
|
+
out.last[2] += fonts_array[i].metrics[c][:wx].to_f
|
390
|
+
out.last[3] += fonts_array[i].metrics[c][:wy].to_f
|
391
|
+
end
|
392
|
+
break
|
393
|
+
end
|
394
|
+
end
|
395
|
+
end
|
396
|
+
out.last[1] << ">" if out.last
|
397
|
+
out
|
398
|
+
end
|
399
|
+
|
400
|
+
# a very primitive text reordering algorithm... I was lazy...
|
401
|
+
# ...still, it works (I think).
|
402
|
+
def reorder_rtl_content text
|
403
|
+
rtl_characters = "\u05d0-\u05ea\u05f0-\u05f4\u0600-\u06ff\u0750-\u077f"
|
404
|
+
return text unless text =~ /[#{rtl_characters}]/
|
405
|
+
|
406
|
+
out = []
|
407
|
+
scanner = StringScanner.new text
|
408
|
+
until scanner.eos? do
|
409
|
+
if scanner.scan /[#{rtl_characters}]/
|
410
|
+
out.unshift scanner.matched
|
411
|
+
end
|
412
|
+
if scanner.scan /[^#{rtl_characters}]+/
|
413
|
+
out.unshift scanner.matched
|
414
|
+
end
|
415
|
+
end
|
416
|
+
out.join
|
417
|
+
end
|
349
418
|
end
|
350
419
|
|
351
420
|
end
|
@@ -47,28 +47,6 @@ module CombinePDF
|
|
47
47
|
self[:referenced_object] = object
|
48
48
|
end
|
49
49
|
|
50
|
-
# This function calculates the dimensions of a string in a PDF.
|
51
|
-
#
|
52
|
-
# UNICODE SUPPORT IS FONT DEPENDENT!
|
53
|
-
#
|
54
|
-
# text:: String containing the text for which the demantion box will be calculated.
|
55
|
-
# size:: the size of the text, as it will be applied in the PDF.
|
56
|
-
def dimensions_of(text, size = 1000)
|
57
|
-
metrics_array = []
|
58
|
-
# the following is only good for latin text - unicode support is missing!!!!
|
59
|
-
text.each_char do |c|
|
60
|
-
metrics_array << (self.metrics[c] or self.metrics[:missing])
|
61
|
-
end
|
62
|
-
height = metrics_array.map {|m| m ? m[:boundingbox][3] : 0} .max
|
63
|
-
height = height - (metrics_array.map {|m| m ? m[:boundingbox][1] : 0} ).min
|
64
|
-
width = 0.0
|
65
|
-
metrics_array.each do |m|
|
66
|
-
width += (m[:wx] or m[:wy])
|
67
|
-
end
|
68
|
-
return [height.to_f/1000*size, width.to_f/1000*size] if metrics_array[0][:wy]
|
69
|
-
[width.to_f/1000*size, height.to_f/1000*size]
|
70
|
-
end
|
71
|
-
|
72
50
|
# This function translate a unicode string, to a character glyph ID stream.
|
73
51
|
def encode text
|
74
52
|
# FixMe: embed RTL text convertion
|
@@ -112,8 +90,9 @@ module CombinePDF
|
|
112
90
|
|
113
91
|
# adds a correctly formatted font object to the font library.
|
114
92
|
# font_name:: a Symbol with the name of the font. if the fonts exists, it will be overwritten!
|
115
|
-
# font_metrics:: a Hash of ont metrics, of the format
|
93
|
+
# font_metrics:: a Hash of ont metrics, of the format char => {wx: char_width, boundingbox: [left_x, buttom_y, right_x, top_y]} where i == character code (i.e. 32 for space). The Hash should contain a special value :missing for the metrics of missing characters. an optional :wy will be supported in the future, for up to down fonts.
|
116
94
|
# font_pdf_object:: a Hash in the internal format recognized by CombinePDF, that represents the font object.
|
95
|
+
# font_cmap:: a CMap dictionary Hash) which maps unicode characters to the hex CID for the font (i.e. {"a" => "61", "z" => "7a" }).
|
117
96
|
def register_font(font_name, font_metrics, font_pdf_object, font_cmap = nil)
|
118
97
|
new_font = Font.new
|
119
98
|
new_font.name = font_name
|
@@ -125,17 +104,37 @@ module CombinePDF
|
|
125
104
|
new_font
|
126
105
|
end
|
127
106
|
|
128
|
-
#
|
129
|
-
#
|
130
|
-
# UNICODE SUPPORT IS MISSING!
|
107
|
+
# gets the dimentions (width and height) of the text, as it will be printed in the PDF.
|
131
108
|
#
|
132
|
-
# text::
|
133
|
-
#
|
134
|
-
# size:: the size of the
|
135
|
-
def dimensions_of(text,
|
136
|
-
|
137
|
-
|
109
|
+
# text:: the text to measure
|
110
|
+
# fonts:: a font name or an Array of font names. Font names should be registered fonts. The 14 standard fonts are pre regitered with the font library.
|
111
|
+
# size:: the size of the font (defaults to 1000 points).
|
112
|
+
def dimensions_of(text, fonts, size = 1000)
|
113
|
+
fonts = [fonts] unless fonts.is_a? Array
|
114
|
+
merged_metrics = {}
|
115
|
+
# merge the metrics last to first (so that important fonts override secondary fonts)
|
116
|
+
fonts.length.downto(1).each do |i|
|
117
|
+
f = get_font(fonts[i-1])
|
118
|
+
if f && f.metrics
|
119
|
+
merged_metrics.update( get_font(fonts[i-1]).metrics)
|
120
|
+
else
|
121
|
+
warn "metrics of font not found!"
|
122
|
+
end
|
123
|
+
end
|
138
124
|
|
125
|
+
metrics_array = []
|
126
|
+
text.each_char do |c|
|
127
|
+
metrics_array << (merged_metrics[c] || {wx: 0, boundingbox: [0,0,0,0]})
|
128
|
+
end
|
129
|
+
height = metrics_array.map {|m| m ? m[:boundingbox][3] : 0} .max
|
130
|
+
height = height - (metrics_array.map {|m| m ? m[:boundingbox][1] : 0} ).min
|
131
|
+
width = 0.0
|
132
|
+
metrics_array.each do |m|
|
133
|
+
width += (m[:wx] || m[:wy])
|
134
|
+
end
|
135
|
+
return [height.to_f/1000*size, width.to_f/1000*size] if metrics_array[0][:wy]
|
136
|
+
[width.to_f/1000*size, height.to_f/1000*size]
|
137
|
+
end
|
139
138
|
# this function registers the 14 standard fonts to the library.
|
140
139
|
#
|
141
140
|
# it will be called when the module first requests a font from the library.
|
@@ -185,7 +184,7 @@ module CombinePDF
|
|
185
184
|
end
|
186
185
|
end
|
187
186
|
|
188
|
-
# Register a
|
187
|
+
# Register a font that already exists in a pdf object into the font library.
|
189
188
|
# DOESN'T WORK YET!!!
|
190
189
|
def register_font_from_pdf_object font_name, font_object
|
191
190
|
# FIXME:
|
@@ -290,7 +290,7 @@ module CombinePDF
|
|
290
290
|
# - :margin_from_height a number (PDF points) for the top and buttom margins. defaults to 45.
|
291
291
|
# - :margin_from_side a number (PDF points) for the left and right margins. defaults to 15.
|
292
292
|
# the options Hash can also take all the options for PDFWriter.textbox.
|
293
|
-
# defaults to
|
293
|
+
# defaults to font: :Helvetica, font_size: 12 and no box (:border_width => 0, :box_color => nil).
|
294
294
|
def number_pages(options = {})
|
295
295
|
opt = {
|
296
296
|
number_format: ' - %s - ',
|
@@ -316,7 +316,7 @@ module CombinePDF
|
|
316
316
|
text_dimantions = stamp.dimensions_of( text, opt[:font], opt[:font_size] )
|
317
317
|
box_width = text_dimantions[0] * 1.2
|
318
318
|
box_height = text_dimantions[1] * 2
|
319
|
-
opt[:
|
319
|
+
opt[:width] = box_width
|
320
320
|
opt[:height] = box_height
|
321
321
|
from_height = 45
|
322
322
|
from_side = 15
|
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.12
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Boaz Segev
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2014-09-
|
12
|
+
date: 2014-09-12 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: ruby-rc4
|