rrtf 0.1.1
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.
- checksums.yaml +7 -0
- data/.byebug_history +4 -0
- data/.gitignore +9 -0
- data/.rspec +2 -0
- data/.travis.yml +5 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +45 -0
- data/README.md +110 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/examples/01.rtf +45 -0
- data/examples/01_mac_libreoffice5_2_3_3.png +0 -0
- data/examples/01_mac_pages6_2.png +0 -0
- data/examples/01_mac_textedit1_12.png +0 -0
- data/examples/01_mac_word15_36.png +0 -0
- data/examples/01_styles_and_paragraphs.rb +33 -0
- data/examples/resources/json/redshirt_styles.json +49 -0
- data/lib/rrtf/colour.rb +182 -0
- data/lib/rrtf/converters/html.rb +123 -0
- data/lib/rrtf/converters.rb +5 -0
- data/lib/rrtf/font.rb +182 -0
- data/lib/rrtf/information.rb +110 -0
- data/lib/rrtf/list.rb +219 -0
- data/lib/rrtf/node.rb +1932 -0
- data/lib/rrtf/paper.rb +53 -0
- data/lib/rrtf/style/character_style.rb +68 -0
- data/lib/rrtf/style/document_style.rb +116 -0
- data/lib/rrtf/style/formatting.rb +276 -0
- data/lib/rrtf/style/paragraph_style.rb +79 -0
- data/lib/rrtf/style/style.rb +101 -0
- data/lib/rrtf/style.rb +8 -0
- data/lib/rrtf/stylesheet.rb +202 -0
- data/lib/rrtf/version.rb +3 -0
- data/lib/rrtf.rb +27 -0
- data/rrtf.gemspec +30 -0
- metadata +163 -0
data/lib/rrtf/node.rb
ADDED
@@ -0,0 +1,1932 @@
|
|
1
|
+
require 'stringio'
|
2
|
+
|
3
|
+
module RRTF
|
4
|
+
# This class represents an element within an RTF document. The class provides
|
5
|
+
# a base class for more specific node types.
|
6
|
+
class Node
|
7
|
+
# Node parent.
|
8
|
+
attr_accessor :parent
|
9
|
+
|
10
|
+
# Constructor for the Node class.
|
11
|
+
#
|
12
|
+
# ==== Parameters
|
13
|
+
# parent:: A reference to the Node that owns the new Node. May be nil
|
14
|
+
# to indicate a base or root node.
|
15
|
+
def initialize(parent)
|
16
|
+
@parent = parent
|
17
|
+
end
|
18
|
+
|
19
|
+
# This method retrieves a Node objects previous peer node, returning nil
|
20
|
+
# if the Node has no previous peer.
|
21
|
+
def previous_node
|
22
|
+
peer = nil
|
23
|
+
if !parent.nil? and parent.respond_to?(:children)
|
24
|
+
index = parent.children.index(self)
|
25
|
+
peer = index > 0 ? parent.children[index - 1] : nil
|
26
|
+
end
|
27
|
+
peer
|
28
|
+
end
|
29
|
+
|
30
|
+
# This method retrieves a Node objects next peer node, returning nil
|
31
|
+
# if the Node has no previous peer.
|
32
|
+
def next_node
|
33
|
+
peer = nil
|
34
|
+
if !parent.nil? and parent.respond_to?(:children)
|
35
|
+
index = parent.children.index(self)
|
36
|
+
peer = parent.children[index + 1]
|
37
|
+
end
|
38
|
+
peer
|
39
|
+
end
|
40
|
+
|
41
|
+
# This method is used to determine whether a Node object represents a
|
42
|
+
# root or base element. The method returns true if the Nodes parent is
|
43
|
+
# nil, false otherwise.
|
44
|
+
def is_root?
|
45
|
+
@parent.nil?
|
46
|
+
end
|
47
|
+
|
48
|
+
# This method traverses a Node tree to locate the root element.
|
49
|
+
def root
|
50
|
+
node = self
|
51
|
+
node = node.parent while !node.parent.nil?
|
52
|
+
node
|
53
|
+
end
|
54
|
+
end # End of the Node class.
|
55
|
+
|
56
|
+
|
57
|
+
# This class represents a specialisation of the Node class to refer to a Node
|
58
|
+
# that simply contains text.
|
59
|
+
class TextNode < Node
|
60
|
+
# Actual text
|
61
|
+
attr_accessor :text
|
62
|
+
|
63
|
+
# This is the constructor for the TextNode class.
|
64
|
+
#
|
65
|
+
# ==== Parameters
|
66
|
+
# parent:: A reference to the Node that owns the TextNode. Must not be
|
67
|
+
# nil.
|
68
|
+
# text:: A String containing the node text. Defaults to nil.
|
69
|
+
#
|
70
|
+
# ==== Exceptions
|
71
|
+
# RTFError:: Generated whenever an nil parent object is specified to
|
72
|
+
# the method.
|
73
|
+
def initialize(parent, text=nil)
|
74
|
+
super(parent)
|
75
|
+
if parent.nil?
|
76
|
+
RTFError.fire("Nil parent specified for text node.")
|
77
|
+
end
|
78
|
+
@parent = parent
|
79
|
+
@text = text
|
80
|
+
end
|
81
|
+
|
82
|
+
# This method concatenates a String on to the end of the existing text
|
83
|
+
# within a TextNode object.
|
84
|
+
#
|
85
|
+
# ==== Parameters
|
86
|
+
# text:: The String to be added to the end of the text node.
|
87
|
+
def append(text)
|
88
|
+
@text = (@text.nil?) ? text.to_s : @text + text.to_s
|
89
|
+
end
|
90
|
+
|
91
|
+
# This method inserts a String into the existing text within a TextNode
|
92
|
+
# object. If the TextNode contains no text then it is simply set to the
|
93
|
+
# text passed in. If the offset specified is past the end of the nodes
|
94
|
+
# text then it is simply appended to the end.
|
95
|
+
#
|
96
|
+
# ==== Parameters
|
97
|
+
# text:: A String containing the text to be added.
|
98
|
+
# offset:: The numbers of characters from the first character to insert
|
99
|
+
# the new text at.
|
100
|
+
def insert(text, offset)
|
101
|
+
if !@text.nil?
|
102
|
+
@text = @text[0, offset] + text.to_s + @text[offset, @text.length]
|
103
|
+
else
|
104
|
+
@text = text.to_s
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
# This method generates the RTF equivalent for a TextNode object. This
|
109
|
+
# method escapes any special sequences that appear in the text.
|
110
|
+
def to_rtf
|
111
|
+
rtf=(@text.nil? ? '' : @text.gsub("{", "\\{").gsub("}", "\\}").gsub("\\", "\\\\"))
|
112
|
+
# This is from lfarcy / rtf-extensions
|
113
|
+
# I don't see the point of coding different 128<n<256 range
|
114
|
+
|
115
|
+
#f1=lambda { |n| n < 128 ? n.chr : n < 256 ? "\\'#{n.to_s(16)}" : "\\u#{n}\\'3f" }
|
116
|
+
# Encode as Unicode.
|
117
|
+
|
118
|
+
f=lambda { |n| n < 128 ? n.chr : "\\u#{n}\\'3f" }
|
119
|
+
# Ruby 1.9 is safe, cause detect original encoding
|
120
|
+
# and convert text to utf-16 first
|
121
|
+
if RUBY_VERSION>"1.9.0"
|
122
|
+
return rtf.encode("UTF-16LE", :undef=>:replace).each_codepoint.map(&f).join('')
|
123
|
+
else
|
124
|
+
# You SHOULD use UTF-8 as input, ok?
|
125
|
+
return rtf.unpack('U*').map(&f).join('')
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end # End of the TextNode class.
|
129
|
+
|
130
|
+
|
131
|
+
# This class represents a Node that can contain other Node objects. Its a
|
132
|
+
# base class for more specific Node types.
|
133
|
+
class ContainerNode < Node
|
134
|
+
include Enumerable
|
135
|
+
|
136
|
+
# Children elements of the node
|
137
|
+
attr_accessor :children
|
138
|
+
|
139
|
+
# This is the constructor for the ContainerNode class.
|
140
|
+
#
|
141
|
+
# ==== Parameters
|
142
|
+
# parent:: A reference to the parent node that owners the new
|
143
|
+
# ContainerNode object.
|
144
|
+
def initialize(parent)
|
145
|
+
super(parent)
|
146
|
+
@children = []
|
147
|
+
@children.concat(yield) if block_given?
|
148
|
+
end
|
149
|
+
|
150
|
+
# This method adds a new node element to the end of the list of nodes
|
151
|
+
# maintained by a ContainerNode object. Nil objects are ignored.
|
152
|
+
#
|
153
|
+
# ==== Parameters
|
154
|
+
# node:: A reference to the Node object to be added.
|
155
|
+
def store(node)
|
156
|
+
if !node.nil?
|
157
|
+
@children.push(node) if !@children.include?(Node)
|
158
|
+
node.parent = self if node.parent != self
|
159
|
+
end
|
160
|
+
node
|
161
|
+
end
|
162
|
+
|
163
|
+
# This method fetches the first node child for a ContainerNode object. If
|
164
|
+
# a container contains no children this method returns nil.
|
165
|
+
def first
|
166
|
+
@children[0]
|
167
|
+
end
|
168
|
+
|
169
|
+
# This method fetches the last node child for a ContainerNode object. If
|
170
|
+
# a container contains no children this method returns nil.
|
171
|
+
def last
|
172
|
+
@children.last
|
173
|
+
end
|
174
|
+
|
175
|
+
# This method provides for iteration over the contents of a ContainerNode
|
176
|
+
# object.
|
177
|
+
def each
|
178
|
+
@children.each {|child| yield child}
|
179
|
+
end
|
180
|
+
|
181
|
+
# This method returns a count of the number of children a ContainerNode
|
182
|
+
# object contains.
|
183
|
+
def size
|
184
|
+
@children.size
|
185
|
+
end
|
186
|
+
|
187
|
+
# This method overloads the array dereference operator to allow for
|
188
|
+
# access to the child elements of a ContainerNode object.
|
189
|
+
#
|
190
|
+
# ==== Parameters
|
191
|
+
# index:: The offset from the first child of the child object to be
|
192
|
+
# returned. Negative index values work from the back of the
|
193
|
+
# list of children. An invalid index will cause a nil value
|
194
|
+
# to be returned.
|
195
|
+
def [](index)
|
196
|
+
@children[index]
|
197
|
+
end
|
198
|
+
|
199
|
+
# This method generates the RTF text for a ContainerNode object.
|
200
|
+
def to_rtf
|
201
|
+
RTFError.fire("#{self.class.name}.to_rtf method not yet implemented.")
|
202
|
+
end
|
203
|
+
end # End of the ContainerNode class.
|
204
|
+
|
205
|
+
|
206
|
+
# This class represents a RTF command element within a document. This class
|
207
|
+
# is concrete enough to be used on its own but will also be used as the
|
208
|
+
# base class for some specific command node types.
|
209
|
+
class CommandNode < ContainerNode
|
210
|
+
# String containing the prefix text for the command
|
211
|
+
attr_accessor :prefix
|
212
|
+
# String containing the suffix text for the command
|
213
|
+
attr_accessor :suffix
|
214
|
+
# A boolean to indicate whether the prefix and suffix should
|
215
|
+
# be written to separate lines whether the node is converted
|
216
|
+
# to RTF. Defaults to true
|
217
|
+
attr_accessor :split
|
218
|
+
# A boolean to indicate whether the prefix and suffix should
|
219
|
+
# be wrapped in curly braces. Defaults to true.
|
220
|
+
attr_accessor :wrap
|
221
|
+
|
222
|
+
# This is the constructor for the CommandNode class.
|
223
|
+
#
|
224
|
+
# ==== Parameters
|
225
|
+
# parent:: A reference to the node that owns the new node.
|
226
|
+
# prefix:: A String containing the prefix text for the command.
|
227
|
+
# suffix:: A String containing the suffix text for the command. Defaults
|
228
|
+
# to nil.
|
229
|
+
# split:: A boolean to indicate whether the prefix and suffix should
|
230
|
+
# be written to separate lines whether the node is converted
|
231
|
+
# to RTF. Defaults to true.
|
232
|
+
# wrap:: A boolean to indicate whether the prefix and suffix should
|
233
|
+
# be wrapped in curly braces. Defaults to true.
|
234
|
+
def initialize(parent, prefix, suffix=nil, split=true, wrap=true)
|
235
|
+
super(parent)
|
236
|
+
@prefix = prefix
|
237
|
+
@suffix = suffix
|
238
|
+
@split = split
|
239
|
+
@wrap = wrap
|
240
|
+
end
|
241
|
+
|
242
|
+
# This method adds text to a command node. If the last child node of the
|
243
|
+
# target node is a TextNode then the text is appended to that. Otherwise
|
244
|
+
# a new TextNode is created and append to the node.
|
245
|
+
#
|
246
|
+
# ==== Parameters
|
247
|
+
# text:: The String of text to be written to the node.
|
248
|
+
def <<(text)
|
249
|
+
if !last.nil? and last.respond_to?(:text=)
|
250
|
+
last.append(text)
|
251
|
+
else
|
252
|
+
self.store(TextNode.new(self, text))
|
253
|
+
end
|
254
|
+
end
|
255
|
+
|
256
|
+
# This method generates the RTF text for a CommandNode object.
|
257
|
+
def to_rtf
|
258
|
+
text = StringIO.new
|
259
|
+
|
260
|
+
text << '{' if wrap?
|
261
|
+
text << @prefix if @prefix
|
262
|
+
|
263
|
+
self.each do |entry|
|
264
|
+
text << "\n" if split?
|
265
|
+
text << entry.to_rtf
|
266
|
+
end
|
267
|
+
|
268
|
+
text << "\n" if split?
|
269
|
+
text << @suffix if @suffix
|
270
|
+
text << '}' if wrap?
|
271
|
+
|
272
|
+
text.string
|
273
|
+
end
|
274
|
+
|
275
|
+
# This method provides a short cut means of creating a paragraph command
|
276
|
+
# node. The method accepts a block that will be passed a single parameter
|
277
|
+
# which will be a reference to the paragraph node created. After the
|
278
|
+
# block is complete the paragraph node is appended to the end of the child
|
279
|
+
# nodes on the object that the method is called against.
|
280
|
+
#
|
281
|
+
# ==== Parameters
|
282
|
+
# style:: A reference to a ParagraphStyle object that defines the style
|
283
|
+
# for the new paragraph. Defaults to nil to indicate that the
|
284
|
+
# currently applied paragraph styling should be used.
|
285
|
+
def paragraph(style=nil)
|
286
|
+
node = ParagraphNode.new(self, style)
|
287
|
+
yield node if block_given?
|
288
|
+
self.store(node)
|
289
|
+
end
|
290
|
+
|
291
|
+
# This method provides a short cut means of creating a new ordered or
|
292
|
+
# unordered list. The method requires a block that will be passed a
|
293
|
+
# single parameter that'll be a reference to the first level of the
|
294
|
+
# list. See the +ListLevelNode+ doc for more information.
|
295
|
+
#
|
296
|
+
# Example usage:
|
297
|
+
#
|
298
|
+
# rtf.list do |level1|
|
299
|
+
# level1.item do |li|
|
300
|
+
# li << 'some text'
|
301
|
+
# li.apply(some_style) {|x| x << 'some styled text'}
|
302
|
+
# end
|
303
|
+
#
|
304
|
+
# level1.list(:decimal) do |level2|
|
305
|
+
# level2.item {|li| li << 'some other text in a decimal list'}
|
306
|
+
# level2.item {|li| li << 'and here we go'}
|
307
|
+
# end
|
308
|
+
# end
|
309
|
+
#
|
310
|
+
def list(kind=:bullets)
|
311
|
+
node = ListNode.new(self)
|
312
|
+
yield node.list(kind)
|
313
|
+
self.store(node)
|
314
|
+
end
|
315
|
+
|
316
|
+
def link(url, text=nil)
|
317
|
+
node = LinkNode.new(self, url)
|
318
|
+
node << text if text
|
319
|
+
yield node if block_given?
|
320
|
+
self.store(node)
|
321
|
+
end
|
322
|
+
|
323
|
+
# This method provides a short cut means of creating a line break command
|
324
|
+
# node. This command node does not take a block and may possess no other
|
325
|
+
# content.
|
326
|
+
def line_break
|
327
|
+
self.store(CommandNode.new(self, '\line', nil, false))
|
328
|
+
nil
|
329
|
+
end
|
330
|
+
|
331
|
+
# This method inserts a footnote at the current position in a node.
|
332
|
+
#
|
333
|
+
# ==== Parameters
|
334
|
+
# text:: A string containing the text for the footnote.
|
335
|
+
def footnote(text)
|
336
|
+
if !text.nil? and text != ''
|
337
|
+
mark = CommandNode.new(self, '\fs16\up6\chftn', nil, false)
|
338
|
+
note = CommandNode.new(self, '\footnote {\fs16\up6\chftn}', nil, false)
|
339
|
+
note.paragraph << text
|
340
|
+
self.store(mark)
|
341
|
+
self.store(note)
|
342
|
+
end
|
343
|
+
end
|
344
|
+
|
345
|
+
# This method inserts a new image at the current position in a node.
|
346
|
+
#
|
347
|
+
# ==== Parameters
|
348
|
+
# source:: Either a string containing the path and name of a file or a
|
349
|
+
# File object for the image file to be inserted.
|
350
|
+
#
|
351
|
+
# ==== Exceptions
|
352
|
+
# RTFError:: Generated whenever an invalid or inaccessible file is
|
353
|
+
# specified or the image file type is not supported.
|
354
|
+
def image(source)
|
355
|
+
self.store(ImageNode.new(self, source, root.get_id))
|
356
|
+
end
|
357
|
+
|
358
|
+
# This method provides a short cut means for applying multiple styles via
|
359
|
+
# single command node. The method accepts a block that will be passed a
|
360
|
+
# reference to the node created. Once the block is complete the new node
|
361
|
+
# will be append as the last child of the CommandNode the method is called
|
362
|
+
# on.
|
363
|
+
#
|
364
|
+
# ==== Parameters
|
365
|
+
# style:: A reference to a CharacterStyle object that contains the style
|
366
|
+
# settings to be applied.
|
367
|
+
#
|
368
|
+
# ==== Exceptions
|
369
|
+
# RTFError:: Generated whenever a non-character style is specified to
|
370
|
+
# the method.
|
371
|
+
def apply(style)
|
372
|
+
# Check the input style.
|
373
|
+
if !style.is_character_style?
|
374
|
+
RTFError.fire("Non-character style specified to the "\
|
375
|
+
"CommandNode#apply() method.")
|
376
|
+
end
|
377
|
+
|
378
|
+
# Store fonts and colours.
|
379
|
+
style.push_colours(root.colours)
|
380
|
+
style.push_fonts(root.fonts)
|
381
|
+
|
382
|
+
# Generate the command node.
|
383
|
+
node = CommandNode.new(self, style.prefix(root))
|
384
|
+
yield node if block_given?
|
385
|
+
self.store(node)
|
386
|
+
end
|
387
|
+
|
388
|
+
# This method provides a short cut means of creating a bold command node.
|
389
|
+
# The method accepts a block that will be passed a single parameter which
|
390
|
+
# will be a reference to the bold node created. After the block is
|
391
|
+
# complete the bold node is appended to the end of the child nodes on
|
392
|
+
# the object that the method is call against.
|
393
|
+
def bold
|
394
|
+
style = CharacterStyle.new
|
395
|
+
style.bold = true
|
396
|
+
if block_given?
|
397
|
+
apply(style) {|node| yield node}
|
398
|
+
else
|
399
|
+
apply(style)
|
400
|
+
end
|
401
|
+
end
|
402
|
+
|
403
|
+
# This method provides a short cut means of creating an italic command
|
404
|
+
# node. The method accepts a block that will be passed a single parameter
|
405
|
+
# which will be a reference to the italic node created. After the block is
|
406
|
+
# complete the italic node is appended to the end of the child nodes on
|
407
|
+
# the object that the method is call against.
|
408
|
+
def italic
|
409
|
+
style = CharacterStyle.new
|
410
|
+
style.italic = true
|
411
|
+
if block_given?
|
412
|
+
apply(style) {|node| yield node}
|
413
|
+
else
|
414
|
+
apply(style)
|
415
|
+
end
|
416
|
+
end
|
417
|
+
|
418
|
+
# This method provides a short cut means of creating an underline command
|
419
|
+
# node. The method accepts a block that will be passed a single parameter
|
420
|
+
# which will be a reference to the underline node created. After the block
|
421
|
+
# is complete the underline node is appended to the end of the child nodes
|
422
|
+
# on the object that the method is call against.
|
423
|
+
def underline
|
424
|
+
style = CharacterStyle.new
|
425
|
+
style.underline = true
|
426
|
+
if block_given?
|
427
|
+
apply(style) {|node| yield node}
|
428
|
+
else
|
429
|
+
apply(style)
|
430
|
+
end
|
431
|
+
end
|
432
|
+
|
433
|
+
# This method provides a short cut means of creating a subscript command
|
434
|
+
# node. The method accepts a block that will be passed a single parameter
|
435
|
+
# which will be a reference to the subscript node created. After the
|
436
|
+
# block is complete the subscript node is appended to the end of the
|
437
|
+
# child nodes on the object that the method is call against.
|
438
|
+
def subscript
|
439
|
+
style = CharacterStyle.new
|
440
|
+
style.subscript = true
|
441
|
+
if block_given?
|
442
|
+
apply(style) {|node| yield node}
|
443
|
+
else
|
444
|
+
apply(style)
|
445
|
+
end
|
446
|
+
end
|
447
|
+
|
448
|
+
# This method provides a short cut means of creating a superscript command
|
449
|
+
# node. The method accepts a block that will be passed a single parameter
|
450
|
+
# which will be a reference to the superscript node created. After the
|
451
|
+
# block is complete the superscript node is appended to the end of the
|
452
|
+
# child nodes on the object that the method is call against.
|
453
|
+
def superscript
|
454
|
+
style = CharacterStyle.new
|
455
|
+
style.superscript = true
|
456
|
+
if block_given?
|
457
|
+
apply(style) {|node| yield node}
|
458
|
+
else
|
459
|
+
apply(style)
|
460
|
+
end
|
461
|
+
end
|
462
|
+
|
463
|
+
# This method provides a short cut means of creating a strike command
|
464
|
+
# node. The method accepts a block that will be passed a single parameter
|
465
|
+
# which will be a reference to the strike node created. After the
|
466
|
+
# block is complete the strike node is appended to the end of the
|
467
|
+
# child nodes on the object that the method is call against.
|
468
|
+
def strike
|
469
|
+
style = CharacterStyle.new
|
470
|
+
style.strike = true
|
471
|
+
if block_given?
|
472
|
+
apply(style) {|node| yield node}
|
473
|
+
else
|
474
|
+
apply(style)
|
475
|
+
end
|
476
|
+
end
|
477
|
+
|
478
|
+
# This method provides a short cut means of creating a font command node.
|
479
|
+
# The method accepts a block that will be passed a single parameter which
|
480
|
+
# will be a reference to the font node created. After the block is
|
481
|
+
# complete the font node is appended to the end of the child nodes on the
|
482
|
+
# object that the method is called against.
|
483
|
+
#
|
484
|
+
# ==== Parameters
|
485
|
+
# font:: A reference to font object that represents the font to be used
|
486
|
+
# within the node.
|
487
|
+
# size:: An integer size setting for the font. Defaults to nil to
|
488
|
+
# indicate that the current font size should be used.
|
489
|
+
def font(font, size=nil)
|
490
|
+
style = CharacterStyle.new
|
491
|
+
style.font = font
|
492
|
+
style.font_size = size
|
493
|
+
root.fonts << font
|
494
|
+
if block_given?
|
495
|
+
apply(style) {|node| yield node}
|
496
|
+
else
|
497
|
+
apply(style)
|
498
|
+
end
|
499
|
+
end
|
500
|
+
|
501
|
+
# This method provides a short cut means of creating a foreground colour
|
502
|
+
# command node. The method accepts a block that will be passed a single
|
503
|
+
# parameter which will be a reference to the foreground colour node
|
504
|
+
# created. After the block is complete the foreground colour node is
|
505
|
+
# appended to the end of the child nodes on the object that the method
|
506
|
+
# is called against.
|
507
|
+
#
|
508
|
+
# ==== Parameters
|
509
|
+
# colour:: The foreground colour to be applied by the command.
|
510
|
+
def foreground(colour)
|
511
|
+
style = CharacterStyle.new
|
512
|
+
style.foreground = colour
|
513
|
+
root.colours << colour
|
514
|
+
if block_given?
|
515
|
+
apply(style) {|node| yield node}
|
516
|
+
else
|
517
|
+
apply(style)
|
518
|
+
end
|
519
|
+
end
|
520
|
+
|
521
|
+
# This method provides a short cut means of creating a background colour
|
522
|
+
# command node. The method accepts a block that will be passed a single
|
523
|
+
# parameter which will be a reference to the background colour node
|
524
|
+
# created. After the block is complete the background colour node is
|
525
|
+
# appended to the end of the child nodes on the object that the method
|
526
|
+
# is called against.
|
527
|
+
#
|
528
|
+
# ==== Parameters
|
529
|
+
# colour:: The background colour to be applied by the command.
|
530
|
+
def background(colour)
|
531
|
+
style = CharacterStyle.new
|
532
|
+
style.background = colour
|
533
|
+
root.colours << colour
|
534
|
+
if block_given?
|
535
|
+
apply(style) {|node| yield node}
|
536
|
+
else
|
537
|
+
apply(style)
|
538
|
+
end
|
539
|
+
end
|
540
|
+
|
541
|
+
# This method provides a short cut menas of creating a colour node that
|
542
|
+
# deals with foreground and background colours. The method accepts a
|
543
|
+
# block that will be passed a single parameter which will be a reference
|
544
|
+
# to the colour node created. After the block is complete the colour node
|
545
|
+
# is append to the end of the child nodes on the object that the method
|
546
|
+
# is called against.
|
547
|
+
#
|
548
|
+
# ==== Parameters
|
549
|
+
# fore:: The foreground colour to be applied by the command.
|
550
|
+
# back:: The background colour to be applied by the command.
|
551
|
+
def colour(fore, back)
|
552
|
+
style = CharacterStyle.new
|
553
|
+
style.foreground = fore
|
554
|
+
style.background = back
|
555
|
+
root.colours << fore
|
556
|
+
root.colours << back
|
557
|
+
if block_given?
|
558
|
+
apply(style) {|node| yield node}
|
559
|
+
else
|
560
|
+
apply(style)
|
561
|
+
end
|
562
|
+
end
|
563
|
+
|
564
|
+
# This method creates a new table node and returns it. The method accepts
|
565
|
+
# a block that will be passed the table as a parameter. The node is added
|
566
|
+
# to the node the method is called upon after the block is complete.
|
567
|
+
#
|
568
|
+
# ==== Parameters
|
569
|
+
# rows:: The number of rows that the table contains.
|
570
|
+
# columns:: The number of columns that the table contains.
|
571
|
+
# *widths:: One or more integers representing the widths for the table
|
572
|
+
# columns.
|
573
|
+
def table(rows, columns, *widths)
|
574
|
+
node = TableNode.new(self, rows, columns, *widths)
|
575
|
+
yield node if block_given?
|
576
|
+
store(node)
|
577
|
+
node
|
578
|
+
end
|
579
|
+
|
580
|
+
alias :write :<<
|
581
|
+
alias :color :colour
|
582
|
+
alias :split? :split
|
583
|
+
alias :wrap? :wrap
|
584
|
+
end # End of the CommandNode class.
|
585
|
+
|
586
|
+
# This class represents a paragraph within an RTF document.
|
587
|
+
class ParagraphNode < CommandNode
|
588
|
+
def initialize(parent, style=nil)
|
589
|
+
prefix = '\pard'
|
590
|
+
prefix << style.prefix(parent.root) if style
|
591
|
+
|
592
|
+
super(parent, prefix, '\par')
|
593
|
+
end
|
594
|
+
end
|
595
|
+
|
596
|
+
# This class represents an ordered/unordered list within an RTF document.
|
597
|
+
#
|
598
|
+
# Currently list nodes can contain any type of node, but this behaviour
|
599
|
+
# will change in future releases. The class overrides the +list+ method
|
600
|
+
# to return a +ListLevelNode+.
|
601
|
+
#
|
602
|
+
class ListNode < CommandNode
|
603
|
+
def initialize(parent)
|
604
|
+
prefix = "\\"
|
605
|
+
|
606
|
+
suffix = '\pard'
|
607
|
+
suffix << ListLevel::ResetTabs.map {|tw| "\\tx#{tw}"}.join
|
608
|
+
suffix << '\ql\qlnatural\pardirnatural\cf0 \\'
|
609
|
+
|
610
|
+
super(parent, prefix, suffix, true, false)
|
611
|
+
|
612
|
+
@template = root.lists.new_template
|
613
|
+
end
|
614
|
+
|
615
|
+
# This method creates a new +ListLevelNode+ of the given kind and
|
616
|
+
# stores it in the document tree.
|
617
|
+
#
|
618
|
+
# ==== Parameters
|
619
|
+
# kind:: The kind of this list level, may be either :bullets or :decimal
|
620
|
+
def list(kind)
|
621
|
+
self.store ListLevelNode.new(self, @template, kind)
|
622
|
+
end
|
623
|
+
end
|
624
|
+
|
625
|
+
# This class represents a list level, and carries out indenting information
|
626
|
+
# and the bullet or number that is prepended to each +ListTextNode+.
|
627
|
+
#
|
628
|
+
# The class overrides the +list+ method to implement nesting, and provides
|
629
|
+
# the +item+ method to add a new list item, the +ListTextNode+.
|
630
|
+
class ListLevelNode < CommandNode
|
631
|
+
def initialize(parent, template, kind, level=1)
|
632
|
+
@template = template
|
633
|
+
@kind = kind
|
634
|
+
@level = template.level_for(level, kind)
|
635
|
+
|
636
|
+
prefix = '\pard'
|
637
|
+
prefix << @level.tabs.map {|tw| "\\tx#{tw}"}.join
|
638
|
+
prefix << "\\li#{@level.indent}\\fi-#{@level.indent}"
|
639
|
+
prefix << "\\ql\\qlnatural\\pardirnatural\n"
|
640
|
+
prefix << "\\ls#{@template.id}\\ilvl#{@level.level-1}\\cf0"
|
641
|
+
|
642
|
+
super(parent, prefix, nil, true, false)
|
643
|
+
end
|
644
|
+
|
645
|
+
# Returns the kind of this level, either :bullets or :decimal
|
646
|
+
attr_reader :kind
|
647
|
+
|
648
|
+
# Returns the indenting level of this list, from 1 to 9
|
649
|
+
def level
|
650
|
+
@level.level
|
651
|
+
end
|
652
|
+
|
653
|
+
# Creates a new +ListTextNode+ and yields it to the calling block
|
654
|
+
def item
|
655
|
+
node = ListTextNode.new(self, @level)
|
656
|
+
yield node
|
657
|
+
self.store(node)
|
658
|
+
end
|
659
|
+
|
660
|
+
# Creates a new +ListLevelNode+ to implement nested lists
|
661
|
+
def list(kind=@kind)
|
662
|
+
node = ListLevelNode.new(self, @template, kind, @level.level+1)
|
663
|
+
yield node
|
664
|
+
self.store(node)
|
665
|
+
end
|
666
|
+
end
|
667
|
+
|
668
|
+
# This class represents a list item, that can contain text or
|
669
|
+
# other nodes. Currently any type of node is accepted, but after
|
670
|
+
# more extensive testing this behaviour may change.
|
671
|
+
class ListTextNode < CommandNode
|
672
|
+
def initialize(parent, level)
|
673
|
+
@level = level
|
674
|
+
@parent = parent
|
675
|
+
|
676
|
+
number = siblings_count + 1 if parent.kind == :decimal
|
677
|
+
prefix = "{\\listtext#{@level.marker.text_format(number)}}"
|
678
|
+
suffix = '\\'
|
679
|
+
|
680
|
+
super(parent, prefix, suffix, false, false)
|
681
|
+
end
|
682
|
+
|
683
|
+
private
|
684
|
+
def siblings_count
|
685
|
+
parent.children.select {|n| n.kind_of?(self.class)}.size
|
686
|
+
end
|
687
|
+
end
|
688
|
+
|
689
|
+
class LinkNode < CommandNode
|
690
|
+
def initialize(parent, url)
|
691
|
+
prefix = "\\field{\\*\\fldinst HYPERLINK \"#{url}\"}{\\fldrslt "
|
692
|
+
suffix = "}"
|
693
|
+
|
694
|
+
super(parent, prefix, suffix, false)
|
695
|
+
end
|
696
|
+
end
|
697
|
+
|
698
|
+
# This class represents a table node within an RTF document. Table nodes are
|
699
|
+
# specialised container nodes that contain only TableRowNodes and have their
|
700
|
+
# size specified when they are created an cannot be resized after that.
|
701
|
+
class TableNode < ContainerNode
|
702
|
+
# Cell margin. Default to 100
|
703
|
+
attr_accessor :cell_margin
|
704
|
+
|
705
|
+
# This is a constructor for the TableNode class.
|
706
|
+
#
|
707
|
+
# ==== Parameters
|
708
|
+
# parent:: A reference to the node that owns the table.
|
709
|
+
# rows:: The number of rows in the tabkle.
|
710
|
+
# columns:: The number of columns in the table.
|
711
|
+
# *widths:: One or more integers specifying the widths of the table
|
712
|
+
# columns.
|
713
|
+
def initialize(parent, *args, &block)
|
714
|
+
if args.size>=2
|
715
|
+
rows=args.shift
|
716
|
+
columns=args.shift
|
717
|
+
widths=args
|
718
|
+
super(parent) do
|
719
|
+
entries = []
|
720
|
+
rows.times {entries.push(TableRowNode.new(self, columns, *widths))}
|
721
|
+
entries
|
722
|
+
end
|
723
|
+
|
724
|
+
elsif block
|
725
|
+
block.arity<1 ? self.instance_eval(&block) : block.call(self)
|
726
|
+
else
|
727
|
+
raise "You should use 0 or >2 args"
|
728
|
+
end
|
729
|
+
@cell_margin = 100
|
730
|
+
end
|
731
|
+
|
732
|
+
# Attribute accessor.
|
733
|
+
def rows
|
734
|
+
entries.size
|
735
|
+
end
|
736
|
+
|
737
|
+
# Attribute accessor.
|
738
|
+
def columns
|
739
|
+
entries[0].length
|
740
|
+
end
|
741
|
+
|
742
|
+
# This method assigns a border width setting to all of the sides on all
|
743
|
+
# of the cells within a table.
|
744
|
+
#
|
745
|
+
# ==== Parameters
|
746
|
+
# width:: The border width setting to apply. Negative values are ignored
|
747
|
+
# and zero switches the border off.
|
748
|
+
def border_width=(width)
|
749
|
+
self.each {|row| row.border_width = width}
|
750
|
+
end
|
751
|
+
|
752
|
+
# This method assigns a shading colour to a specified row within a
|
753
|
+
# TableNode object.
|
754
|
+
#
|
755
|
+
# ==== Parameters
|
756
|
+
# index:: The offset from the first row of the row to have shading
|
757
|
+
# applied to it.
|
758
|
+
# colour:: A reference to a Colour object representing the shading colour
|
759
|
+
# to be used. Set to nil to clear shading.
|
760
|
+
def row_shading_colour(index, colour)
|
761
|
+
row = self[index]
|
762
|
+
row.shading_colour = colour if row != nil
|
763
|
+
end
|
764
|
+
|
765
|
+
# This method assigns a shading colour to a specified column within a
|
766
|
+
# TableNode object.
|
767
|
+
#
|
768
|
+
# ==== Parameters
|
769
|
+
# index:: The offset from the first column of the column to have shading
|
770
|
+
# applied to it.
|
771
|
+
# colour:: A reference to a Colour object representing the shading colour
|
772
|
+
# to be used. Set to nil to clear shading.
|
773
|
+
def column_shading_colour(index, colour)
|
774
|
+
self.each do |row|
|
775
|
+
cell = row[index]
|
776
|
+
cell.shading_colour = colour if cell != nil
|
777
|
+
end
|
778
|
+
end
|
779
|
+
|
780
|
+
# This method provides a means of assigning a shading colour to a
|
781
|
+
# selection of cells within a table. The method accepts a block that
|
782
|
+
# takes three parameters - a TableCellNode representing a cell within the
|
783
|
+
# table, an integer representing the x offset of the cell and an integer
|
784
|
+
# representing the y offset of the cell. If the block returns true then
|
785
|
+
# shading will be applied to the cell.
|
786
|
+
#
|
787
|
+
# ==== Parameters
|
788
|
+
# colour:: A reference to a Colour object representing the shading colour
|
789
|
+
# to be applied. Set to nil to remove shading.
|
790
|
+
def shading_colour(colour)
|
791
|
+
if block_given?
|
792
|
+
0.upto(self.size - 1) do |x|
|
793
|
+
row = self[x]
|
794
|
+
0.upto(row.size - 1) do |y|
|
795
|
+
apply = yield row[y], x, y
|
796
|
+
row[y].shading_colour = colour if apply
|
797
|
+
end
|
798
|
+
end
|
799
|
+
end
|
800
|
+
end
|
801
|
+
|
802
|
+
# This method overloads the store method inherited from the ContainerNode
|
803
|
+
# class to forbid addition of further nodes.
|
804
|
+
#
|
805
|
+
# ==== Parameters
|
806
|
+
# node:: A reference to the node to be added.
|
807
|
+
def store(node)
|
808
|
+
RTFError.fire("Table nodes cannot have nodes added to.")
|
809
|
+
end
|
810
|
+
|
811
|
+
# This method generates the RTF document text for a TableCellNode object.
|
812
|
+
def to_rtf
|
813
|
+
text = StringIO.new
|
814
|
+
size = 0
|
815
|
+
|
816
|
+
self.each do |row|
|
817
|
+
if size > 0
|
818
|
+
text << "\n"
|
819
|
+
else
|
820
|
+
size = 1
|
821
|
+
end
|
822
|
+
text << row.to_rtf
|
823
|
+
end
|
824
|
+
|
825
|
+
text.string.sub(/\\row(?!.*\\row)/m, "\\lastrow\n\\row")
|
826
|
+
end
|
827
|
+
|
828
|
+
alias :column_shading_color :column_shading_colour
|
829
|
+
alias :row_shading_color :row_shading_colour
|
830
|
+
alias :shading_color :shading_colour
|
831
|
+
end # End of the TableNode class.
|
832
|
+
|
833
|
+
|
834
|
+
# This class represents a row within an RTF table. The TableRowNode is a
|
835
|
+
# specialised container node that can hold only TableCellNodes and, once
|
836
|
+
# created, cannot be resized. Its also not possible to change the parent
|
837
|
+
# of a TableRowNode object.
|
838
|
+
class TableRowNode < ContainerNode
|
839
|
+
# This is the constructor for the TableRowNode class.
|
840
|
+
#
|
841
|
+
# ===== Parameters
|
842
|
+
# table:: A reference to table that owns the row.
|
843
|
+
# cells:: The number of cells that the row will contain.
|
844
|
+
# widths:: One or more integers specifying the widths for the table
|
845
|
+
# columns
|
846
|
+
def initialize(table, cells, *widths)
|
847
|
+
super(table) do
|
848
|
+
entries = []
|
849
|
+
cells.times do |index|
|
850
|
+
entries.push(TableCellNode.new(self, widths[index]))
|
851
|
+
end
|
852
|
+
entries
|
853
|
+
end
|
854
|
+
end
|
855
|
+
|
856
|
+
# Attribute accessors
|
857
|
+
def length
|
858
|
+
entries.size
|
859
|
+
end
|
860
|
+
|
861
|
+
# This method assigns a border width setting to all of the sides on all
|
862
|
+
# of the cells within a table row.
|
863
|
+
#
|
864
|
+
# ==== Parameters
|
865
|
+
# width:: The border width setting to apply. Negative values are ignored
|
866
|
+
# and zero switches the border off.
|
867
|
+
def border_width=(width)
|
868
|
+
self.each {|cell| cell.border_width = width}
|
869
|
+
end
|
870
|
+
|
871
|
+
# This method overloads the parent= method inherited from the Node class
|
872
|
+
# to forbid the alteration of the cells parent.
|
873
|
+
#
|
874
|
+
# ==== Parameters
|
875
|
+
# parent:: A reference to the new node parent.
|
876
|
+
def parent=(parent)
|
877
|
+
RTFError.fire("Table row nodes cannot have their parent changed.")
|
878
|
+
end
|
879
|
+
|
880
|
+
# This method sets the shading colour for a row.
|
881
|
+
#
|
882
|
+
# ==== Parameters
|
883
|
+
# colour:: A reference to the Colour object that represents the new
|
884
|
+
# shading colour. Set to nil to switch shading off.
|
885
|
+
def shading_colour=(colour)
|
886
|
+
self.each {|cell| cell.shading_colour = colour}
|
887
|
+
end
|
888
|
+
|
889
|
+
# This method overloads the store method inherited from the ContainerNode
|
890
|
+
# class to forbid addition of further nodes.
|
891
|
+
#
|
892
|
+
# ==== Parameters
|
893
|
+
# node:: A reference to the node to be added.
|
894
|
+
#def store(node)
|
895
|
+
# RTFError.fire("Table row nodes cannot have nodes added to.")
|
896
|
+
#end
|
897
|
+
|
898
|
+
# This method generates the RTF document text for a TableCellNode object.
|
899
|
+
def to_rtf
|
900
|
+
text = StringIO.new
|
901
|
+
temp = StringIO.new
|
902
|
+
offset = 0
|
903
|
+
|
904
|
+
text << "\\trowd\\tgraph#{parent.cell_margin}"
|
905
|
+
self.each do |entry|
|
906
|
+
widths = entry.border_widths
|
907
|
+
colour = entry.shading_colour
|
908
|
+
|
909
|
+
text << "\n"
|
910
|
+
text << "\\clbrdrt\\brdrw#{widths[0]}\\brdrs" if widths[0] != 0
|
911
|
+
text << "\\clbrdrl\\brdrw#{widths[3]}\\brdrs" if widths[3] != 0
|
912
|
+
text << "\\clbrdrb\\brdrw#{widths[2]}\\brdrs" if widths[2] != 0
|
913
|
+
text << "\\clbrdrr\\brdrw#{widths[1]}\\brdrs" if widths[1] != 0
|
914
|
+
text << "\\clcbpat#{root.colours.index(colour)}" if colour != nil
|
915
|
+
text << "\\cellx#{entry.width + offset}"
|
916
|
+
temp << "\n#{entry.to_rtf}"
|
917
|
+
offset += entry.width
|
918
|
+
end
|
919
|
+
text << "#{temp.string}\n\\row"
|
920
|
+
|
921
|
+
text.string
|
922
|
+
end
|
923
|
+
end # End of the TableRowNode class.
|
924
|
+
|
925
|
+
|
926
|
+
# This class represents a cell within an RTF table. The TableCellNode is a
|
927
|
+
# specialised command node that is forbidden from creating tables or having
|
928
|
+
# its parent changed.
|
929
|
+
class TableCellNode < CommandNode
|
930
|
+
# A definition for the default width for the cell.
|
931
|
+
DEFAULT_WIDTH = 300
|
932
|
+
# Top border
|
933
|
+
TOP = 0
|
934
|
+
# Right border
|
935
|
+
RIGHT = 1
|
936
|
+
# Bottom border
|
937
|
+
BOTTOM = 2
|
938
|
+
# Left border
|
939
|
+
LEFT = 3
|
940
|
+
# Width of cell
|
941
|
+
attr_accessor :width
|
942
|
+
# Attribute accessor.
|
943
|
+
attr_reader :shading_colour, :style
|
944
|
+
|
945
|
+
# This is the constructor for the TableCellNode class.
|
946
|
+
#
|
947
|
+
# ==== Parameters
|
948
|
+
# row:: The row that the cell belongs to.
|
949
|
+
# width:: The width to be assigned to the cell. This defaults to
|
950
|
+
# TableCellNode::DEFAULT_WIDTH.
|
951
|
+
# style:: The style that is applied to the cell. This must be a
|
952
|
+
# ParagraphStyle class. Defaults to nil.
|
953
|
+
# top:: The border width for the cells top border. Defaults to nil.
|
954
|
+
# right:: The border width for the cells right hand border. Defaults to
|
955
|
+
# nil.
|
956
|
+
# bottom:: The border width for the cells bottom border. Defaults to nil.
|
957
|
+
# left:: The border width for the cells left hand border. Defaults to
|
958
|
+
# nil.
|
959
|
+
#
|
960
|
+
# ==== Exceptions
|
961
|
+
# RTFError:: Generated whenever an invalid style setting is specified.
|
962
|
+
def initialize(row, width=DEFAULT_WIDTH, style=nil, top=nil, right=nil,
|
963
|
+
bottom=nil, left=nil)
|
964
|
+
super(row, nil)
|
965
|
+
if !style.nil? and !style.is_paragraph_style?
|
966
|
+
RTFError.fire("Non-paragraph style specified for TableCellNode "\
|
967
|
+
"constructor.")
|
968
|
+
end
|
969
|
+
|
970
|
+
@width = (width != nil && width > 0) ? width : DEFAULT_WIDTH
|
971
|
+
@borders = [(top != nil && top > 0) ? top : nil,
|
972
|
+
(right != nil && right > 0) ? right : nil,
|
973
|
+
(bottom != nil && bottom > 0) ? bottom : nil,
|
974
|
+
(left != nil && left > 0) ? left : nil]
|
975
|
+
@shading_colour = nil
|
976
|
+
@style = style
|
977
|
+
end
|
978
|
+
|
979
|
+
# Attribute mutator.
|
980
|
+
#
|
981
|
+
# ==== Parameters
|
982
|
+
# style:: A reference to the style object to be applied to the cell.
|
983
|
+
# Must be an instance of the ParagraphStyle class. Set to nil
|
984
|
+
# to clear style settings.
|
985
|
+
#
|
986
|
+
# ==== Exceptions
|
987
|
+
# RTFError:: Generated whenever an invalid style setting is specified.
|
988
|
+
def style=(style)
|
989
|
+
if !style.nil? and !style.is_paragraph_style?
|
990
|
+
RTFError.fire("Non-paragraph style specified for TableCellNode "\
|
991
|
+
"constructor.")
|
992
|
+
end
|
993
|
+
@style = style
|
994
|
+
end
|
995
|
+
|
996
|
+
# This method assigns a width, in twips, for the borders on all sides of
|
997
|
+
# the cell. Negative widths will be ignored and a width of zero will
|
998
|
+
# switch the border off.
|
999
|
+
#
|
1000
|
+
# ==== Parameters
|
1001
|
+
# width:: The setting for the width of the border.
|
1002
|
+
def border_width=(width)
|
1003
|
+
size = width.nil? ? 0 : width
|
1004
|
+
if size > 0
|
1005
|
+
@borders[TOP] = @borders[RIGHT] = @borders[BOTTOM] = @borders[LEFT] = size.to_i
|
1006
|
+
else
|
1007
|
+
@borders = [nil, nil, nil, nil]
|
1008
|
+
end
|
1009
|
+
end
|
1010
|
+
|
1011
|
+
# This method assigns a border width to the top side of a table cell.
|
1012
|
+
# Negative values are ignored and a value of 0 switches the border off.
|
1013
|
+
#
|
1014
|
+
# ==== Parameters
|
1015
|
+
# width:: The new border width setting.
|
1016
|
+
def top_border_width=(width)
|
1017
|
+
size = width.nil? ? 0 : width
|
1018
|
+
if size > 0
|
1019
|
+
@borders[TOP] = size.to_i
|
1020
|
+
else
|
1021
|
+
@borders[TOP] = nil
|
1022
|
+
end
|
1023
|
+
end
|
1024
|
+
|
1025
|
+
# This method assigns a border width to the right side of a table cell.
|
1026
|
+
# Negative values are ignored and a value of 0 switches the border off.
|
1027
|
+
#
|
1028
|
+
# ==== Parameters
|
1029
|
+
# width:: The new border width setting.
|
1030
|
+
def right_border_width=(width)
|
1031
|
+
size = width.nil? ? 0 : width
|
1032
|
+
if size > 0
|
1033
|
+
@borders[RIGHT] = size.to_i
|
1034
|
+
else
|
1035
|
+
@borders[RIGHT] = nil
|
1036
|
+
end
|
1037
|
+
end
|
1038
|
+
|
1039
|
+
# This method assigns a border width to the bottom side of a table cell.
|
1040
|
+
# Negative values are ignored and a value of 0 switches the border off.
|
1041
|
+
#
|
1042
|
+
# ==== Parameters
|
1043
|
+
# width:: The new border width setting.
|
1044
|
+
def bottom_border_width=(width)
|
1045
|
+
size = width.nil? ? 0 : width
|
1046
|
+
if size > 0
|
1047
|
+
@borders[BOTTOM] = size.to_i
|
1048
|
+
else
|
1049
|
+
@borders[BOTTOM] = nil
|
1050
|
+
end
|
1051
|
+
end
|
1052
|
+
|
1053
|
+
# This method assigns a border width to the left side of a table cell.
|
1054
|
+
# Negative values are ignored and a value of 0 switches the border off.
|
1055
|
+
#
|
1056
|
+
# ==== Parameters
|
1057
|
+
# width:: The new border width setting.
|
1058
|
+
def left_border_width=(width)
|
1059
|
+
size = width.nil? ? 0 : width
|
1060
|
+
if size > 0
|
1061
|
+
@borders[LEFT] = size.to_i
|
1062
|
+
else
|
1063
|
+
@borders[LEFT] = nil
|
1064
|
+
end
|
1065
|
+
end
|
1066
|
+
|
1067
|
+
# This method alters the shading colour associated with a TableCellNode
|
1068
|
+
# object.
|
1069
|
+
#
|
1070
|
+
# ==== Parameters
|
1071
|
+
# colour:: A reference to the Colour object to use in shading the cell.
|
1072
|
+
# Assign nil to clear cell shading.
|
1073
|
+
def shading_colour=(colour)
|
1074
|
+
root.colours << colour
|
1075
|
+
@shading_colour = colour
|
1076
|
+
end
|
1077
|
+
|
1078
|
+
# This method retrieves an array with the cell border width settings.
|
1079
|
+
# The values are inserted in top, right, bottom, left order.
|
1080
|
+
def border_widths
|
1081
|
+
widths = []
|
1082
|
+
@borders.each {|entry| widths.push(entry.nil? ? 0 : entry)}
|
1083
|
+
widths
|
1084
|
+
end
|
1085
|
+
|
1086
|
+
# This method fetches the width for top border of a cell.
|
1087
|
+
def top_border_width
|
1088
|
+
@borders[TOP].nil? ? 0 : @borders[TOP]
|
1089
|
+
end
|
1090
|
+
|
1091
|
+
# This method fetches the width for right border of a cell.
|
1092
|
+
def right_border_width
|
1093
|
+
@borders[RIGHT].nil? ? 0 : @borders[RIGHT]
|
1094
|
+
end
|
1095
|
+
|
1096
|
+
# This method fetches the width for bottom border of a cell.
|
1097
|
+
def bottom_border_width
|
1098
|
+
@borders[BOTTOM].nil? ? 0 : @borders[BOTTOM]
|
1099
|
+
end
|
1100
|
+
|
1101
|
+
# This method fetches the width for left border of a cell.
|
1102
|
+
def left_border_width
|
1103
|
+
@borders[LEFT].nil? ? 0 : @borders[LEFT]
|
1104
|
+
end
|
1105
|
+
|
1106
|
+
# This method overloads the paragraph method inherited from the
|
1107
|
+
# ComamndNode class to forbid the creation of paragraphs.
|
1108
|
+
#
|
1109
|
+
# ==== Parameters
|
1110
|
+
# style:: The paragraph style, ignored
|
1111
|
+
def paragraph(style=nil)
|
1112
|
+
RTFError.fire("TableCellNode#paragraph() called. Table cells cannot "\
|
1113
|
+
"contain paragraphs.")
|
1114
|
+
end
|
1115
|
+
|
1116
|
+
# This method overloads the parent= method inherited from the Node class
|
1117
|
+
# to forbid the alteration of the cells parent.
|
1118
|
+
#
|
1119
|
+
# ==== Parameters
|
1120
|
+
# parent:: A reference to the new node parent.
|
1121
|
+
def parent=(parent)
|
1122
|
+
RTFError.fire("Table cell nodes cannot have their parent changed.")
|
1123
|
+
end
|
1124
|
+
|
1125
|
+
# This method overrides the table method inherited from CommandNode to
|
1126
|
+
# forbid its use in table cells.
|
1127
|
+
#
|
1128
|
+
# ==== Parameters
|
1129
|
+
# rows:: The number of rows for the table.
|
1130
|
+
# columns:: The number of columns for the table.
|
1131
|
+
# *widths:: One or more integers representing the widths for the table
|
1132
|
+
# columns.
|
1133
|
+
def table(rows, columns, *widths)
|
1134
|
+
RTFError.fire("TableCellNode#table() called. Nested tables not allowed.")
|
1135
|
+
end
|
1136
|
+
|
1137
|
+
# This method generates the RTF document text for a TableCellNode object.
|
1138
|
+
def to_rtf
|
1139
|
+
text = StringIO.new
|
1140
|
+
separator = split? ? "\n" : " "
|
1141
|
+
line = (separator == " ")
|
1142
|
+
|
1143
|
+
text << "\\pard\\intbl"
|
1144
|
+
text << @style.prefix(root) if @style != nil
|
1145
|
+
text << separator
|
1146
|
+
self.each do |entry|
|
1147
|
+
text << "\n" if line
|
1148
|
+
line = true
|
1149
|
+
text << entry.to_rtf
|
1150
|
+
end
|
1151
|
+
text << (split? ? "\n" : " ")
|
1152
|
+
text << "\\cell"
|
1153
|
+
|
1154
|
+
text.string
|
1155
|
+
end
|
1156
|
+
end # End of the TableCellNode class.
|
1157
|
+
|
1158
|
+
|
1159
|
+
# This class represents a document header.
|
1160
|
+
class HeaderNode < CommandNode
|
1161
|
+
# A definition for a header type.
|
1162
|
+
UNIVERSAL = :header
|
1163
|
+
|
1164
|
+
# A definition for a header type.
|
1165
|
+
LEFT_PAGE = :headerl
|
1166
|
+
|
1167
|
+
# A definition for a header type.
|
1168
|
+
RIGHT_PAGE = :headerr
|
1169
|
+
|
1170
|
+
# A definition for a header type.
|
1171
|
+
FIRST_PAGE = :headerf
|
1172
|
+
|
1173
|
+
# Attribute accessor.
|
1174
|
+
attr_reader :type
|
1175
|
+
|
1176
|
+
# Attribute mutator.
|
1177
|
+
attr_writer :type
|
1178
|
+
|
1179
|
+
|
1180
|
+
# This is the constructor for the HeaderNode class.
|
1181
|
+
#
|
1182
|
+
# ==== Parameters
|
1183
|
+
# document:: A reference to the Document object that will own the new
|
1184
|
+
# header.
|
1185
|
+
# type:: The style type for the new header. Defaults to a value of
|
1186
|
+
# HeaderNode::UNIVERSAL.
|
1187
|
+
def initialize(document, type=UNIVERSAL)
|
1188
|
+
super(document, "\\#{type.id2name}", nil, false)
|
1189
|
+
@type = type
|
1190
|
+
end
|
1191
|
+
|
1192
|
+
# This method overloads the footnote method inherited from the CommandNode
|
1193
|
+
# class to prevent footnotes being added to headers.
|
1194
|
+
#
|
1195
|
+
# ==== Parameters
|
1196
|
+
# text:: Not used.
|
1197
|
+
#
|
1198
|
+
# ==== Exceptions
|
1199
|
+
# RTFError:: Always generated whenever this method is called.
|
1200
|
+
def footnote(text)
|
1201
|
+
RTFError.fire("Footnotes are not permitted in page headers.")
|
1202
|
+
end
|
1203
|
+
end # End of the HeaderNode class.
|
1204
|
+
|
1205
|
+
|
1206
|
+
# This class represents a document footer.
|
1207
|
+
class FooterNode < CommandNode
|
1208
|
+
# A definition for a header type.
|
1209
|
+
UNIVERSAL = :footer
|
1210
|
+
|
1211
|
+
# A definition for a header type.
|
1212
|
+
LEFT_PAGE = :footerl
|
1213
|
+
|
1214
|
+
# A definition for a header type.
|
1215
|
+
RIGHT_PAGE = :footerr
|
1216
|
+
|
1217
|
+
# A definition for a header type.
|
1218
|
+
FIRST_PAGE = :footerf
|
1219
|
+
|
1220
|
+
# Attribute accessor.
|
1221
|
+
attr_reader :type
|
1222
|
+
|
1223
|
+
# Attribute mutator.
|
1224
|
+
attr_writer :type
|
1225
|
+
|
1226
|
+
|
1227
|
+
# This is the constructor for the FooterNode class.
|
1228
|
+
#
|
1229
|
+
# ==== Parameters
|
1230
|
+
# document:: A reference to the Document object that will own the new
|
1231
|
+
# footer.
|
1232
|
+
# type:: The style type for the new footer. Defaults to a value of
|
1233
|
+
# FooterNode::UNIVERSAL.
|
1234
|
+
def initialize(document, type=UNIVERSAL)
|
1235
|
+
super(document, "\\#{type.id2name}", nil, false)
|
1236
|
+
@type = type
|
1237
|
+
end
|
1238
|
+
|
1239
|
+
# This method overloads the footnote method inherited from the CommandNode
|
1240
|
+
# class to prevent footnotes being added to footers.
|
1241
|
+
#
|
1242
|
+
# ==== Parameters
|
1243
|
+
# text:: Not used.
|
1244
|
+
#
|
1245
|
+
# ==== Exceptions
|
1246
|
+
# RTFError:: Always generated whenever this method is called.
|
1247
|
+
def footnote(text)
|
1248
|
+
RTFError.fire("Footnotes are not permitted in page footers.")
|
1249
|
+
end
|
1250
|
+
end # End of the FooterNode class.
|
1251
|
+
|
1252
|
+
|
1253
|
+
# This class represents an image within a RTF document. Currently only the
|
1254
|
+
# PNG, JPEG and Windows Bitmap formats are supported. Efforts are made to
|
1255
|
+
# identify the file type but these are not guaranteed to work.
|
1256
|
+
class ImageNode < Node
|
1257
|
+
# A definition for an image type constant.
|
1258
|
+
PNG = :pngblip
|
1259
|
+
|
1260
|
+
# A definition for an image type constant.
|
1261
|
+
JPEG = :jpegblip
|
1262
|
+
|
1263
|
+
# A definition for an image type constant.
|
1264
|
+
BITMAP = :dibitmap0
|
1265
|
+
|
1266
|
+
# A definition for an architecture endian constant.
|
1267
|
+
LITTLE_ENDIAN = :little
|
1268
|
+
|
1269
|
+
# A definition for an architecture endian constant.
|
1270
|
+
BIG_ENDIAN = :big
|
1271
|
+
|
1272
|
+
# Offsets for reading dimension data by filetype
|
1273
|
+
DIMENSIONS_OFFSET = {
|
1274
|
+
JPEG => 2,
|
1275
|
+
PNG => 8,
|
1276
|
+
BITMAP => 8,
|
1277
|
+
}.freeze
|
1278
|
+
|
1279
|
+
# Attribute accessor.
|
1280
|
+
attr_reader :x_scaling, :y_scaling, :top_crop, :right_crop, :bottom_crop,
|
1281
|
+
:left_crop, :width, :height, :displayed_width, :displayed_height
|
1282
|
+
|
1283
|
+
# Attribute mutator.
|
1284
|
+
attr_writer :x_scaling, :y_scaling, :top_crop, :right_crop, :bottom_crop,
|
1285
|
+
:left_crop, :displayed_width, :displayed_height
|
1286
|
+
|
1287
|
+
|
1288
|
+
# This is the constructor for the ImageNode class.
|
1289
|
+
#
|
1290
|
+
# ==== Parameters
|
1291
|
+
# parent:: A reference to the node that owns the new image node.
|
1292
|
+
# source:: A reference to the image source. This must be a String or a
|
1293
|
+
# File.
|
1294
|
+
# id:: The unique identifier for the image node.
|
1295
|
+
#
|
1296
|
+
# ==== Exceptions
|
1297
|
+
# RTFError:: Generated whenever the image specified is not recognised as
|
1298
|
+
# a supported image type, something other than a String or
|
1299
|
+
# File or IO is passed as the source parameter or if the
|
1300
|
+
# specified source does not exist or cannot be accessed.
|
1301
|
+
def initialize(parent, source, id)
|
1302
|
+
super(parent)
|
1303
|
+
@source = nil
|
1304
|
+
@id = id
|
1305
|
+
@type = nil
|
1306
|
+
@x_scaling = @y_scaling = nil
|
1307
|
+
@top_crop = @right_crop = @bottom_crop = @left_crop = nil
|
1308
|
+
@width = @height = nil
|
1309
|
+
@displayed_width = @displayed_height = nil
|
1310
|
+
|
1311
|
+
# store path to image
|
1312
|
+
@source = source if source.instance_of?(String) || source.instance_of?(Tempfile)
|
1313
|
+
@source = source.path if source.instance_of?(File)
|
1314
|
+
|
1315
|
+
# Check the file's existence and accessibility.
|
1316
|
+
if !File.exist?(@source)
|
1317
|
+
RTFError.fire("Unable to find the #{File.basename(@source)} file.")
|
1318
|
+
end
|
1319
|
+
if !File.readable?(@source)
|
1320
|
+
RTFError.fire("Access to the #{File.basename(@source)} file denied.")
|
1321
|
+
end
|
1322
|
+
|
1323
|
+
@type = get_file_type
|
1324
|
+
if @type == nil
|
1325
|
+
RTFError.fire("The #{File.basename(@source)} file contains an "\
|
1326
|
+
"unknown or unsupported image type.")
|
1327
|
+
end
|
1328
|
+
|
1329
|
+
@width, @height = get_dimensions
|
1330
|
+
end
|
1331
|
+
|
1332
|
+
def open_file(&block)
|
1333
|
+
if block
|
1334
|
+
File.open(@source, 'rb', &block)
|
1335
|
+
else
|
1336
|
+
File.open(@source, 'rb')
|
1337
|
+
end
|
1338
|
+
end
|
1339
|
+
|
1340
|
+
# This method attempts to determine the image type associated with a
|
1341
|
+
# file, returning nil if it fails to make the determination.
|
1342
|
+
def get_file_type
|
1343
|
+
type = nil
|
1344
|
+
read = []
|
1345
|
+
open_file do |file|
|
1346
|
+
|
1347
|
+
# Check if the file is a JPEG.
|
1348
|
+
read_source(file, read, 2)
|
1349
|
+
if read[0,2] == [255, 216]
|
1350
|
+
type = JPEG
|
1351
|
+
else
|
1352
|
+
# Check if it's a PNG.
|
1353
|
+
read_source(file, read, 6)
|
1354
|
+
if read[0,8] == [137, 80, 78, 71, 13, 10, 26, 10]
|
1355
|
+
type = PNG
|
1356
|
+
else
|
1357
|
+
# Check if its a bitmap.
|
1358
|
+
if read[0,2] == [66, 77]
|
1359
|
+
size = to_integer(read[2,4])
|
1360
|
+
type = BITMAP if size == File.size(@source)
|
1361
|
+
end
|
1362
|
+
end
|
1363
|
+
end
|
1364
|
+
|
1365
|
+
end
|
1366
|
+
|
1367
|
+
type
|
1368
|
+
end
|
1369
|
+
|
1370
|
+
# This method generates the RTF for an ImageNode object.
|
1371
|
+
def to_rtf
|
1372
|
+
text = StringIO.new
|
1373
|
+
count = 0
|
1374
|
+
|
1375
|
+
#text << '{\pard{\*\shppict{\pict'
|
1376
|
+
text << '{\*\shppict{\pict'
|
1377
|
+
text << "\\picscalex#{@x_scaling}" if @x_scaling != nil
|
1378
|
+
text << "\\picscaley#{@y_scaling}" if @y_scaling != nil
|
1379
|
+
text << "\\piccropl#{@left_crop}" if @left_crop != nil
|
1380
|
+
text << "\\piccropr#{@right_crop}" if @right_crop != nil
|
1381
|
+
text << "\\piccropt#{@top_crop}" if @top_crop != nil
|
1382
|
+
text << "\\piccropb#{@bottom_crop}" if @bottom_crop != nil
|
1383
|
+
text << "\\picwgoal#{@displayed_width}" if @displayed_width != nil
|
1384
|
+
text << "\\pichgoal#{@displayed_height}" if @displayed_height != nil
|
1385
|
+
text << "\\picw#{@width}\\pich#{@height}\\bliptag#{@id}"
|
1386
|
+
text << "\\#{@type.id2name}\n"
|
1387
|
+
|
1388
|
+
open_file do |file|
|
1389
|
+
file.each_byte do |byte|
|
1390
|
+
hex_str = byte.to_s(16)
|
1391
|
+
hex_str.insert(0,'0') if hex_str.length == 1
|
1392
|
+
text << hex_str
|
1393
|
+
count += 1
|
1394
|
+
if count == 40
|
1395
|
+
text << "\n"
|
1396
|
+
count = 0
|
1397
|
+
end
|
1398
|
+
end
|
1399
|
+
end
|
1400
|
+
#text << "\n}}\\par}"
|
1401
|
+
text << "\n}}"
|
1402
|
+
|
1403
|
+
text.string
|
1404
|
+
end
|
1405
|
+
|
1406
|
+
# This method is used to determine the underlying endianness of a
|
1407
|
+
# platform.
|
1408
|
+
def get_endian
|
1409
|
+
[0, 125].pack('c2').unpack('s') == [125] ? BIG_ENDIAN : LITTLE_ENDIAN
|
1410
|
+
end
|
1411
|
+
|
1412
|
+
# This method converts an array to an integer. The array must be either
|
1413
|
+
# two or four bytes in length.
|
1414
|
+
#
|
1415
|
+
# ==== Parameters
|
1416
|
+
# array:: A reference to the array containing the data to be converted.
|
1417
|
+
# signed:: A boolean to indicate whether the value is signed. Defaults
|
1418
|
+
# to false.
|
1419
|
+
def to_integer(array, signed=false)
|
1420
|
+
from = nil
|
1421
|
+
to = nil
|
1422
|
+
data = []
|
1423
|
+
|
1424
|
+
if array.size == 2
|
1425
|
+
data.concat(get_endian == BIG_ENDIAN ? array.reverse : array)
|
1426
|
+
from = 'C2'
|
1427
|
+
to = signed ? 's' : 'S'
|
1428
|
+
else
|
1429
|
+
data.concat(get_endian == BIG_ENDIAN ? array[0,4].reverse : array)
|
1430
|
+
from = 'C4'
|
1431
|
+
to = signed ? 'l' : 'L'
|
1432
|
+
end
|
1433
|
+
data.pack(from).unpack(to)[0]
|
1434
|
+
end
|
1435
|
+
|
1436
|
+
# This method loads the data for an image from its source. The method
|
1437
|
+
# accepts two call approaches. If called without a block then the method
|
1438
|
+
# considers the size parameter it is passed. If called with a block the
|
1439
|
+
# method executes until the block returns true.
|
1440
|
+
#
|
1441
|
+
# ==== Parameters
|
1442
|
+
# size:: The maximum number of bytes to be read from the file. Defaults
|
1443
|
+
# to nil to indicate that the remainder of the file should be read
|
1444
|
+
# in.
|
1445
|
+
def read_source(file, read, size=nil)
|
1446
|
+
if block_given?
|
1447
|
+
done = false
|
1448
|
+
|
1449
|
+
while !done and !file.eof?
|
1450
|
+
read << file.getbyte
|
1451
|
+
done = yield read[-1]
|
1452
|
+
end
|
1453
|
+
else
|
1454
|
+
if size != nil
|
1455
|
+
if size > 0
|
1456
|
+
total = 0
|
1457
|
+
while !file.eof? and total < size
|
1458
|
+
read << file.getbyte
|
1459
|
+
total += 1
|
1460
|
+
end
|
1461
|
+
end
|
1462
|
+
else
|
1463
|
+
file.each_byte {|byte| read << byte}
|
1464
|
+
end
|
1465
|
+
end
|
1466
|
+
end
|
1467
|
+
|
1468
|
+
|
1469
|
+
# This method fetches details of the dimensions associated with an image.
|
1470
|
+
def get_dimensions
|
1471
|
+
dimensions = nil
|
1472
|
+
|
1473
|
+
open_file do |file|
|
1474
|
+
file.pos = DIMENSIONS_OFFSET[@type]
|
1475
|
+
read = []
|
1476
|
+
|
1477
|
+
# Check the image type.
|
1478
|
+
if @type == JPEG
|
1479
|
+
# Read until we can't anymore or we've found what we're looking for.
|
1480
|
+
done = false
|
1481
|
+
while !file.eof? and !done
|
1482
|
+
# Read to the next marker.
|
1483
|
+
read_source(file,read) {|c| c == 0xff} # Read to the marker.
|
1484
|
+
read_source(file,read) {|c| c != 0xff} # Skip any padding.
|
1485
|
+
|
1486
|
+
if read[-1] >= 0xc0 && read[-1] <= 0xc3
|
1487
|
+
# Read in the width and height details.
|
1488
|
+
read_source(file, read, 7)
|
1489
|
+
dimensions = read[-4,4].pack('C4').unpack('nn').reverse
|
1490
|
+
done = true
|
1491
|
+
else
|
1492
|
+
# Skip the marker block.
|
1493
|
+
read_source(file, read, 2)
|
1494
|
+
read_source(file, read, read[-2,2].pack('C2').unpack('n')[0] - 2)
|
1495
|
+
end
|
1496
|
+
end
|
1497
|
+
elsif @type == PNG
|
1498
|
+
# Read in the data to contain the width and height.
|
1499
|
+
read_source(file, read, 16)
|
1500
|
+
dimensions = read[-8,8].pack('C8').unpack('N2')
|
1501
|
+
elsif @type == BITMAP
|
1502
|
+
# Read in the data to contain the width and height.
|
1503
|
+
read_source(file, read, 18)
|
1504
|
+
dimensions = [to_integer(read[-8,4]), to_integer(read[-4,4])]
|
1505
|
+
end
|
1506
|
+
end
|
1507
|
+
|
1508
|
+
dimensions
|
1509
|
+
end
|
1510
|
+
|
1511
|
+
private :get_file_type, :to_integer, :get_endian, :get_dimensions, :open_file
|
1512
|
+
end # End of the ImageNode class.
|
1513
|
+
|
1514
|
+
|
1515
|
+
# This class represents an RTF document. In actuality it is just a
|
1516
|
+
# specialised Node type that cannot be assigned a parent and that holds
|
1517
|
+
# document font, colour and information tables.
|
1518
|
+
class Document < CommandNode
|
1519
|
+
# A definition for a document character set setting.
|
1520
|
+
CS_ANSI = :ansi
|
1521
|
+
|
1522
|
+
# A definition for a document character set setting.
|
1523
|
+
CS_MAC = :mac
|
1524
|
+
|
1525
|
+
# A definition for a document character set setting.
|
1526
|
+
CS_PC = :pc
|
1527
|
+
|
1528
|
+
# A definition for a document character set setting.
|
1529
|
+
CS_PCA = :pca
|
1530
|
+
|
1531
|
+
# A definition for a document language setting.
|
1532
|
+
LC_AFRIKAANS = 1078
|
1533
|
+
|
1534
|
+
# A definition for a document language setting.
|
1535
|
+
LC_ARABIC = 1025
|
1536
|
+
|
1537
|
+
# A definition for a document language setting.
|
1538
|
+
LC_CATALAN = 1027
|
1539
|
+
|
1540
|
+
# A definition for a document language setting.
|
1541
|
+
LC_CHINESE_TRADITIONAL = 1028
|
1542
|
+
|
1543
|
+
# A definition for a document language setting.
|
1544
|
+
LC_CHINESE_SIMPLIFIED = 2052
|
1545
|
+
|
1546
|
+
# A definition for a document language setting.
|
1547
|
+
LC_CZECH = 1029
|
1548
|
+
|
1549
|
+
# A definition for a document language setting.
|
1550
|
+
LC_DANISH = 1030
|
1551
|
+
|
1552
|
+
# A definition for a document language setting.
|
1553
|
+
LC_DUTCH = 1043
|
1554
|
+
|
1555
|
+
# A definition for a document language setting.
|
1556
|
+
LC_DUTCH_BELGIAN = 2067
|
1557
|
+
|
1558
|
+
# A definition for a document language setting.
|
1559
|
+
LC_ENGLISH_UK = 2057
|
1560
|
+
|
1561
|
+
# A definition for a document language setting.
|
1562
|
+
LC_ENGLISH_US = 1033
|
1563
|
+
|
1564
|
+
# A definition for a document language setting.
|
1565
|
+
LC_FINNISH = 1035
|
1566
|
+
|
1567
|
+
# A definition for a document language setting.
|
1568
|
+
LC_FRENCH = 1036
|
1569
|
+
|
1570
|
+
# A definition for a document language setting.
|
1571
|
+
LC_FRENCH_BELGIAN = 2060
|
1572
|
+
|
1573
|
+
# A definition for a document language setting.
|
1574
|
+
LC_FRENCH_CANADIAN = 3084
|
1575
|
+
|
1576
|
+
# A definition for a document language setting.
|
1577
|
+
LC_FRENCH_SWISS = 4108
|
1578
|
+
|
1579
|
+
# A definition for a document language setting.
|
1580
|
+
LC_GERMAN = 1031
|
1581
|
+
|
1582
|
+
# A definition for a document language setting.
|
1583
|
+
LC_GERMAN_SWISS = 2055
|
1584
|
+
|
1585
|
+
# A definition for a document language setting.
|
1586
|
+
LC_GREEK = 1032
|
1587
|
+
|
1588
|
+
# A definition for a document language setting.
|
1589
|
+
LC_HEBREW = 1037
|
1590
|
+
|
1591
|
+
# A definition for a document language setting.
|
1592
|
+
LC_HUNGARIAN = 1038
|
1593
|
+
|
1594
|
+
# A definition for a document language setting.
|
1595
|
+
LC_ICELANDIC = 1039
|
1596
|
+
|
1597
|
+
# A definition for a document language setting.
|
1598
|
+
LC_INDONESIAN = 1057
|
1599
|
+
|
1600
|
+
# A definition for a document language setting.
|
1601
|
+
LC_ITALIAN = 1040
|
1602
|
+
|
1603
|
+
# A definition for a document language setting.
|
1604
|
+
LC_JAPANESE = 1041
|
1605
|
+
|
1606
|
+
# A definition for a document language setting.
|
1607
|
+
LC_KOREAN = 1042
|
1608
|
+
|
1609
|
+
# A definition for a document language setting.
|
1610
|
+
LC_NORWEGIAN_BOKMAL = 1044
|
1611
|
+
|
1612
|
+
# A definition for a document language setting.
|
1613
|
+
LC_NORWEGIAN_NYNORSK = 2068
|
1614
|
+
|
1615
|
+
# A definition for a document language setting.
|
1616
|
+
LC_POLISH = 1045
|
1617
|
+
|
1618
|
+
# A definition for a document language setting.
|
1619
|
+
LC_PORTUGUESE = 2070
|
1620
|
+
|
1621
|
+
# A definition for a document language setting.
|
1622
|
+
LC_POTUGUESE_BRAZILIAN = 1046
|
1623
|
+
|
1624
|
+
# A definition for a document language setting.
|
1625
|
+
LC_ROMANIAN = 1048
|
1626
|
+
|
1627
|
+
# A definition for a document language setting.
|
1628
|
+
LC_RUSSIAN = 1049
|
1629
|
+
|
1630
|
+
# A definition for a document language setting.
|
1631
|
+
LC_SERBO_CROATIAN_CYRILLIC = 2074
|
1632
|
+
|
1633
|
+
# A definition for a document language setting.
|
1634
|
+
LC_SERBO_CROATIAN_LATIN = 1050
|
1635
|
+
|
1636
|
+
# A definition for a document language setting.
|
1637
|
+
LC_SLOVAK = 1051
|
1638
|
+
|
1639
|
+
# A definition for a document language setting.
|
1640
|
+
LC_SPANISH_CASTILLIAN = 1034
|
1641
|
+
|
1642
|
+
# A definition for a document language setting.
|
1643
|
+
LC_SPANISH_MEXICAN = 2058
|
1644
|
+
|
1645
|
+
# A definition for a document language setting.
|
1646
|
+
LC_SWAHILI = 1089
|
1647
|
+
|
1648
|
+
# A definition for a document language setting.
|
1649
|
+
LC_SWEDISH = 1053
|
1650
|
+
|
1651
|
+
# A definition for a document language setting.
|
1652
|
+
LC_THAI = 1054
|
1653
|
+
|
1654
|
+
# A definition for a document language setting.
|
1655
|
+
LC_TURKISH = 1055
|
1656
|
+
|
1657
|
+
# A definition for a document language setting.
|
1658
|
+
LC_UNKNOWN = 1024
|
1659
|
+
|
1660
|
+
# A definition for a document language setting.
|
1661
|
+
LC_VIETNAMESE = 1066
|
1662
|
+
|
1663
|
+
# Attribute accessor.
|
1664
|
+
attr_reader :fonts, :lists, :colours, :information, :character_set,
|
1665
|
+
:language, :style, :stylesheet
|
1666
|
+
|
1667
|
+
# Attribute mutator.
|
1668
|
+
attr_writer :character_set, :language, :stylesheet
|
1669
|
+
|
1670
|
+
|
1671
|
+
# This is a constructor for the Document class.
|
1672
|
+
#
|
1673
|
+
# ==== Parameters
|
1674
|
+
# options:: A hashmap of options to use in initializing the document
|
1675
|
+
# default_font:: A font object OR string encapsulating the default
|
1676
|
+
# font to be used by the document.
|
1677
|
+
# STRING FORMAT "<FAMILY>:<Name>"
|
1678
|
+
# DEFAULT "SWISS:Helvetica".
|
1679
|
+
# document_style:: A DocumentStyle object OR options hash encapsulating
|
1680
|
+
# the style settings to be applied to the document.
|
1681
|
+
# DEFAULT DocumentStyle object with default settings.
|
1682
|
+
# character_set:: The character set to be applied to the document.
|
1683
|
+
# DEFAULT Document::CS_ANSI.
|
1684
|
+
# language:: The language setting to be applied to document.
|
1685
|
+
# DEFAULT Document::LC_ENGLISH_UK.
|
1686
|
+
# suppress_system_styles:: A Boolean indicating whether or not to add
|
1687
|
+
# the \noqfpromote control word, which indicates
|
1688
|
+
# "quick" or default styles should be suppressed.
|
1689
|
+
# DEFAULT false.
|
1690
|
+
# stylesheet:: A Stylesheet object OR hashmap array encapsulating
|
1691
|
+
# the styles to make available throughout the document.
|
1692
|
+
# DEFAULT nil.
|
1693
|
+
def initialize(options = {})
|
1694
|
+
# load default options
|
1695
|
+
options = {
|
1696
|
+
"default_font" => "SWISS:Helvetica",
|
1697
|
+
"document_style" => DocumentStyle.new,
|
1698
|
+
"character_set" => CS_ANSI,
|
1699
|
+
"language" => LC_ENGLISH_US,
|
1700
|
+
"suppress_system_styles" => false,
|
1701
|
+
"stylesheet" => nil
|
1702
|
+
}.merge(options)
|
1703
|
+
|
1704
|
+
super(nil, '\rtf1')
|
1705
|
+
|
1706
|
+
# parse font
|
1707
|
+
font = options.delete("default_font")
|
1708
|
+
case font
|
1709
|
+
when Font
|
1710
|
+
when String
|
1711
|
+
font = Font.from_string(font)
|
1712
|
+
else
|
1713
|
+
RTFError.fire("Unreconized font format #{font.class.to_s}")
|
1714
|
+
end # case
|
1715
|
+
|
1716
|
+
# parse document style
|
1717
|
+
style = options.delete("document_style")
|
1718
|
+
case style
|
1719
|
+
when DocumentStyle
|
1720
|
+
when Hash
|
1721
|
+
style = DocumentStyle.new(style)
|
1722
|
+
else
|
1723
|
+
RTFError.fire("Unreconized document style format #{font.class.to_s}")
|
1724
|
+
end # case
|
1725
|
+
|
1726
|
+
@fonts = FontTable.new(font)
|
1727
|
+
@lists = ListTable.new
|
1728
|
+
@default_font = 0
|
1729
|
+
@colours = ColourTable.new
|
1730
|
+
@information = Information.new
|
1731
|
+
@character_set = options.delete("character_set")
|
1732
|
+
@language = options.delete("language")
|
1733
|
+
@style = style
|
1734
|
+
@headers = [nil, nil, nil, nil]
|
1735
|
+
@footers = [nil, nil, nil, nil]
|
1736
|
+
@id = 0
|
1737
|
+
|
1738
|
+
# parse stylesheet (must be done after font and colour tables are
|
1739
|
+
# initialized since declared styles may push fonts/colours onto the
|
1740
|
+
# tables)
|
1741
|
+
stylesheet = options.delete("stylesheet")
|
1742
|
+
case stylesheet
|
1743
|
+
when Stylesheet
|
1744
|
+
stylesheet.document = self
|
1745
|
+
when Array
|
1746
|
+
stylesheet = Stylesheet.new(self, "styles" => stylesheet)
|
1747
|
+
when Hash
|
1748
|
+
stylesheet = Stylesheet.new(self, stylesheet)
|
1749
|
+
else
|
1750
|
+
RTFError.fire("Unreconized stylesheet format #{font.class.to_s}")
|
1751
|
+
end unless stylesheet.nil? # case
|
1752
|
+
|
1753
|
+
@stylesheet = stylesheet
|
1754
|
+
# additional options
|
1755
|
+
@options = options
|
1756
|
+
end
|
1757
|
+
|
1758
|
+
# This method provides a method that can be called to generate an
|
1759
|
+
# identifier that is unique within the document.
|
1760
|
+
def get_id
|
1761
|
+
@id += 1
|
1762
|
+
Time.now().strftime('%d%m%y') + @id.to_s
|
1763
|
+
end
|
1764
|
+
|
1765
|
+
# Attribute accessor.
|
1766
|
+
def default_font
|
1767
|
+
@fonts[@default_font]
|
1768
|
+
end
|
1769
|
+
|
1770
|
+
# This method assigns a new header to a document. A Document object can
|
1771
|
+
# have up to four header - a default header, a header for left pages, a
|
1772
|
+
# header for right pages and a header for the first page. The method
|
1773
|
+
# checks the header type and stores it appropriately.
|
1774
|
+
#
|
1775
|
+
# ==== Parameters
|
1776
|
+
# header:: A reference to the header object to be stored. Existing header
|
1777
|
+
# objects are overwritten.
|
1778
|
+
def header=(header)
|
1779
|
+
if header.type == HeaderNode::UNIVERSAL
|
1780
|
+
@headers[0] = header
|
1781
|
+
elsif header.type == HeaderNode::LEFT_PAGE
|
1782
|
+
@headers[1] = header
|
1783
|
+
elsif header.type == HeaderNode::RIGHT_PAGE
|
1784
|
+
@headers[2] = header
|
1785
|
+
elsif header.type == HeaderNode::FIRST_PAGE
|
1786
|
+
@headers[3] = header
|
1787
|
+
end
|
1788
|
+
end
|
1789
|
+
|
1790
|
+
# This method assigns a new footer to a document. A Document object can
|
1791
|
+
# have up to four footers - a default footer, a footer for left pages, a
|
1792
|
+
# footer for right pages and a footer for the first page. The method
|
1793
|
+
# checks the footer type and stores it appropriately.
|
1794
|
+
#
|
1795
|
+
# ==== Parameters
|
1796
|
+
# footer:: A reference to the footer object to be stored. Existing footer
|
1797
|
+
# objects are overwritten.
|
1798
|
+
def footer=(footer)
|
1799
|
+
if footer.type == FooterNode::UNIVERSAL
|
1800
|
+
@footers[0] = footer
|
1801
|
+
elsif footer.type == FooterNode::LEFT_PAGE
|
1802
|
+
@footers[1] = footer
|
1803
|
+
elsif footer.type == FooterNode::RIGHT_PAGE
|
1804
|
+
@footers[2] = footer
|
1805
|
+
elsif footer.type == FooterNode::FIRST_PAGE
|
1806
|
+
@footers[3] = footer
|
1807
|
+
end
|
1808
|
+
end
|
1809
|
+
|
1810
|
+
# This method fetches a header from a Document object.
|
1811
|
+
#
|
1812
|
+
# ==== Parameters
|
1813
|
+
# type:: One of the header types defined in the header class. Defaults to
|
1814
|
+
# HeaderNode::UNIVERSAL.
|
1815
|
+
def header(type=HeaderNode::UNIVERSAL)
|
1816
|
+
index = 0
|
1817
|
+
if type == HeaderNode::LEFT_PAGE
|
1818
|
+
index = 1
|
1819
|
+
elsif type == HeaderNode::RIGHT_PAGE
|
1820
|
+
index = 2
|
1821
|
+
elsif type == HeaderNode::FIRST_PAGE
|
1822
|
+
index = 3
|
1823
|
+
end
|
1824
|
+
@headers[index]
|
1825
|
+
end
|
1826
|
+
|
1827
|
+
# This method fetches a footer from a Document object.
|
1828
|
+
#
|
1829
|
+
# ==== Parameters
|
1830
|
+
# type:: One of the footer types defined in the footer class. Defaults to
|
1831
|
+
# FooterNode::UNIVERSAL.
|
1832
|
+
def footer(type=FooterNode::UNIVERSAL)
|
1833
|
+
index = 0
|
1834
|
+
if type == FooterNode::LEFT_PAGE
|
1835
|
+
index = 1
|
1836
|
+
elsif type == FooterNode::RIGHT_PAGE
|
1837
|
+
index = 2
|
1838
|
+
elsif type == FooterNode::FIRST_PAGE
|
1839
|
+
index = 3
|
1840
|
+
end
|
1841
|
+
@footers[index]
|
1842
|
+
end
|
1843
|
+
|
1844
|
+
# Loads a stylesheet for the document from an array of hashmaps
|
1845
|
+
# representing styles
|
1846
|
+
def load_stylesheet(hashmap_array)
|
1847
|
+
@stylesheet = Stylesheet.new(self, hashmap_array)
|
1848
|
+
end
|
1849
|
+
|
1850
|
+
# Attribute mutator.
|
1851
|
+
#
|
1852
|
+
# ==== Parameters
|
1853
|
+
# font:: The new default font for the Document object.
|
1854
|
+
def default_font=(font)
|
1855
|
+
@fonts << font
|
1856
|
+
@default_font = @fonts.index(font)
|
1857
|
+
end
|
1858
|
+
|
1859
|
+
# This method provides a short cut for obtaining the Paper object
|
1860
|
+
# associated with a Document object.
|
1861
|
+
def paper
|
1862
|
+
@style.paper
|
1863
|
+
end
|
1864
|
+
|
1865
|
+
# This method overrides the parent=() method inherited from the
|
1866
|
+
# CommandNode class to disallow setting a parent on a Document object.
|
1867
|
+
#
|
1868
|
+
# ==== Parameters
|
1869
|
+
# parent:: A reference to the new parent node for the Document object.
|
1870
|
+
#
|
1871
|
+
# ==== Exceptions
|
1872
|
+
# RTFError:: Generated whenever this method is called.
|
1873
|
+
def parent=(parent)
|
1874
|
+
RTFError.fire("Document objects may not have a parent.")
|
1875
|
+
end
|
1876
|
+
|
1877
|
+
# This method inserts a page break into a document.
|
1878
|
+
def page_break
|
1879
|
+
self.store(CommandNode.new(self, '\page', nil, false))
|
1880
|
+
nil
|
1881
|
+
end
|
1882
|
+
|
1883
|
+
# This method fetches the width of the available work area space for a
|
1884
|
+
# typical Document object page.
|
1885
|
+
def body_width
|
1886
|
+
@style.body_width
|
1887
|
+
end
|
1888
|
+
|
1889
|
+
# This method fetches the height of the available work area space for a
|
1890
|
+
# a typical Document object page.
|
1891
|
+
def body_height
|
1892
|
+
@style.body_height
|
1893
|
+
end
|
1894
|
+
|
1895
|
+
# This method generates the RTF text for a Document object.
|
1896
|
+
def to_rtf
|
1897
|
+
text = StringIO.new
|
1898
|
+
|
1899
|
+
text << "{#{prefix}\\#{@character_set.id2name}"
|
1900
|
+
text << "\\deff#{@default_font}"
|
1901
|
+
text << "\\deflang#{@language}" if !@language.nil?
|
1902
|
+
text << "\\plain\\fs24\\fet1"
|
1903
|
+
text << "\n#{@fonts.to_rtf}"
|
1904
|
+
text << "\n#{@colours.to_rtf}" if @colours.size > 0
|
1905
|
+
text << "\n\\noqfpromote" if @options["suppress_system_styles"]
|
1906
|
+
text << "\n#{@stylesheet.to_rtf}" if !@stylesheet.nil?
|
1907
|
+
text << "\n#{@information.to_rtf}"
|
1908
|
+
text << "\n#{@lists.to_rtf}"
|
1909
|
+
if @headers.compact != []
|
1910
|
+
text << "\n#{@headers[3].to_rtf}" if !@headers[3].nil?
|
1911
|
+
text << "\n#{@headers[2].to_rtf}" if !@headers[2].nil?
|
1912
|
+
text << "\n#{@headers[1].to_rtf}" if !@headers[1].nil?
|
1913
|
+
if @headers[1].nil? or @headers[2].nil?
|
1914
|
+
text << "\n#{@headers[0].to_rtf}"
|
1915
|
+
end
|
1916
|
+
end
|
1917
|
+
if @footers.compact != []
|
1918
|
+
text << "\n#{@footers[3].to_rtf}" if !@footers[3].nil?
|
1919
|
+
text << "\n#{@footers[2].to_rtf}" if !@footers[2].nil?
|
1920
|
+
text << "\n#{@footers[1].to_rtf}" if !@footers[1].nil?
|
1921
|
+
if @footers[1].nil? or @footers[2].nil?
|
1922
|
+
text << "\n#{@footers[0].to_rtf}"
|
1923
|
+
end
|
1924
|
+
end
|
1925
|
+
text << "\n#{@style.prefix(self)}" if !@style.nil?
|
1926
|
+
self.each {|entry| text << "\n#{entry.to_rtf}"}
|
1927
|
+
text << "\n}"
|
1928
|
+
|
1929
|
+
text.string
|
1930
|
+
end
|
1931
|
+
end # End of the Document class.
|
1932
|
+
end # End of the RTF module.
|