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.
- data/ChangeLog +44 -0
- data/LICENCE +118 -0
- data/README +32 -0
- data/bin/loader +54 -0
- data/bin/manual +22 -0
- data/bin/manual.bat +2 -0
- data/demo/chunkybacon.rb +28 -0
- data/demo/code.rb +63 -0
- data/demo/colornames.rb +843 -0
- data/demo/demo.rb +65 -0
- data/demo/gettysburg.rb +58 -0
- data/demo/hello.rb +18 -0
- data/demo/individual-i.rb +81 -0
- data/demo/pac.rb +62 -0
- data/demo/pagenumber.rb +67 -0
- data/demo/qr-language.rb +573 -0
- data/demo/qr-library.rb +371 -0
- data/images/chunkybacon.jpg +0 -0
- data/images/chunkybacon.png +0 -0
- data/images/pdfwriter-icon.jpg +0 -0
- data/images/pdfwriter-small.jpg +0 -0
- data/lib/pdf/charts.rb +13 -0
- data/lib/pdf/charts/stddev.rb +431 -0
- data/lib/pdf/grid.rb +135 -0
- data/lib/pdf/math.rb +108 -0
- data/lib/pdf/quickref.rb +330 -0
- data/lib/pdf/simpletable.rb +946 -0
- data/lib/pdf/techbook.rb +890 -0
- data/lib/pdf/writer.rb +2661 -0
- data/lib/pdf/writer/arc4.rb +63 -0
- data/lib/pdf/writer/fontmetrics.rb +201 -0
- data/lib/pdf/writer/fonts/Courier-Bold.afm +342 -0
- data/lib/pdf/writer/fonts/Courier-BoldOblique.afm +342 -0
- data/lib/pdf/writer/fonts/Courier-Oblique.afm +342 -0
- data/lib/pdf/writer/fonts/Courier.afm +342 -0
- data/lib/pdf/writer/fonts/Helvetica-Bold.afm +2827 -0
- data/lib/pdf/writer/fonts/Helvetica-BoldOblique.afm +2827 -0
- data/lib/pdf/writer/fonts/Helvetica-Oblique.afm +3051 -0
- data/lib/pdf/writer/fonts/Helvetica.afm +3051 -0
- data/lib/pdf/writer/fonts/MustRead.html +1 -0
- data/lib/pdf/writer/fonts/Symbol.afm +213 -0
- data/lib/pdf/writer/fonts/Times-Bold.afm +2588 -0
- data/lib/pdf/writer/fonts/Times-BoldItalic.afm +2384 -0
- data/lib/pdf/writer/fonts/Times-Italic.afm +2667 -0
- data/lib/pdf/writer/fonts/Times-Roman.afm +2419 -0
- data/lib/pdf/writer/fonts/ZapfDingbats.afm +225 -0
- data/lib/pdf/writer/graphics.rb +727 -0
- data/lib/pdf/writer/graphics/imageinfo.rb +365 -0
- data/lib/pdf/writer/lang.rb +43 -0
- data/lib/pdf/writer/lang/en.rb +77 -0
- data/lib/pdf/writer/object.rb +23 -0
- data/lib/pdf/writer/object/action.rb +40 -0
- data/lib/pdf/writer/object/annotation.rb +42 -0
- data/lib/pdf/writer/object/catalog.rb +39 -0
- data/lib/pdf/writer/object/contents.rb +68 -0
- data/lib/pdf/writer/object/destination.rb +40 -0
- data/lib/pdf/writer/object/encryption.rb +53 -0
- data/lib/pdf/writer/object/font.rb +76 -0
- data/lib/pdf/writer/object/fontdescriptor.rb +34 -0
- data/lib/pdf/writer/object/fontencoding.rb +39 -0
- data/lib/pdf/writer/object/image.rb +168 -0
- data/lib/pdf/writer/object/info.rb +55 -0
- data/lib/pdf/writer/object/outline.rb +30 -0
- data/lib/pdf/writer/object/outlines.rb +30 -0
- data/lib/pdf/writer/object/page.rb +195 -0
- data/lib/pdf/writer/object/pages.rb +115 -0
- data/lib/pdf/writer/object/procset.rb +46 -0
- data/lib/pdf/writer/object/viewerpreferences.rb +74 -0
- data/lib/pdf/writer/ohash.rb +58 -0
- data/lib/pdf/writer/oreader.rb +25 -0
- data/lib/pdf/writer/state.rb +48 -0
- data/lib/pdf/writer/strokestyle.rb +138 -0
- data/manual.pwd +5151 -0
- metadata +147 -0
data/lib/pdf/techbook.rb
ADDED
@@ -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 &lt;; > must be
|
55
|
+
# written as &gt;; and & must be written as &amp;.
|
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><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><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
|
+
# <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
|