panmind-rtf 0.3.1

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