hexapdf 0.7.0 → 0.8.0

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