panmind-rtf 0.3.1

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