pdf-writer 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (74) hide show
  1. data/ChangeLog +44 -0
  2. data/LICENCE +118 -0
  3. data/README +32 -0
  4. data/bin/loader +54 -0
  5. data/bin/manual +22 -0
  6. data/bin/manual.bat +2 -0
  7. data/demo/chunkybacon.rb +28 -0
  8. data/demo/code.rb +63 -0
  9. data/demo/colornames.rb +843 -0
  10. data/demo/demo.rb +65 -0
  11. data/demo/gettysburg.rb +58 -0
  12. data/demo/hello.rb +18 -0
  13. data/demo/individual-i.rb +81 -0
  14. data/demo/pac.rb +62 -0
  15. data/demo/pagenumber.rb +67 -0
  16. data/demo/qr-language.rb +573 -0
  17. data/demo/qr-library.rb +371 -0
  18. data/images/chunkybacon.jpg +0 -0
  19. data/images/chunkybacon.png +0 -0
  20. data/images/pdfwriter-icon.jpg +0 -0
  21. data/images/pdfwriter-small.jpg +0 -0
  22. data/lib/pdf/charts.rb +13 -0
  23. data/lib/pdf/charts/stddev.rb +431 -0
  24. data/lib/pdf/grid.rb +135 -0
  25. data/lib/pdf/math.rb +108 -0
  26. data/lib/pdf/quickref.rb +330 -0
  27. data/lib/pdf/simpletable.rb +946 -0
  28. data/lib/pdf/techbook.rb +890 -0
  29. data/lib/pdf/writer.rb +2661 -0
  30. data/lib/pdf/writer/arc4.rb +63 -0
  31. data/lib/pdf/writer/fontmetrics.rb +201 -0
  32. data/lib/pdf/writer/fonts/Courier-Bold.afm +342 -0
  33. data/lib/pdf/writer/fonts/Courier-BoldOblique.afm +342 -0
  34. data/lib/pdf/writer/fonts/Courier-Oblique.afm +342 -0
  35. data/lib/pdf/writer/fonts/Courier.afm +342 -0
  36. data/lib/pdf/writer/fonts/Helvetica-Bold.afm +2827 -0
  37. data/lib/pdf/writer/fonts/Helvetica-BoldOblique.afm +2827 -0
  38. data/lib/pdf/writer/fonts/Helvetica-Oblique.afm +3051 -0
  39. data/lib/pdf/writer/fonts/Helvetica.afm +3051 -0
  40. data/lib/pdf/writer/fonts/MustRead.html +1 -0
  41. data/lib/pdf/writer/fonts/Symbol.afm +213 -0
  42. data/lib/pdf/writer/fonts/Times-Bold.afm +2588 -0
  43. data/lib/pdf/writer/fonts/Times-BoldItalic.afm +2384 -0
  44. data/lib/pdf/writer/fonts/Times-Italic.afm +2667 -0
  45. data/lib/pdf/writer/fonts/Times-Roman.afm +2419 -0
  46. data/lib/pdf/writer/fonts/ZapfDingbats.afm +225 -0
  47. data/lib/pdf/writer/graphics.rb +727 -0
  48. data/lib/pdf/writer/graphics/imageinfo.rb +365 -0
  49. data/lib/pdf/writer/lang.rb +43 -0
  50. data/lib/pdf/writer/lang/en.rb +77 -0
  51. data/lib/pdf/writer/object.rb +23 -0
  52. data/lib/pdf/writer/object/action.rb +40 -0
  53. data/lib/pdf/writer/object/annotation.rb +42 -0
  54. data/lib/pdf/writer/object/catalog.rb +39 -0
  55. data/lib/pdf/writer/object/contents.rb +68 -0
  56. data/lib/pdf/writer/object/destination.rb +40 -0
  57. data/lib/pdf/writer/object/encryption.rb +53 -0
  58. data/lib/pdf/writer/object/font.rb +76 -0
  59. data/lib/pdf/writer/object/fontdescriptor.rb +34 -0
  60. data/lib/pdf/writer/object/fontencoding.rb +39 -0
  61. data/lib/pdf/writer/object/image.rb +168 -0
  62. data/lib/pdf/writer/object/info.rb +55 -0
  63. data/lib/pdf/writer/object/outline.rb +30 -0
  64. data/lib/pdf/writer/object/outlines.rb +30 -0
  65. data/lib/pdf/writer/object/page.rb +195 -0
  66. data/lib/pdf/writer/object/pages.rb +115 -0
  67. data/lib/pdf/writer/object/procset.rb +46 -0
  68. data/lib/pdf/writer/object/viewerpreferences.rb +74 -0
  69. data/lib/pdf/writer/ohash.rb +58 -0
  70. data/lib/pdf/writer/oreader.rb +25 -0
  71. data/lib/pdf/writer/state.rb +48 -0
  72. data/lib/pdf/writer/strokestyle.rb +138 -0
  73. data/manual.pwd +5151 -0
  74. metadata +147 -0
@@ -0,0 +1,890 @@
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: techbook.rb,v 1.11 2005/06/08 12:16:11 austin Exp $
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>), italic (<i>) and underlined
69
+ # (<u>) text. TechBook supports standard PDF::Writer callback tags
70
+ # (<c:alink>, <c:ilink>, <C:bullet/>, and <C:disc/>) and adds two new ones
71
+ # (<r:xref/>, <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 +title+. This value will be
246
+ # used as the display for the internal link.
247
+ class TagXref
248
+ def self.[](pdf, params)
249
+ name = params["name"]
250
+ item = params["label"]
251
+ text = params["text"]
252
+
253
+ xref = pdf.xref_table[name]
254
+ if xref
255
+ case item
256
+ when 'page'
257
+ label = xref[:page]
258
+ if text.nil? or text.empty?
259
+ label ||= text
260
+ else
261
+ label ||= xref[:title]
262
+ end
263
+ when 'title'
264
+ label = xref[:title]
265
+ when 'text'
266
+ label = text
267
+ end
268
+
269
+ "<c:ilink dest='#{xref[:xref]}'>#{label}</c:ilink>"
270
+ else
271
+ warn PDF::Writer::Lang[:techbook_unknown_xref] % [ name ]
272
+ PDF::Writer::Lang[:techbook_unknown_xref] % [ name ]
273
+ end
274
+ end
275
+ end
276
+ PDF::Writer::TAGS[:replace]["xref"] = PDF::TechBook::TagXref
277
+
278
+ # A stand-alone callback that draws a dotted line over to the right and
279
+ # appends a page number. The info[:params] will be like a standard XML
280
+ # tag with three named parameters:
281
+ #
282
+ # level:: The table of contents level that corresponds to a particular
283
+ # style. In the current TechBook implementation, there are only
284
+ # two levels. Level 1 uses a 16 point font and #level1_style;
285
+ # level 2 uses a 12 point font and #level2_style.
286
+ # page:: The page number that is to be printed.
287
+ # xref:: The target destination that will be used as a link.
288
+ #
289
+ # All parameters are required.
290
+ class TagTocDots
291
+ DEFAULT_L1_STYLE = {
292
+ :width => 1,
293
+ :cap => :round,
294
+ :dash => { :pattern => [ 1, 3 ], :phase => 1 },
295
+ :font_size => 16
296
+ }
297
+
298
+ DEFAULT_L2_STYLE = {
299
+ :width => 1,
300
+ :cap => :round,
301
+ :dash => { :pattern => [ 1, 5 ], :phase => 1 },
302
+ :font_size => 12
303
+ }
304
+
305
+ class << self
306
+ # Controls the level 1 style.
307
+ attr_accessor :level1_style
308
+ # Controls the level 2 style.
309
+ attr_accessor :level2_style
310
+
311
+ def [](pdf, info)
312
+ if @level1_style.nil?
313
+ @level1_style = sh = DEFAULT_L1_STYLE
314
+ ss = PDF::Writer::StrokeStyle.new(sh[:width])
315
+ ss.cap = sh[:cap] if sh[:cap]
316
+ ss.dash = sh[:dash] if sh[:dash]
317
+ @_level1_style = ss
318
+ end
319
+ if @level2_style.nil?
320
+ @level2_style = sh = DEFAULT_L2_STYLE
321
+ ss = PDF::Writer::StrokeStyle.new(sh[:width])
322
+ ss.cap = sh[:cap] if sh[:cap]
323
+ ss.dash = sh[:dash] if sh[:dash]
324
+ @_level2_style = ss
325
+ end
326
+
327
+ level = info[:params]["level"]
328
+ page = info[:params]["page"]
329
+ xref = info[:params]["xref"]
330
+
331
+ xpos = 520
332
+
333
+ pdf.save_state
334
+ case level
335
+ when "1"
336
+ pdf.stroke_style @_level1_style
337
+ size = @level1_style[:font_size]
338
+ when "2"
339
+ pdf.stroke_style @_level2_style
340
+ size = @level2_style[:font_size]
341
+ end
342
+
343
+ page = "<c:ilink dest='#{xref}'>#{page}</c:ilink>" if xref
344
+
345
+ pdf.line(xpos, info[:y], info[:x] + 5, info[:y]).stroke
346
+ pdf.restore_state
347
+ pdf.add_text(xpos + 5, info[:y], size, page)
348
+ end
349
+ end
350
+ end
351
+ PDF::Writer::TAGS[:single]["tocdots"] = PDF::TechBook::TagTocDots
352
+
353
+ attr_reader :xref_table
354
+ def __build_xref_table(data)
355
+ headings = data.grep(HEADING_FORMAT_RE)
356
+
357
+ @xref_table = {}
358
+
359
+ headings.each_with_index do |text, idx|
360
+ level, label, name = HEADING_FORMAT_RE.match(text).captures
361
+
362
+ xref = "xref#{idx}"
363
+
364
+ name ||= idx.to_s
365
+ @xref_table[name] = {
366
+ :title => __send__("__heading#{level}", label),
367
+ :page => nil,
368
+ :level => level.to_i,
369
+ :xref => xref
370
+ }
371
+ end
372
+ end
373
+ private :__build_xref_table
374
+
375
+ def __render_paragraph
376
+ unless @techbook_para.empty?
377
+ techbook_text(@techbook_para.squeeze(" "))
378
+ @techbook_para.replace ""
379
+ end
380
+ end
381
+ private :__render_paragraph
382
+
383
+ LINE_DIRECTIVE_RE = %r{^\.([a-z]\w+)(?:$|\s+(.*)$)}io #:nodoc:
384
+
385
+ def techbook_find_directive(line)
386
+ directive = nil
387
+ arguments = nil
388
+ dmatch = LINE_DIRECTIVE_RE.match(line)
389
+ if dmatch
390
+ directive = dmatch.captures[0].downcase.chomp
391
+ arguments = dmatch.captures[1]
392
+ end
393
+ [directive, arguments]
394
+ end
395
+ private :techbook_find_directive
396
+
397
+ H1_STYLE = {
398
+ :background => Color::Black,
399
+ :foreground => Color::White,
400
+ :justification => :center,
401
+ :font_size => 26,
402
+ :bar => true
403
+ }
404
+ H2_STYLE = {
405
+ :background => Color::Grey80,
406
+ :foreground => Color::Black,
407
+ :justification => :left,
408
+ :font_size => 18,
409
+ :bar => true
410
+ }
411
+ H3_STYLE = {
412
+ :background => Color::White,
413
+ :foreground => Color::Black,
414
+ :justification => :left,
415
+ :font_size => 18,
416
+ :bar => false
417
+ }
418
+ H4_STYLE = {
419
+ :background => Color::White,
420
+ :foreground => Color::Black,
421
+ :justification => :left,
422
+ :font_size => 14,
423
+ :bar => false
424
+ }
425
+ H5_STYLE = {
426
+ :background => Color::White,
427
+ :foreground => Color::Black,
428
+ :justification => :left,
429
+ :font_size => 12,
430
+ :bar => false
431
+ }
432
+ def __heading1(heading)
433
+ @chapter_number ||= 0
434
+ @chapter_number = @chapter_number.succ
435
+ "#{chapter_number}. #{heading}"
436
+ end
437
+ def __heading2(heading)
438
+ heading
439
+ end
440
+ def __heading3(heading)
441
+ "<b>#{heading}</b>"
442
+ end
443
+ def __heading4(heading)
444
+ "<i>#{heading}</i>"
445
+ end
446
+ def __heading5(heading)
447
+ "<c:uline>#{heading}</c:uline>"
448
+ end
449
+
450
+ HEADING_FORMAT_RE = %r{^([\d])<(.*)>([a-z\w]+)?$}o #:nodoc:
451
+
452
+ def techbook_heading(line)
453
+ head = HEADING_FORMAT_RE.match(line)
454
+ if head
455
+ __render_paragraph
456
+
457
+ @heading_num ||= -1
458
+ @heading_num += 1
459
+
460
+ level, heading, name = head.captures
461
+ level = level.to_i
462
+
463
+ name ||= @heading_num.to_s
464
+ heading = @xref_table[name]
465
+
466
+ style = self.class.const_get("H#{level}_STYLE")
467
+
468
+ start_transaction(:heading_level)
469
+ ok = false
470
+
471
+ loop do # while not ok
472
+ break if ok
473
+ this_page = pageset.size
474
+
475
+ save_state
476
+
477
+ if style[:bar]
478
+ fill_color style[:background]
479
+ fh = font_height(style[:font_size]) * 1.01
480
+ fd = font_descender(style[:font_size]) * 1.01
481
+ x = absolute_left_margin
482
+ w = absolute_right_margin - absolute_left_margin
483
+ rectangle(x, y - fh + fd, w, fh).fill
484
+ end
485
+
486
+ fill_color style[:foreground]
487
+ text(heading[:title], :font_size => style[:font_size],
488
+ :justification => style[:justification])
489
+
490
+ restore_state
491
+
492
+ if (pageset.size == this_page)
493
+ commit_transaction(:heading_level)
494
+ ok = true
495
+ else
496
+ # We have moved onto a new page. This is bad, as the background
497
+ # colour will be on the old one.
498
+ rewind_transaction(:heading_level)
499
+ start_new_page
500
+ end
501
+ end
502
+
503
+ heading[:page] = which_page_number(current_page_number)
504
+
505
+ case level
506
+ when 1, 2
507
+ @table_of_contents << heading
508
+ end
509
+
510
+ add_destination(heading[:xref], 'FitH', @y + font_height(style[:font_size]))
511
+ end
512
+ head
513
+ end
514
+ private :techbook_heading
515
+
516
+ def techbook_parse(document, progress = nil)
517
+ @table_of_contents = []
518
+
519
+ @toc_title = "Table of Contents"
520
+ @gen_toc = false
521
+ @techbook_code = ""
522
+ @techbook_para = ""
523
+ @techbook_fontsize = 12
524
+ @techbook_textopt = { :justification => :full }
525
+ @techbook_lastmode = @techbook_mode = :normal
526
+
527
+ @techbook_textfont = "Times-Roman"
528
+ @techbook_codefont = "Courier"
529
+
530
+ @blist_info = []
531
+
532
+ @techbook_line__ = 0
533
+
534
+ __build_xref_table(document)
535
+
536
+ document.each do |line|
537
+ begin
538
+ progress.inc if progress
539
+ @techbook_line__ += 1
540
+
541
+ next if line =~ %r{^#}o
542
+
543
+ directive, args = techbook_find_directive(line)
544
+ if directive
545
+ # Just try to call the method/directive. It will be far more
546
+ # common to *find* the method than not to.
547
+ res = __send__("techbook_directive_#{directive}", args) rescue nil
548
+ break if :break == res
549
+ next
550
+ end
551
+
552
+ case @techbook_mode
553
+ when :eval
554
+ @techbook_code << line << "\n"
555
+ next
556
+ when :code
557
+ techbook_text(line)
558
+ next
559
+ when :blist
560
+ line = "<C:#{@blist_info[-1][:style]}/>#{line}"
561
+ techbook_text(line)
562
+ next
563
+ end
564
+
565
+ next if techbook_heading(line)
566
+
567
+ if :preserved == @techbook_mode
568
+ techbook_text(line)
569
+ next
570
+ end
571
+
572
+ line.chomp!
573
+
574
+ if line.empty?
575
+ __render_paragraph
576
+ techbook_text("\n")
577
+ else
578
+ @techbook_para << " " unless @techbook_para.empty?
579
+ @techbook_para << line
580
+ end
581
+ rescue Exception => ex
582
+ $stderr.puts PDF::Writer::Lang[:techbook_exception] % [ ex, @techbook_line ]
583
+ raise
584
+ end
585
+ end
586
+ end
587
+
588
+ def techbook_toc(progress = nil)
589
+ insert_mode :on
590
+ insert_position :after
591
+ insert_page 1
592
+ start_new_page
593
+
594
+ style = H1_STYLE
595
+ save_state
596
+
597
+ if style[:bar]
598
+ fill_color style[:background]
599
+ fh = font_height(style[:font_size]) * 1.01
600
+ fd = font_descender(style[:font_size]) * 1.01
601
+ x = absolute_left_margin
602
+ w = absolute_right_margin - absolute_left_margin
603
+ rectangle(x, y - fh + fd, w, fh).fill
604
+ end
605
+
606
+ fill_color style[:foreground]
607
+ text(@toc_title, :font_size => style[:font_size],
608
+ :justification => style[:justification])
609
+
610
+ restore_state
611
+
612
+ self.y += font_descender(style[:font_size])#* 0.5
613
+
614
+ right = absolute_right_margin
615
+
616
+ # TODO -- implement tocdots as a replace tag and a single drawing tag.
617
+ @table_of_contents.each do |entry|
618
+ progress.inc if progress
619
+
620
+ info = "<c:ilink dest='#{entry[:xref]}'>#{entry[:title]}</c:ilink>"
621
+ info << "<C:tocdots level='#{entry[:level]}' page='#{entry[:page]}' xref='#{entry[:xref]}'/>"
622
+
623
+ case entry[:level]
624
+ when 1
625
+ text info, :font_size => 16, :absolute_right => right
626
+ when 2
627
+ text info, :font_size => 12, :left => 50, :absolute_right => right
628
+ end
629
+ end
630
+ end
631
+
632
+ attr_accessor :techbook_codefont
633
+ attr_accessor :techbook_textfont
634
+ attr_accessor :techbook_encoding
635
+ attr_accessor :techbook_fontsize
636
+
637
+ # Start a new page: .newpage
638
+ def techbook_directive_newpage(args)
639
+ __render_paragraph
640
+
641
+ if args =~ /^force/
642
+ start_new_page true
643
+ else
644
+ start_new_page
645
+ end
646
+ end
647
+
648
+ # Preserved newlines: .pre
649
+ def techbook_directive_pre(args)
650
+ __render_paragraph
651
+ @techbook_mode = :preserved
652
+ end
653
+
654
+ # End preserved newlines: .endpre
655
+ def techbook_directive_endpre(args)
656
+ @techbook_mode = :normal
657
+ end
658
+
659
+ # Code: .code
660
+ def techbook_directive_code(args)
661
+ __render_paragraph
662
+ select_font @techbook_codefont, @techbook_encoding
663
+ @techbook_lastmode, @techbook_mode = @techbook_mode, :code
664
+ @techbook_textopt = { :justification => :left, :left => 20, :right => 20 }
665
+ @techbook_fontsize = 10
666
+ end
667
+
668
+ # End Code: .endcode
669
+ def techbook_directive_endcode(args)
670
+ select_font @techbook_textfont, @techbook_encoding
671
+ @techbook_lastmode, @techbook_mode = @techbook_mode, @techbook_lastmode
672
+ @techbook_textopt = { :justification => :full }
673
+ @techbook_fontsize = 12
674
+ end
675
+
676
+ # Eval: .eval
677
+ def techbook_directive_eval(args)
678
+ __render_paragraph
679
+ @techbook_lastmode, @techbook_mode = @techbook_mode, :eval
680
+ end
681
+
682
+ # End Eval: .endeval
683
+ def techbook_directive_endeval(args)
684
+ save_state
685
+
686
+ thread = Thread.new do
687
+ begin
688
+ @techbook_code.untaint
689
+ pdf = self
690
+ eval @techbook_code
691
+ rescue Exception => ex
692
+ err = PDF::Writer::Lang[:techbook_eval_exception]
693
+ $stderr.puts err % [ @techbook_line__, ex, ex.backtrace.join("\n") ]
694
+ raise ex
695
+ end
696
+ end
697
+ thread.abort_on_exception = true
698
+ thread.join
699
+
700
+ restore_state
701
+ select_font @techbook_textfont, @techbook_encoding
702
+
703
+ @techbook_code = ""
704
+ @techbook_mode, @techbook_lastmode = @techbook_lastmode, @techbook_mode
705
+ end
706
+
707
+ # Done. Stop parsing: .done
708
+ def techbook_directive_done(args)
709
+ unless @techbook_code.empty?
710
+ $stderr.puts PDF::Writer::Lang[:techbook_code_not_empty]
711
+ $stderr.puts @techbook_code
712
+ end
713
+ __render_paragraph
714
+ :break
715
+ end
716
+
717
+ # Columns. .columns <number-of-columns>|off
718
+ def techbook_directive_columns(args)
719
+ av = /^(\d+|off)(?: (\d+))?(?: .*)?$/o.match(args)
720
+ unless av
721
+ $stderr.puts PDF::Writer::Lang[:techbook_bad_columns_directive] % args
722
+ raise ArgumentError
723
+ end
724
+ cols = av.captures[0]
725
+
726
+ # Flush the paragraph cache.
727
+ __render_paragraph
728
+
729
+ if cols == "off" or cols.to_i < 2
730
+ stop_columns
731
+ else
732
+ if av.captures[1]
733
+ start_columns(cols.to_i, av.captures[1].to_i)
734
+ else
735
+ start_columns(cols.to_i)
736
+ end
737
+ end
738
+ end
739
+
740
+ def techbook_directive_toc(args)
741
+ @toc_title = args unless args.empty?
742
+ @gen_toc = true
743
+ end
744
+
745
+ def techbook_directive_author(args)
746
+ info.author = args
747
+ end
748
+
749
+ def techbook_directive_title(args)
750
+ info.title = args
751
+ end
752
+
753
+ def techbook_directive_subject(args)
754
+ info.subject = args
755
+ end
756
+
757
+ def techbook_directive_keywords(args)
758
+ info.keywords = args
759
+ end
760
+
761
+ LIST_ITEM_STYLES = %w(bullet disc)
762
+
763
+ def techbook_directive_blist(args)
764
+ __render_paragraph
765
+ sm = /^(\w+).*$/o.match(args)
766
+ style = sm.captures[0] if sm
767
+ style = "bullet" unless LIST_ITEM_STYLES.include?(style)
768
+
769
+ @blist_factor = @left_margin * 0.10 if @blist_info.empty?
770
+
771
+ info = {
772
+ :left_margin => @left_margin,
773
+ :style => style
774
+ }
775
+ @blist_info << info
776
+ @left_margin += @blist_factor
777
+
778
+ @techbook_lastmode, @techbook_mode = @techbook_mode, :blist if :blist != @techbook_mode
779
+ end
780
+
781
+ def techbook_directive_endblist(args)
782
+ self.left_margin = @blist_info.pop[:left_margin]
783
+ @techbook_lastmode, @techbook_mode = @techbook_mode, @techbook_lastmode if @blist_info.empty?
784
+ end
785
+
786
+ def generate_table_of_contents?
787
+ @gen_toc
788
+ end
789
+
790
+ def self.run(args)
791
+ config = OpenStruct.new
792
+ config.regen = false
793
+ config.cache = true
794
+ config.compressed = false
795
+
796
+ args.options do |opt|
797
+ opt.banner = PDF::Writer::Lang[:techbook_usage_banner] % [ File.basename($0) ]
798
+ PDF::Writer::Lang[:techbook_usage_banner_1].each do |ll|
799
+ opt.separator " #{ll}"
800
+ end
801
+ opt.on('-f', '--force-regen', *PDF::Writer::Lang[:techbook_help_force_regen]) { config.regen = true }
802
+ opt.on('-n', '--no-cache', *PDF::Writer::Lang[:techbook_help_no_cache]) { config.cache = false }
803
+ opt.on('-z', '--compress', *PDF::Writer::Lang[:techbook_help_compress]) { config.compressed = true }
804
+ opt.on_tail ""
805
+ opt.on_tail("--help", *PDF::Writer::Lang[:techbook_help_help]) { $stderr << opt; exit(0) }
806
+ opt.parse!
807
+ end
808
+
809
+ config.document = args[0]
810
+
811
+ unless config.document
812
+ config.document = "manual.pwd"
813
+ unless File.exist?(config.document)
814
+ dirn = File.dirname(__FILE__)
815
+ config.document = File.join(dirn, config.document)
816
+ unless File.exist?(config.document)
817
+ dirn = File.join(dirn, "..")
818
+ config.document = File.join(dirn, config.document)
819
+ unless File.exist?(config.document)
820
+ $stderr.puts PDF::Writer::Lang[:techbook_cannot_find_document]
821
+ end
822
+ end
823
+ end
824
+
825
+ $stderr.puts PDF::Writer::Lang[:techbook_using_default_doc] % config.document
826
+ end
827
+
828
+ dirn = File.dirname(config.document)
829
+ extn = File.extname(config.document)
830
+ base = File.basename(config.document, extn)
831
+
832
+ files = {
833
+ :document => config.document,
834
+ :cache => "#{base}._mc",
835
+ :pdf => "#{base}.pdf"
836
+ }
837
+
838
+ unless config.regen
839
+ if File.exist?(files[:cache])
840
+ _tm_doc = File.mtime(config.document)
841
+ _tm_prg = File.mtime(__FILE__)
842
+ _tm_cch = File.mtime(files[:cache])
843
+
844
+ # If the cached file is newer than either the document or the
845
+ # class program, then regenerate.
846
+ if (_tm_doc < _tm_cch) and (_tm_prg < _tm_cch)
847
+ $stderr.puts PDF::Writer::Lang[:techbook_using_cached_doc] % File.basename(files[:cache])
848
+ pdf = File.open(files[:cache], "rb") { |cf| Marshal.load(cf.read) }
849
+ pdf.save_as(files[:pdf], config.compressed)
850
+ File.open(files[:pdf], "wb") { |pf| pf.write pdf.render }
851
+ exit 0
852
+ else
853
+ $stderr.puts PDF::Writer::Lang[:techbook_regenerating]
854
+ end
855
+ end
856
+ else
857
+ $stderr.puts PDF::Writer::Lang[:techbook_ignoring_cache] if File.exist?(files[:cache])
858
+ end
859
+
860
+ # Create the manual object.
861
+ pdf = PDF::TechBook.new
862
+
863
+ document = open(files[:document]) { |io| io.read.split($/) }
864
+ progress = ProgressBar.new(base.capitalize, document.size)
865
+ pdf.techbook_parse(document, progress)
866
+ progress.finish
867
+
868
+ if pdf.generate_table_of_contents?
869
+ progress = ProgressBar.new("TOC", pdf.table_of_contents.size)
870
+ pdf.techbook_toc(progress)
871
+ progress.finish
872
+ end
873
+
874
+ if config.cache
875
+ File.open(files[:cache], "wb") { |f| f.write Marshal.dump(pdf) }
876
+ end
877
+
878
+ pdf.save_as(files[:pdf], config.compressed)
879
+ end
880
+
881
+ def techbook_text(line)
882
+ opt = @techbook_textopt.dup
883
+ opt[:font_size] = @techbook_fontsize
884
+ text(line, opt)
885
+ end
886
+
887
+ instance_methods.grep(/^techbook_directive_/).each do |mname|
888
+ private mname.intern
889
+ end
890
+ end