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