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