origami 2.0.0 → 2.0.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 (80) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -0
  3. data/bin/gui/config.rb +2 -1
  4. data/bin/gui/file.rb +118 -240
  5. data/bin/gui/gtkhex.rb +5 -5
  6. data/bin/gui/hexview.rb +20 -16
  7. data/bin/gui/imgview.rb +1 -1
  8. data/bin/gui/menu.rb +138 -158
  9. data/bin/gui/properties.rb +46 -48
  10. data/bin/gui/signing.rb +183 -214
  11. data/bin/gui/textview.rb +1 -1
  12. data/bin/gui/treeview.rb +13 -7
  13. data/bin/gui/walker.rb +102 -71
  14. data/bin/gui/xrefs.rb +1 -1
  15. data/bin/pdf2ruby +3 -3
  16. data/bin/pdfcop +18 -11
  17. data/bin/pdfextract +14 -5
  18. data/bin/pdfmetadata +3 -3
  19. data/bin/shell/console.rb +8 -8
  20. data/bin/shell/hexdump.rb +4 -4
  21. data/examples/attachments/nested_document.rb +1 -1
  22. data/examples/javascript/hello_world.rb +3 -3
  23. data/lib/origami.rb +0 -1
  24. data/lib/origami/acroform.rb +3 -3
  25. data/lib/origami/array.rb +1 -3
  26. data/lib/origami/boolean.rb +1 -3
  27. data/lib/origami/catalog.rb +3 -9
  28. data/lib/origami/destinations.rb +2 -2
  29. data/lib/origami/dictionary.rb +15 -29
  30. data/lib/origami/encryption.rb +334 -692
  31. data/lib/origami/extensions/fdf.rb +3 -2
  32. data/lib/origami/extensions/ppklite.rb +5 -9
  33. data/lib/origami/filespec.rb +2 -2
  34. data/lib/origami/filters.rb +54 -36
  35. data/lib/origami/filters/ascii.rb +67 -49
  36. data/lib/origami/filters/ccitt.rb +4 -236
  37. data/lib/origami/filters/ccitt/tables.rb +267 -0
  38. data/lib/origami/filters/crypt.rb +1 -1
  39. data/lib/origami/filters/dct.rb +0 -1
  40. data/lib/origami/filters/flate.rb +3 -43
  41. data/lib/origami/filters/lzw.rb +62 -99
  42. data/lib/origami/filters/predictors.rb +135 -105
  43. data/lib/origami/filters/runlength.rb +34 -22
  44. data/lib/origami/graphics.rb +2 -2
  45. data/lib/origami/graphics/colors.rb +89 -63
  46. data/lib/origami/graphics/path.rb +14 -14
  47. data/lib/origami/graphics/patterns.rb +31 -33
  48. data/lib/origami/graphics/render.rb +0 -1
  49. data/lib/origami/graphics/state.rb +9 -9
  50. data/lib/origami/graphics/text.rb +17 -17
  51. data/lib/origami/graphics/xobject.rb +102 -92
  52. data/lib/origami/javascript.rb +91 -68
  53. data/lib/origami/linearization.rb +22 -20
  54. data/lib/origami/metadata.rb +1 -1
  55. data/lib/origami/name.rb +1 -3
  56. data/lib/origami/null.rb +1 -3
  57. data/lib/origami/numeric.rb +3 -13
  58. data/lib/origami/object.rb +100 -72
  59. data/lib/origami/page.rb +24 -28
  60. data/lib/origami/parser.rb +34 -51
  61. data/lib/origami/parsers/fdf.rb +2 -2
  62. data/lib/origami/parsers/pdf.rb +41 -18
  63. data/lib/origami/parsers/pdf/lazy.rb +83 -46
  64. data/lib/origami/parsers/pdf/linear.rb +19 -10
  65. data/lib/origami/parsers/ppklite.rb +1 -1
  66. data/lib/origami/pdf.rb +150 -206
  67. data/lib/origami/reference.rb +4 -6
  68. data/lib/origami/signature.rb +76 -48
  69. data/lib/origami/stream.rb +69 -63
  70. data/lib/origami/string.rb +2 -19
  71. data/lib/origami/trailer.rb +25 -22
  72. data/lib/origami/version.rb +1 -1
  73. data/lib/origami/xfa.rb +6 -4
  74. data/lib/origami/xreftable.rb +29 -29
  75. data/test/test_annotations.rb +16 -38
  76. data/test/test_pdf_attachment.rb +1 -1
  77. data/test/test_pdf_parse.rb +1 -1
  78. data/test/test_xrefs.rb +2 -2
  79. metadata +4 -4
  80. data/lib/origami/export.rb +0 -247
@@ -105,16 +105,6 @@ module Origami
105
105
  end
106
106
  end
107
107
 
108
- module ClassMethods #:nodoc:all
109
- def native_type; Origami::String end
110
- end
111
-
112
- def self.included(receiver) #:nodoc:
113
- receiver.extend(ClassMethods)
114
- end
115
-
116
- def self.native_type; Origami::String end #:nodoc:
117
-
118
108
  include Origami::Object
119
109
 
120
110
  attr_accessor :encoding
@@ -188,7 +178,7 @@ module Origami
188
178
  super(str)
189
179
  end
190
180
 
191
- def self.parse(stream, parser = nil) #:nodoc:
181
+ def self.parse(stream, _parser = nil) #:nodoc:
192
182
  offset = stream.pos
193
183
 
194
184
  if stream.skip(@@regexp_open).nil?
@@ -252,7 +242,7 @@ module Origami
252
242
  super(str)
253
243
  end
254
244
 
255
- def self.parse(stream, parser = nil) #:nodoc:
245
+ def self.parse(stream, _parser = nil) #:nodoc:
256
246
  offset = stream.pos
257
247
 
258
248
  unless stream.skip(@@regexp_open)
@@ -381,13 +371,6 @@ module Origami
381
371
  attr_reader :year, :month, :day, :hour, :min, :sec, :utc_offset
382
372
 
383
373
  def initialize(year:, month: 1, day: 1, hour: 0, min: 0, sec: 0, utc_offset: 0)
384
- raise InvalidDateError, "Invalid year #{year}" unless (0..9999) === year
385
- raise InvalidDateError, "Invalid month #{month}" unless (1..12) === month
386
- raise InvalidDateError, "Invalid day #{day}" unless (1..31) === day
387
- raise InvalidDateError, "Invalid hour #{hour}" unless (0..23) === hour
388
- raise InvalidDateError, "Invalid minute #{min}" unless (0..59) === min
389
- raise InvalidDateError, "Invalid second #{sec}" unless (0..59) === sec
390
-
391
374
  @year, @month, @day, @hour, @min, @sec = year, month, day, hour, min, sec
392
375
  @utc_offset = utc_offset
393
376
 
@@ -22,6 +22,29 @@ module Origami
22
22
 
23
23
  class PDF
24
24
 
25
+ #
26
+ # Returns the current trailer.
27
+ # This might be either a Trailer or XRefStream.
28
+ #
29
+ def trailer
30
+ #
31
+ # First look for a standard trailer dictionary
32
+ #
33
+ if @revisions.last.trailer.has_dictionary?
34
+ trl = @revisions.last.trailer
35
+
36
+ #
37
+ # Otherwise look for a xref stream.
38
+ #
39
+ else
40
+ trl = @revisions.last.xrefstm
41
+ end
42
+
43
+ raise InvalidPDFError, "No trailer found" if trl.nil?
44
+
45
+ trl
46
+ end
47
+
25
48
  private
26
49
 
27
50
  def trailer_key?(attr) #:nodoc:
@@ -35,7 +58,7 @@ module Origami
35
58
  return rev.trailer[attr].solve
36
59
  elsif rev.has_xrefstm?
37
60
  xrefstm = rev.xrefstm
38
- if xrefstm.is_a?(XRefStream) and xrefstm.has_field?(attr)
61
+ if xrefstm.is_a?(XRefStream) and xrefstm.key?(attr)
39
62
  return xrefstm[attr].solve
40
63
  end
41
64
  end
@@ -44,29 +67,9 @@ module Origami
44
67
  nil
45
68
  end
46
69
 
47
- def get_trailer_info #:nodoc:
48
- #
49
- # First look for a standard trailer dictionary
50
- #
51
- if @revisions.last.trailer.has_dictionary?
52
- @revisions.last.trailer
53
-
54
- #
55
- # Otherwise look for a xref stream.
56
- #
57
- else
58
- @revisions.last.xrefstm
59
- end
60
- end
61
-
62
70
  def generate_id
63
- info = get_trailer_info
64
- if info.nil?
65
- raise InvalidPDFError, "Cannot access trailer information"
66
- end
67
-
68
71
  id = HexaString.new Random.new.bytes 16
69
- info.ID = [ id, id ]
72
+ self.trailer.ID = [ id, id ]
70
73
  end
71
74
  end
72
75
 
@@ -19,5 +19,5 @@
19
19
  =end
20
20
 
21
21
  module Origami
22
- VERSION = "2.0.0"
22
+ VERSION = "2.0.1"
23
23
  end
@@ -47,21 +47,21 @@ module Origami
47
47
 
48
48
  def xfa_attribute(name)
49
49
  # Attribute getter.
50
- attr_getter = "attr_#{name.to_s}"
50
+ attr_getter = "attr_#{name}"
51
51
  remove_method(attr_getter) rescue NameError
52
52
  define_method(attr_getter) do
53
53
  self.attributes[name.to_s]
54
54
  end
55
55
 
56
56
  # Attribute setter.
57
- attr_setter = "attr_#{name.to_s}="
57
+ attr_setter = "attr_#{name}="
58
58
  remove_method(attr_setter) rescue NameError
59
59
  define_method(attr_setter) do |value|
60
60
  self.attributes[names.to_s] = value
61
61
  end
62
62
  end
63
63
 
64
- def xfa_node(name, type, range = (0..(1.0/0)))
64
+ def xfa_node(name, type, _range = (0..Float::INFINITY))
65
65
 
66
66
  adder = "add_#{name}"
67
67
  remove_method(adder) rescue NameError
@@ -220,7 +220,7 @@ module Origami
220
220
  def initialize(record = "")
221
221
  super('record')
222
222
 
223
- self.text = ""
223
+ self.text = record
224
224
  end
225
225
  end
226
226
 
@@ -1099,6 +1099,8 @@ module Origami
1099
1099
 
1100
1100
  def initialize(text = "")
1101
1101
  super('toolTip')
1102
+
1103
+ self.text = text
1102
1104
  end
1103
1105
  end
1104
1106
 
@@ -25,26 +25,15 @@ module Origami
25
25
  # Tries to strip any xrefs information off the document.
26
26
  #
27
27
  def remove_xrefs
28
-
29
- # Delete a XRefStream and its ancestors.
30
- delete_xrefstm = -> (xrefstm) do
31
- prev = xrefstm.Prev
32
- delete_object(xrefstm.reference)
33
-
34
- if prev.is_a?(Integer) and (prev_stm = get_object_by_offset(prev)).is_a?(XRefStream)
35
- delete_xrefstm.call(prev_stm)
36
- end
37
- end
38
-
39
28
  @revisions.reverse_each do |rev|
40
29
  if rev.has_xrefstm?
41
- delete_xrefstm.call(rev.xrefstm)
30
+ delete_object(rev.xrefstm.reference)
42
31
  end
43
32
 
44
- if rev.trailer.has_dictionary? and rev.trailer.XRefStm.is_a?(Integer)
33
+ if rev.trailer.XRefStm.is_a?(Integer)
45
34
  xrefstm = get_object_by_offset(rev.trailer.XRefStm)
46
35
 
47
- delete_xrefstm.call(xrefstm) if xrefstm.is_a?(XRefStream)
36
+ delete_object(xrefstm.reference) if xrefstm.is_a?(XRefStream)
48
37
  end
49
38
 
50
39
  rev.xrefstm = rev.xreftable = nil
@@ -135,6 +124,8 @@ module Origami
135
124
  # A subsection contains a continute set of XRef.
136
125
  #
137
126
  class Subsection
127
+ include Enumerable
128
+
138
129
  @@regexp = Regexp.new("(?<start>\\d+) (?<size>\\d+)" + WHITESPACES + "(\\r?\\n|\\r\\n?)")
139
130
 
140
131
  attr_reader :range
@@ -228,6 +219,8 @@ module Origami
228
219
  # A section contains a set of XRefSubsection.
229
220
  #
230
221
  class Section
222
+ include Enumerable
223
+
231
224
  TOKEN = "xref"
232
225
 
233
226
  @@regexp_open = Regexp.new(WHITESPACES + TOKEN + WHITESPACES + "(\\r?\\n|\\r\\n?)")
@@ -337,7 +330,7 @@ module Origami
337
330
  #
338
331
  # An xref poiting to an Object embedded in an ObjectStream.
339
332
  #
340
- class XRefToCompressedObj
333
+ class XRefToCompressedObject
341
334
  attr_accessor :objstmno, :index
342
335
 
343
336
  def initialize(objstmno, index)
@@ -356,6 +349,9 @@ module Origami
356
349
 
357
350
  [ type , objstmno, index ].pack("B#{type_w}B#{field1_w}B#{field2_w}")
358
351
  end
352
+
353
+ def used?; true end
354
+ def free?; false end
359
355
  end
360
356
 
361
357
  class InvalidXRefStreamObjectError < InvalidStreamObjectError ; end
@@ -406,7 +402,7 @@ module Origami
406
402
  def pre_build #:nodoc:
407
403
  load! if @xrefs.nil?
408
404
 
409
- self.W = [ 1, 2, 2 ] unless has_field?(:W)
405
+ self.W = [ 1, 2, 2 ] unless self.key?(:W)
410
406
  self.Size = @xrefs.length + 1
411
407
 
412
408
  save!
@@ -496,18 +492,10 @@ module Origami
496
492
  end
497
493
 
498
494
  def load! #:nodoc:
499
- if @xrefs.nil? and has_field?(:W)
500
- widths = self.W
501
-
502
- if not widths.is_a?(Array) or widths.length != 3 or widths.any?{|width| not width.is_a?(Integer) }
503
- raise InvalidXRefStreamObjectError, "W field must be an array of 3 integers"
504
- end
505
-
495
+ if @xrefs.nil? and self.key?(:W)
506
496
  decode!
507
497
 
508
- type_w = self.W[0]
509
- field1_w = self.W[1]
510
- field2_w = self.W[2]
498
+ type_w, field1_w, field2_w = field_widths
511
499
 
512
500
  entrymask = "B#{type_w << 3}B#{field1_w << 3}B#{field2_w << 3}"
513
501
  size = @data.size / (type_w + field1_w + field2_w)
@@ -515,15 +503,14 @@ module Origami
515
503
  xentries = @data.unpack(entrymask * size).map!{|field| field.to_i(2) }
516
504
 
517
505
  @xrefs = []
518
- size.times do |i|
519
- type,field1,field2 = xentries[i*3].ord,xentries[i*3+1].ord,xentries[i*3+2].ord
506
+ xentries.each_slice(3) do |type, field1, field2|
520
507
  case type
521
508
  when XREF_FREE
522
509
  @xrefs << XRef.new(field1, field2, XRef::FREE)
523
510
  when XREF_USED
524
511
  @xrefs << XRef.new(field1, field2, XRef::USED)
525
512
  when XREF_COMPRESSED
526
- @xrefs << XRefToCompressedObj.new(field1, field2)
513
+ @xrefs << XRefToCompressedObject.new(field1, field2)
527
514
  end
528
515
  end
529
516
  else
@@ -539,6 +526,19 @@ module Origami
539
526
 
540
527
  encode!
541
528
  end
529
+
530
+ #
531
+ # Check and return the internal field widths.
532
+ #
533
+ def field_widths
534
+ widths = self.W
535
+
536
+ unless widths.is_a?(Array) and widths.length == 3 and widths.all? {|w| w.is_a?(Integer) and w >= 0 }
537
+ raise InvalidXRefStreamObjectError, "Invalid W field: #{widths}"
538
+ end
539
+
540
+ widths
541
+ end
542
542
  end
543
543
 
544
544
  end
@@ -7,55 +7,33 @@ class TestAnnotations < Minitest::Test
7
7
  @page = Page.new
8
8
  @action = Action::JavaScript["app.alert(null);"]
9
9
  @output = StringIO.new
10
- end
11
-
12
- def test_annotations
13
- circle = Annotation::Circle.new
14
- square = Annotation::Square.new
15
- text = Annotation::Text.new
16
- link = Annotation::Link.new
17
- file = Annotation::FileAttachment.new
18
- screen = Annotation::Screen.new
19
- sound = Annotation::Sound.new
20
- pushbutton = Annotation::Widget::PushButton.new
21
- checkbox = Annotation::Widget::CheckBox.new
22
- radio = Annotation::Widget::Radio.new
23
- edit = Annotation::Widget::Text.new
24
- combo = Annotation::Widget::ComboBox.new
25
- list = Annotation::Widget::ListBox.new
26
- sig = Annotation::Widget::Signature.new
27
10
 
28
- all_annots = [
29
- circle, square, text, link,
30
- file, screen, sound, pushbutton,
31
- checkbox, radio, edit, combo,
32
- list, sig
11
+ @types = [
12
+ Annotation::Circle, Annotation::Square,
13
+ Annotation::Text, Annotation::Link,
14
+ Annotation::FileAttachment, Annotation::Screen,
15
+ Annotation::Sound, Annotation::Widget::CheckBox,
16
+ Annotation::Widget::Radio, Annotation::Widget::Text,
17
+ Annotation::Widget::ComboBox, Annotation::Widget::ListBox,
18
+ Annotation::Widget::Signature
33
19
  ]
20
+ end
34
21
 
22
+ def test_annotations
35
23
  @target.append_page @page
36
24
 
37
- @page.add_annotation circle
38
- @page.add_annotation square
39
- @page.add_annotation text
40
- @page.add_annotation link
41
- @page.add_annotation file
42
- @page.add_annotation screen
43
- @page.add_annotation sound
44
- @page.add_annotation pushbutton
45
- @page.add_annotation checkbox
46
- @page.add_annotation radio
47
- @page.add_annotation edit
48
- @page.add_annotation combo
49
- @page.add_annotation list
50
- @page.add_annotation sig
25
+ annotations = @types.map(&:new)
26
+ annotations.each do |annotation|
27
+ @page.add_annotation(annotation)
28
+ end
51
29
 
52
30
  @page.each_annotation do |annotation|
53
31
  assert_kind_of Annotation, annotation
54
32
 
55
- assert all_annots.include?(annotation)
33
+ assert annotations.include?(annotation)
56
34
  end
57
35
 
58
- assert_equal @page.annotations.size, all_annots.size
36
+ assert_equal @page.annotations.size, annotations.size
59
37
 
60
38
  @target.save(@output)
61
39
  end
@@ -17,7 +17,7 @@ class TestAttachment < Minitest::Test
17
17
  pdf = PDF.read(@output, ignore_errors: false, verbosity: Parser::VERBOSE_QUIET)
18
18
 
19
19
  assert_equal pdf.each_named_embedded_file.count, 1
20
- assert_equal pdf.get_embedded_file_by_name("foo.baz"), nil
20
+ assert_nil pdf.get_embedded_file_by_name("foo.baz")
21
21
 
22
22
  file = pdf.get_embedded_file_by_name('foo.bar')
23
23
  refute_equal file, nil
@@ -62,7 +62,7 @@ class TestPDFParser < Minitest::Test
62
62
  assert_instance_of Boolean, b_false
63
63
 
64
64
  assert b_false.false?
65
- assert (not b_true.false?)
65
+ refute b_true.false?
66
66
  end
67
67
 
68
68
  def test_parse_real
@@ -47,13 +47,13 @@ class TestXrefs < MiniTest::Test
47
47
  xrefstm = pdf.revisions.last.xrefstm
48
48
 
49
49
  assert_instance_of XRefStream, xrefstm
50
- assert xrefstm.entries.all?{ |xref| xref.is_a?(XRef) or xref.is_a?(XRefToCompressedObj) }
50
+ assert xrefstm.entries.all?{ |xref| xref.is_a?(XRef) or xref.is_a?(XRefToCompressedObject) }
51
51
 
52
52
  pdf.each_object(compressed: true) do |object|
53
53
  xref = xrefstm.find(object.no)
54
54
 
55
55
  if object.parent.is_a?(ObjectStream)
56
- assert_instance_of XRefToCompressedObj, xref
56
+ assert_instance_of XRefToCompressedObject, xref
57
57
  assert_equal xref.objstmno, object.parent.no
58
58
  assert_equal xref.index, object.parent.index(object.no)
59
59
  else
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: origami
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.0
4
+ version: 2.0.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Guillaume Delugré
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-08-19 00:00:00.000000000 Z
11
+ date: 2017-02-14 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: colorize
@@ -116,13 +116,13 @@ files:
116
116
  - lib/origami/destinations.rb
117
117
  - lib/origami/dictionary.rb
118
118
  - lib/origami/encryption.rb
119
- - lib/origami/export.rb
120
119
  - lib/origami/extensions/fdf.rb
121
120
  - lib/origami/extensions/ppklite.rb
122
121
  - lib/origami/filespec.rb
123
122
  - lib/origami/filters.rb
124
123
  - lib/origami/filters/ascii.rb
125
124
  - lib/origami/filters/ccitt.rb
125
+ - lib/origami/filters/ccitt/tables.rb
126
126
  - lib/origami/filters/crypt.rb
127
127
  - lib/origami/filters/dct.rb
128
128
  - lib/origami/filters/flate.rb
@@ -208,7 +208,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
208
208
  requirements:
209
209
  - gtk2 to run the graphical interface
210
210
  rubyforge_project:
211
- rubygems_version: 2.5.1
211
+ rubygems_version: 2.6.8
212
212
  signing_key:
213
213
  specification_version: 4
214
214
  summary: Ruby framework to manipulate PDF documents
@@ -1,247 +0,0 @@
1
- =begin
2
-
3
- This file is part of Origami, PDF manipulation framework for Ruby
4
- Copyright (C) 2016 Guillaume Delugré.
5
-
6
- Origami is free software: you can redistribute it and/or modify
7
- it under the terms of the GNU Lesser General Public License as published by
8
- the Free Software Foundation, either version 3 of the License, or
9
- (at your option) any later version.
10
-
11
- Origami is distributed in the hope that it will be useful,
12
- but WITHOUT ANY WARRANTY; without even the implied warranty of
13
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
- GNU Lesser General Public License for more details.
15
-
16
- You should have received a copy of the GNU Lesser General Public License
17
- along with Origami. If not, see <http://www.gnu.org/licenses/>.
18
-
19
- =end
20
-
21
- module Origami
22
-
23
- class PDF
24
-
25
- #
26
- # Exports the document to a dot Graphiz file.
27
- # _filename_:: The path where to save the file.
28
- #
29
- def export_to_graph(path)
30
-
31
- appearance = -> (object) do
32
- label = object.type.to_s
33
- case object
34
- when Catalog
35
- fontcolor = "red"
36
- color = "mistyrose"
37
- shape = "ellipse"
38
- when Name, Number
39
- label = object.value
40
- fontcolor = "brown"
41
- color = "lightgoldenrodyellow"
42
- shape = "polygon"
43
- when String
44
- label = object.value if (object.ascii_only? and object.length <= 50)
45
- fontcolor = "red"
46
- color = "white"
47
- shape = "polygon"
48
- when Array
49
- fontcolor = "darkgreen"
50
- color = "lightcyan"
51
- shape = "ellipse"
52
- else
53
- fontcolor = "blue"
54
- color = "aliceblue"
55
- shape = "ellipse"
56
- end
57
-
58
- { label: label, fontcolor: fontcolor, color: color, shape: shape }
59
- end
60
-
61
- add_edges = -> (fd, object) do
62
- if object.is_a?(Array) or object.is_a?(ObjectStream)
63
- object.each do |subobj|
64
- fd << "\t#{object.object_id} -> #{subobj.solve.object_id}\n"
65
- end
66
-
67
- elsif object.is_a?(Dictionary)
68
- object.each_pair do |name, subobj|
69
- fd << "\t#{object.object_id} -> #{subobj.solve.object_id} "
70
- fd << "[label=\"#{name.value}\",fontsize=9];\n"
71
- end
72
- end
73
-
74
- if object.is_a?(Stream)
75
- object.dictionary.each_pair do |key, value|
76
- fd << "\t#{object.object_id} -> #{value.solve.object_id} "
77
- fd << "[label=\"#{key.value}\",fontsize=9];\n"
78
- end
79
- end
80
- end
81
-
82
- graph_name = "PDF" if graph_name.nil? or graph_name.empty?
83
- fd = File.open(path, "w")
84
-
85
- begin
86
- fd << "digraph #{graph_name} {\n\n"
87
-
88
- objects = self.objects(include_keys: false).find_all{ |obj| not obj.is_a?(Reference) }
89
-
90
- objects.each do |object|
91
- attr = appearance[object]
92
-
93
- fd << "\t#{object.object_id} "
94
- fd << "[label=\"#{attr[:label]}\",shape=#{attr[:shape]},color=#{attr[:color]},style=filled,fontcolor=#{attr[:fontcolor]},fontsize=16];\n"
95
-
96
- if object.is_a?(Stream)
97
- object.dictionary.each do |value|
98
- unless value.is_a?(Reference)
99
- attr = appearance[value]
100
- fd << "\t#{value.object_id} "
101
- fd << "[label=\"#{attr[:label]}\",shape=#{attr[:shape]},color=#{attr[:color]},style=filled,fontcolor=#{attr[:fontcolor]},fontsize=16];\n"
102
- end
103
- end
104
- end
105
-
106
- add_edges.call(fd, object)
107
- end
108
-
109
- fd << "\n}"
110
- ensure
111
- fd.close
112
- end
113
- end
114
-
115
- #
116
- # Exports the document to a GraphML file.
117
- # _filename_:: The path where to save the file.
118
- #
119
- def export_to_graphml(path)
120
- require 'rexml/document'
121
-
122
- declare_node = -> (id, attr) do
123
- <<-XML
124
- <node id="#{id}">
125
- <data key="d0">
126
- <y:ShapeNode>
127
- <y:NodeLabel>#{attr[:label]}</y:NodeLabel>
128
- </y:ShapeNode>
129
- </data>
130
- </node>
131
- XML
132
- end
133
-
134
- declare_edge = -> (id, src, dest, label = nil) do
135
- <<-XML
136
- <edge id="#{id}" source="#{src}" target="#{dest}">
137
- <data key="d1">
138
- <y:PolyLineEdge>
139
- <y:LineStyle type="line" width="1.0" color="#000000"/>
140
- <y:Arrows source="none" target="standard"/>
141
- <y:EdgeLabel>#{label.to_s}</y:EdgeLabel>
142
- </y:PolyLineEdge>
143
- </data>
144
- </edge>
145
- XML
146
- end
147
-
148
- appearance = -> (object) do
149
- label = object.type.to_s
150
- case object
151
- when Catalog
152
- fontcolor = "red"
153
- color = "mistyrose"
154
- shape = "doublecircle"
155
- when Name, Number
156
- label = object.value
157
- fontcolor = "orange"
158
- color = "lightgoldenrodyellow"
159
- shape = "polygon"
160
- when String
161
- label = object.value if (object.ascii_only? and object.length <= 50)
162
- fontcolor = "red"
163
- color = "white"
164
- shape = "polygon"
165
- when Array
166
- fontcolor = "green"
167
- color = "lightcyan"
168
- shape = "ellipse"
169
- else
170
- fontcolor = "blue"
171
- color = "aliceblue"
172
- shape = "ellipse"
173
- end
174
-
175
- { label: label, fontcolor: fontcolor, color: color, shape: shape }
176
- end
177
-
178
- add_edges = -> (xml, object, id) do
179
- if object.is_a?(Array) or object.is_a?(ObjectStream)
180
- object.each do |subobj|
181
- xml << declare_edge["e#{id}", "n#{object.object_id}", "n#{subobj.solve.object_id}"]
182
- id = id + 1
183
- end
184
-
185
- elsif object.is_a?(Dictionary)
186
- object.each_pair do |name, subobj|
187
- xml << declare_edge["e#{id}", "n#{object.object_id}", "n#{subobj.solve.object_id}",
188
- name.value]
189
- id = id + 1
190
- end
191
- end
192
-
193
- if object.is_a?(Stream)
194
- object.dictionary.each_pair do |key, value|
195
- xml << declare_edge["e#{id}", "n#{object.object_id}", "n#{value.object_id}", key.value]
196
- id = id + 1
197
- end
198
- end
199
-
200
- id
201
- end
202
-
203
- graph_name = "PDF" if graph_name.nil? or graph_name.empty?
204
-
205
- edge_nb = 1
206
- xml = <<-XML
207
- <?xml version="1.0" encoding="UTF-8"?>
208
- <graphml xmlns="http://graphml.graphdrawing.org/xmlns/graphml"
209
- xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
210
- xsi:schemaLocation="http://graphml.graphdrawing.org/xmlns/graphml
211
- http://www.yworks.com/xml/schema/graphml/1.0/ygraphml.xsd"
212
- xmlns:y="http://www.yworks.com/xml/graphml">
213
- <key id="d0" for="node" yfiles.type="nodegraphics"/>
214
- <key id="d1" for="edge" yfiles.type="edgegraphics"/>
215
- <graph id="#{graph_name}" edgedefault="directed">
216
- XML
217
-
218
- objects = self.objects(include_keys: false).find_all{ |obj| not obj.is_a?(Reference) }
219
-
220
- objects.each do |object|
221
- xml << declare_node["n#{object.object_id}", appearance[object]]
222
-
223
- if object.is_a?(Stream)
224
- object.dictionary.each do |value|
225
- unless value.is_a?(Reference)
226
- xml << declare_node[value.object_id, appearance[value]]
227
- end
228
- end
229
- end
230
-
231
- edge_nb = add_edges[xml, object, edge_nb]
232
- end
233
-
234
- xml << '</graph>' << "\n"
235
- xml << '</graphml>'
236
-
237
- doc = REXML::Document.new(xml)
238
- formatter = REXML::Formatters::Pretty.new(4)
239
- formatter.compact = true
240
-
241
- File.open(path, "w") do |fd|
242
- formatter.write(doc, fd)
243
- end
244
- end
245
- end
246
-
247
- end