ypdf-writer 1.3.2

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.
Files changed (71) hide show
  1. checksums.yaml +7 -0
  2. data/ChangeLog +134 -0
  3. data/LICENCE +131 -0
  4. data/bin/techbook +24 -0
  5. data/demo/chunkybacon.rb +40 -0
  6. data/demo/code.rb +71 -0
  7. data/demo/colornames.rb +47 -0
  8. data/demo/demo.rb +73 -0
  9. data/demo/gettysburg.rb +66 -0
  10. data/demo/hello.rb +26 -0
  11. data/demo/individual-i.rb +89 -0
  12. data/demo/pac.rb +70 -0
  13. data/demo/qr-language.rb +580 -0
  14. data/demo/qr-library.rb +380 -0
  15. data/images/bluesmoke.jpg +0 -0
  16. data/images/chunkybacon.jpg +0 -0
  17. data/images/chunkybacon.png +0 -0
  18. data/lib/pdf/charts.rb +13 -0
  19. data/lib/pdf/charts/stddev.rb +431 -0
  20. data/lib/pdf/core_ext/mutex.rb +12 -0
  21. data/lib/pdf/math.rb +108 -0
  22. data/lib/pdf/quickref.rb +333 -0
  23. data/lib/pdf/simpletable.rb +952 -0
  24. data/lib/pdf/techbook.rb +907 -0
  25. data/lib/pdf/writer.rb +2760 -0
  26. data/lib/pdf/writer/arc4.rb +63 -0
  27. data/lib/pdf/writer/fontmetrics.rb +203 -0
  28. data/lib/pdf/writer/fonts/Courier-Bold.afm +342 -0
  29. data/lib/pdf/writer/fonts/Courier-BoldOblique.afm +342 -0
  30. data/lib/pdf/writer/fonts/Courier-Oblique.afm +342 -0
  31. data/lib/pdf/writer/fonts/Courier.afm +342 -0
  32. data/lib/pdf/writer/fonts/Helvetica-Bold.afm +2827 -0
  33. data/lib/pdf/writer/fonts/Helvetica-BoldOblique.afm +2827 -0
  34. data/lib/pdf/writer/fonts/Helvetica-Oblique.afm +3051 -0
  35. data/lib/pdf/writer/fonts/Helvetica.afm +3051 -0
  36. data/lib/pdf/writer/fonts/MustRead.html +19 -0
  37. data/lib/pdf/writer/fonts/Symbol.afm +213 -0
  38. data/lib/pdf/writer/fonts/Times-Bold.afm +2588 -0
  39. data/lib/pdf/writer/fonts/Times-BoldItalic.afm +2384 -0
  40. data/lib/pdf/writer/fonts/Times-Italic.afm +2667 -0
  41. data/lib/pdf/writer/fonts/Times-Roman.afm +2419 -0
  42. data/lib/pdf/writer/fonts/ZapfDingbats.afm +225 -0
  43. data/lib/pdf/writer/graphics.rb +813 -0
  44. data/lib/pdf/writer/graphics/imageinfo.rb +366 -0
  45. data/lib/pdf/writer/lang.rb +43 -0
  46. data/lib/pdf/writer/lang/en.rb +99 -0
  47. data/lib/pdf/writer/object.rb +23 -0
  48. data/lib/pdf/writer/object/action.rb +35 -0
  49. data/lib/pdf/writer/object/annotation.rb +42 -0
  50. data/lib/pdf/writer/object/catalog.rb +39 -0
  51. data/lib/pdf/writer/object/contents.rb +70 -0
  52. data/lib/pdf/writer/object/destination.rb +40 -0
  53. data/lib/pdf/writer/object/encryption.rb +53 -0
  54. data/lib/pdf/writer/object/font.rb +72 -0
  55. data/lib/pdf/writer/object/fontdescriptor.rb +34 -0
  56. data/lib/pdf/writer/object/fontencoding.rb +40 -0
  57. data/lib/pdf/writer/object/image.rb +305 -0
  58. data/lib/pdf/writer/object/info.rb +51 -0
  59. data/lib/pdf/writer/object/outline.rb +30 -0
  60. data/lib/pdf/writer/object/outlines.rb +30 -0
  61. data/lib/pdf/writer/object/page.rb +195 -0
  62. data/lib/pdf/writer/object/pages.rb +115 -0
  63. data/lib/pdf/writer/object/procset.rb +46 -0
  64. data/lib/pdf/writer/object/viewerpreferences.rb +74 -0
  65. data/lib/pdf/writer/ohash.rb +58 -0
  66. data/lib/pdf/writer/oreader.rb +25 -0
  67. data/lib/pdf/writer/state.rb +48 -0
  68. data/lib/pdf/writer/strokestyle.rb +138 -0
  69. data/manual.pwd +5965 -0
  70. data/readme.md +36 -0
  71. metadata +151 -0
@@ -0,0 +1,907 @@
1
+ #! /usr/bin/env ruby
2
+ #--
3
+ # PDF::Writer for Ruby.
4
+ # http://rubyforge.org/projects/ruby-pdf/
5
+ # Copyright 2003 - 2005 Austin Ziegler.
6
+ #
7
+ # Licensed under a MIT-style licence. See LICENCE in the main distribution
8
+ # for full licensing information.
9
+ #
10
+ # $Id$
11
+ #++
12
+ require 'pdf/simpletable'
13
+ require 'pdf/charts/stddev'
14
+
15
+ require 'cgi'
16
+ require 'open-uri'
17
+
18
+ begin
19
+ require 'progressbar'
20
+ rescue LoadError
21
+ class ProgressBar #:nodoc:
22
+ def initialize(*args)
23
+ end
24
+ def method_missing(*args)
25
+ end
26
+ end
27
+ end
28
+
29
+ require 'optparse'
30
+ require 'ostruct'
31
+
32
+ # = PDF::TechBook
33
+ # The TechBook class is a markup language interpreter. This will read a
34
+ # file containing the "TechBook" markukp, described below, and create a
35
+ # PDF document from it. This is intended as a complete document language,
36
+ # but it does have a number of limitations.
37
+ #
38
+ # The TechBook markup language and class are used to format the
39
+ # PDF::Writer manual, represented in the distrubtion by the file
40
+ # "manual.pwd".
41
+ #
42
+ # The TechBook markup language is *primarily* stream-oriented with
43
+ # awareness of lines. That is to say that the document will be read and
44
+ # generated from beginning to end in the order of the markup stream.
45
+ #
46
+ # == TechBook Markup
47
+ # TechBook markup is relatively simple. The simplest markup is no markup
48
+ # at all (flowed paragraphs). This means that two lines separated by a
49
+ # single line separator will be treaed as part of the same paragraph and
50
+ # formatted appropriately by PDF::Writer. Paragaphs are terminated by
51
+ # empty lines, valid line markup directives, or valid headings.
52
+ #
53
+ # Certain XML entitites will need to be escaped as they would in normal
54
+ # XML usage, that is, < must be written as <; > must be
55
+ # written as >; and & must be written as &.
56
+ #
57
+ # Comments, headings, and directives are line-oriented where the first
58
+ # mandatory character is in the first column of the document and take up
59
+ # the whole line. Styling and callback tags may appear anywhere in the
60
+ # text.
61
+ #
62
+ # === Comments
63
+ # Comments begin with the hash-mark ('#') at the beginning of the line.
64
+ # Comment lines are ignored.
65
+ #
66
+ # === Styling and Callback Tags
67
+ # Within normal, preserved, or code text, or in headings, HTML-like markup
68
+ # may be used for bold (<b>) and italic (<i>) text. TechBook
69
+ # supports standard PDF::Writer callback tags (<c:alink>, <c:ilink>,
70
+ # <C:bullet/>, and <C:disc/>) and adds two new ones (<r:xref/>,
71
+ # <C:tocdots/>).
72
+ #
73
+ # <tt>&lt;r:xref/></tt>:: Creates an internal document link to the
74
+ # named cross-reference destination. Works
75
+ # with the heading format (see below). See
76
+ # #tag_xref_replace for more information.
77
+ # <tt>&lt;C:tocdots/></tt>:: This is used internally to create and
78
+ # display a row of dots between a table of
79
+ # contents entry and the page number to which
80
+ # it refers. This is used internally by
81
+ # TechBook.
82
+ #
83
+ # === Directives
84
+ # Directives begin with a period ('.') and are followed by a letter
85
+ # ('a'..'z') and then any combination of word characters ('a'..'z',
86
+ # '0'..'9', and '_'). Directives are case-insensitive. A directive may
87
+ # have arguments; if there are arguments, they must follow the directive
88
+ # name after whitespace. After the arguments for a directive, if any, all
89
+ # other text is ignored and may be considered a comment.
90
+ #
91
+ # ==== <tt>.newpage [force]</tt>
92
+ # The <tt>.newpage</tt> directive starts a new page. If multicolumn mode
93
+ # is on, a new column will be started if the current column is not the
94
+ # last column. If the optional argument <tt>force</tt> follows the
95
+ # <tt>.newpage</tt> directive, a new page will be started even if
96
+ # multicolumn mode is on.
97
+ #
98
+ # .newpage
99
+ # .newpage force
100
+ #
101
+ # ==== <tt>.pre</tt>, <tt>.endpre</tt>
102
+ # The <tt>.pre</tt> and <tt>.endpre</tt> directives enclose a block of
103
+ # text with preserved newlines. This is similar to normal text, but the
104
+ # lines in the <tt>.pre</tt> block are not flowed together. This is useful
105
+ # for poetic forms or other text that must end when each line ends.
106
+ # <tt>.pre</tt> blocks may not be nested in any other formatting block.
107
+ # When an <tt>.endpre</tt> directive is encountered, the text format will
108
+ # be returned to normal (flowed text) mode.
109
+ #
110
+ # .pre
111
+ # The Way that can be told of is not the eternal Way;
112
+ # The name that can be named is not the eternal name.
113
+ # The Nameless is the origin of Heaven and Earth;
114
+ # The Named is the mother of all things.
115
+ # Therefore let there always be non-being,
116
+ # so we may see their subtlety,
117
+ # And let there always be being,
118
+ # so we may see their outcome.
119
+ # The two are the same,
120
+ # But after they are produced,
121
+ # they have different names.
122
+ # .endpre
123
+ #
124
+ # ==== <tt>.code</tt>, <tt>.endcode</tt>
125
+ # The <tt>.code</tt> and <tt>.endcode</tt> directives enclose a block of
126
+ # text with preserved newlines. In addition, the font is changed from the
127
+ # normal #techbook_textfont to #techbook_codefont. The #techbook_codefont
128
+ # is normally a fixed pitched font and defaults to Courier. At the end of
129
+ # the code block, the text state is restored to its prior state, which
130
+ # will either be <tt>.pre</tt> or normal.
131
+ #
132
+ # .code
133
+ # require 'pdf/writer'
134
+ # PDF::Writer.prepress # US Letter, portrait, 1.3, prepress
135
+ # .endcode
136
+ #
137
+ # ==== <tt>.blist</tt>, <tt>.endblist</tt>
138
+ # These directives enclose a bulleted list block. Lists may be nested
139
+ # within other text states. If lists are nested, each list will be
140
+ # appropriately indented. Each line in the list block will be treated as a
141
+ # single list item with a bullet inserted in front using either the
142
+ # <C:bullet/> or <C:disc/> callbacks. Nested lists are successively
143
+ # indented. <tt>.blist</tt> directives accept one optional argument, the
144
+ # name of the type of bullet callback desired (e.g., 'bullet' for
145
+ # <C:bullet/> and 'disc' for <C:disc/>).
146
+ #
147
+ # .blist
148
+ # Item 1
149
+ # .blist disc
150
+ # Item 1.1
151
+ # .endblist
152
+ # .endblist
153
+ #
154
+ # ==== <tt>.eval</tt>, <tt>.endeval</tt>
155
+ # With these directives, the block enclosed will collected and passed to
156
+ # Ruby's Kernel#eval. <tt>.eval</tt> blocks may be present within normal
157
+ # text, <tt>.pre</tt>, <tt>.code</tt>, and <tt>.blist</tt> blocks. No
158
+ # other block may be embedded within an <tt>.eval</tt> block.
159
+ #
160
+ # .eval
161
+ # puts "Hello"
162
+ # .endeval
163
+ #
164
+ # ==== <tt>.columns</tt>
165
+ # Multi-column output is controlled with this directive, which accepts one
166
+ # or two parameters. The first parameter is mandatory and is either the
167
+ # number of columns (2 or more) or the word 'off' (turning off
168
+ # multi-column output). When starting multi-column output, a second
169
+ # parameter with the gutter size may be specified.
170
+ #
171
+ # .columns 3
172
+ # Column 1
173
+ # .newpage
174
+ # Column 2
175
+ # .newpage
176
+ # Column 3
177
+ # .columns off
178
+ #
179
+ # ==== <tt>.toc</tt>
180
+ # This directive is used to tell TechBook to generate a table of contents
181
+ # after the first page (assumed to be a title page). If this is not
182
+ # present, then a table of contents will not be generated.
183
+ #
184
+ # ==== <tt>.author</tt>, <tt>.title</tt>, <tt>.subject</tt>, <tt>.keywords</tt>
185
+ # Sets values in the PDF information object. The arguments -- to the end
186
+ # of the line -- are used to populate the values.
187
+ #
188
+ # ==== <tt>.done</tt>
189
+ # Stops the processing of the document at this point.
190
+ #
191
+ # === Headings
192
+ # Headings begin with a number followed by the rest of the heading format.
193
+ # This format is "#<heading-text>" or "#<heading-text>xref_name". TechBook
194
+ # supports five levels of headings. Headings may include markup, but
195
+ # should not exceed a single line in size; those headings which have boxes
196
+ # as part of their layout are not currently configured to work with
197
+ # multiple lines of heading output. If an xref_name is specified, then the
198
+ # &lt;r:xref> tag can use this name to find the target for the heading. If
199
+ # xref_name is not specified, then the "name" associated with the heading
200
+ # is the index of the order of insertion. The xref_name is case sensitive.
201
+ #
202
+ # 1<Chapter>xChapter
203
+ # 2<Section>Section23
204
+ # 3<Subsection>
205
+ # 4<Subsection>
206
+ # 5<Subsection>
207
+ #
208
+ # ==== Heading Level 1
209
+ # First level headings are generally chapters. As such, the standard
210
+ # implementation of the heading level 1 method (#__heading1), will be
211
+ # rendered as "chapter#. heading-text" in centered white on a black
212
+ # background, at 26 point (H1_STYLE). First level headings are added to
213
+ # the table of contents.
214
+ #
215
+ # ==== Heading Level 2
216
+ # Second level headings are major sections in chapters. The headings are
217
+ # rendered by default as black on 80% grey, left-justified at 18 point
218
+ # (H2_STYLE). The text is unchanged (#__heading2). Second level headings
219
+ # are added to the table of contents.
220
+ #
221
+ # ==== Heading Level 3, 4, and 5
222
+ # The next three heading levels are used for varying sections within
223
+ # second level chapter sections. They are rendered by default in black on
224
+ # the background (there is no bar) at 18, 14, and 12 points, respectively
225
+ # (H3_STYLE, H4_STYLE, and H5_STYLE). Third level headings are bold-faced
226
+ # (#__heading3); fourth level headings are italicised (#__heading4), and
227
+ # fifth level headings are underlined (#__heading5).
228
+ #
229
+ class PDF::TechBook < PDF::Writer
230
+ attr_accessor :table_of_contents
231
+ attr_accessor :chapter_number
232
+
233
+ # A stand-alone replacement callback that will return an internal link
234
+ # with either the name of the cross-reference or the page on which the
235
+ # cross-reference appears as the label. If the page number is not yet
236
+ # known (when the cross-referenced item has not yet been rendered, e.g.,
237
+ # forward-references), the label will be used in any case.
238
+ #
239
+ # The parameters are:
240
+ # name:: The name of the cross-reference.
241
+ # label:: Either +page+, +title+, or +text+. +page+ will <em>not</em> be
242
+ # used for forward references; only +title+ or +text+ will be
243
+ # used.
244
+ # text:: Required if +label+ has a value of +text+. Ignored if +label+
245
+ # is +title+, optional if +label+ is +page+. This value will be
246
+ # used as the display text for the internal link. +text+
247
+ # takes precedence over +title+ if +label+ is +page+.
248
+ class TagXref
249
+ def self.[](pdf, params)
250
+ name = params["name"]
251
+ item = params["label"]
252
+ text = params["text"]
253
+
254
+ xref = pdf.xref_table[name]
255
+ if xref
256
+ case item
257
+ when 'page'
258
+ label = xref[:page]
259
+ if text.nil? or text.empty?
260
+ label ||= xref[:title]
261
+ else
262
+ label ||= text
263
+ end
264
+ when 'title'
265
+ label = xref[:title]
266
+ when 'text'
267
+ label = text
268
+ end
269
+
270
+ "<c:ilink dest='#{xref[:xref]}'>#{label}</c:ilink>"
271
+ else
272
+ warn PDF::Writer::Lang[:techbook_unknown_xref] % [ name ]
273
+ PDF::Writer::Lang[:techbook_unknown_xref] % [ name ]
274
+ end
275
+ end
276
+ end
277
+ PDF::Writer::TAGS[:replace]["xref"] = PDF::TechBook::TagXref
278
+
279
+ # A stand-alone callback that draws a dotted line over to the right and
280
+ # appends a page number. The info[:params] will be like a standard XML
281
+ # tag with three named parameters:
282
+ #
283
+ # level:: The table of contents level that corresponds to a particular
284
+ # style. In the current TechBook implementation, there are only
285
+ # two levels. Level 1 uses a 16 point font and #level1_style;
286
+ # level 2 uses a 12 point font and #level2_style.
287
+ # page:: The page number that is to be printed.
288
+ # xref:: The target destination that will be used as a link.
289
+ #
290
+ # All parameters are required.
291
+ class TagTocDots
292
+ DEFAULT_L1_STYLE = {
293
+ :width => 1,
294
+ :cap => :round,
295
+ :dash => { :pattern => [ 1, 3 ], :phase => 1 },
296
+ :font_size => 16
297
+ }
298
+
299
+ DEFAULT_L2_STYLE = {
300
+ :width => 1,
301
+ :cap => :round,
302
+ :dash => { :pattern => [ 1, 5 ], :phase => 1 },
303
+ :font_size => 12
304
+ }
305
+
306
+ class << self
307
+ # Controls the level 1 style.
308
+ attr_accessor :level1_style
309
+ # Controls the level 2 style.
310
+ attr_accessor :level2_style
311
+
312
+ def [](pdf, info)
313
+ if @level1_style.nil?
314
+ @level1_style = sh = DEFAULT_L1_STYLE
315
+ ss = PDF::Writer::StrokeStyle.new(sh[:width])
316
+ ss.cap = sh[:cap] if sh[:cap]
317
+ ss.dash = sh[:dash] if sh[:dash]
318
+ @_level1_style = ss
319
+ end
320
+ if @level2_style.nil?
321
+ @level2_style = sh = DEFAULT_L2_STYLE
322
+ ss = PDF::Writer::StrokeStyle.new(sh[:width])
323
+ ss.cap = sh[:cap] if sh[:cap]
324
+ ss.dash = sh[:dash] if sh[:dash]
325
+ @_level2_style = ss
326
+ end
327
+
328
+ level = info[:params]["level"]
329
+ page = info[:params]["page"]
330
+ xref = info[:params]["xref"]
331
+
332
+ xpos = 520
333
+
334
+ pdf.save_state
335
+ case level
336
+ when "1"
337
+ pdf.stroke_style @_level1_style
338
+ size = @level1_style[:font_size]
339
+ when "2"
340
+ pdf.stroke_style @_level2_style
341
+ size = @level2_style[:font_size]
342
+ end
343
+
344
+ page = "<c:ilink dest='#{xref}'>#{page}</c:ilink>" if xref
345
+
346
+ pdf.line(xpos, info[:y], info[:x] + 5, info[:y]).stroke
347
+ pdf.restore_state
348
+ pdf.add_text(xpos + 5, info[:y], page, size)
349
+ end
350
+ end
351
+ end
352
+ PDF::Writer::TAGS[:single]["tocdots"] = PDF::TechBook::TagTocDots
353
+
354
+ attr_reader :xref_table
355
+ def __build_xref_table(data)
356
+ headings = data.grep(HEADING_FORMAT_RE)
357
+
358
+ @xref_table = {}
359
+
360
+ headings.each_with_index do |text, idx|
361
+ level, label, name = HEADING_FORMAT_RE.match(text).captures
362
+
363
+ xref = "xref#{idx}"
364
+
365
+ name ||= idx.to_s
366
+ @xref_table[name] = {
367
+ :title => __send__("__heading#{level}", label),
368
+ :page => nil,
369
+ :level => level.to_i,
370
+ :xref => xref
371
+ }
372
+ end
373
+ end
374
+ private :__build_xref_table
375
+
376
+ def __render_paragraph
377
+ unless @techbook_para.empty?
378
+ techbook_text(@techbook_para.squeeze(" "))
379
+ @techbook_para.replace ""
380
+ end
381
+ end
382
+ private :__render_paragraph
383
+
384
+ LINE_DIRECTIVE_RE = %r{^\.([a-z]\w+)(?:$|\s+(.*)$)}io #:nodoc:
385
+
386
+ def techbook_find_directive(line)
387
+ directive = nil
388
+ arguments = nil
389
+ dmatch = LINE_DIRECTIVE_RE.match(line)
390
+ if dmatch
391
+ directive = dmatch.captures[0].downcase.chomp
392
+ arguments = dmatch.captures[1]
393
+ end
394
+ [directive, arguments]
395
+ end
396
+ private :techbook_find_directive
397
+
398
+ H1_STYLE = {
399
+ :background => Color::RGB::Black,
400
+ :foreground => Color::RGB::White,
401
+ :justification => :center,
402
+ :font_size => 26,
403
+ :bar => true
404
+ }
405
+ H2_STYLE = {
406
+ :background => Color::RGB::Grey80,
407
+ :foreground => Color::RGB::Black,
408
+ :justification => :left,
409
+ :font_size => 18,
410
+ :bar => true
411
+ }
412
+ H3_STYLE = {
413
+ :background => Color::RGB::White,
414
+ :foreground => Color::RGB::Black,
415
+ :justification => :left,
416
+ :font_size => 18,
417
+ :bar => false
418
+ }
419
+ H4_STYLE = {
420
+ :background => Color::RGB::White,
421
+ :foreground => Color::RGB::Black,
422
+ :justification => :left,
423
+ :font_size => 14,
424
+ :bar => false
425
+ }
426
+ H5_STYLE = {
427
+ :background => Color::RGB::White,
428
+ :foreground => Color::RGB::Black,
429
+ :justification => :left,
430
+ :font_size => 12,
431
+ :bar => false
432
+ }
433
+ def __heading1(heading)
434
+ @chapter_number ||= 0
435
+ @chapter_number = @chapter_number.succ
436
+ "#{chapter_number}. #{heading}"
437
+ end
438
+ def __heading2(heading)
439
+ heading
440
+ end
441
+ def __heading3(heading)
442
+ "<b>#{heading}</b>"
443
+ end
444
+ def __heading4(heading)
445
+ "<i>#{heading}</i>"
446
+ end
447
+ def __heading5(heading)
448
+ "<c:uline>#{heading}</c:uline>"
449
+ end
450
+
451
+ HEADING_FORMAT_RE = %r{^([\d])<(.*)>([a-z\w]+)?$}o #:nodoc:
452
+
453
+ def techbook_heading(line)
454
+ head = HEADING_FORMAT_RE.match(line)
455
+ if head
456
+ __render_paragraph
457
+
458
+ @heading_num ||= -1
459
+ @heading_num += 1
460
+
461
+ level, heading, name = head.captures
462
+ level = level.to_i
463
+
464
+ name ||= @heading_num.to_s
465
+ heading = @xref_table[name]
466
+
467
+ style = self.class.const_get("H#{level}_STYLE")
468
+
469
+ start_transaction(:heading_level)
470
+ ok = false
471
+
472
+ loop do # while not ok
473
+ break if ok
474
+ this_page = pageset.size
475
+
476
+ save_state
477
+
478
+ if style[:bar]
479
+ fill_color style[:background]
480
+ fh = font_height(style[:font_size]) * 1.01
481
+ fd = font_descender(style[:font_size]) * 1.01
482
+ x = absolute_left_margin
483
+ w = absolute_right_margin - absolute_left_margin
484
+ rectangle(x, y - fh + fd, w, fh).fill
485
+ end
486
+
487
+ fill_color style[:foreground]
488
+ text(heading[:title], :font_size => style[:font_size],
489
+ :justification => style[:justification])
490
+
491
+ restore_state
492
+
493
+ if (pageset.size == this_page)
494
+ commit_transaction(:heading_level)
495
+ ok = true
496
+ else
497
+ # We have moved onto a new page. This is bad, as the background
498
+ # colour will be on the old one.
499
+ rewind_transaction(:heading_level)
500
+ start_new_page
501
+ end
502
+ end
503
+
504
+ heading[:page] = which_page_number(current_page_number)
505
+
506
+ case level
507
+ when 1, 2
508
+ @table_of_contents << heading
509
+ end
510
+
511
+ add_destination(heading[:xref], 'FitH', @y + font_height(style[:font_size]))
512
+ end
513
+ head
514
+ end
515
+ private :techbook_heading
516
+
517
+ def techbook_parse(document, progress = nil)
518
+ @table_of_contents = []
519
+
520
+ @toc_title = "Table of Contents"
521
+ @gen_toc = false
522
+ @techbook_code = ""
523
+ @techbook_para = ""
524
+ @techbook_fontsize = 12
525
+ @techbook_textopt = { :justification => :full }
526
+ @techbook_lastmode = @techbook_mode = :normal
527
+
528
+ @techbook_textfont = "Times-Roman"
529
+ @techbook_codefont = "Courier"
530
+
531
+ @blist_info = []
532
+
533
+ @techbook_line__ = 0
534
+
535
+ __build_xref_table(document)
536
+
537
+ document.each do |line|
538
+ begin
539
+ progress.inc if progress
540
+ @techbook_line__ += 1
541
+
542
+ next if line =~ %r{^#}o
543
+
544
+ directive, args = techbook_find_directive(line)
545
+ if directive
546
+ # Just try to call the method/directive. It will be far more
547
+ # common to *find* the method than not to.
548
+ res = __send__("techbook_directive_#{directive}", args) rescue nil
549
+ break if :break == res
550
+ next
551
+ end
552
+
553
+ case @techbook_mode
554
+ when :eval
555
+ @techbook_code << line << "\n"
556
+ next
557
+ when :code
558
+ techbook_text(line)
559
+ next
560
+ when :blist
561
+ line = "<C:#{@blist_info[-1][:style]}/>#{line}"
562
+ techbook_text(line)
563
+ next
564
+ end
565
+
566
+ next if techbook_heading(line)
567
+
568
+ if :preserved == @techbook_mode
569
+ techbook_text(line)
570
+ next
571
+ end
572
+
573
+ line.chomp!
574
+
575
+ if line.empty?
576
+ __render_paragraph
577
+ techbook_text("\n")
578
+ else
579
+ @techbook_para << " " unless @techbook_para.empty?
580
+ @techbook_para << line
581
+ end
582
+ rescue Exception => ex
583
+ $stderr.puts PDF::Writer::Lang[:techbook_exception] % [ ex, @techbook_line ]
584
+ raise
585
+ end
586
+ end
587
+ end
588
+
589
+ def techbook_toc(progress = nil)
590
+ insert_mode :on
591
+ insert_position :after
592
+ insert_page 1
593
+ start_new_page
594
+
595
+ style = H1_STYLE
596
+ save_state
597
+
598
+ if style[:bar]
599
+ fill_color style[:background]
600
+ fh = font_height(style[:font_size]) * 1.01
601
+ fd = font_descender(style[:font_size]) * 1.01
602
+ x = absolute_left_margin
603
+ w = absolute_right_margin - absolute_left_margin
604
+ rectangle(x, y - fh + fd, w, fh).fill
605
+ end
606
+
607
+ fill_color style[:foreground]
608
+ text(@toc_title, :font_size => style[:font_size],
609
+ :justification => style[:justification])
610
+
611
+ restore_state
612
+
613
+ self.y += font_descender(style[:font_size])#* 0.5
614
+
615
+ right = absolute_right_margin
616
+
617
+ # TODO -- implement tocdots as a replace tag and a single drawing tag.
618
+ @table_of_contents.each do |entry|
619
+ progress.inc if progress
620
+
621
+ info = "<c:ilink dest='#{entry[:xref]}'>#{entry[:title]}</c:ilink>"
622
+ info << "<C:tocdots level='#{entry[:level]}' page='#{entry[:page]}' xref='#{entry[:xref]}'/>"
623
+
624
+ case entry[:level]
625
+ when 1
626
+ text info, :font_size => 16, :absolute_right => right
627
+ when 2
628
+ text info, :font_size => 12, :left => 50, :absolute_right => right
629
+ end
630
+ end
631
+ end
632
+
633
+ attr_accessor :techbook_codefont
634
+ attr_accessor :techbook_textfont
635
+ attr_accessor :techbook_encoding
636
+ attr_accessor :techbook_fontsize
637
+
638
+ # Start a new page: .newpage
639
+ def techbook_directive_newpage(args)
640
+ __render_paragraph
641
+
642
+ if args =~ /^force/
643
+ start_new_page true
644
+ else
645
+ start_new_page
646
+ end
647
+ end
648
+
649
+ # Preserved newlines: .pre
650
+ def techbook_directive_pre(args)
651
+ __render_paragraph
652
+ @techbook_mode = :preserved
653
+ end
654
+
655
+ # End preserved newlines: .endpre
656
+ def techbook_directive_endpre(args)
657
+ @techbook_mode = :normal
658
+ end
659
+
660
+ # Code: .code
661
+ def techbook_directive_code(args)
662
+ __render_paragraph
663
+ select_font @techbook_codefont, @techbook_encoding
664
+ @techbook_lastmode, @techbook_mode = @techbook_mode, :code
665
+ @techbook_textopt = { :justification => :left, :left => 20, :right => 20 }
666
+ @techbook_fontsize = 10
667
+ end
668
+
669
+ # End Code: .endcode
670
+ def techbook_directive_endcode(args)
671
+ select_font @techbook_textfont, @techbook_encoding
672
+ @techbook_lastmode, @techbook_mode = @techbook_mode, @techbook_lastmode
673
+ @techbook_textopt = { :justification => :full }
674
+ @techbook_fontsize = 12
675
+ end
676
+
677
+ # Eval: .eval
678
+ def techbook_directive_eval(args)
679
+ __render_paragraph
680
+ @techbook_lastmode, @techbook_mode = @techbook_mode, :eval
681
+ end
682
+
683
+ # End Eval: .endeval
684
+ def techbook_directive_endeval(args)
685
+ save_state
686
+
687
+ thread = Thread.new do
688
+ begin
689
+ @techbook_code.untaint
690
+ pdf = self
691
+ eval @techbook_code
692
+ rescue Exception => ex
693
+ err = PDF::Writer::Lang[:techbook_eval_exception]
694
+ $stderr.puts err % [ @techbook_line__, ex, ex.backtrace.join("\n") ]
695
+ raise ex
696
+ end
697
+ end
698
+ thread.abort_on_exception = true
699
+ thread.join
700
+
701
+ restore_state
702
+ select_font @techbook_textfont, @techbook_encoding
703
+
704
+ @techbook_code = ""
705
+ @techbook_mode, @techbook_lastmode = @techbook_lastmode, @techbook_mode
706
+ end
707
+
708
+ # Done. Stop parsing: .done
709
+ def techbook_directive_done(args)
710
+ unless @techbook_code.empty?
711
+ $stderr.puts PDF::Writer::Lang[:techbook_code_not_empty]
712
+ $stderr.puts @techbook_code
713
+ end
714
+ __render_paragraph
715
+ :break
716
+ end
717
+
718
+ # Columns. .columns <number-of-columns>|off
719
+ def techbook_directive_columns(args)
720
+ av = /^(\d+|off)(?: (\d+))?(?: .*)?$/o.match(args)
721
+ unless av
722
+ $stderr.puts PDF::Writer::Lang[:techbook_bad_columns_directive] % args
723
+ raise ArgumentError
724
+ end
725
+ cols = av.captures[0]
726
+
727
+ # Flush the paragraph cache.
728
+ __render_paragraph
729
+
730
+ if cols == "off" or cols.to_i < 2
731
+ stop_columns
732
+ else
733
+ if av.captures[1]
734
+ start_columns(cols.to_i, av.captures[1].to_i)
735
+ else
736
+ start_columns(cols.to_i)
737
+ end
738
+ end
739
+ end
740
+
741
+ def techbook_directive_toc(args)
742
+ @toc_title = args unless args.empty?
743
+ @gen_toc = true
744
+ end
745
+
746
+ def techbook_directive_author(args)
747
+ info.author = args
748
+ end
749
+
750
+ def techbook_directive_title(args)
751
+ info.title = args
752
+ end
753
+
754
+ def techbook_directive_subject(args)
755
+ info.subject = args
756
+ end
757
+
758
+ def techbook_directive_keywords(args)
759
+ info.keywords = args
760
+ end
761
+
762
+ LIST_ITEM_STYLES = %w(bullet disc)
763
+
764
+ def techbook_directive_blist(args)
765
+ __render_paragraph
766
+ sm = /^(\w+).*$/o.match(args)
767
+ style = sm.captures[0] if sm
768
+ style = "bullet" unless LIST_ITEM_STYLES.include?(style)
769
+
770
+ @blist_factor = @left_margin * 0.10 if @blist_info.empty?
771
+
772
+ info = {
773
+ :left_margin => @left_margin,
774
+ :style => style
775
+ }
776
+ @blist_info << info
777
+ @left_margin += @blist_factor
778
+
779
+ @techbook_lastmode, @techbook_mode = @techbook_mode, :blist if :blist != @techbook_mode
780
+ end
781
+
782
+ def techbook_directive_endblist(args)
783
+ self.left_margin = @blist_info.pop[:left_margin]
784
+ @techbook_lastmode, @techbook_mode = @techbook_mode, @techbook_lastmode if @blist_info.empty?
785
+ end
786
+
787
+ def generate_table_of_contents?
788
+ @gen_toc
789
+ end
790
+
791
+ attr_accessor :techbook_source_dir
792
+
793
+ def self.run(args)
794
+ config = OpenStruct.new
795
+ config.regen = false
796
+ config.cache = true
797
+ config.compressed = false
798
+
799
+ opts = OptionParser.new do |opt|
800
+ opt.banner = PDF::Writer::Lang[:techbook_usage_banner] % [ File.basename($0) ]
801
+ PDF::Writer::Lang[:techbook_usage_banner_1].each do |ll|
802
+ opt.separator " #{ll}"
803
+ end
804
+ opt.on('-f', '--force-regen', *PDF::Writer::Lang[:techbook_help_force_regen]) { config.regen = true }
805
+ opt.on('-n', '--no-cache', *PDF::Writer::Lang[:techbook_help_no_cache]) { config.cache = false }
806
+ opt.on('-z', '--compress', *PDF::Writer::Lang[:techbook_help_compress]) { config.compressed = true }
807
+ opt.on_tail ""
808
+ opt.on_tail("--help", *PDF::Writer::Lang[:techbook_help_help]) { $stderr << opt; exit(0) }
809
+ end
810
+ opts.parse!(args)
811
+
812
+ config.document = args[0]
813
+
814
+ unless config.document
815
+ config.document = "manual.pwd"
816
+ unless File.exist?(config.document)
817
+ dirn = File.dirname(__FILE__)
818
+ config.document = File.join(dirn, File.basename(config.document))
819
+ unless File.exist?(config.document)
820
+ dirn = File.join(dirn, "..")
821
+ config.document = File.join(dirn, File.basename(config.document))
822
+ unless File.exist?(config.document)
823
+ dirn = File.join(dirn, "..")
824
+ config.document = File.join(dirn,
825
+ File.basename(config.document))
826
+ unless File.exist?(config.document)
827
+ $stderr.puts PDF::Writer::Lang[:techbook_cannot_find_document]
828
+ exit(1)
829
+ end
830
+ end
831
+ end
832
+ end
833
+
834
+ $stderr.puts PDF::Writer::Lang[:techbook_using_default_doc] % config.document
835
+ end
836
+
837
+ dirn = File.dirname(config.document)
838
+ extn = File.extname(config.document)
839
+ base = File.basename(config.document, extn)
840
+
841
+ files = {
842
+ :document => config.document,
843
+ :cache => "#{base}._mc",
844
+ :pdf => "#{base}.pdf"
845
+ }
846
+
847
+ unless config.regen
848
+ if File.exist?(files[:cache])
849
+ _tm_doc = File.mtime(config.document)
850
+ _tm_prg = File.mtime(__FILE__)
851
+ _tm_cch = File.mtime(files[:cache])
852
+
853
+ # If the cached file is newer than either the document or the
854
+ # class program, then regenerate.
855
+ if (_tm_doc < _tm_cch) and (_tm_prg < _tm_cch)
856
+ $stderr.puts PDF::Writer::Lang[:techbook_using_cached_doc] % File.basename(files[:cache])
857
+ if RUBY_VERSION >= '1.9'
858
+ pdf = File.open(files[:cache], "rb:binary") { |cf| Marshal.load(cf.read) }
859
+ else
860
+ pdf = File.open(files[:cache], "rb") { |cf| Marshal.load(cf.read) }
861
+ end
862
+ pdf.save_as(files[:pdf])
863
+ File.open(files[:pdf], "wb") { |pf| pf.write pdf.render }
864
+ exit(0)
865
+ else
866
+ $stderr.puts PDF::Writer::Lang[:techbook_regenerating]
867
+ end
868
+ end
869
+ else
870
+ $stderr.puts PDF::Writer::Lang[:techbook_ignoring_cache] if File.exist?(files[:cache])
871
+ end
872
+
873
+ # Create the manual object.
874
+ pdf = PDF::TechBook.new
875
+ pdf.compressed = config.compressed
876
+ pdf.techbook_source_dir = File.expand_path(dirn)
877
+
878
+ document = open(files[:document]) do |io|
879
+ io.read.encode!('UTF-8', 'binary', invalid: :replace, undef: :replace, replace: '').split($/)
880
+ end
881
+ progress = ProgressBar.new(base.capitalize, document.size)
882
+ pdf.techbook_parse(document, progress)
883
+ progress.finish
884
+
885
+ if pdf.generate_table_of_contents?
886
+ progress = ProgressBar.new("TOC", pdf.table_of_contents.size)
887
+ pdf.techbook_toc(progress)
888
+ progress.finish
889
+ end
890
+
891
+ if config.cache
892
+ File.open(files[:cache], "wb") { |f| f.write Marshal.dump(pdf) }
893
+ end
894
+
895
+ pdf.save_as(files[:pdf])
896
+ end
897
+
898
+ def techbook_text(line)
899
+ opt = @techbook_textopt.dup
900
+ opt[:font_size] = @techbook_fontsize
901
+ text(line, opt)
902
+ end
903
+
904
+ instance_methods.grep(/^techbook_directive_/).each do |mname|
905
+ private mname.intern
906
+ end
907
+ end