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 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