hexapdf 0.7.0 → 0.8.0

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 (106) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +39 -1
  3. data/CONTRIBUTERS +1 -1
  4. data/LICENSE +3 -0
  5. data/README.md +2 -1
  6. data/Rakefile +3 -1
  7. data/VERSION +1 -1
  8. data/examples/{hello_world.rb → 001-hello_world.rb} +0 -0
  9. data/examples/{graphics.rb → 002-graphics.rb} +1 -1
  10. data/examples/{arc.rb → 003-arcs.rb} +2 -2
  11. data/examples/{optimizing.rb → 004-optimizing.rb} +0 -0
  12. data/examples/{merging.rb → 005-merging.rb} +0 -0
  13. data/examples/{standard_pdf_fonts.rb → 006-standard_pdf_fonts.rb} +0 -0
  14. data/examples/{truetype.rb → 007-truetype.rb} +0 -0
  15. data/examples/{show_char_bboxes.rb → 008-show_char_bboxes.rb} +0 -0
  16. data/examples/{text_layouter_alignment.rb → 009-text_layouter_alignment.rb} +3 -3
  17. data/examples/{text_layouter_inline_boxes.rb → 010-text_layouter_inline_boxes.rb} +7 -9
  18. data/examples/{text_layouter_line_wrapping.rb → 011-text_layouter_line_wrapping.rb} +6 -5
  19. data/examples/{text_layouter_styling.rb → 012-text_layouter_styling.rb} +6 -8
  20. data/examples/013-text_layouter_shapes.rb +176 -0
  21. data/examples/014-text_in_polygon.rb +60 -0
  22. data/examples/{boxes.rb → 015-boxes.rb} +29 -21
  23. data/examples/016-frame_automatic_box_placement.rb +90 -0
  24. data/examples/017-frame_text_flow.rb +60 -0
  25. data/lib/hexapdf/cli/command.rb +4 -3
  26. data/lib/hexapdf/cli/files.rb +1 -1
  27. data/lib/hexapdf/cli/inspect.rb +0 -1
  28. data/lib/hexapdf/cli/merge.rb +1 -1
  29. data/lib/hexapdf/cli/modify.rb +1 -1
  30. data/lib/hexapdf/configuration.rb +2 -0
  31. data/lib/hexapdf/content/canvas.rb +3 -3
  32. data/lib/hexapdf/content/graphic_object.rb +1 -0
  33. data/lib/hexapdf/content/graphic_object/geom2d.rb +132 -0
  34. data/lib/hexapdf/dictionary.rb +7 -1
  35. data/lib/hexapdf/dictionary_fields.rb +35 -83
  36. data/lib/hexapdf/document.rb +9 -5
  37. data/lib/hexapdf/document/fonts.rb +1 -1
  38. data/lib/hexapdf/encryption/standard_security_handler.rb +1 -1
  39. data/lib/hexapdf/filter/ascii85_decode.rb +1 -1
  40. data/lib/hexapdf/filter/ascii_hex_decode.rb +1 -1
  41. data/lib/hexapdf/font/cmap/writer.rb +2 -2
  42. data/lib/hexapdf/font/true_type/builder.rb +1 -1
  43. data/lib/hexapdf/font/true_type/table.rb +1 -1
  44. data/lib/hexapdf/font/true_type/table/cmap.rb +1 -1
  45. data/lib/hexapdf/font/true_type/table/cmap_subtable.rb +3 -3
  46. data/lib/hexapdf/font/true_type/table/kern.rb +1 -1
  47. data/lib/hexapdf/font/true_type/table/post.rb +1 -1
  48. data/lib/hexapdf/font/type1/character_metrics.rb +1 -1
  49. data/lib/hexapdf/font/type1/font_metrics.rb +1 -1
  50. data/lib/hexapdf/image_loader/jpeg.rb +1 -1
  51. data/lib/hexapdf/image_loader/png.rb +2 -2
  52. data/lib/hexapdf/layout.rb +3 -0
  53. data/lib/hexapdf/layout/box.rb +64 -46
  54. data/lib/hexapdf/layout/frame.rb +348 -0
  55. data/lib/hexapdf/layout/inline_box.rb +2 -2
  56. data/lib/hexapdf/layout/line.rb +3 -3
  57. data/lib/hexapdf/layout/style.rb +81 -14
  58. data/lib/hexapdf/layout/text_box.rb +84 -0
  59. data/lib/hexapdf/layout/text_fragment.rb +8 -8
  60. data/lib/hexapdf/layout/text_layouter.rb +278 -169
  61. data/lib/hexapdf/layout/width_from_polygon.rb +246 -0
  62. data/lib/hexapdf/rectangle.rb +9 -9
  63. data/lib/hexapdf/stream.rb +2 -2
  64. data/lib/hexapdf/type.rb +1 -0
  65. data/lib/hexapdf/type/action.rb +1 -1
  66. data/lib/hexapdf/type/annotations/markup_annotation.rb +1 -1
  67. data/lib/hexapdf/type/catalog.rb +1 -1
  68. data/lib/hexapdf/type/cid_font.rb +2 -1
  69. data/lib/hexapdf/type/font.rb +0 -1
  70. data/lib/hexapdf/type/font_descriptor.rb +1 -1
  71. data/lib/hexapdf/type/font_simple.rb +3 -3
  72. data/lib/hexapdf/type/font_true_type.rb +8 -0
  73. data/lib/hexapdf/type/font_type0.rb +2 -1
  74. data/lib/hexapdf/type/font_type1.rb +7 -1
  75. data/lib/hexapdf/type/font_type3.rb +61 -0
  76. data/lib/hexapdf/type/graphics_state_parameter.rb +8 -8
  77. data/lib/hexapdf/type/image.rb +10 -0
  78. data/lib/hexapdf/type/page.rb +83 -10
  79. data/lib/hexapdf/version.rb +1 -1
  80. data/test/hexapdf/common_tokenizer_tests.rb +2 -2
  81. data/test/hexapdf/content/graphic_object/test_geom2d.rb +79 -0
  82. data/test/hexapdf/encryption/test_standard_security_handler.rb +1 -1
  83. data/test/hexapdf/font/test_true_type_wrapper.rb +1 -1
  84. data/test/hexapdf/font/test_type1_wrapper.rb +1 -1
  85. data/test/hexapdf/font/true_type/table/test_cmap.rb +1 -1
  86. data/test/hexapdf/font/true_type/table/test_directory.rb +1 -1
  87. data/test/hexapdf/font/true_type/table/test_head.rb +7 -3
  88. data/test/hexapdf/layout/test_box.rb +57 -15
  89. data/test/hexapdf/layout/test_frame.rb +313 -0
  90. data/test/hexapdf/layout/test_inline_box.rb +1 -1
  91. data/test/hexapdf/layout/test_style.rb +74 -0
  92. data/test/hexapdf/layout/test_text_box.rb +77 -0
  93. data/test/hexapdf/layout/test_text_layouter.rb +220 -239
  94. data/test/hexapdf/layout/test_width_from_polygon.rb +108 -0
  95. data/test/hexapdf/test_dictionary_fields.rb +22 -26
  96. data/test/hexapdf/test_document.rb +3 -3
  97. data/test/hexapdf/test_reference.rb +1 -0
  98. data/test/hexapdf/test_writer.rb +2 -2
  99. data/test/hexapdf/type/test_font_true_type.rb +25 -0
  100. data/test/hexapdf/type/test_font_type1.rb +6 -0
  101. data/test/hexapdf/type/test_font_type3.rb +26 -0
  102. data/test/hexapdf/type/test_image.rb +10 -0
  103. data/test/hexapdf/type/test_page.rb +114 -0
  104. data/test/test_helper.rb +1 -1
  105. metadata +65 -17
  106. data/examples/text_layouter_shapes.rb +0 -170
@@ -17,7 +17,7 @@ doc = HexaPDF::Document.new
17
17
 
18
18
  annotate_box = lambda do |canvas, box|
19
19
  text = ""
20
- canvas.font("Times", size: 6)
20
+ canvas.font("Times", size: 6).leading(7)
21
21
 
22
22
  if (data = box.style.padding)
23
23
  text << "Padding (TRBL): #{data.top}, #{data.right}, #{data.bottom}, #{data.left}\n"
@@ -37,32 +37,40 @@ end
37
37
 
38
38
  canvas = doc.pages.add.canvas
39
39
 
40
- [[1, 200], [5, 220], [15, 240]].each_with_index do |(width, red), row|
41
- [[:solid, 180], [:dashed, 200], [:dashed_round, 220],
40
+ [[1, 140], [5, 190], [15, 240]].each_with_index do |(width, red), row|
41
+ [[:solid, 140], [:dashed, 177], [:dashed_round, 207],
42
42
  [:dotted, 240]].each_with_index do |(style, green), column|
43
- box = HexaPDF::Layout::Box.new(content_width: 100, content_height: 100, &annotate_box)
44
- box.style.border(width: width, style: style)
45
- box.style.background_color([red, green, 0])
43
+ box = HexaPDF::Layout::Box.create(
44
+ width: 100, height: 100, content_box: true,
45
+ border: {width: width, style: style},
46
+ background_color: [red, green, 0],
47
+ &annotate_box)
46
48
  box.draw(canvas, 20 + 140 * column, 700 - 150 * row)
47
49
  end
48
50
  end
49
51
 
50
52
  # The whole kitchen sink
51
- box = HexaPDF::Layout::Box.new(content_width: 470, content_height: 200, &annotate_box)
52
- box.style.background_color([255, 255, 180])
53
- box.style.padding([20, 5, 10, 15])
54
- box.style.border(width: [20, 40, 30, 15],
55
- color: [[46, 185, 206], [206, 199, 46], [188, 46, 206], [59, 206, 46]],
56
- style: [:solid, :dashed, :dashed_round, :dotted])
57
- box.style.underlays.add do |canv, _|
58
- canv.stroke_color([255, 0, 0]).line_width(10).line_cap_style(:butt).
59
- line(0, 0, box.width, box.height).line(0, box.height, box.width, 0).
60
- stroke
61
- end
62
- box.style.overlays.add do |canv, _|
63
- canv.stroke_color([0, 0, 255]).line_width(5).
64
- rectangle(10, 10, box.width - 20, box.height - 20).stroke
65
- end
53
+ box = HexaPDF::Layout::Box.create(
54
+ width: 470, height: 200, content_box: true,
55
+ padding: [20, 5, 10, 15],
56
+ border: {width: [20, 40, 30, 15],
57
+ color: [[46, 185, 206], [206, 199, 46], [188, 46, 206], [59, 206, 46]],
58
+ style: [:solid, :dashed, :dashed_round, :dotted]},
59
+ background_color: [255, 255, 180],
60
+ underlays: [
61
+ lambda do |canv, _|
62
+ canv.stroke_color([255, 0, 0]).line_width(10).line_cap_style(:butt).
63
+ line(0, 0, box.width, box.height).line(0, box.height, box.width, 0).
64
+ stroke
65
+ end
66
+ ],
67
+ overlays: [
68
+ lambda do |canv, _|
69
+ canv.stroke_color([0, 0, 255]).line_width(5).
70
+ rectangle(10, 10, box.width - 20, box.height - 20).stroke
71
+ end
72
+ ],
73
+ &annotate_box)
66
74
  box.draw(canvas, 20, 100)
67
75
 
68
76
  doc.write("boxes.pdf", optimize: true)
@@ -0,0 +1,90 @@
1
+ # ## Frame - Automatic Box Placement
2
+ #
3
+ # The [HexaPDF::Layout::Frame] class is used for placing rectangular boxes.
4
+ #
5
+ # This example shows how to create a frame and how different box styles can be
6
+ # used to specify where a box should be placed. After each box is drawn, the
7
+ # frame's shape is drawn and then a new page is started. This is done to easily
8
+ # compare the changes after each added box.
9
+ #
10
+ # Note how the absolutely positioned box cuts a hole into the frame's shape and
11
+ # how that influences the positioning.
12
+ #
13
+ # Usage:
14
+ # : `ruby frame_automatic_box_placement.rb`
15
+ #
16
+
17
+ require 'hexapdf'
18
+
19
+ include HexaPDF::Layout
20
+
21
+ doc = HexaPDF::Document.new
22
+ page = doc.pages.add
23
+ media_box = page.box(:media)
24
+ canvas = page.canvas
25
+
26
+ frame = Frame.new(media_box.left + 20, media_box.bottom + 20,
27
+ media_box.width - 40, media_box.height - 40)
28
+
29
+ box_counter = 1
30
+ draw_box = lambda do |**args|
31
+ b = Box.create(**args, border: {width: 1, color: [[255, 0, 0]]}) do |canv, box|
32
+ canv.save_graphics_state do
33
+ canv.stroke_color(255, 0, 0)
34
+ canv.line(0, 0, box.content_width, box.content_height).
35
+ line(0, box.content_height, box.content_width, 0).
36
+ stroke
37
+ end
38
+ text = box_counter.to_s << "\n" + args.map {|k, v| "#{k}: #{v}"}.join("\n")
39
+ canv.font("Times", size: 15).leading(15).
40
+ text(text, at: [10, box.content_height - 20])
41
+ box_counter += 1
42
+ end
43
+
44
+ drawn = false
45
+ until drawn
46
+ drawn = frame.draw(canvas, b)
47
+ frame.find_next_region unless drawn
48
+ end
49
+
50
+ canvas.line_width(3).draw(:geom2d, object: frame.shape)
51
+ canvas = doc.pages.add.canvas
52
+ end
53
+
54
+ # Absolutely positioned box with margin
55
+ draw_box.call(width: 100, height: 100, position: :absolute, margin: 10,
56
+ position_hint: [250, 250])
57
+
58
+ # Fixed sized box with automatic width
59
+ draw_box.call(height: 100)
60
+
61
+ # Fixed sized box
62
+ draw_box.call(width: 100, height: 100)
63
+
64
+ # Fixed sized box, placed below the other because the space to the right can't
65
+ # be used
66
+ draw_box.call(width: 100, height: 100)
67
+
68
+ # Fixed sized floating box, space to the right can be used
69
+ draw_box.call(width: 100, height: 100, position: :float, position_hint: :left)
70
+
71
+ # Fixed sized floating box again, floating to the right
72
+ draw_box.call(width: 100, height: 100, position: :float, position_hint: :right)
73
+
74
+ # Fixed sized floating box again, floating to the left with margin
75
+ draw_box.call(width: 100, height: 100, position: :float, position_hint: :left,
76
+ margin: [0, 10])
77
+
78
+ # Fixed sized box, no floating
79
+ draw_box.call(width: 100, height: 100)
80
+
81
+ # Fixed sized box, center aligned in the available space
82
+ draw_box.call(width: 100, height: 100, position_hint: :center)
83
+
84
+ # Fixed sized box, right aligned in the available space
85
+ draw_box.call(width: 100, height: 100, position_hint: :right)
86
+
87
+ # Fixed sized box, consuming the whole remaining available space
88
+ draw_box.call
89
+
90
+ doc.write("frame_automatic_box_placement.pdf", optimize: true)
@@ -0,0 +1,60 @@
1
+ # ## Frame - Text Flow
2
+ #
3
+ # This example shows how [HexaPDF::Layout::Frame] and [HexaPDF::Layout::TextBox]
4
+ # can be used to flow text around objects.
5
+ #
6
+ # Three boxes are placed repeatedly onto the frame until it is filled: two
7
+ # floating boxes (one left, one right) and a text box. The text box is styled to
8
+ # flow its content around the other two boxes.
9
+ #
10
+ # Usage:
11
+ # : `ruby frame_text_flow.rb`
12
+ #
13
+
14
+ require 'hexapdf'
15
+ require 'hexapdf/utils/graphics_helpers'
16
+
17
+ include HexaPDF::Layout
18
+ include HexaPDF::Utils::GraphicsHelpers
19
+
20
+ doc = HexaPDF::Document.new
21
+
22
+ sample_text = "Lorem ipsum dolor sit amet, con\u{00AD}sectetur
23
+ adipis\u{00AD}cing elit, sed do eiusmod tempor incididunt ut labore et
24
+ dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation
25
+ ullamco laboris nisi ut aliquip ex ea commodo consequat.
26
+ ".tr("\n", ' ') * 10
27
+ items = [TextFragment.create(sample_text, font: doc.fonts.add("Times"))]
28
+
29
+ page = doc.pages.add
30
+ media_box = page.box(:media)
31
+ canvas = page.canvas
32
+ frame = Frame.new(media_box.left + 20, media_box.bottom + 20,
33
+ media_box.width - 40, media_box.height - 40)
34
+
35
+ image = doc.images.add(File.join(__dir__, 'machupicchu.jpg'))
36
+ iw, ih = calculate_dimensions(image.width, image.height, rwidth: 100)
37
+
38
+ boxes = []
39
+ boxes << Box.create(width: iw, height: ih,
40
+ margin: [10, 30], position: :float) do |canv, box|
41
+ canv.image(image, at: [0, 0], width: 100)
42
+ end
43
+ boxes << Box.create(width: 50, height: 50, margin: 20,
44
+ position: :float, position_hint: :right,
45
+ border: {width: 1, color: [[255, 0, 0]]})
46
+ boxes << TextBox.new(items, style: {position: :flow, align: :justify})
47
+
48
+ i = 0
49
+ frame_filled = false
50
+ until frame_filled
51
+ box = boxes[i]
52
+ drawn = false
53
+ until drawn || frame_filled
54
+ drawn = frame.draw(canvas, box)
55
+ frame_filled = !frame.find_next_region unless drawn
56
+ end
57
+ i = (i + 1) % boxes.length
58
+ end
59
+
60
+ doc.write("frame_text_flow.pdf", optimize: true)
@@ -115,11 +115,12 @@ module HexaPDF
115
115
  # Writes the document to the given file or does nothing if +out_file+ is +nil+.
116
116
  def write_document(doc, out_file)
117
117
  if out_file
118
- doc.validate(auto_correct: true) do |msg, correctable|
118
+ doc.validate(auto_correct: true) do |object, msg, correctable|
119
119
  if command_parser.strict && !correctable
120
120
  raise "Validation error: #{msg}"
121
121
  elsif command_parser.verbosity_info?
122
- $stderr.puts "#{correctable ? 'Corrected' : 'Ignored'} validation problem: #{msg}"
122
+ $stderr.puts "#{correctable ? 'Corrected' : 'Ignored'} validation problem " \
123
+ "for object (#{object.oid},#{object.gen}): #{msg}"
123
124
  end
124
125
  end
125
126
  doc.write(out_file, validate: false)
@@ -278,7 +279,7 @@ module HexaPDF
278
279
  end
279
280
 
280
281
  PAGE_NUMBER_SPEC = "([1-9]\\d*|e)" #:nodoc:
281
- ROTATE_MAP = {'l' => -90, 'r' => 90, 'd' => 180, 'n' => :none}.freeze #:nodoc:
282
+ ROTATE_MAP = {'l' => 90, 'r' => -90, 'd' => 180, 'n' => :none}.freeze #:nodoc:
282
283
 
283
284
  # Parses the pages specification string and returns an array of tuples containing a page
284
285
  # number and a rotation value (either -90, 90, 180, :none or +nil+ where an integer means
@@ -88,7 +88,7 @@ module HexaPDF
88
88
  if (params = ef_stream[:Params]) && !params.empty?
89
89
  data = []
90
90
  data << "size: #{params[:Size]}" if params.key?(:Size)
91
- data << "md5: #{params[:CheckSum].unpack('H*').first}" if params.key?(:CheckSum)
91
+ data << "md5: #{params[:CheckSum].unpack1('H*')}" if params.key?(:CheckSum)
92
92
  data << "ctime: #{params[:CreationDate]}" if params.key?(:CreationDate)
93
93
  data << "mtime: #{params[:ModDate]}" if params.key?(:ModDate)
94
94
  $stdout.write(" (#{data.join(', ')})")
@@ -74,7 +74,6 @@ module HexaPDF
74
74
  "numbers. The generation number defaults to 0 if not given.") do |str|
75
75
  @exec = :stream
76
76
  @param = str
77
- @raw = (@raw ? @raw : false)
78
77
  end
79
78
  options.on("--raw", "Modifies --stream to show the raw stream data instead of the " \
80
79
  "filtered one.") do
@@ -181,7 +181,7 @@ module HexaPDF
181
181
  if rotation == :none
182
182
  page.delete(:Rotate)
183
183
  elsif rotation.kind_of?(Integer)
184
- page[:Rotate] = ((page[:Rotate] || 0) + rotation) % 360
184
+ page.rotate(rotation)
185
185
  end
186
186
  page_tree.document.add(page)
187
187
  page_tree.add_page(page)
@@ -97,7 +97,7 @@ module HexaPDF
97
97
  if rotation == :none
98
98
  page.delete(:Rotate)
99
99
  elsif rotation.kind_of?(Integer)
100
- page[:Rotate] = ((page[:Rotate] || 0) + rotation) % 360
100
+ page.rotate(rotation)
101
101
  end
102
102
  new_page_tree.add_page(page)
103
103
  end
@@ -349,6 +349,7 @@ module HexaPDF
349
349
  arc: 'HexaPDF::Content::GraphicObject::Arc',
350
350
  endpoint_arc: 'HexaPDF::Content::GraphicObject::EndpointArc',
351
351
  solid_arc: 'HexaPDF::Content::GraphicObject::SolidArc',
352
+ geom2d: 'HexaPDF::Content::GraphicObject::Geom2D',
352
353
  },
353
354
  'graphic_object.arc.max_curves' => 6,
354
355
  'image_loader' => [
@@ -467,6 +468,7 @@ module HexaPDF
467
468
  Font: {
468
469
  Type0: 'HexaPDF::Type::FontType0',
469
470
  Type1: 'HexaPDF::Type::FontType1',
471
+ Type3: 'HexaPDF::Type::FontType3',
470
472
  TrueType: 'HexaPDF::Type::FontTrueType',
471
473
  CIDFontType0: 'HexaPDF::Type::CIDFont',
472
474
  CIDFontType2: 'HexaPDF::Type::CIDFont',
@@ -861,14 +861,14 @@ module HexaPDF
861
861
  # canvas.rectangle(x, y, width, height, radius: 0) => canvas
862
862
  #
863
863
  # Appends a rectangle to the current path as a complete subpath (drawn in counterclockwise
864
- # direction), with the lower-left corner specified by +x+ and +y+ and the given +width+ and
864
+ # direction), with the bottom left corner specified by +x+ and +y+ and the given +width+ and
865
865
  # +height+.
866
866
  #
867
867
  # If +radius+ is greater than 0, the corners are rounded with the given radius.
868
868
  #
869
869
  # If there is no current path when the method is invoked, a new path is automatically begun.
870
870
  #
871
- # The current point is set to the lower-left corner if +radius+ is zero, otherwise it is set
871
+ # The current point is set to the bottom left corner if +radius+ is zero, otherwise it is set
872
872
  # to (x, y + radius).
873
873
  #
874
874
  # Examples:
@@ -1226,7 +1226,7 @@ module HexaPDF
1226
1226
  # If the filename or the IO specifies a PDF file, the first page of this file is used to
1227
1227
  # create a form XObject which is then drawn.
1228
1228
  #
1229
- # The +at+ argument has to be an array containing two numbers specifying the lower-left
1229
+ # The +at+ argument has to be an array containing two numbers specifying the bottom left
1230
1230
  # corner at which to draw the XObject.
1231
1231
  #
1232
1232
  # If +width+ and +height+ are specified, the drawn XObject will have exactly these
@@ -74,6 +74,7 @@ module HexaPDF
74
74
  autoload(:Arc, 'hexapdf/content/graphic_object/arc')
75
75
  autoload(:EndpointArc, 'hexapdf/content/graphic_object/endpoint_arc')
76
76
  autoload(:SolidArc, 'hexapdf/content/graphic_object/solid_arc')
77
+ autoload(:Geom2D, 'hexapdf/content/graphic_object/geom2d')
77
78
 
78
79
  end
79
80
 
@@ -0,0 +1,132 @@
1
+ # -*- encoding: utf-8; frozen_string_literal: true -*-
2
+ #
3
+ #--
4
+ # This file is part of HexaPDF.
5
+ #
6
+ # HexaPDF - A Versatile PDF Creation and Manipulation Library For Ruby
7
+ # Copyright (C) 2014-2018 Thomas Leitner
8
+ #
9
+ # HexaPDF is free software: you can redistribute it and/or modify it
10
+ # under the terms of the GNU Affero General Public License version 3 as
11
+ # published by the Free Software Foundation with the addition of the
12
+ # following permission added to Section 15 as permitted in Section 7(a):
13
+ # FOR ANY PART OF THE COVERED WORK IN WHICH THE COPYRIGHT IS OWNED BY
14
+ # THOMAS LEITNER, THOMAS LEITNER DISCLAIMS THE WARRANTY OF NON
15
+ # INFRINGEMENT OF THIRD PARTY RIGHTS.
16
+ #
17
+ # HexaPDF is distributed in the hope that it will be useful, but WITHOUT
18
+ # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
19
+ # FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
20
+ # License for more details.
21
+ #
22
+ # You should have received a copy of the GNU Affero General Public License
23
+ # along with HexaPDF. If not, see <http://www.gnu.org/licenses/>.
24
+ #
25
+ # The interactive user interfaces in modified source and object code
26
+ # versions of HexaPDF must display Appropriate Legal Notices, as required
27
+ # under Section 5 of the GNU Affero General Public License version 3.
28
+ #
29
+ # In accordance with Section 7(b) of the GNU Affero General Public
30
+ # License, a covered work must retain the producer line in every PDF that
31
+ # is created or manipulated using HexaPDF.
32
+ #++
33
+
34
+ require 'geom2d'
35
+ require 'hexapdf/error'
36
+
37
+ module HexaPDF
38
+ module Content
39
+ module GraphicObject
40
+
41
+ # This class provides support for drawing Geom2D objects like line segments and polygons.
42
+ #
43
+ # See: Geom2D - https://github.com/gettalong/geom2d
44
+ class Geom2D
45
+
46
+ # Creates and configures a new Geom2D drawing support object.
47
+ #
48
+ # See #configure for the allowed keyword arguments.
49
+ def self.configure(**kwargs)
50
+ new.configure(kwargs)
51
+ end
52
+
53
+ # The Geom2D object that should be drawn
54
+ attr_accessor :object
55
+
56
+ # The radius to use when drawing Geom2D::Point objects; defaults to 1
57
+ attr_accessor :point_radius
58
+
59
+ # Specifies whether only paths should be drawn or if they should be stroked/filled too
60
+ attr_accessor :path_only
61
+
62
+ # Creates a Geom2D drawing support object.
63
+ def initialize
64
+ @object = nil
65
+ @point_radius = 1
66
+ @path_only = false
67
+ end
68
+
69
+ # Configures the Geom2D drawing support object. The following arguments are allowed:
70
+ #
71
+ # :object:: The object that should be drawn.
72
+ # :point_radius:: The radius of the points when drawing points.
73
+ # :path_only:: Whether only the path should be drawn.
74
+ #
75
+ # Any arguments not specified are not modified and retain their old value, see the getter
76
+ # methods for the inital values.
77
+ #
78
+ # Returns self.
79
+ def configure(object:, point_radius: nil, path_only: nil)
80
+ @object = object
81
+ @point_radius = point_radius if point_radius
82
+ @path_only = path_only if path_only
83
+ self
84
+ end
85
+
86
+ # Draws the Geom2D object onto the given Canvas.
87
+ def draw(canvas)
88
+ case @object
89
+ when ::Geom2D::Point then draw_point(canvas)
90
+ when ::Geom2D::Segment then draw_segment(canvas)
91
+ when ::Geom2D::Polygon then draw_polygon(canvas)
92
+ when ::Geom2D::PolygonSet then draw_polygon_set(canvas)
93
+ else
94
+ raise HexaPDF::Error, "Object of type #{@object.class} unusable"
95
+ end
96
+ end
97
+
98
+ private
99
+
100
+ def draw_point(canvas)
101
+ canvas.circle(@object.x, @object.y, @point_radius)
102
+ canvas.fill unless @path_only
103
+ end
104
+
105
+ def draw_segment(canvas)
106
+ canvas.line(@object.start_point.x, @object.start_point.y,
107
+ @object.end_point.x, @object.end_point.y)
108
+ canvas.stroke unless @path_only
109
+ end
110
+
111
+ def draw_polygon(canvas)
112
+ return unless @object.nr_of_vertices > 1
113
+ canvas.move_to(@object[0].x, @object[0].y)
114
+ 1.upto(@object.nr_of_vertices - 1) {|i| canvas.line_to(@object[i].x, @object[i].y) }
115
+ canvas.stroke unless @path_only
116
+ end
117
+
118
+ def draw_polygon_set(canvas)
119
+ return if @object.nr_of_contours == 0
120
+ @object.polygons.each do |poly|
121
+ canvas.move_to(poly[0].x, poly[0].y)
122
+ 1.upto(poly.nr_of_vertices - 1) {|i| canvas.line_to(poly[i].x, poly[i].y) }
123
+ canvas.close_subpath
124
+ end
125
+ canvas.stroke unless @path_only
126
+ end
127
+
128
+ end
129
+
130
+ end
131
+ end
132
+ end