ypdf-writer 1.3.2

Sign up to get free protection for your applications and to get access to all the features.
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