clbustos-rtf 0.1.2

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