clbustos-rtf 0.1.2

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