combine_pdf 0.0.11 → 0.0.12
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 +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
|