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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 054f1e2421105408519f4e0296607b75c32a62e1
4
- data.tar.gz: a716e13987ecd1cefd7f011f3df802cf4a33a3cc
3
+ metadata.gz: a8c0bc3044a3d626227cd92230f5469ab88daac5
4
+ data.tar.gz: 1fbf45c126ddf79754f2660f9802d489d8583031
5
5
  SHA512:
6
- metadata.gz: 6d1f843526f1204ee9ef9ed2994fc6cb8e4253c94387e170b50e9adc5ced0831b4838e55484fa64df19034e0869e4b3cdd0f6c5a3db3b360f54450e8b75991e3
7
- data.tar.gz: 7b5f9a3622630684f2234df0e1d53d52c37a6a0bb6ee7a9b94215661a94a70156f75065922e2cc71701faac6a9a245cdd00233a82b83b5375dfa91171f9368db
6
+ metadata.gz: 39080aa09fef9475249937e92d0eb5c425a840c6663762ef2c896ad6326fc717e35bfcc1f698f9aa882cae6e05d47bb5caad8c0073b2e96043a49a9856a7f044
7
+ data.tar.gz: 95cd39ba719bd65323c84da9f5d86195faa9e68874158c40e4c872c060d328bc0a3b212d1b79841efc3240b8d09b6c505a28b57dd62aad7c2cc306b2c105b9df
@@ -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") # one way to combine, very fast.
57
+ # pdf << CombinePDF.new("file1.pdf")
43
58
  # pdf << CombinePDF.new("file2.pdf")
44
59
  # pdf.save "combined.pdf"
45
- # or even a one liner:
46
- # (CombinePDF.new("file1.pdf") << CombinePDF.new("file2.pdf") << CombinePDF.new("file3.pdf")).save("combined.pdf")
47
- # you can also add just odd or even pages:
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 all the pages one by one is slower then adding the whole file.
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, we will add a company logo to each page:
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
- # Loading data from a file is easy:
85
- # pdf = CombinePDF.new("file.pdf")
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
- # length:: the length of the box. negative values will be computed from edge of page. defaults to 0 (end of page).
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 Symbol representing one of the 14 standard fonts. defaults to ":Helvetica". the options are:
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
- length: 0,
104
+ width: 0,
104
105
  height: -1,
105
106
  text_align: :center,
106
107
  text_valign: :center,
107
- font: :Helvetica,
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[:length] = mediabox[2] - options[:x] + options[:length] if options[:length] <= 0
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(' ')} scn\n"
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(' ')} SCN\n"
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[:length] - radius} #{options[:y]} l\n" #buttom
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[:length] - half_radius} #{options[:y]} "
163
- box_stream << "#{options[:x] + options[:length]} #{options[:y] + half_radius} "
164
- box_stream << "#{options[:x] + options[:length]} #{options[:y] + radius} c\n"
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[:length]} #{options[:y] + options[:height] - radius} l\n"
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[:length]} #{options[:y] + options[:height] - half_radius} "
170
- box_stream << "#{options[:x] + options[:length] - half_radius} #{options[:y] + options[:height]} "
171
- box_stream << "#{options[:x] + options[:length] - radius} #{options[:y] + options[:height]} c\n"
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
- font_object = Fonts.get_font(options[:font])
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 = font_object.dimensions_of text, font_size
214
+ text_size = dimensions_of text, fonts, font_size
214
215
 
215
216
  if options[:text_align] == :center
216
- x = (options[:length] - text_size[0])/2 + x
217
+ x = (options[:width] - text_size[0])/2 + x
217
218
  elsif options[:text_align] == :right
218
- x = (options[:length] - text_size[0]) + x
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(' ')} scn\n"
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(' ')} SCN\n"
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
- text_stream << "BT\n" # the Begine Text marker
249
- text_stream << PDFOperations._format_name_to_pdf(set_font options[:font]) # Set font name
250
- text_stream << " #{font_size} Tf\n" # set font size and add font operator
251
- text_stream << "#{options[:font_color].join(' ')} rg\n" # sets the color state
252
- text_stream << "#{x} #{y} Td\n" # set location for text object
253
- text_stream << ( font_object.encode(text) ) # insert the string in PDF format, after mapping to font glyphs
254
- text_stream << " Tj\n ET\n" # the Text object operator and the End Text marker
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
- def dimensions_of(text, font_name, size = 1000)
263
- Fonts.get_font(font_name).dimensions_of text, size
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.get_font(font).dimensions_of text, size
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 14 Type 1 fonts, known as the standard 14 fonts:
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 = (SecureRandom.urlsafe_base64(9)).to_sym
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 i => {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.
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
- # This function calculates the dimensions of a string in a PDF.
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:: String containing the text for which the demantion box will be calculated.
133
- # font:: the font name, from the 14 fonts possible. @see font
134
- # size:: the size of the text, as it will be applied in the PDF.
135
- def dimensions_of(text, font_name, size = 1000)
136
- get_font(font_name).dimensions_of text, size
137
- end
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 fong that already exists in the pdf object into the font library.
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 font_name: :Helvetica, font_size: 12 and no box (:border_width => 0, :box_color => nil).
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[:length] = box_width
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.11
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-04 00:00:00.000000000 Z
12
+ date: 2014-09-12 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: ruby-rc4