hexapdf 0.34.1 ā 0.35.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +76 -0
- data/examples/009-text_layouter_alignment.rb +7 -7
- data/examples/010-text_layouter_inline_boxes.rb +1 -1
- data/examples/011-text_layouter_line_wrapping.rb +2 -4
- data/examples/013-text_layouter_shapes.rb +9 -11
- data/examples/014-text_in_polygon.rb +2 -2
- data/examples/016-frame_automatic_box_placement.rb +6 -7
- data/examples/017-frame_text_flow.rb +2 -2
- data/examples/018-composer.rb +5 -6
- data/examples/020-column_box.rb +2 -2
- data/examples/021-list_box.rb +1 -1
- data/examples/027-composer_optional_content.rb +5 -5
- data/examples/028-frame_mask_mode.rb +23 -0
- data/examples/029-composer_fallback_fonts.rb +22 -0
- data/lib/hexapdf/cli/info.rb +1 -0
- data/lib/hexapdf/cli/inspect.rb +55 -2
- data/lib/hexapdf/composer.rb +4 -3
- data/lib/hexapdf/configuration.rb +61 -1
- data/lib/hexapdf/content/canvas.rb +63 -0
- data/lib/hexapdf/content/canvas_composer.rb +142 -0
- data/lib/hexapdf/content/graphic_object/endpoint_arc.rb +2 -8
- data/lib/hexapdf/content.rb +1 -0
- data/lib/hexapdf/dictionary.rb +14 -3
- data/lib/hexapdf/document/layout.rb +35 -13
- data/lib/hexapdf/document.rb +1 -0
- data/lib/hexapdf/encryption/standard_security_handler.rb +15 -0
- data/lib/hexapdf/error.rb +2 -1
- data/lib/hexapdf/font/invalid_glyph.rb +22 -6
- data/lib/hexapdf/font/true_type_wrapper.rb +48 -20
- data/lib/hexapdf/font/type1_wrapper.rb +48 -24
- data/lib/hexapdf/layout/box.rb +14 -10
- data/lib/hexapdf/layout/column_box.rb +5 -3
- data/lib/hexapdf/layout/frame.rb +78 -40
- data/lib/hexapdf/layout/image_box.rb +3 -3
- data/lib/hexapdf/layout/list_box.rb +20 -19
- data/lib/hexapdf/layout/style.rb +173 -68
- data/lib/hexapdf/layout/table_box.rb +3 -3
- data/lib/hexapdf/layout/text_box.rb +5 -5
- data/lib/hexapdf/layout/text_fragment.rb +50 -0
- data/lib/hexapdf/layout/text_layouter.rb +7 -6
- data/lib/hexapdf/layout/width_from_polygon.rb +1 -1
- data/lib/hexapdf/object.rb +5 -2
- data/lib/hexapdf/pdf_array.rb +5 -0
- data/lib/hexapdf/type/acro_form/appearance_generator.rb +16 -11
- data/lib/hexapdf/type/icon_fit.rb +2 -2
- data/lib/hexapdf/type/page.rb +35 -35
- data/lib/hexapdf/utils/sorted_tree_node.rb +0 -10
- data/lib/hexapdf/utils.rb +66 -0
- data/lib/hexapdf/version.rb +1 -1
- data/test/hexapdf/content/test_canvas.rb +37 -0
- data/test/hexapdf/content/test_canvas_composer.rb +112 -0
- data/test/hexapdf/document/test_layout.rb +40 -12
- data/test/hexapdf/document/test_pages.rb +5 -5
- data/test/hexapdf/encryption/test_standard_security_handler.rb +43 -0
- data/test/hexapdf/font/test_invalid_glyph.rb +13 -1
- data/test/hexapdf/font/test_true_type_wrapper.rb +15 -2
- data/test/hexapdf/font/test_type1_wrapper.rb +21 -2
- data/test/hexapdf/layout/test_box.rb +7 -0
- data/test/hexapdf/layout/test_column_box.rb +18 -4
- data/test/hexapdf/layout/test_frame.rb +181 -95
- data/test/hexapdf/layout/test_list_box.rb +7 -7
- data/test/hexapdf/layout/test_page_style.rb +5 -5
- data/test/hexapdf/layout/test_style.rb +14 -10
- data/test/hexapdf/layout/test_table_box.rb +3 -3
- data/test/hexapdf/layout/test_text_box.rb +2 -2
- data/test/hexapdf/layout/test_text_fragment.rb +37 -0
- data/test/hexapdf/layout/test_text_layouter.rb +10 -10
- data/test/hexapdf/test_composer.rb +13 -13
- data/test/hexapdf/test_configuration.rb +49 -0
- data/test/hexapdf/test_dictionary.rb +1 -1
- data/test/hexapdf/test_object.rb +13 -12
- data/test/hexapdf/test_pdf_array.rb +9 -0
- data/test/hexapdf/test_utils.rb +20 -0
- data/test/hexapdf/test_writer.rb +3 -3
- data/test/hexapdf/type/acro_form/test_appearance_generator.rb +41 -13
- data/test/hexapdf/type/test_page.rb +3 -3
- data/test/hexapdf/type/test_page_tree_node.rb +1 -1
- data/test/hexapdf/utils/test_sorted_tree_node.rb +1 -1
- metadata +9 -3
data/lib/hexapdf/type/page.rb
CHANGED
|
@@ -62,41 +62,41 @@ module HexaPDF
|
|
|
62
62
|
# * ISO sizes: A0x4, A0x2, A0-A10, B0-B10, C0-C10
|
|
63
63
|
# * Letter, Legal, Ledger, Tabloid, Executive
|
|
64
64
|
PAPER_SIZE = {
|
|
65
|
-
A0x4: [0, 0,
|
|
66
|
-
A0x2: [0, 0, 3370,
|
|
67
|
-
A0: [0, 0,
|
|
68
|
-
A1: [0, 0,
|
|
69
|
-
A2: [0, 0,
|
|
70
|
-
A3: [0, 0,
|
|
71
|
-
A4: [0, 0, 595,
|
|
72
|
-
A5: [0, 0,
|
|
73
|
-
A6: [0, 0,
|
|
74
|
-
A7: [0, 0,
|
|
75
|
-
A8: [0, 0, 147,
|
|
76
|
-
A9: [0, 0,
|
|
77
|
-
A10: [0, 0,
|
|
78
|
-
B0: [0, 0,
|
|
79
|
-
B1: [0, 0, 2004,
|
|
80
|
-
B2: [0, 0, 1417, 2004].freeze,
|
|
81
|
-
B3: [0, 0,
|
|
82
|
-
B4: [0, 0,
|
|
83
|
-
B5: [0, 0,
|
|
84
|
-
B6: [0, 0, 354,
|
|
85
|
-
B7: [0, 0, 249, 354].freeze,
|
|
86
|
-
B8: [0, 0,
|
|
87
|
-
B9: [0, 0,
|
|
88
|
-
B10: [0, 0,
|
|
89
|
-
C0: [0, 0, 2599,
|
|
90
|
-
C1: [0, 0,
|
|
91
|
-
C2: [0, 0, 1298,
|
|
92
|
-
C3: [0, 0, 918, 1298].freeze,
|
|
93
|
-
C4: [0, 0, 649, 918].freeze,
|
|
94
|
-
C5: [0, 0, 459, 649].freeze,
|
|
95
|
-
C6: [0, 0, 323, 459].freeze,
|
|
96
|
-
C7: [0, 0,
|
|
97
|
-
C8: [0, 0,
|
|
98
|
-
C9: [0, 0, 113,
|
|
99
|
-
C10: [0, 0, 79, 113].freeze,
|
|
65
|
+
A0x4: [0, 0, 4767.874016, 6740.787402].freeze,
|
|
66
|
+
A0x2: [0, 0, 3370.393701, 4767.874016].freeze,
|
|
67
|
+
A0: [0, 0, 2383.937008, 3370.393701].freeze,
|
|
68
|
+
A1: [0, 0, 1683.779528, 2383.937008].freeze,
|
|
69
|
+
A2: [0, 0, 1190.551181, 1683.779528].freeze,
|
|
70
|
+
A3: [0, 0, 841.889764, 1190.551181].freeze,
|
|
71
|
+
A4: [0, 0, 595.275591, 841.889764].freeze,
|
|
72
|
+
A5: [0, 0, 419.527559, 595.275591].freeze,
|
|
73
|
+
A6: [0, 0, 297.637795, 419.527559].freeze,
|
|
74
|
+
A7: [0, 0, 209.76378, 297.637795].freeze,
|
|
75
|
+
A8: [0, 0, 147.401575, 209.76378].freeze,
|
|
76
|
+
A9: [0, 0, 104.88189, 147.401575].freeze,
|
|
77
|
+
A10: [0, 0, 73.700787, 104.88189].freeze,
|
|
78
|
+
B0: [0, 0, 2834.645669, 4008.188976].freeze,
|
|
79
|
+
B1: [0, 0, 2004.094488, 2834.645669].freeze,
|
|
80
|
+
B2: [0, 0, 1417.322835, 2004.094488].freeze,
|
|
81
|
+
B3: [0, 0, 1000.629921, 1417.322835].freeze,
|
|
82
|
+
B4: [0, 0, 708.661417, 1000.629921].freeze,
|
|
83
|
+
B5: [0, 0, 498.897638, 708.661417].freeze,
|
|
84
|
+
B6: [0, 0, 354.330709, 498.897638].freeze,
|
|
85
|
+
B7: [0, 0, 249.448819, 354.330709].freeze,
|
|
86
|
+
B8: [0, 0, 175.748031, 249.448819].freeze,
|
|
87
|
+
B9: [0, 0, 124.724409, 175.748031].freeze,
|
|
88
|
+
B10: [0, 0, 87.874016, 124.724409].freeze,
|
|
89
|
+
C0: [0, 0, 2599.370079, 3676.535433].freeze,
|
|
90
|
+
C1: [0, 0, 1836.850394, 2599.370079].freeze,
|
|
91
|
+
C2: [0, 0, 1298.267717, 1836.850394].freeze,
|
|
92
|
+
C3: [0, 0, 918.425197, 1298.267717].freeze,
|
|
93
|
+
C4: [0, 0, 649.133858, 918.425197].freeze,
|
|
94
|
+
C5: [0, 0, 459.212598, 649.133858].freeze,
|
|
95
|
+
C6: [0, 0, 323.149606, 459.212598].freeze,
|
|
96
|
+
C7: [0, 0, 229.606299, 323.149606].freeze,
|
|
97
|
+
C8: [0, 0, 161.574803, 229.606299].freeze,
|
|
98
|
+
C9: [0, 0, 113.385827, 161.574803].freeze,
|
|
99
|
+
C10: [0, 0, 79.370079, 113.385827].freeze,
|
|
100
100
|
Letter: [0, 0, 612, 792].freeze,
|
|
101
101
|
Legal: [0, 0, 612, 1008].freeze,
|
|
102
102
|
Ledger: [0, 0, 792, 1224].freeze,
|
|
@@ -307,16 +307,6 @@ module HexaPDF
|
|
|
307
307
|
super
|
|
308
308
|
container_name = leaf_node_container_name
|
|
309
309
|
|
|
310
|
-
# All kids entries must be indirect objects
|
|
311
|
-
if key?(:Kids)
|
|
312
|
-
self[:Kids].each_with_index do |kid, index|
|
|
313
|
-
unless kid.kind_of?(HexaPDF::Object) && kid.indirect?
|
|
314
|
-
yield("Child entries of sorted tree nodes must be indirect objects", true)
|
|
315
|
-
value[:Kids][index] = document.add(kid)
|
|
316
|
-
end
|
|
317
|
-
end
|
|
318
|
-
end
|
|
319
|
-
|
|
320
310
|
# All keys of the container must be lexically ordered strings and the container must be
|
|
321
311
|
# correctly formatted
|
|
322
312
|
if key?(container_name)
|
|
@@ -0,0 +1,66 @@
|
|
|
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-2023 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
|
+
# If the GNU Affero General Public License doesn't fit your need,
|
|
34
|
+
# commercial licenses are available at <https://gettalong.at/hexapdf/>.
|
|
35
|
+
#++
|
|
36
|
+
|
|
37
|
+
require 'geom2d/utils'
|
|
38
|
+
|
|
39
|
+
module HexaPDF
|
|
40
|
+
module Utils
|
|
41
|
+
|
|
42
|
+
# The precision with which to compare floating point numbers.
|
|
43
|
+
#
|
|
44
|
+
# This is chosen with respect to precision that is used for serializing floating point numbers.
|
|
45
|
+
EPSILON = 1e-6
|
|
46
|
+
|
|
47
|
+
# Best effort of setting Geom2D's precision to the one used by HexaPDF.
|
|
48
|
+
::Geom2D::Utils.precision = EPSILON
|
|
49
|
+
|
|
50
|
+
module_function
|
|
51
|
+
|
|
52
|
+
private
|
|
53
|
+
|
|
54
|
+
# Compares two floats whether they are equal using the FLOAT_EPSILON precision.
|
|
55
|
+
def float_equal(a, b)
|
|
56
|
+
(a - b).abs < EPSILON
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Compares two floats like the <=> operator but using the FLOAT_EPSILON precision for detecting
|
|
60
|
+
# whether they are equal.
|
|
61
|
+
def float_compare(a, b)
|
|
62
|
+
(a - b).abs < EPSILON ? 0 : a <=> b
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
end
|
|
66
|
+
end
|
data/lib/hexapdf/version.rb
CHANGED
|
@@ -756,6 +756,28 @@ describe HexaPDF::Content::Canvas do
|
|
|
756
756
|
end
|
|
757
757
|
end
|
|
758
758
|
|
|
759
|
+
describe "form" do
|
|
760
|
+
it "uses the context dimensions if none are given" do
|
|
761
|
+
form = @canvas.form
|
|
762
|
+
assert_equal(@canvas.context.box.value, form.box.value)
|
|
763
|
+
end
|
|
764
|
+
|
|
765
|
+
it "uses the provided dimensions" do
|
|
766
|
+
form = @canvas.form(300, 200)
|
|
767
|
+
assert_equal([0, 0, 300, 200], form.box.value)
|
|
768
|
+
end
|
|
769
|
+
|
|
770
|
+
it "yields the canvas for defining the form's content" do
|
|
771
|
+
yielded_canvas = nil
|
|
772
|
+
form = @canvas.form {|canvas| yielded_canvas = canvas }
|
|
773
|
+
assert_equal(form.canvas, yielded_canvas)
|
|
774
|
+
end
|
|
775
|
+
|
|
776
|
+
it "raises an ArgumentError if only one of width/height is provided" do
|
|
777
|
+
assert_raises(ArgumentError) { @canvas.form(20) }
|
|
778
|
+
end
|
|
779
|
+
end
|
|
780
|
+
|
|
759
781
|
describe "graphic_object" do
|
|
760
782
|
it "returns a new graphic object given a name" do
|
|
761
783
|
arc = @canvas.graphic_object(:arc)
|
|
@@ -1329,6 +1351,21 @@ describe HexaPDF::Content::Canvas do
|
|
|
1329
1351
|
end
|
|
1330
1352
|
end
|
|
1331
1353
|
|
|
1354
|
+
describe "composer" do
|
|
1355
|
+
it "creates a CanvasComposer, yields it and returns it" do
|
|
1356
|
+
comp1 = nil
|
|
1357
|
+
comp2 = @canvas.composer {|composer| comp1 = composer }
|
|
1358
|
+
assert_kind_of(HexaPDF::Content::CanvasComposer, comp1)
|
|
1359
|
+
assert_same(comp1, comp2)
|
|
1360
|
+
assert_same(@canvas, comp1.canvas)
|
|
1361
|
+
end
|
|
1362
|
+
|
|
1363
|
+
it "passes on the margin argument" do
|
|
1364
|
+
comp = @canvas.composer(margin: 20)
|
|
1365
|
+
assert_equal(20, comp.frame.x)
|
|
1366
|
+
end
|
|
1367
|
+
end
|
|
1368
|
+
|
|
1332
1369
|
describe "color_from_specification "do
|
|
1333
1370
|
it "accepts a color string" do
|
|
1334
1371
|
assert_equal([1, 0, 0], @canvas.color_from_specification("red").components)
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
|
2
|
+
|
|
3
|
+
require 'test_helper'
|
|
4
|
+
require 'hexapdf/content/canvas_composer'
|
|
5
|
+
require 'hexapdf/document'
|
|
6
|
+
|
|
7
|
+
describe HexaPDF::Content::CanvasComposer do
|
|
8
|
+
before do
|
|
9
|
+
@doc = HexaPDF::Document.new
|
|
10
|
+
@page = @doc.pages.add
|
|
11
|
+
@canvas = @page.canvas
|
|
12
|
+
@composer = @canvas.composer
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
describe "initialize" do
|
|
16
|
+
it "creates the necessary objects like frame for doing the work" do
|
|
17
|
+
assert_equal(@page.box.width, @composer.frame.width)
|
|
18
|
+
assert_equal(@page.box.height, @composer.frame.height)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
it 'allows specifying a value for the margin' do
|
|
22
|
+
composer = @canvas.composer(margin: [10, 30])
|
|
23
|
+
assert_equal(@page.box.width - 60, composer.frame.width)
|
|
24
|
+
assert_equal(@page.box.height - 20, composer.frame.height)
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
it "provides easy access to the global styles" do
|
|
29
|
+
assert_same(@doc.layout.style(:base), @composer.style(:base))
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
describe "draw_box" do
|
|
33
|
+
def create_box(**kwargs)
|
|
34
|
+
HexaPDF::Layout::Box.new(**kwargs) {}
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
it "draws the box if it completely fits" do
|
|
38
|
+
@composer.draw_box(create_box(height: 100))
|
|
39
|
+
@composer.draw_box(create_box)
|
|
40
|
+
assert_operators(@composer.canvas.contents,
|
|
41
|
+
[[:save_graphics_state],
|
|
42
|
+
[:concatenate_matrix, [1, 0, 0, 1, 0, 741.889764]],
|
|
43
|
+
[:restore_graphics_state],
|
|
44
|
+
[:save_graphics_state],
|
|
45
|
+
[:concatenate_matrix, [1, 0, 0, 1, 0, 0]],
|
|
46
|
+
[:restore_graphics_state]])
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
it "splits the box if possible" do
|
|
50
|
+
@composer.draw_box(create_box(width: 400, style: {position: :float}))
|
|
51
|
+
box = create_box(width: 400, height: 100)
|
|
52
|
+
box.define_singleton_method(:split) do |*|
|
|
53
|
+
[box, HexaPDF::Layout::Box.new(height: 100) {}]
|
|
54
|
+
end
|
|
55
|
+
@composer.draw_box(box)
|
|
56
|
+
assert_operators(@composer.canvas.contents,
|
|
57
|
+
[[:save_graphics_state],
|
|
58
|
+
[:concatenate_matrix, [1, 0, 0, 1, 0, 0]],
|
|
59
|
+
[:restore_graphics_state],
|
|
60
|
+
[:save_graphics_state],
|
|
61
|
+
[:concatenate_matrix, [1, 0, 0, 1, 400, 741.889764]],
|
|
62
|
+
[:restore_graphics_state],
|
|
63
|
+
[:save_graphics_state],
|
|
64
|
+
[:concatenate_matrix, [1, 0, 0, 1, 400, 641.889764]],
|
|
65
|
+
[:restore_graphics_state]])
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
it "finds a new region if splitting doesn't work" do
|
|
69
|
+
@composer.draw_box(create_box(width: 400, height: 100, style: {position: :float}))
|
|
70
|
+
@composer.draw_box(create_box(width: 400, height: 100))
|
|
71
|
+
assert_operators(@composer.canvas.contents,
|
|
72
|
+
[[:save_graphics_state],
|
|
73
|
+
[:concatenate_matrix, [1, 0, 0, 1, 0, 741.889764]],
|
|
74
|
+
[:restore_graphics_state],
|
|
75
|
+
[:save_graphics_state],
|
|
76
|
+
[:concatenate_matrix, [1, 0, 0, 1, 0, 641.889764]],
|
|
77
|
+
[:restore_graphics_state]])
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
it "returns the last drawn box" do
|
|
81
|
+
box = create_box(height: 400)
|
|
82
|
+
assert_same(box, @composer.draw_box(box))
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
it "raises an error if the frame is full" do
|
|
86
|
+
@composer.draw_box(create_box)
|
|
87
|
+
exception = assert_raises(HexaPDF::Error) { @composer.draw_box(create_box(height: 10)) }
|
|
88
|
+
assert_match(/Frame.*full/, exception.message)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
it "raises an error if a new region cannot be found after splitting" do
|
|
92
|
+
@composer.draw_box(create_box(height: 400))
|
|
93
|
+
exception = assert_raises(HexaPDF::Error) { @composer.draw_box(create_box(height: 500)) }
|
|
94
|
+
assert_match(/Frame.*full/, exception.message)
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
describe "method_missing" do
|
|
99
|
+
it "delegates box methods to @document.layout" do
|
|
100
|
+
box = @composer.column(width: 100)
|
|
101
|
+
assert_equal(100, box.width)
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
it "fails for missing methods that can't be delegated to @document.layout" do
|
|
105
|
+
assert_raises(NameError) { @composer.unknown_box }
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
it "can be asked whether a missing method is supported" do
|
|
110
|
+
assert(@composer.respond_to?(:column))
|
|
111
|
+
end
|
|
112
|
+
end
|
|
@@ -110,6 +110,9 @@ end
|
|
|
110
110
|
describe HexaPDF::Document::Layout do
|
|
111
111
|
before do
|
|
112
112
|
@doc = HexaPDF::Document.new
|
|
113
|
+
@doc.config['font.on_invalid_glyph'] = lambda do |codepoint, invalid_glyph|
|
|
114
|
+
[@doc.fonts.add('ZapfDingbats').decode_codepoint(codepoint)]
|
|
115
|
+
end
|
|
113
116
|
@layout = @doc.layout
|
|
114
117
|
end
|
|
115
118
|
|
|
@@ -177,14 +180,37 @@ describe HexaPDF::Document::Layout do
|
|
|
177
180
|
end
|
|
178
181
|
end
|
|
179
182
|
|
|
183
|
+
describe "text_fragments" do
|
|
184
|
+
it "creates an array of text fragments with fallback glyph support" do
|
|
185
|
+
result = @layout.text_fragments("Tomā")
|
|
186
|
+
assert_equal(2, result.size)
|
|
187
|
+
assert_equal(@doc.fonts.add('ZapfDingbats'), result[1].style.font)
|
|
188
|
+
|
|
189
|
+
@doc.config['font.on_invalid_glyph'] = nil
|
|
190
|
+
assert_equal(1, @layout.text_fragments("Tomā").size)
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
it "uses the standard rules for creating the style object" do
|
|
194
|
+
@layout.style(:named, font_size: 20)
|
|
195
|
+
result = @layout.text_fragments("Test", style: :named)
|
|
196
|
+
assert_equal(20, result[0].style.font_size)
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
it "optionally assigns the properties to all fragments" do
|
|
200
|
+
result = @layout.text_fragments("Tomā", properties: {key: :value})
|
|
201
|
+
assert_equal(:value, result[0].properties[:key])
|
|
202
|
+
assert_equal(:value, result[1].properties[:key])
|
|
203
|
+
end
|
|
204
|
+
end
|
|
205
|
+
|
|
180
206
|
describe "text_box" do
|
|
181
207
|
it "creates a text box" do
|
|
182
|
-
box = @layout.text_box("Test", width: 10, height: 15, properties: {key: :value})
|
|
208
|
+
box = @layout.text_box("Testā", width: 10, height: 15, properties: {key: :value})
|
|
183
209
|
assert_equal(10, box.width)
|
|
184
210
|
assert_equal(15, box.height)
|
|
185
211
|
assert_same(@doc.fonts.add("Times"), box.style.font)
|
|
186
212
|
items = box.instance_variable_get(:@items)
|
|
187
|
-
assert_equal(
|
|
213
|
+
assert_equal(2, items.length)
|
|
188
214
|
assert_same(box.style, items.first.style)
|
|
189
215
|
assert_equal({key: :value}, box.properties)
|
|
190
216
|
end
|
|
@@ -227,20 +253,22 @@ describe HexaPDF::Document::Layout do
|
|
|
227
253
|
|
|
228
254
|
describe "formatted_text" do
|
|
229
255
|
it "creates a text box with the given text" do
|
|
230
|
-
box = @layout.formatted_text_box(["Test"], width: 10, height: 15)
|
|
256
|
+
box = @layout.formatted_text_box(["Testā"], width: 10, height: 15)
|
|
231
257
|
assert_equal(10, box.width)
|
|
232
258
|
assert_equal(15, box.height)
|
|
233
|
-
assert_equal(
|
|
259
|
+
assert_equal(2, box.instance_variable_get(:@items).length)
|
|
234
260
|
end
|
|
235
261
|
|
|
236
262
|
it "allows setting custom properties on the whole box" do
|
|
237
|
-
box = @layout.formatted_text_box(["Test"
|
|
263
|
+
box = @layout.formatted_text_box([{text: "Test", properties: {key: :novalue}}],
|
|
264
|
+
properties: {key: :value})
|
|
238
265
|
assert_equal({key: :value}, box.properties)
|
|
239
266
|
end
|
|
240
267
|
|
|
241
268
|
it "allows using a hash with :text key instead of a simple string" do
|
|
242
|
-
box = @layout.formatted_text_box([{text: "Test"}])
|
|
269
|
+
box = @layout.formatted_text_box([{text: "Testā"}])
|
|
243
270
|
items = box.instance_variable_get(:@items)
|
|
271
|
+
assert_equal(2, items.length)
|
|
244
272
|
assert_equal(4, items[0].items.length)
|
|
245
273
|
end
|
|
246
274
|
|
|
@@ -259,29 +287,29 @@ describe HexaPDF::Document::Layout do
|
|
|
259
287
|
end
|
|
260
288
|
|
|
261
289
|
it "allows using custom style properties for a single part" do
|
|
262
|
-
box = @layout.formatted_text_box([{text: "Test", font_size: 20}, "test"],
|
|
290
|
+
box = @layout.formatted_text_box([{text: "Test", font_size: 20}, "test"], text_align: :center)
|
|
263
291
|
items = box.instance_variable_get(:@items)
|
|
264
292
|
assert_equal(10, box.style.font_size)
|
|
265
293
|
|
|
266
294
|
assert_equal(20, items[0].style.font_size)
|
|
267
|
-
assert_equal(:center, items[0].style.
|
|
295
|
+
assert_equal(:center, items[0].style.text_align)
|
|
268
296
|
|
|
269
297
|
assert_equal(10, items[1].style.font_size)
|
|
270
|
-
assert_equal(:center, items[1].style.
|
|
298
|
+
assert_equal(:center, items[1].style.text_align)
|
|
271
299
|
end
|
|
272
300
|
|
|
273
301
|
it "allows using a custom style as basis for a single part" do
|
|
274
302
|
box = @layout.formatted_text_box([{text: "Test", style: {font_size: 20}, subscript: true},
|
|
275
|
-
"test"],
|
|
303
|
+
"test"], text_align: :center)
|
|
276
304
|
items = box.instance_variable_get(:@items)
|
|
277
305
|
assert_equal(10, box.style.font_size)
|
|
278
306
|
|
|
279
307
|
assert_equal(20, items[0].style.font_size)
|
|
280
|
-
assert_equal(:left, items[0].style.
|
|
308
|
+
assert_equal(:left, items[0].style.text_align)
|
|
281
309
|
assert(items[0].style.subscript)
|
|
282
310
|
|
|
283
311
|
assert_equal(10, items[1].style.font_size)
|
|
284
|
-
assert_equal(:center, items[1].style.
|
|
312
|
+
assert_equal(:center, items[1].style.text_align)
|
|
285
313
|
refute(items[1].style.subscript)
|
|
286
314
|
end
|
|
287
315
|
|
|
@@ -17,20 +17,20 @@ describe HexaPDF::Document::Pages do
|
|
|
17
17
|
describe "create" do
|
|
18
18
|
it "uses the defaults from the configuration for missing arguments" do
|
|
19
19
|
page = @doc.pages.create
|
|
20
|
-
assert_equal([0, 0, 595,
|
|
20
|
+
assert_equal([0, 0, 595.275591, 841.889764], page.box(:media).value)
|
|
21
21
|
end
|
|
22
22
|
|
|
23
23
|
it "allows specifying a reference to a predefined page size" do
|
|
24
24
|
page = @doc.pages.create(media_box: :A3)
|
|
25
|
-
assert_equal([0, 0,
|
|
25
|
+
assert_equal([0, 0, 841.889764, 1190.551181], page.box(:media).value)
|
|
26
26
|
end
|
|
27
27
|
|
|
28
28
|
it "allows specifying the orientation for a predefined page size" do
|
|
29
29
|
page = @doc.pages.create(media_box: :A4, orientation: :landscape)
|
|
30
|
-
assert_equal([0, 0,
|
|
30
|
+
assert_equal([0, 0, 841.889764, 595.275591], page.box(:media).value)
|
|
31
31
|
|
|
32
32
|
page = @doc.pages.create(orientation: :landscape)
|
|
33
|
-
assert_equal([0, 0,
|
|
33
|
+
assert_equal([0, 0, 841.889764, 595.275591], page.box(:media).value)
|
|
34
34
|
end
|
|
35
35
|
|
|
36
36
|
it "allows using a media box array" do
|
|
@@ -54,7 +54,7 @@ describe HexaPDF::Document::Pages do
|
|
|
54
54
|
it "adds a new empty page with the given page format" do
|
|
55
55
|
page = @doc.pages.add(:A4, orientation: :landscape)
|
|
56
56
|
assert_same(page, @doc.pages[0])
|
|
57
|
-
assert_equal([0, 0,
|
|
57
|
+
assert_equal([0, 0, 841.889764, 595.275591], @doc.pages[0].box(:media).value)
|
|
58
58
|
end
|
|
59
59
|
|
|
60
60
|
it "adds the given page to the end" do
|
|
@@ -301,6 +301,49 @@ describe HexaPDF::Encryption::StandardSecurityHandler do
|
|
|
301
301
|
assert_equal([:copy_content, :extract_content, :modify_content], @handler.permissions.sort)
|
|
302
302
|
end
|
|
303
303
|
|
|
304
|
+
test_files = Dir[File.join(TEST_DATA_DIR, 'standard-security-handler', '*.pdf')].sort
|
|
305
|
+
user_password = 'uhexapdf'
|
|
306
|
+
owner_password = 'ohexapdf'
|
|
307
|
+
|
|
308
|
+
describe "decryption_password_type" do
|
|
309
|
+
it "doesn't need a password for encrypted files without a password" do
|
|
310
|
+
file = test_files.find {|name| name =~ /nopwd-aes-256bit-V5.pdf/}
|
|
311
|
+
HexaPDF::Document.open(file) do |doc|
|
|
312
|
+
assert_equal(:none, doc.security_handler.decryption_password_type)
|
|
313
|
+
end
|
|
314
|
+
end
|
|
315
|
+
|
|
316
|
+
it "doesn't need a password for owner encrypted files" do
|
|
317
|
+
file = test_files.find {|name| name =~ /ownerpwd-aes-256bit-V5.pdf/}
|
|
318
|
+
HexaPDF::Document.open(file) do |doc|
|
|
319
|
+
assert_equal(:none, doc.security_handler.decryption_password_type)
|
|
320
|
+
end
|
|
321
|
+
end
|
|
322
|
+
|
|
323
|
+
it "needs the user password for user encrypted files" do
|
|
324
|
+
file = test_files.find {|name| name =~ /userpwd-aes-256bit-V5.pdf/}
|
|
325
|
+
HexaPDF::Document.open(file, decryption_opts: {password: user_password}) do |doc|
|
|
326
|
+
assert_equal(:user, doc.security_handler.decryption_password_type)
|
|
327
|
+
end
|
|
328
|
+
end
|
|
329
|
+
|
|
330
|
+
it "can user either the user or owner password for user+owner encrypted files" do
|
|
331
|
+
file = test_files.find {|name| name =~ /bothpwd-aes-256bit-V5.pdf/}
|
|
332
|
+
HexaPDF::Document.open(file, decryption_opts: {password: user_password}) do |doc|
|
|
333
|
+
assert_equal(:user, doc.security_handler.decryption_password_type)
|
|
334
|
+
end
|
|
335
|
+
HexaPDF::Document.open(file, decryption_opts: {password: owner_password}) do |doc|
|
|
336
|
+
assert_equal(:owner, doc.security_handler.decryption_password_type)
|
|
337
|
+
end
|
|
338
|
+
end
|
|
339
|
+
|
|
340
|
+
it "returns :unknown for loaded or created and then encrypted PDF documents" do
|
|
341
|
+
doc = HexaPDF::Document.new
|
|
342
|
+
doc.encrypt
|
|
343
|
+
assert_equal(:unknown, doc.security_handler.decryption_password_type)
|
|
344
|
+
end
|
|
345
|
+
end
|
|
346
|
+
|
|
304
347
|
describe "handling of metadata streams" do
|
|
305
348
|
before do
|
|
306
349
|
@doc = HexaPDF::Document.new
|
|
@@ -8,7 +8,9 @@ describe HexaPDF::Font::InvalidGlyph do
|
|
|
8
8
|
font = Object.new
|
|
9
9
|
font.define_singleton_method(:missing_glyph_id) { 0 }
|
|
10
10
|
font.define_singleton_method(:full_name) { "Test Roman" }
|
|
11
|
-
|
|
11
|
+
font_wrapper = Object.new
|
|
12
|
+
font_wrapper.define_singleton_method(:wrapped_font) { font }
|
|
13
|
+
@glyph = HexaPDF::Font::InvalidGlyph.new(font_wrapper, "str")
|
|
12
14
|
end
|
|
13
15
|
|
|
14
16
|
it "returns the missing glyph id for id/name" do
|
|
@@ -27,6 +29,16 @@ describe HexaPDF::Font::InvalidGlyph do
|
|
|
27
29
|
refute(@glyph.apply_word_spacing?)
|
|
28
30
|
end
|
|
29
31
|
|
|
32
|
+
it "returns false when asked whether it is valid" do
|
|
33
|
+
refute(@glyph.valid?)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
it "returns true if the glyph represents a control character" do
|
|
37
|
+
refute(@glyph.control_char?)
|
|
38
|
+
assert(HexaPDF::Font::InvalidGlyph.new(nil, "\n"))
|
|
39
|
+
assert(HexaPDF::Font::InvalidGlyph.new(nil, "\u{8203}"))
|
|
40
|
+
end
|
|
41
|
+
|
|
30
42
|
it "can represent itself for debug purposes" do
|
|
31
43
|
assert_equal('#<HexaPDF::Font::InvalidGlyph font="Test Roman" id=0 "str">',
|
|
32
44
|
@glyph.inspect)
|
|
@@ -24,17 +24,29 @@ describe HexaPDF::Font::TrueTypeWrapper do
|
|
|
24
24
|
end
|
|
25
25
|
end
|
|
26
26
|
|
|
27
|
+
it "can be asked whether the font is a bold one" do
|
|
28
|
+
refute(@font_wrapper.bold?)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
it "can be asked whether the font is an italic one" do
|
|
32
|
+
refute(@font_wrapper.italic?)
|
|
33
|
+
end
|
|
34
|
+
|
|
27
35
|
it "can be asked whether font wil be subset" do
|
|
28
36
|
assert(@font_wrapper.subset?)
|
|
29
37
|
refute(HexaPDF::Font::TrueTypeWrapper.new(@doc, @font, subset: false).subset?)
|
|
30
38
|
end
|
|
31
39
|
|
|
32
|
-
describe "
|
|
33
|
-
it "returns an array of glyph objects" do
|
|
40
|
+
describe "decode_*" do
|
|
41
|
+
it "decode_utf8 returns an array of glyph objects" do
|
|
34
42
|
assert_equal("Test",
|
|
35
43
|
@font_wrapper.decode_utf8("Test").map {|g| @cmap.gid_to_code(g.id) }.pack('U*'))
|
|
36
44
|
end
|
|
37
45
|
|
|
46
|
+
it "decode_codepoint returns a single glyph object" do
|
|
47
|
+
assert_equal("A", @font_wrapper.decode_codepoint(65).str)
|
|
48
|
+
end
|
|
49
|
+
|
|
38
50
|
it "invokes font.on_missing_glyph for UTF-8 characters for which no glyph exists" do
|
|
39
51
|
glyphs = @font_wrapper.decode_utf8("š")
|
|
40
52
|
assert_equal(1, glyphs.length)
|
|
@@ -54,6 +66,7 @@ describe HexaPDF::Font::TrueTypeWrapper do
|
|
|
54
66
|
assert_equal(584, glyph.x_max)
|
|
55
67
|
assert_equal(696, glyph.y_max)
|
|
56
68
|
refute(glyph.apply_word_spacing?)
|
|
69
|
+
assert(glyph.valid?)
|
|
57
70
|
assert_equal('#<HexaPDF::Font::TrueTypeWrapper::Glyph font="Ubuntu-Title" id=17 "0">',
|
|
58
71
|
glyph.inspect)
|
|
59
72
|
end
|
|
@@ -21,15 +21,33 @@ describe HexaPDF::Font::Type1Wrapper do
|
|
|
21
21
|
assert_equal("A", wrapper.encode(wrapper.glyph(:B)))
|
|
22
22
|
end
|
|
23
23
|
|
|
24
|
+
it "can be asked whether the font is a bold one" do
|
|
25
|
+
refute(@times_wrapper.bold?)
|
|
26
|
+
refute(@symbol_wrapper.bold?)
|
|
27
|
+
assert(@doc.fonts.add("Times", variant: :bold).bold?)
|
|
28
|
+
refute(@doc.fonts.add("Helvetica").bold?)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
it "can be asked whether the font is an italic one" do
|
|
32
|
+
refute(@times_wrapper.italic?)
|
|
33
|
+
refute(@symbol_wrapper.italic?)
|
|
34
|
+
assert(@doc.fonts.add("Times", variant: :italic).italic?)
|
|
35
|
+
assert(@doc.fonts.add("Helvetica", variant: :bold_italic).italic?)
|
|
36
|
+
end
|
|
37
|
+
|
|
24
38
|
it "returns 1 for the scaling factor" do
|
|
25
39
|
assert_equal(1, @times_wrapper.scaling_factor)
|
|
26
40
|
end
|
|
27
41
|
|
|
28
|
-
describe "
|
|
29
|
-
it "returns an array of glyph objects" do
|
|
42
|
+
describe "decode_*" do
|
|
43
|
+
it "decode_utf8 returns an array of glyph objects" do
|
|
30
44
|
assert_equal([:T, :e, :s, :t], @times_wrapper.decode_utf8("Test").map(&:name))
|
|
31
45
|
end
|
|
32
46
|
|
|
47
|
+
it "decode_codepoint returns a single glyph object" do
|
|
48
|
+
assert_equal(:A, @times_wrapper.decode_codepoint(65).name)
|
|
49
|
+
end
|
|
50
|
+
|
|
33
51
|
it "falls back to the internal font encoding if the Unicode codepoint is not mapped" do
|
|
34
52
|
assert_equal([:Delta, :Delta], @symbol_wrapper.decode_utf8("Dā").map(&:name))
|
|
35
53
|
end
|
|
@@ -53,6 +71,7 @@ describe HexaPDF::Font::Type1Wrapper do
|
|
|
53
71
|
assert_equal(706, glyph.x_max)
|
|
54
72
|
assert_equal(674, glyph.y_max)
|
|
55
73
|
refute(glyph.apply_word_spacing?)
|
|
74
|
+
assert(glyph.valid?)
|
|
56
75
|
assert_equal('#<HexaPDF::Font::Type1Wrapper::Glyph font="Times Roman" id=:A "A">',
|
|
57
76
|
glyph.inspect)
|
|
58
77
|
end
|
|
@@ -95,6 +95,13 @@ describe HexaPDF::Layout::Box do
|
|
|
95
95
|
assert_equal(100, box.height)
|
|
96
96
|
end
|
|
97
97
|
|
|
98
|
+
it "uses float comparison" do
|
|
99
|
+
box = create_box(width: 50.0000002, height: 49.9999996)
|
|
100
|
+
assert(box.fit(50, 50, @frame))
|
|
101
|
+
assert_equal(50.0000002, box.width)
|
|
102
|
+
assert_equal(49.9999996, box.height)
|
|
103
|
+
end
|
|
104
|
+
|
|
98
105
|
it "returns false if the box doesn't fit" do
|
|
99
106
|
box = create_box(width: 101)
|
|
100
107
|
refute(box.fit(100, 100, @frame))
|