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