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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +39 -1
- data/CONTRIBUTERS +1 -1
- data/LICENSE +3 -0
- data/README.md +2 -1
- data/Rakefile +3 -1
- data/VERSION +1 -1
- data/examples/{hello_world.rb → 001-hello_world.rb} +0 -0
- data/examples/{graphics.rb → 002-graphics.rb} +1 -1
- data/examples/{arc.rb → 003-arcs.rb} +2 -2
- data/examples/{optimizing.rb → 004-optimizing.rb} +0 -0
- data/examples/{merging.rb → 005-merging.rb} +0 -0
- data/examples/{standard_pdf_fonts.rb → 006-standard_pdf_fonts.rb} +0 -0
- data/examples/{truetype.rb → 007-truetype.rb} +0 -0
- data/examples/{show_char_bboxes.rb → 008-show_char_bboxes.rb} +0 -0
- data/examples/{text_layouter_alignment.rb → 009-text_layouter_alignment.rb} +3 -3
- data/examples/{text_layouter_inline_boxes.rb → 010-text_layouter_inline_boxes.rb} +7 -9
- data/examples/{text_layouter_line_wrapping.rb → 011-text_layouter_line_wrapping.rb} +6 -5
- data/examples/{text_layouter_styling.rb → 012-text_layouter_styling.rb} +6 -8
- data/examples/013-text_layouter_shapes.rb +176 -0
- data/examples/014-text_in_polygon.rb +60 -0
- data/examples/{boxes.rb → 015-boxes.rb} +29 -21
- data/examples/016-frame_automatic_box_placement.rb +90 -0
- data/examples/017-frame_text_flow.rb +60 -0
- data/lib/hexapdf/cli/command.rb +4 -3
- data/lib/hexapdf/cli/files.rb +1 -1
- data/lib/hexapdf/cli/inspect.rb +0 -1
- data/lib/hexapdf/cli/merge.rb +1 -1
- data/lib/hexapdf/cli/modify.rb +1 -1
- data/lib/hexapdf/configuration.rb +2 -0
- data/lib/hexapdf/content/canvas.rb +3 -3
- data/lib/hexapdf/content/graphic_object.rb +1 -0
- data/lib/hexapdf/content/graphic_object/geom2d.rb +132 -0
- data/lib/hexapdf/dictionary.rb +7 -1
- data/lib/hexapdf/dictionary_fields.rb +35 -83
- data/lib/hexapdf/document.rb +9 -5
- data/lib/hexapdf/document/fonts.rb +1 -1
- data/lib/hexapdf/encryption/standard_security_handler.rb +1 -1
- data/lib/hexapdf/filter/ascii85_decode.rb +1 -1
- data/lib/hexapdf/filter/ascii_hex_decode.rb +1 -1
- data/lib/hexapdf/font/cmap/writer.rb +2 -2
- data/lib/hexapdf/font/true_type/builder.rb +1 -1
- data/lib/hexapdf/font/true_type/table.rb +1 -1
- data/lib/hexapdf/font/true_type/table/cmap.rb +1 -1
- data/lib/hexapdf/font/true_type/table/cmap_subtable.rb +3 -3
- data/lib/hexapdf/font/true_type/table/kern.rb +1 -1
- data/lib/hexapdf/font/true_type/table/post.rb +1 -1
- data/lib/hexapdf/font/type1/character_metrics.rb +1 -1
- data/lib/hexapdf/font/type1/font_metrics.rb +1 -1
- data/lib/hexapdf/image_loader/jpeg.rb +1 -1
- data/lib/hexapdf/image_loader/png.rb +2 -2
- data/lib/hexapdf/layout.rb +3 -0
- data/lib/hexapdf/layout/box.rb +64 -46
- data/lib/hexapdf/layout/frame.rb +348 -0
- data/lib/hexapdf/layout/inline_box.rb +2 -2
- data/lib/hexapdf/layout/line.rb +3 -3
- data/lib/hexapdf/layout/style.rb +81 -14
- data/lib/hexapdf/layout/text_box.rb +84 -0
- data/lib/hexapdf/layout/text_fragment.rb +8 -8
- data/lib/hexapdf/layout/text_layouter.rb +278 -169
- data/lib/hexapdf/layout/width_from_polygon.rb +246 -0
- data/lib/hexapdf/rectangle.rb +9 -9
- data/lib/hexapdf/stream.rb +2 -2
- data/lib/hexapdf/type.rb +1 -0
- data/lib/hexapdf/type/action.rb +1 -1
- data/lib/hexapdf/type/annotations/markup_annotation.rb +1 -1
- data/lib/hexapdf/type/catalog.rb +1 -1
- data/lib/hexapdf/type/cid_font.rb +2 -1
- data/lib/hexapdf/type/font.rb +0 -1
- data/lib/hexapdf/type/font_descriptor.rb +1 -1
- data/lib/hexapdf/type/font_simple.rb +3 -3
- data/lib/hexapdf/type/font_true_type.rb +8 -0
- data/lib/hexapdf/type/font_type0.rb +2 -1
- data/lib/hexapdf/type/font_type1.rb +7 -1
- data/lib/hexapdf/type/font_type3.rb +61 -0
- data/lib/hexapdf/type/graphics_state_parameter.rb +8 -8
- data/lib/hexapdf/type/image.rb +10 -0
- data/lib/hexapdf/type/page.rb +83 -10
- data/lib/hexapdf/version.rb +1 -1
- data/test/hexapdf/common_tokenizer_tests.rb +2 -2
- data/test/hexapdf/content/graphic_object/test_geom2d.rb +79 -0
- data/test/hexapdf/encryption/test_standard_security_handler.rb +1 -1
- data/test/hexapdf/font/test_true_type_wrapper.rb +1 -1
- data/test/hexapdf/font/test_type1_wrapper.rb +1 -1
- data/test/hexapdf/font/true_type/table/test_cmap.rb +1 -1
- data/test/hexapdf/font/true_type/table/test_directory.rb +1 -1
- data/test/hexapdf/font/true_type/table/test_head.rb +7 -3
- data/test/hexapdf/layout/test_box.rb +57 -15
- data/test/hexapdf/layout/test_frame.rb +313 -0
- data/test/hexapdf/layout/test_inline_box.rb +1 -1
- data/test/hexapdf/layout/test_style.rb +74 -0
- data/test/hexapdf/layout/test_text_box.rb +77 -0
- data/test/hexapdf/layout/test_text_layouter.rb +220 -239
- data/test/hexapdf/layout/test_width_from_polygon.rb +108 -0
- data/test/hexapdf/test_dictionary_fields.rb +22 -26
- data/test/hexapdf/test_document.rb +3 -3
- data/test/hexapdf/test_reference.rb +1 -0
- data/test/hexapdf/test_writer.rb +2 -2
- data/test/hexapdf/type/test_font_true_type.rb +25 -0
- data/test/hexapdf/type/test_font_type1.rb +6 -0
- data/test/hexapdf/type/test_font_type3.rb +26 -0
- data/test/hexapdf/type/test_image.rb +10 -0
- data/test/hexapdf/type/test_page.rb +114 -0
- data/test/test_helper.rb +1 -1
- metadata +65 -17
- 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,
|
|
41
|
-
[[:solid,
|
|
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.
|
|
44
|
-
|
|
45
|
-
|
|
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.
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
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)
|
data/lib/hexapdf/cli/command.rb
CHANGED
|
@@ -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
|
|
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' =>
|
|
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
|
data/lib/hexapdf/cli/files.rb
CHANGED
|
@@ -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].
|
|
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(', ')})")
|
data/lib/hexapdf/cli/inspect.rb
CHANGED
|
@@ -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
|
data/lib/hexapdf/cli/merge.rb
CHANGED
|
@@ -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
|
|
184
|
+
page.rotate(rotation)
|
|
185
185
|
end
|
|
186
186
|
page_tree.document.add(page)
|
|
187
187
|
page_tree.add_page(page)
|
data/lib/hexapdf/cli/modify.rb
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
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
|