pdf-writer 1.0.0

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