iodine 0.7.56 → 0.7.57

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
  SHA256:
3
- metadata.gz: e2f42cc055212f19560dc49734f4ab9b4e0be6382172d5b9e7eed59791b88381
4
- data.tar.gz: 2addb1393f4689b6dd0ab826c43a7dc9b0942e42fdc1ced57b153a910ef6e621
3
+ metadata.gz: 8efbe9762562b087f91eae2c69bf2d79e574b4119af4c7163f34b45b2d0aaa8e
4
+ data.tar.gz: 8ae978cabf57c0a622e97b3f8128a68a02150fca5c11ee4d00c52f179aaa20bb
5
5
  SHA512:
6
- metadata.gz: 26cad762bc6d533c7a1828550a371b7db6107fae7439d02176eb02b70bd8887c8a30f7b1c68b931b2088aee728c7e098bbdbf9eec1cefcac697d31ccaf8ebe09
7
- data.tar.gz: 7582b83233ce7ef198cc9a1d561b738396b5fe6d0b1701ba3984ec164735b3d8ce3fe3973481772217f7d580ec8b070d0493550859abfe0cbe03a5b8c9b6a738
6
+ metadata.gz: c14ddb0e815d811fda11d87cc52631d2b63639d39ff3f1c4382b7eeee39886ad5f44a809f20b488983fdd62ed239c761293e832e22653af950154880667b2dd3
7
+ data.tar.gz: 644e50ef7a3d213c0d44aa73dfebebf7db5c6410b39be8714844632bab6d000e608bd25160fb76c8aea66f9afd0257d50036d4f4cd6c967c4c7976aa5d3d9893
data/CHANGELOG.md CHANGED
@@ -6,6 +6,10 @@ Please notice that this change log contains changes for upcoming releases as wel
6
6
 
7
7
  ## Changes:
8
8
 
9
+ #### Change log v.0.7.57 (2023-09-04)
10
+
11
+ **Fix**: Fixes possible name collision when loading gem (`.rb` vs. `.so` loading). Credit to @noraj (Alexandre ZANNI) for opening issue #148. Credit to @janbiedermann (Jan Biedermann) for discovering the root cause and offering a solution.
12
+
9
13
  #### Change log v.0.7.56 (2023-07-07)
10
14
 
11
15
  **Support**: Adds teapot support (HTTP code 418). Credit to Aleksandar N. Kostadinov (@akostadinov) for issue #144 and PR #145.
@@ -0,0 +1,3 @@
1
+ # Bates numbering with CombinePDF and Rack
2
+
3
+ This folder holds a demo application combining Iodine's `X-Sendfile` support with the [`combine_pdf` gem](https://github.com/boazsegev/combine_pdf) to create a "bates numbering" tool that is common to the one used by some court systems (such as Israeli courts).
@@ -0,0 +1,342 @@
1
+ require 'combine_pdf'
2
+ require 'rack'
3
+ require 'base64'
4
+ # require_relative 'pdf_controller'
5
+
6
+ module BatesAPP
7
+ # This is the HTTP response object according to the Rack specification.
8
+ STATIC_RESPONSE = [200, { 'Content-Type' => 'text/html', 'X-Sendfile' => File.expand_path('./public/index.html') }.freeze, []].freeze
9
+ WS_RESPONSE = [403, { 'Content-Type' => 'text/html' }, "Forbidden"]
10
+
11
+ # this is function will be called by the Rack server (iodine) for every request.
12
+ def self.call env
13
+ req = Rack::Request.new(env)
14
+ # check if this is an upgrade request (WebsSocket / SSE).
15
+ return WS_RESPONSE if(env['rack.upgrade?'.freeze])
16
+ # simply return the RESPONSE object, no matter what request was received.
17
+ return STATIC_RESPONSE if req.params.empty?
18
+ return bates(req.params)
19
+ end
20
+
21
+
22
+ def self.bates(params)
23
+ params['file'] = params['file'].values # convert to Array
24
+ # catch any exception and tell the user which PDF caused the exception.
25
+ begin
26
+ # set the file_name variable
27
+ # this will be used in case an exception is caught
28
+ # to state which file caused the exception.
29
+ file_name = ''
30
+
31
+ # container for the complete pdf
32
+ # the complete pdf container will hold all
33
+ # the merged pdf data.
34
+ completed_pdf = CombinePDF.new
35
+
36
+ # get the output file name
37
+ # this will be used to name the download for the client's browser
38
+ output_name = params['bates']['output'].to_s + '.pdf'
39
+ output_name = "unknown.pdf" if output_name == '.pdf'
40
+
41
+ # get some paramaters that will be used while combining pages
42
+ params['bates'] ||= {}
43
+ first_page_number = params['bates']['first_page_number'].to_i
44
+ first_index_number = params['bates']['first_index_number'].to_i
45
+
46
+ # we will add an option for the stamping to ignore the first pdf
47
+ # this is useful for the court cases that use bates numbering
48
+ # (the "cover PDF" will be the briefs of submissions that contain exhibits to be bates stamped)
49
+ ignore_first_file = params['bates']['first_pdf_is_cover']
50
+ first_page = nil
51
+
52
+ # we will pick some data up while running combining the different pdf files.
53
+ # this will be used for the table of contents later on.
54
+ pdfs_pages_count = []
55
+ pdf_dates = []
56
+ pdf_titles = []
57
+
58
+ # we will be creating title pages before each PDF file.
59
+ # the title pages will be sized using the mediabox variable
60
+ # wich will be set with the dimentions of each pdf file's first page.
61
+ mediabox = nil
62
+
63
+ # register UNICODE fonts if necessary
64
+ # in this example I will register the Hebrew font David from an existing PDF file.
65
+ unless CombinePDF::Fonts.get_font :my_new_david
66
+ # if your running Rails, consider Rails.root instead of Root
67
+ fonts = CombinePDF.new(File.expand_path('./david+bold.pdf').to_s).fonts(true)
68
+ # I know the first font is David regular, after using the
69
+ # ruby console and looking at the fonts array for the file.
70
+ CombinePDF.register_font_from_pdf_object :my_new_david, fonts[0]
71
+ # the second font of the array was the latin font for a newline and space... useless
72
+ # the third was the david bold. I will now add that font.
73
+ CombinePDF.register_font_from_pdf_object :my_new_david_bold, fonts[2]
74
+ end
75
+
76
+ # iterate through the different files sent from the client's browser's web form
77
+ params['file'].each do |v|
78
+ # set the file_name variable in case an exception will be raised
79
+ file_name = v['name']
80
+
81
+ # parse the pdf data
82
+ # we will use the CombinePDF.parse method which allows us
83
+ # to parse data without saving the PDF to the file system.
84
+ # our javascript encoded the file data using base64, which we will need to decode.
85
+ # (this is specific to my form which uses the HTML5 File API in this specific manner)
86
+ # begin
87
+ puts "parsing file: #{file_name}"
88
+ pdf_file = CombinePDF.parse(
89
+ Base64.urlsafe_decode64(
90
+ v['data'].slice('data:application/pdf;base64,'.length,
91
+ v['data'].length)
92
+ )
93
+ )
94
+ # rescue
95
+ # puts "Failed when parsing #{file_name} with data:\n#{v['data'].inspect}"
96
+ # raise
97
+ # end
98
+
99
+ # we will use the pages array a few times, so in order to avoid
100
+ # recomputing the array every time, we will save it to a local variable.
101
+ pdf_file_pages = pdf_file.pages
102
+
103
+ # we will add the page count to the page count array,
104
+ # used by the table of contents
105
+ pdfs_pages_count << pdf_file_pages.length
106
+
107
+ ######
108
+ # create and add title page to arrays.
109
+
110
+ if ignore_first_file && first_page.nil?
111
+ # if the first PDF file is a "cover page" PDF,
112
+ # we will not add a title, nor add the file.
113
+ # instead we will save the data in a variable to add it after we're done
114
+ pdf_dates << ''
115
+ pdf_titles << ''
116
+ first_page = pdf_file
117
+ else
118
+ # set title page mediabox size
119
+ # the mediabox data (page size) is contained in the page's
120
+ # :CropBox or :MediaBox keys (pages are Hash objects).
121
+ mediabox ||= pdf_file_pages[0][:CropBox] || pdf_file_pages[0][:MediaBox]
122
+
123
+ # create a title page unless we're only merging or there is no indexing
124
+ if params['bates']['should_index'] && params['bates']['numbering'] != 3
125
+
126
+ # create an empty page object
127
+ title_page = CombinePDF.create_page mediabox
128
+
129
+ # write the content to the title page.
130
+ # we will be using the I18n.t shortcut to write some of the data.
131
+ # the rest of the data, like the title, we got from the form.
132
+ title_page.textbox("#{params['bates']['title_type']} #{pdfs_pages_count.length + first_index_number - (ignore_first_file ? 2 : 1)}",
133
+ max_font_size: 34,
134
+ font: :my_new_david,
135
+ y: (mediabox[3] - mediabox[1]) / 2) unless params['bates']['title_type'].to_s.empty?
136
+ title_page.textbox v['title'].to_s, max_font_size: 36, font: :my_new_david_bold
137
+ title_page.textbox v['date'].to_s, max_font_size: 24, font: :my_new_david, height: (mediabox[3] - mediabox[1]) / 2
138
+
139
+ # we will add the page object to the completed pdf object.
140
+ # notice that page objects are created as "floating" pages,
141
+ # not attached to any specific PDF file/object.
142
+ completed_pdf << title_page
143
+
144
+ # we will add some data that will be used to create the
145
+ # table of contents at a later stage.
146
+ page_count = pdfs_pages_count.pop + 1
147
+ pdfs_pages_count << page_count
148
+ pdf_dates << v['date'].to_s
149
+ pdf_titles << v['title'].to_s
150
+ end
151
+
152
+ # now we are ready to add the pdf file data to the completed pdf object.
153
+ # there is no need to add each page, we can add the pdf as a whole.
154
+ # (it's actually faster, as the PDF page catalog isn't recomputed for each page)
155
+ completed_pdf << pdf_file
156
+ end
157
+ end
158
+
159
+ ##########
160
+ # create the index pdf...
161
+ # ...unless we're only merging or there is no indexing
162
+ if params['bates']['should_index'] && params['bates']['numbering'].to_i != 3
163
+
164
+ # set the fonts and formatting for the table of contents.
165
+ #
166
+ # also, add an empty array for the table data.
167
+ #
168
+ # the table data array will contain arrays of String objects, each one
169
+ # corresponding to a row in the table.
170
+ table_options = { font: :my_new_david,
171
+ header_font: :my_new_david_bold,
172
+ max_font_size: 12,
173
+ column_widths: (params['bates']['date_header'].to_s.empty? ? [3, 40, 4] : [3, 10, 30, 4]),
174
+ table_data: [] }
175
+
176
+ # set the table header array.
177
+ # this is an array of strings.
178
+ # to chose the localized strings
179
+ table_options[:headers] = [params['bates']['number_header'],
180
+ (params['bates']['date_header'].to_s.empty? ? nil : params['bates']['date_header']),
181
+ params['bates']['title_header'].to_s,
182
+ params['bates']['page_header'].to_s]
183
+ table_options[:headers].compact!
184
+
185
+ # by default, there are 25 rows per page for table pdf created by CombinePDF
186
+ # we can override this in the formatting (but we didn't).
187
+ #
188
+ # the 25 rows include 1 header row per page - so there are only 24 effective rows.
189
+ #
190
+ # we will calculate how many pages the table of contents pdf will have once completes,
191
+ # so we can add the count to the page numbers in the index.
192
+ index_page_length = pdfs_pages_count.length / 24
193
+ index_page_length += 1 if pdfs_pages_count.length % 24 > 0
194
+
195
+ # set the page number for the first entry in the table of contents.
196
+ page_number = first_page_number + index_page_length
197
+
198
+ # set the index count to 0, we will use it to change the index for each entry.
199
+ # we need a different variable in case the first PDF file is a "cover page".
200
+ index_count = 0
201
+
202
+ # iterate over the data we collected before and add it to the table data.
203
+ pdfs_pages_count.each_index do |i|
204
+ # add the data unless it is set to be ignored
205
+ unless ignore_first_file
206
+
207
+ # add an array of strings to the :table_data array,
208
+ # representing a row in our table.
209
+ # remember there might not be a date column.
210
+ if params['bates']['date_header'].to_s.empty?
211
+ table_options[:table_data] << [(first_index_number + index_count).to_s,
212
+ pdf_titles[i], page_number]
213
+ else
214
+ table_options[:table_data] << [(first_index_number + index_count).to_s,
215
+ pdf_dates[i],
216
+ pdf_titles[i], page_number]
217
+ end
218
+
219
+ # if the data was added to the index table, bump the index count
220
+ index_count += 1
221
+
222
+ end
223
+
224
+ # make sure future data will not be ignored
225
+ ignore_first_file = false
226
+
227
+ # add the page count to the page number, so that the next
228
+ # index's page number is up to date.
229
+ page_number += pdfs_pages_count[i]
230
+ end
231
+
232
+ # if out current locale is hebrew, which is a right to left language,
233
+ # set the direction for the table to Right-To-left (:rtl).
234
+ #
235
+ # notice that RTL text should be automatically recognized, but that
236
+ # feature isn't available (and shouldn't be available) for tables.
237
+ table_options[:direction] = :rtl if params['bates']['dir'] == 'rtl'
238
+
239
+ # if there is table data, we will create an index pdf.
240
+ unless table_options[:table_data].empty?
241
+
242
+ # create the index PDF from the table data and options we have.
243
+ index_pdf = CombinePDF.create_table table_options
244
+
245
+ # We will now add the words "Table of Contents" (or the I18n equivilant)
246
+ # to the first page of our new index_pdf PDF object.
247
+ #
248
+ # the table PDF object was created by CombinePDF using writable PDF pages,
249
+ # so we have properties like .mediabox and methods like .textbox
250
+ # at our disposal.
251
+
252
+ # get the first page of the index_pdf object, we will use this reference a lot.
253
+ title_page = index_pdf.pages[0]
254
+
255
+ # write the textbox, using the mediabox page data [x,y,width,height] to place
256
+ # the text we want to write.
257
+ #
258
+ # we will use the I18n.t shortcut to choose the text to write down.
259
+ title_page.textbox params['bates']['index_title'],
260
+ y: ((title_page.mediabox[3] - title_page.mediabox[1]) * 0.91),
261
+ height: ((title_page.mediabox[3] - title_page.mediabox[1]) * 0.03),
262
+ font: :my_new_david,
263
+ max_font_size: 36,
264
+ text_valign: :bottom
265
+
266
+ # now we will add the index_pdf to the BEGINING of the completed pdf.
267
+ # for this we will use the >> operator instead of the << operator.
268
+ completed_pdf >> index_pdf
269
+
270
+ end
271
+
272
+ end
273
+
274
+ # add first file if it was skipped
275
+ completed_pdf >> first_page unless first_page.nil?
276
+
277
+ #####
278
+ # number pages
279
+ # unless no numbering
280
+ if params['bates']['numbering'].to_i != 3
281
+ # list the numbering options
282
+ numbering_options = [[:top, :bottom], [:top_left, :bottom_left], [:top_right, :bottom_right]]
283
+
284
+ # set the first visible page number to the page where numbering starts
285
+ # this assumes that the bates numbering include the numbering of the "cover page",
286
+ # yet at the same time the numbering isn't visible on the "cover page"
287
+ first_page_number += pdfs_pages_count[0] if params['bates']['first_pdf_is_cover'] && params['bates']['skip_cover']
288
+
289
+ # call the page numbering method and
290
+ # add the special properties we want for the textbox
291
+ completed_pdf.number_pages(start_at: first_page_number,
292
+ page_range: params['bates']['skip_cover'] ? (pdfs_pages_count[0].to_i..-1) : nil,
293
+ font_name: :my_new_david,
294
+ font_size: 14,
295
+ font_color: [0, 0, 0.4],
296
+ box_color: [0.8, 0.8, 0.8],
297
+ border_width: 1,
298
+ border_color: [0.3, 0.3, 0.3],
299
+ box_radius: 8,
300
+ number_location: numbering_options[params['bates']['numbering'].to_i],
301
+ opacity: 0.75)
302
+ end
303
+
304
+ # send the completed PDF to the client.
305
+ # if the completed PDF is empty, raise an error.
306
+ if completed_pdf.pages.empty?
307
+ # inform the client there was an unknown error.
308
+ response = STATIC_RESPONSE.dup
309
+ response[1] = response[1].dup
310
+ response[1]['set-cookie'] = "Unknown error - 0 pages."
311
+ return response
312
+ end
313
+
314
+ # make sure the PDF version is high enough for the opacity we used in the page numbering.
315
+ completed_pdf.version = [completed_pdf.version, 1.6].max
316
+
317
+ # we will format the PDF to a pdf file WITHOUT saving it to the file system,
318
+ # using the .to_pdf method (instead of the .save method).
319
+ # we will send the raw PDF data stream.
320
+ return [200, {'content-type' => "application/pdf",
321
+ 'content-disposition' => "attachment; filename=\"#{output_name}\""}, completed_pdf.to_pdf]
322
+
323
+ rescue Exception => e
324
+ puts e.message, e.backtrace
325
+ puts "The file causing the exception is: #{file_name}"
326
+ # if an exception was raised, tell the user which PDF caused the exception
327
+ response = STATIC_RESPONSE.dup
328
+ response[1] = response[1].dup
329
+ response[1]['set-cookie'] = "notice=Unsupported or error reading file: #{file_name}"
330
+ return response
331
+ end
332
+ end
333
+ end
334
+
335
+ Iodine.workers = 1
336
+ Iodine.threads = 5
337
+ Iodine::DEFAULT_SETTINGS[:public] ||= './public'
338
+
339
+ p Iodine::DEFAULT_SETTINGS
340
+ Iodine::DEFAULT_SETTINGS[:max_body] = 70 if Iodine::DEFAULT_SETTINGS[:max_body].to_i < 5
341
+
342
+ run BatesAPP
Binary file
Binary file
@@ -0,0 +1,600 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en"><head>
3
+ <title>Bates Numbering for Court</title>
4
+ <meta charset='utf-8'>
5
+ <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1.0, user-scalable=no, minimal-ui=yes">
6
+ <style>
7
+ html, body {
8
+ height: 100%; width: 100%;
9
+ padding: 0; margin: 0;
10
+ color: #111;
11
+ background-color: #99A3CE;
12
+ }
13
+
14
+ h2, h3 {
15
+ font-size: 1.2em;
16
+ background-color: #44518E;
17
+ color: #C6CCE7;
18
+ }
19
+ h2 a, h3 a
20
+ {
21
+ color: inherit;
22
+ text-decoration: none;
23
+ }
24
+ h2 a:hover, h3 a:hover
25
+ {
26
+ color: #00f;
27
+ }
28
+
29
+ h1 {
30
+ font-size: 1.5em;
31
+ text-align: center;
32
+ background-color: #1B2864;
33
+ color: #99A3CE;
34
+ margin:0; padding: 0.5em;
35
+ }
36
+ h2 {
37
+ border-radius: 0.5em;
38
+ text-align: center;
39
+ }
40
+ h3 {
41
+ text-align: left;
42
+ }
43
+ .hide {
44
+ display: none !important;
45
+ }
46
+ span.en {
47
+ direction: ltr;
48
+ display: inline-block;
49
+ text-align: left;
50
+ width: auto;
51
+ }
52
+ #notice
53
+ {
54
+ z-index: 99999;
55
+ position: absolute;
56
+ top: 0; left: 0;
57
+ background-color: rgba(0, 0, 0, 0.75);
58
+ box-sizing: border-box;
59
+ padding: 1em 0;
60
+ text-align: center;
61
+ height: 100px;
62
+ width: 100%;
63
+ overflow-y: auto;
64
+ color: #fff; font-weight: bold;
65
+ transition:All 0.25s ease;
66
+ -webkit-transition:All 0.25s ease;
67
+ -moz-transition:All 0.25s ease;
68
+ -o-transition:All 0.25s ease;
69
+ -ms-transition:All 0.25s ease;
70
+ transform-origin: 0 50%;
71
+ -webkit-transform-origin: 50% 0; /* Chrome, Safari, Opera */
72
+ -ms-transform-origin: 50% 0; /* IE 9 */
73
+ -moz-transform-origin: 50% 0;
74
+ -o-transform-origin: 50% 0;
75
+ transform: scale(1, 1);
76
+ -webkit-transform: scale(1, 1);
77
+ -moz-transform: scale(1, 1);
78
+ -o-transform: scale(1, 1);
79
+ -ms-transform: scale(1, 1);
80
+ }
81
+ #notice.hidden
82
+ {
83
+ transform: scale(1, 0);
84
+ -webkit-transform: scale(1, 0);
85
+ -moz-transform: scale(1, 0);
86
+ -o-transform: scale(1, 0);
87
+ -ms-transform: scale(1, 0);
88
+ }
89
+ #notice .notice_close
90
+ {
91
+ display:block;
92
+ position: absolute;
93
+ top:0; left:0;
94
+ padding:0.5em;
95
+ height:100%;
96
+ width:5em;
97
+ text-align:left;
98
+ cursor: pointer;
99
+ border-right:1px solid #eee;
100
+ color: #ccc;
101
+ }
102
+ #notice .notice_close:hover
103
+ {
104
+ color: #eee;
105
+ background-color: rgba(255, 255, 255, 0.25);
106
+ }
107
+
108
+ #content {
109
+ background-color: #fff;
110
+ color: #111;
111
+ min-height: 100%;
112
+ padding: 4em 1.5em;
113
+ margin: 0 1em;
114
+ overflow-x: hidden;
115
+ box-sizing: border-box;
116
+ }
117
+ #content h1, #content h2, #content h3 {
118
+ background-color: #1B2864;
119
+ color: #99A3CE;
120
+ padding: 0.2em 1em;
121
+ margin: 0.2em 0;
122
+ }
123
+ #content h1 {
124
+ padding: 0.5em;
125
+ margin: 0;
126
+ }
127
+ #content p {
128
+ padding: 0.5em;
129
+ margin: 0;
130
+ }
131
+ #content a {
132
+ color: #0000aa;
133
+ text-decoration: none;
134
+ }
135
+ #content a:hover {
136
+ color: blue;
137
+ text-decoration: underline;
138
+ background: white;
139
+ position: relative;
140
+ top: 1px;
141
+ left: 2px;
142
+ }
143
+ #content h1 a, #content h2 a, #content h3 a, #content table th a {
144
+ color: #aaaaff;
145
+ }
146
+ #content h1 a:hover, #content h2 a:hover, #content h3 a:hover, #content table th a:hover {
147
+ background: inherit;
148
+ color: #ddddff;
149
+ }
150
+ #content #notice {
151
+ background: #ffaadd;
152
+ color: #551111;
153
+ margin: 0.3em 15%;
154
+ padding: 1em 0.5em;
155
+ text-align: center;
156
+ border-radius: 1em;
157
+ font-weight: bold;
158
+ font-size: 1.1em;
159
+ }
160
+
161
+ .desc, form {
162
+ text-align: justify;
163
+ display: block;
164
+ margin: 0.5em 15%; padding: 0 0 0.5em 0;
165
+ background: #eeeefa;
166
+ color: #111111;
167
+ border-radius: 1em;
168
+ }
169
+ .with_border
170
+ {
171
+ border: 1px solid #aab;
172
+ }
173
+ .desc p, form p {
174
+ padding: 0.2em;
175
+ margin: 0.5em 0;
176
+ }
177
+ form input[type='submit'] {
178
+ margin: 0.5em 20%;
179
+ width: 60%;
180
+ color: #000033;
181
+ background: #eeeeff;
182
+ }
183
+ form table {
184
+ width: 100%;
185
+ table-layout: fixed;
186
+ border-collapse: collapse;
187
+ }
188
+ form table th {
189
+ background: #bbb;
190
+ color: #111111;
191
+ }
192
+ form table td {
193
+ width: 2%;
194
+ border-collapse: collapse;
195
+ border-top: #bbbbcc dotted 1px;
196
+ border-bottom: #bbbbcc dotted 1px;
197
+ padding: 1px 2px;
198
+ }
199
+ form table tr {
200
+ border-collapse: collapse;
201
+ }
202
+ form table .en {
203
+ direction: ltr;
204
+ }
205
+ form table input, form table select {
206
+ width: 100%;
207
+ margin: 0.5em 0;
208
+ padding: 0;
209
+ color: #000033;
210
+ background: #eeeeff;
211
+ }
212
+ form table .no_resize input, form table .no_resize select {
213
+ width: auto;
214
+ margin: 0.5em 0;
215
+ padding: inherit;
216
+ }
217
+ form .field {
218
+ width: 100%;
219
+ display: block;
220
+ text-align: justify;
221
+ padding: 0.5em 0;
222
+ margin: 0;
223
+ margin-bottom: 0;
224
+ vertical-align: top;
225
+ border-collapse: collapse;
226
+ border-top: #bbbbcc dotted 1px;
227
+ border-bottom: #bbbbcc dotted 1px;
228
+ }
229
+ form .field label {
230
+ width: 34%;
231
+ margin: 0 2%;
232
+ padding: 0;
233
+ display: inline-block;
234
+ vertical-align: top;
235
+ }
236
+ form .field input {
237
+ width: 60%;
238
+ margin: 0;
239
+ padding: 0;
240
+ display: inline-block;
241
+ vertical-align: top;
242
+ color: #000033;
243
+ background: #eeeeff;
244
+ }
245
+ form .field select {
246
+ width: 60%;
247
+ margin: 0;
248
+ padding: 0;
249
+ }
250
+ form .field .no_resize input, form .field .no_resize select {
251
+ width: auto;
252
+ margin: inherit;
253
+ padding: inherit;
254
+ color: #000033;
255
+ background: #eeeeff;
256
+ }
257
+ form .field textarea {
258
+ width: 98%;
259
+ padding: 0;
260
+ margin: 0;
261
+ height: 5em;
262
+ display: block;
263
+ color: #000033;
264
+ background: #eeeeff;
265
+ }
266
+ form .en input, form .en textarea {
267
+ text-align: left;
268
+ direction: ltr;
269
+ }
270
+
271
+ table.one_liners {
272
+ table-layout: fixed !important;
273
+ max-width: 100% !important;
274
+ width: 100% !important;
275
+ text-overflow: ellipsis;
276
+ overflow: none;
277
+ }
278
+ table.one_liners td, table.one_liners th {
279
+ width: inherit;
280
+ max-width: 10%;
281
+ white-space: nowrap;
282
+ text-overflow: ellipsis;
283
+ }
284
+
285
+ h1#dropzone {
286
+ font-size: 2em;
287
+ text-align: center;
288
+ background-color: #44518E;
289
+ color: #C6CCE7;
290
+ padding: 0.5em 0;
291
+ background-image: url(/drop-pdf.png);
292
+ background-repeat: no-repeat;
293
+ background-position: 10% center;
294
+ background-size: auto 100%;
295
+ border-radius: 0.5em 0.5em 0 0;
296
+ }
297
+
298
+ ul.bates_file_table_list, ol.bates_file_table_list {
299
+ display: block;
300
+ margin: 0;
301
+ padding: 0;
302
+ list-style-position: inside;
303
+ width: 100%;
304
+ }
305
+ ul.bates_file_table_list li, ol.bates_file_table_list li {
306
+ border-top: 1px dotted #111111;
307
+ border-bottom: 1px dotted #111111;
308
+ border-collapse: collapse;
309
+ }
310
+ ul.bates_file_table_list li div, ol.bates_file_table_list li div {
311
+ display: inline-block;
312
+ width: 28%;
313
+ }
314
+ ul.bates_file_table_list input, ol.bates_file_table_list input {
315
+ width: 90%;
316
+ color: #000033;
317
+ background: #eeeeff;
318
+ }
319
+ ul.bates_file_table_list {
320
+ list-style-type: circle;
321
+ }
322
+ ul.bates_file_table_list li:first-child {
323
+ background: #aab;
324
+ color: #111111;
325
+ }
326
+ input[type=checkbox]:checked#bates_first_pdf_is_cover ~ ol.bates_file_table_list li:first-child {
327
+ background: #bbc;
328
+ color: #551111;
329
+ }
330
+ input[type=checkbox]
331
+ {
332
+ width: auto;
333
+ display: inline;
334
+ }
335
+ #file_remover {
336
+ float: left;
337
+ }
338
+
339
+ /* RESPONSIVE RELATED */
340
+ @media screen and (max-width: 46em) {
341
+ /* STYLING CONTENT (none at the moment)*/
342
+ /* line 800, /Users/2Be/Ruby/combine_pdf_demo/app/assets/stylesheets/welcome.css.scss */
343
+ body, html {
344
+ height: auto;
345
+ }
346
+
347
+ /* line 803, /Users/2Be/Ruby/combine_pdf_demo/app/assets/stylesheets/welcome.css.scss */
348
+ #content {
349
+ padding: 3em 0;
350
+ margin: 0;
351
+ }
352
+ #content #notice {
353
+ margin: 0.2em 1em;
354
+ }
355
+ /* line 814, /Users/2Be/Ruby/combine_pdf_demo/app/assets/stylesheets/welcome.css.scss */
356
+ #content .hide_when_small {
357
+ display: none;
358
+ }
359
+ form, .desc {
360
+ margin: 0.3em 1em;
361
+ }
362
+ }
363
+ </style>
364
+ <script>
365
+ function show_notice() { document.getElementById("notice").classList.remove('hidden'); }
366
+ function hide_notice() { document.getElementById("notice").classList.add('hidden'); }
367
+ </script>
368
+ </head><body>
369
+ <h1>Bates Numbering - PDF file merge and number</h1>
370
+ <div id="notice" class="hidden"><a class='notice_close'onclick='hide_notice()'>X</a><div id="notice_text"></div></div>
371
+ <div id="contents">
372
+ <ol class='bates_file_table_list' id='demo' style='display:none;'>
373
+ <li id='file_NUM'>
374
+ <div>
375
+ <input id="file_NUM_name" name="file[NUM][name]" readonly="readonly" type="text" value="FILE_NAME">
376
+ <input id="file_NUM_data" name="file[NUM][data]" type="hidden" value="NUM">
377
+ </div>
378
+ <div>
379
+ <input id="file_NUM_date" name="file[NUM][date]" type="text" value="DATE">
380
+ </div><div>
381
+ <input id="file_NUM_title" name="file[NUM][title]" type="text" value="TITLE">
382
+ </div>
383
+ |||
384
+ <a href="#" class="file_remover" onclick='this.parentNode.parentNode.removeChild(this.parentNode); return false;'>[x]</a>
385
+ </li>
386
+ </ol>
387
+ <form accept-charset="UTF-8" enctype="multipart/form-data" method="post" onsubmit="onSubmit()">
388
+ <h1 id='dropzone'>Drag PDF files here</h1>
389
+ <p>
390
+ <ul class='bates_file_table_list'>
391
+ <li>
392
+ <div>File Name</div>
393
+ <div>Date</div>
394
+ <div>Title</div>
395
+ </li>
396
+ </ul>
397
+ <input checked="checked" id="bates_first_pdf_is_cover" name="bates[first_pdf_is_cover]" type="checkbox" value="true">
398
+ <label for="bates_first_pdf_is_cover">Place the first PDF file before the Index page.</label>
399
+ <ol class='bates_file_table_list' id='file_list'></ol>
400
+ <input type="submit" value="Submit">
401
+ </p>
402
+ <h3>Settings</h3>
403
+ <table>
404
+ <tr><td>Output file name:</td><td><input type='text' id="output_filename" name='bates[output]' value="combined"></td></tr><tr><td>First page number:</td><td><input type='number' name='bates[first_page_number]' value="1">
405
+ </td></tr><tr><td>First index number:</td><td><input type='number' name='bates[first_index_number]' value="1">
406
+ </td></tr><tr><td>Numbering:</td><td><select id='numbering' name='bates[numbering]'>
407
+ <option value='0'>Center</option>
408
+ <option value='1'>Left</option>
409
+ <option value='2'>Right</option>
410
+ <option value='3'>None</option>
411
+ </select>
412
+ </td></tr><tr><td><input checked="checked" id="bates_should_index_files" name="bates[should_index]" type="checkbox" value="true"><label for='bates_should_index_files'>Index Files?</label></td><td><label for='bates_should_index_files'>un-check to avoid an index and title pages.</label>
413
+ </td></tr><tr><td><input id="bates_skip_cover" name="bates[skip_cover]" type="checkbox" value="true"><label for='bates_skip_cover'>Skip Cover?</label></td><td><label for='bates_skip_cover'>check to skip numbering the cover page.</label>
414
+ </td></tr><tr><td>Language:</td><td><select id='dir' name='bates[dir]'>
415
+ <option value='rtl'>עברית</option>
416
+ <option value='ltr'>English</option>
417
+ </select>
418
+ </td></tr><tr><td>Index Title:</td><td>
419
+ <input type='text' id='bates_index_title' name='bates[index_title]' value="Index">
420
+ </td></tr><tr><td>"Number" Header:</td><td>
421
+ <input type='text' id='bates_number_header' name='bates[number_header]' value="#">
422
+ </td></tr><tr><td>"Date" Header (Leave empty to ignore):</td><td>
423
+ <input type='text' id='bates_date_header' name='bates[date_header]' value="Date">
424
+ </td></tr><tr><td>"Title" Header:</td><td>
425
+ <input type='text' id='bates_title_header' name='bates[title_header]' value="Title">
426
+ </td></tr><tr><td> "Page Number" Header:</td><td>
427
+ <input type='text' id='bates_page_header' name='bates[page_header]' value="Page">
428
+ </td></tr><tr><td>Cover Page Title:</td><td>
429
+ <input type='text' id='bates_title_type' name='bates[title_type]' value="Exhibit">
430
+ <input type="submit" value="Submit">
431
+ </form>
432
+ </div>
433
+ <script>
434
+ function onSubmit()
435
+ {
436
+ return true;
437
+ }
438
+ file_i=0
439
+ files_read = 0
440
+
441
+ template = document.getElementById('demo').innerHTML
442
+
443
+ function transform_file(file)
444
+ {
445
+ // Get Time and Title if the name matches standard naming
446
+ obj_date_string = file.name.match("^[0-9\\-–]*") + ""
447
+ if(obj_date_string.length == 8) {
448
+ obj_date = obj_date_string.substr(6,2) + "." + obj_date_string.substr(4,2) + "." + obj_date_string.substr(0,4)
449
+ obj_description = file.name.substring(9, file.name.toLowerCase().search("\.pdf") )
450
+ } else if(obj_date_string.length == 10 && obj_date_string[4] == '-' && obj_date_string[7] == '-') {
451
+ obj_date = obj_date_string.substr(8,2) + "." + obj_date_string.substr(5,2) + "." + obj_date_string.substr(0,4)
452
+ obj_description = file.name.substring(11, file.name.toLowerCase().search("\.pdf") )
453
+ } else {
454
+ obj_date = ""
455
+ obj_description = file.name.substring(0, file.name.toLowerCase().search("\.pdf") )
456
+ }
457
+
458
+ // copy template and adjust details
459
+
460
+ document.getElementById('file_list').innerHTML += template.replace(RegExp("NUM", "g"), "" + file_i).replace(RegExp("FILE_NAME", "g"), file.name).replace(RegExp("DATE", "g"), obj_date).replace(RegExp("TITLE", "g"), obj_description)
461
+
462
+ // set reader for file
463
+ reader = new FileReader();
464
+ reader.futureObject = document.getElementById("file_" + file_i + "_data")
465
+
466
+ file_i += 1;
467
+
468
+ reader.onloadend = function(e)
469
+ {
470
+ //var id = this.futureObject.value
471
+ console.log("adding result to: " + this.futureObject.id )
472
+ document.getElementById(this.futureObject.id).value = e.target.result
473
+ files_read +=1
474
+ }
475
+
476
+ //reader.readAsText(file)
477
+ reader.readAsDataURL(file)
478
+ true
479
+ }
480
+
481
+ // make drop_zone droppable and set callbacks
482
+ var dropzone = document.getElementById('dropzone')
483
+
484
+ dropzone.ondragend = function (event) {event.stopPropagation();return false; };
485
+ dropzone.ondragover = function (event) {event.stopPropagation();return false; };
486
+ dropzone.ondragenter = function (event) {event.stopPropagation();return false; };
487
+
488
+ dropzone.ondrop = function(event) {
489
+ event.preventDefault()
490
+ event.stopPropagation();
491
+ var files = event.dataTransfer.files; // FileList object.
492
+ i = 0
493
+ while(files[i])
494
+ {
495
+ console.log('transforming:' + files[i] + ' name: ' + files[i].name.toLowerCase())
496
+ if( files[i].name.toLowerCase().search("\.pdf") != -1) transform_file(files[i])
497
+ i++
498
+ }
499
+
500
+ set_list_sotrable()
501
+ return false;
502
+ }
503
+
504
+ // make drag-drop and sortable list from file_list
505
+ var Dragged_id = ''
506
+
507
+ function start_drag_list_item(e) {
508
+ Dragged_id = this.id
509
+ e.dataTransfer.setData('text/plain', this.id)
510
+ this.style.opacity = '0.3'
511
+ }
512
+ function end_drag_list_item(e) {
513
+ this.style.opacity = '1'
514
+ Dragged_id = ''
515
+ }
516
+ function enter_drag_list_item(e) {
517
+ if(Dragged_id == '') return false;
518
+ if(this.id == Dragged_id) return false; //this.style.opacity = '0.7'
519
+ this.parentNode.insertBefore(this.parentNode.removeChild(document.getElementById(Dragged_id)), this);
520
+ }
521
+ function leave_drag_list_item(e) {
522
+ if(Dragged_id == '') return false;
523
+ if(this.id != Dragged_id) this.style.opacity = '1'
524
+ }
525
+ function drop_list_item(e)
526
+ {
527
+ e.stopPropagation()
528
+ e.preventDefault()
529
+ alert('move ' + Dragged_id + ' before ' + this.id)
530
+ if (this.id == Dragged_id) return false;
531
+ }
532
+ function set_list_sotrable()
533
+ {
534
+ all_list_items = document.querySelectorAll('#file_list li');
535
+ for(i=0; i < all_list_items.length; i++)
536
+ {
537
+ all_list_items[i].setAttribute("draggable","true");
538
+ all_list_items[i].ondragstart = start_drag_list_item
539
+ all_list_items[i].ondragenter = enter_drag_list_item
540
+ all_list_items[i].ondragleave = leave_drag_list_item
541
+ all_list_items[i].ondragend = end_drag_list_item
542
+ all_list_items[i].ondrop = drop_list_item
543
+ }
544
+ }
545
+
546
+ function get_today_as_str(){
547
+ const n = new Date();
548
+ return (n.getUTCFullYear().toString() + (n.getUTCMonth() < 10 ? ("0" + n.getUTCMonth().toString()) : n.getUTCMonth().toString()) + (n.getUTCMonth() < 10 ? ("0" + n.getUTCDate().toString()) : n.getUTCDate().toString()));
549
+ }
550
+
551
+ // set some initial values
552
+ function set_common_values(o){
553
+ for (let [key, value] of Object.entries(o)) {
554
+ console.log(key, value);
555
+ var tmp = document.getElementById(key);
556
+ if(tmp){
557
+ if(tmp.value)
558
+ tmp.value = value;
559
+ else
560
+ tmp.innerHTML = value;
561
+ }
562
+ }
563
+ }
564
+ function set_en_values(){
565
+
566
+ set_common_values({
567
+ output_filename: get_today_as_str() + " name",
568
+ bates_index_title: "Index",
569
+ bates_number_header: "Ex.",
570
+ bates_date_header: "Date",
571
+ bates_title_header: "Title",
572
+ bates_page_header: "Page",
573
+ bates_title_type: "Exhibit",
574
+ });
575
+ }
576
+ function set_he_values(){
577
+ set_common_values({
578
+ output_filename: get_today_as_str() + " שם",
579
+ bates_index_title: "תוכן עניינים",
580
+ bates_number_header: "נספח",
581
+ bates_date_header: "תאריך",
582
+ bates_title_header: "כותרת",
583
+ bates_page_header: "עמוד",
584
+ bates_title_type: "נספח",
585
+ });
586
+ }
587
+
588
+ function update_defaults(e) {
589
+ if(e.target.value == 'ltr') set_en_values();
590
+ else set_he_values();
591
+ }
592
+
593
+ function init_page(e){
594
+ set_he_values();
595
+ var tmp = document.getElementById("dir");
596
+ tmp.onchange = update_defaults;
597
+ }
598
+ init_page();
599
+ </script>
600
+ </body></html>
@@ -107,4 +107,4 @@ EOS
107
107
  end
108
108
  end
109
109
 
110
- create_makefile 'iodine/iodine'
110
+ create_makefile 'iodine/iodine_ext'
data/ext/iodine/iodine.c CHANGED
@@ -1325,7 +1325,7 @@ Ruby loads the library and invokes the Init_<lib_name> function...
1325
1325
  Here we connect all the C code to the Ruby interface, completing the bridge
1326
1326
  between Lib-Server and Ruby.
1327
1327
  ***************************************************************************** */
1328
- void Init_iodine(void) {
1328
+ void Init_iodine_ext(void) {
1329
1329
  /* common Symbol objects in use by Iodine */
1330
1330
  #define IODINE_MAKE_SYM(name) \
1331
1331
  do { \
@@ -1,3 +1,3 @@
1
1
  module Iodine
2
- VERSION = '0.7.56'.freeze
2
+ VERSION = '0.7.57'.freeze
3
3
  end
data/lib/iodine.rb CHANGED
@@ -3,7 +3,7 @@ require 'socket' # TCPSocket is used internally for Hijack support
3
3
  # require 'openssl' # For SSL/TLS support using OpenSSL
4
4
 
5
5
  require_relative './iodine/version'
6
- require_relative './iodine/iodine' # loading a binary C extension
6
+ require_relative './iodine/iodine_ext' # loading a binary C extension
7
7
 
8
8
  # Iodine is an HTTP / WebSocket server as well as an Evented Network Tool Library. In essense, Iodine is a Ruby port for the [facil.io](http://facil.io) C library.
9
9
  #
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: iodine
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.56
4
+ version: 0.7.57
5
5
  platform: ruby
6
6
  authors:
7
7
  - Boaz Segev
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2023-07-07 00:00:00.000000000 Z
11
+ date: 2023-09-03 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake
@@ -143,6 +143,11 @@ files:
143
143
  - bin/poc/gemfile
144
144
  - bin/poc/www/index.html
145
145
  - examples/async_task.ru
146
+ - examples/bates/README.md
147
+ - examples/bates/config.ru
148
+ - examples/bates/david+bold.pdf
149
+ - examples/bates/public/drop-pdf.png
150
+ - examples/bates/public/index.html
146
151
  - examples/config.ru
147
152
  - examples/echo.ru
148
153
  - examples/etag.ru
@@ -246,7 +251,7 @@ licenses:
246
251
  metadata:
247
252
  allowed_push_host: https://rubygems.org
248
253
  post_install_message: |-
249
- Thank you for installing Iodine 0.7.56.
254
+ Thank you for installing Iodine 0.7.57.
250
255
  Remember: if iodine supports your business, it's only fair to give value back (code contributions / donations).
251
256
  rdoc_options: []
252
257
  require_paths: