hexapdf 0.4.0 → 0.5.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 +46 -0
- data/CONTRIBUTERS +1 -1
- data/README.md +5 -5
- data/VERSION +1 -1
- data/examples/emoji-smile.png +0 -0
- data/examples/emoji-wink.png +0 -0
- data/examples/graphics.rb +9 -8
- data/examples/standard_pdf_fonts.rb +2 -1
- data/examples/text_box_alignment.rb +47 -0
- data/examples/text_box_inline_boxes.rb +56 -0
- data/examples/text_box_line_wrapping.rb +57 -0
- data/examples/text_box_shapes.rb +166 -0
- data/examples/text_box_styling.rb +72 -0
- data/examples/truetype.rb +3 -4
- data/lib/hexapdf/cli/optimize.rb +2 -2
- data/lib/hexapdf/configuration.rb +8 -6
- data/lib/hexapdf/content/canvas.rb +8 -5
- data/lib/hexapdf/content/parser.rb +3 -2
- data/lib/hexapdf/content/processor.rb +14 -3
- data/lib/hexapdf/document.rb +1 -0
- data/lib/hexapdf/document/fonts.rb +2 -1
- data/lib/hexapdf/document/pages.rb +23 -0
- data/lib/hexapdf/font/invalid_glyph.rb +78 -0
- data/lib/hexapdf/font/true_type/font.rb +14 -3
- data/lib/hexapdf/font/true_type/table.rb +1 -0
- data/lib/hexapdf/font/true_type/table/cmap.rb +1 -1
- data/lib/hexapdf/font/true_type/table/cmap_subtable.rb +1 -0
- data/lib/hexapdf/font/true_type/table/glyf.rb +4 -0
- data/lib/hexapdf/font/true_type/table/kern.rb +170 -0
- data/lib/hexapdf/font/true_type/table/post.rb +5 -1
- data/lib/hexapdf/font/true_type_wrapper.rb +71 -24
- data/lib/hexapdf/font/type1/afm_parser.rb +3 -2
- data/lib/hexapdf/font/type1/character_metrics.rb +0 -9
- data/lib/hexapdf/font/type1/font.rb +11 -0
- data/lib/hexapdf/font/type1/font_metrics.rb +6 -1
- data/lib/hexapdf/font/type1_wrapper.rb +51 -7
- data/lib/hexapdf/font_loader/standard14.rb +1 -1
- data/lib/hexapdf/layout.rb +51 -0
- data/lib/hexapdf/layout/inline_box.rb +95 -0
- data/lib/hexapdf/layout/line_fragment.rb +333 -0
- data/lib/hexapdf/layout/numeric_refinements.rb +56 -0
- data/lib/hexapdf/layout/style.rb +365 -0
- data/lib/hexapdf/layout/text_box.rb +727 -0
- data/lib/hexapdf/layout/text_fragment.rb +206 -0
- data/lib/hexapdf/layout/text_shaper.rb +155 -0
- data/lib/hexapdf/task.rb +0 -1
- data/lib/hexapdf/task/dereference.rb +1 -1
- data/lib/hexapdf/tokenizer.rb +3 -2
- data/lib/hexapdf/type/font_descriptor.rb +2 -1
- data/lib/hexapdf/type/font_type0.rb +3 -1
- data/lib/hexapdf/type/form.rb +12 -4
- data/lib/hexapdf/version.rb +1 -1
- data/test/hexapdf/common_tokenizer_tests.rb +7 -0
- data/test/hexapdf/content/common.rb +8 -0
- data/test/hexapdf/content/test_canvas.rb +10 -22
- data/test/hexapdf/content/test_processor.rb +4 -1
- data/test/hexapdf/document/test_pages.rb +16 -0
- data/test/hexapdf/font/test_invalid_glyph.rb +34 -0
- data/test/hexapdf/font/test_true_type_wrapper.rb +25 -11
- data/test/hexapdf/font/test_type1_wrapper.rb +26 -10
- data/test/hexapdf/font/true_type/table/common.rb +27 -0
- data/test/hexapdf/font/true_type/table/test_cmap.rb +14 -20
- data/test/hexapdf/font/true_type/table/test_cmap_subtable.rb +7 -0
- data/test/hexapdf/font/true_type/table/test_glyf.rb +8 -6
- data/test/hexapdf/font/true_type/table/test_head.rb +9 -13
- data/test/hexapdf/font/true_type/table/test_hhea.rb +16 -23
- data/test/hexapdf/font/true_type/table/test_hmtx.rb +4 -7
- data/test/hexapdf/font/true_type/table/test_kern.rb +61 -0
- data/test/hexapdf/font/true_type/table/test_loca.rb +7 -13
- data/test/hexapdf/font/true_type/table/test_maxp.rb +4 -9
- data/test/hexapdf/font/true_type/table/test_name.rb +14 -17
- data/test/hexapdf/font/true_type/table/test_os2.rb +3 -5
- data/test/hexapdf/font/true_type/table/test_post.rb +21 -19
- data/test/hexapdf/font/true_type/test_font.rb +4 -0
- data/test/hexapdf/font/type1/common.rb +6 -0
- data/test/hexapdf/font/type1/test_afm_parser.rb +9 -0
- data/test/hexapdf/font/type1/test_font.rb +6 -0
- data/test/hexapdf/layout/test_inline_box.rb +40 -0
- data/test/hexapdf/layout/test_line_fragment.rb +206 -0
- data/test/hexapdf/layout/test_style.rb +143 -0
- data/test/hexapdf/layout/test_text_box.rb +640 -0
- data/test/hexapdf/layout/test_text_fragment.rb +208 -0
- data/test/hexapdf/layout/test_text_shaper.rb +64 -0
- data/test/hexapdf/task/test_dereference.rb +1 -0
- data/test/hexapdf/test_writer.rb +2 -2
- data/test/hexapdf/type/test_font_descriptor.rb +4 -2
- data/test/hexapdf/type/test_font_type0.rb +7 -0
- data/test/hexapdf/type/test_form.rb +12 -0
- metadata +29 -2
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# -*- encoding: utf-8 -*-
|
|
2
2
|
|
|
3
3
|
require 'test_helper'
|
|
4
|
+
require_relative 'common'
|
|
4
5
|
require 'hexapdf/font/type1'
|
|
5
6
|
require 'hexapdf/data_dir'
|
|
6
7
|
require 'tempfile'
|
|
@@ -38,6 +39,14 @@ describe HexaPDF::Font::Type1::AFMParser do
|
|
|
38
39
|
end
|
|
39
40
|
end
|
|
40
41
|
|
|
42
|
+
it "extracts kerning and ligature information" do
|
|
43
|
+
metrics = FONT_TIMES.metrics
|
|
44
|
+
glyph = metrics.character_metrics[:f]
|
|
45
|
+
assert_equal([20, 0, 383, 683], glyph.bbox)
|
|
46
|
+
assert_equal(-20, metrics.kerning_pairs.dig(:f, :i))
|
|
47
|
+
assert_equal(:fi, metrics.ligature_pairs.dig(:f, :i))
|
|
48
|
+
end
|
|
49
|
+
|
|
41
50
|
it "calculates an ascender and descender value from the font bounding box if necessary" do
|
|
42
51
|
metrics = HexaPDF::Font::Type1::AFMParser.parse(File.join(HexaPDF.data_dir, 'afm/Symbol.afm'))
|
|
43
52
|
assert_equal(metrics.bounding_box[1], metrics.descender)
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# -*- encoding: utf-8 -*-
|
|
2
2
|
|
|
3
3
|
require 'test_helper'
|
|
4
|
+
require_relative 'common'
|
|
4
5
|
require 'hexapdf/font/type1'
|
|
5
6
|
|
|
6
7
|
describe HexaPDF::Font::Type1::Font do
|
|
@@ -65,4 +66,9 @@ describe HexaPDF::Font::Type1::Font do
|
|
|
65
66
|
it "is able to return the ID of the missing glyph" do
|
|
66
67
|
assert_equal(:'.notdef', @font.missing_glyph_id)
|
|
67
68
|
end
|
|
69
|
+
|
|
70
|
+
it "returns the features available for a font" do
|
|
71
|
+
assert_equal([:kern, :liga].to_set, FONT_TIMES.features)
|
|
72
|
+
assert(FONT_SYMBOL.features.empty?)
|
|
73
|
+
end
|
|
68
74
|
end
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
|
2
|
+
|
|
3
|
+
require 'test_helper'
|
|
4
|
+
require 'hexapdf/layout/inline_box'
|
|
5
|
+
|
|
6
|
+
describe HexaPDF::Layout::InlineBox do
|
|
7
|
+
before do
|
|
8
|
+
@box = HexaPDF::Layout::InlineBox.new(10, 15) {|box, canvas| [box, canvas]}
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
describe "draw" do
|
|
12
|
+
before do
|
|
13
|
+
@canvas = Object.new
|
|
14
|
+
@canvas.define_singleton_method(:translate) {|x, y, &block| [x, y, block.call] }
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
it "returns the value of the drawing block" do
|
|
18
|
+
assert_equal([1, 2, [@box, @canvas]], @box.draw(@canvas, 1, 2))
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
describe "valign" do
|
|
23
|
+
it "has a default value of :baseline" do
|
|
24
|
+
assert_equal(:baseline, @box.valign)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
it "can be changed on creation" do
|
|
28
|
+
box = HexaPDF::Layout::InlineBox.new(10, 15, valign: :test) {}
|
|
29
|
+
assert_equal(:test, box.valign)
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
it "returns 0 for x_min" do
|
|
34
|
+
assert_equal(0, @box.x_min)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
it "returns width for x_max" do
|
|
38
|
+
assert_equal(10, @box.x_max)
|
|
39
|
+
end
|
|
40
|
+
end
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
|
2
|
+
|
|
3
|
+
require 'test_helper'
|
|
4
|
+
require 'hexapdf/document'
|
|
5
|
+
|
|
6
|
+
describe HexaPDF::Layout::LineFragment::HeightCalculator do
|
|
7
|
+
before do
|
|
8
|
+
@calc = HexaPDF::Layout::LineFragment::HeightCalculator.new
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
it "simulate the height as if an item was added" do
|
|
12
|
+
@calc << HexaPDF::Layout::InlineBox.new(10, 20, valign: :baseline) {}
|
|
13
|
+
assert_equal([0, 20, 0, 0], @calc.result)
|
|
14
|
+
new_item = HexaPDF::Layout::InlineBox.new(10, 30, valign: :top) {}
|
|
15
|
+
assert_equal(30, @calc.simulate_height(new_item))
|
|
16
|
+
assert_equal([0, 20, 0, 0], @calc.result)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
describe HexaPDF::Layout::LineFragment do
|
|
21
|
+
before do
|
|
22
|
+
@doc = HexaPDF::Document.new
|
|
23
|
+
@font = @doc.fonts.load("Times", custom_encoding: true)
|
|
24
|
+
@line = HexaPDF::Layout::LineFragment.new
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def setup_fragment(text)
|
|
28
|
+
HexaPDF::Layout::TextFragment.create(text, font: @font, font_size: 10)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def setup_box(width, height, valign = :baseline)
|
|
32
|
+
HexaPDF::Layout::InlineBox.new(width, height, valign: valign) {}
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
describe "initialize" do
|
|
36
|
+
it "allows setting the items of the line fragment" do
|
|
37
|
+
frag1 = setup_fragment("Hello")
|
|
38
|
+
frag2 = HexaPDF::Layout::TextFragment.new(items: frag1.items.slice!(3, 2), style: frag1.style)
|
|
39
|
+
line = HexaPDF::Layout::LineFragment.new([frag1, frag2])
|
|
40
|
+
assert_equal(1, line.items.count)
|
|
41
|
+
assert_equal(5, line.items[0].items.count)
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
describe "add" do
|
|
46
|
+
it "adds items to the line" do
|
|
47
|
+
@line << :test << :other
|
|
48
|
+
assert_equal([:test, :other], @line.items)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
it "combines text fragments if possible" do
|
|
52
|
+
frag1 = setup_fragment("Home")
|
|
53
|
+
frag2 = HexaPDF::Layout::TextFragment.new(items: frag1.items.slice!(2, 2), style: frag1.style)
|
|
54
|
+
@line << setup_fragment("o") << :other << frag1 << frag2
|
|
55
|
+
assert_equal(3, @line.items.length)
|
|
56
|
+
assert_equal(4, @line.items.last.items.length)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
it "duplicates the first of two combinable text fragments if its items are frozen" do
|
|
60
|
+
frag1 = setup_fragment("Home")
|
|
61
|
+
frag2 = HexaPDF::Layout::TextFragment.new(items: frag1.items.slice!(2, 2), style: frag1.style)
|
|
62
|
+
frag1.items.freeze
|
|
63
|
+
frag2.items.freeze
|
|
64
|
+
|
|
65
|
+
@line << setup_fragment("o") << frag1 << frag2 << :other
|
|
66
|
+
assert_equal(3, @line.items.length)
|
|
67
|
+
assert_equal(4, @line.items[-2].items.length)
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
describe "with text fragments" do
|
|
72
|
+
before do
|
|
73
|
+
@frag_h = setup_fragment("H")
|
|
74
|
+
@frag_y = setup_fragment("y")
|
|
75
|
+
@line << @frag_h << @frag_y << @frag_h
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
it "calculates the various x/y values correctly" do
|
|
79
|
+
assert_equal(@frag_h.x_min, @line.x_min)
|
|
80
|
+
assert_equal(@frag_h.width + @frag_y.width + @frag_h.x_max, @line.x_max)
|
|
81
|
+
assert_equal(@frag_y.y_min, @line.y_min)
|
|
82
|
+
assert_equal(@frag_h.y_max, @line.y_max)
|
|
83
|
+
assert_equal(@frag_y.y_min, @line.text_y_min)
|
|
84
|
+
assert_equal(@frag_h.y_max, @line.text_y_max)
|
|
85
|
+
assert_equal(2 * @frag_h.width + @frag_y.width, @line.width)
|
|
86
|
+
assert_equal(@frag_h.y_max - @frag_y.y_min, @line.height)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
describe "and with inline boxes" do
|
|
90
|
+
it "x_min is correct if an inline box is the first item" do
|
|
91
|
+
@line.items.unshift(setup_box(10, 10))
|
|
92
|
+
assert_equal(0, @line.x_min)
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
it "x_max is correct if an inline box is the last item" do
|
|
96
|
+
@line << setup_box(10, 10)
|
|
97
|
+
assert_equal(@line.width, @line.x_max)
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
it "doesn't change text_y_min/text_y_max" do
|
|
101
|
+
text_y_min, text_y_max = @line.text_y_min, @line.text_y_max
|
|
102
|
+
@line << setup_box(10, 30, :text_top) << setup_box(10, 30, :text_bottom)
|
|
103
|
+
@line.clear_cache
|
|
104
|
+
assert_equal(text_y_min, @line.text_y_min)
|
|
105
|
+
assert_equal(text_y_max, @line.text_y_max)
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
it "y values are not changed if all boxes are smaller than the text's height" do
|
|
109
|
+
*y_values = @line.y_min, @line.y_max, @line.text_y_min, @line.text_y_max
|
|
110
|
+
@line << setup_box(10, 5, :baseline)
|
|
111
|
+
@line.clear_cache
|
|
112
|
+
assert_equal(y_values, [@line.y_min, @line.y_max, @line.text_y_min, @line.text_y_max])
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
it "changes y_max to fit if baseline boxes are higher than the text" do
|
|
116
|
+
y_min = @line.y_min
|
|
117
|
+
box = setup_box(10, 50, :baseline)
|
|
118
|
+
@line.add(box)
|
|
119
|
+
|
|
120
|
+
@line.clear_cache
|
|
121
|
+
assert_equal(50, @line.y_max)
|
|
122
|
+
assert_equal(y_min, @line.y_min)
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
it "changes y_max to fit if text_bottom boxes are higher than the text" do
|
|
126
|
+
y_min = @line.y_min
|
|
127
|
+
box = setup_box(10, 50, :text_bottom)
|
|
128
|
+
@line.add(box)
|
|
129
|
+
|
|
130
|
+
@line.clear_cache
|
|
131
|
+
assert_equal(50 + @line.text_y_min, @line.y_max)
|
|
132
|
+
assert_equal(y_min, @line.y_min)
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
it "changes y_max to fit if bottom boxes are higher than the text" do
|
|
136
|
+
y_min = @line.y_min
|
|
137
|
+
box = setup_box(10, 50, :bottom)
|
|
138
|
+
@line.add(box)
|
|
139
|
+
|
|
140
|
+
@line.clear_cache
|
|
141
|
+
assert_equal(50 + @line.text_y_min, @line.y_max)
|
|
142
|
+
assert_equal(y_min, @line.y_min)
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
it "changes y_min to fit if text_top/top boxes are higher than the text" do
|
|
146
|
+
y_max = @line.y_max
|
|
147
|
+
box = setup_box(10, 50, :text_top)
|
|
148
|
+
@line.add(box)
|
|
149
|
+
|
|
150
|
+
@line.clear_cache
|
|
151
|
+
assert_equal(@line.text_y_max - 50, @line.y_min)
|
|
152
|
+
assert_equal(y_max, @line.y_max)
|
|
153
|
+
|
|
154
|
+
box.instance_variable_set(:@valign, :top)
|
|
155
|
+
@line.clear_cache
|
|
156
|
+
assert_equal(@line.text_y_max - 50, @line.y_min)
|
|
157
|
+
assert_equal(y_max, @line.y_max)
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
it "changes y_min/y_max to fit if boxes are aligned in both directions" do
|
|
161
|
+
@line << setup_box(10, 20, :text_top) <<
|
|
162
|
+
setup_box(10, 20, :text_bottom) <<
|
|
163
|
+
setup_box(10, 20, :top) <<
|
|
164
|
+
setup_box(10, 70, :bottom)
|
|
165
|
+
assert_equal(@line.text_y_max - 20, @line.y_min)
|
|
166
|
+
assert_equal(@line.text_y_max - 20 + 70, @line.y_max)
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
it "fails when accessing a vertical measurement if an item uses an invalid valign value" do
|
|
172
|
+
@line << setup_box(10, 20, :invalid)
|
|
173
|
+
assert_raises(HexaPDF::Error) { @line.y_min }
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
describe "each" do
|
|
177
|
+
it "iterates over all items and yields them with their offset values" do
|
|
178
|
+
@line << setup_fragment("H") <<
|
|
179
|
+
setup_box(10, 10, :top) <<
|
|
180
|
+
setup_box(10, 10, :text_top) <<
|
|
181
|
+
setup_box(10, 10, :baseline) <<
|
|
182
|
+
setup_box(10, 10, :text_bottom) <<
|
|
183
|
+
setup_box(10, 10, :bottom)
|
|
184
|
+
result = [
|
|
185
|
+
[@line.items[0], 0, 0],
|
|
186
|
+
[@line.items[1], @line.items[0].width, @line.y_max - 10],
|
|
187
|
+
[@line.items[2], @line.items[0].width + 10, @line.text_y_max - 10],
|
|
188
|
+
[@line.items[3], @line.items[0].width + 20, 0],
|
|
189
|
+
[@line.items[4], @line.items[0].width + 30, @line.text_y_min],
|
|
190
|
+
[@line.items[5], @line.items[0].width + 40, @line.y_min],
|
|
191
|
+
]
|
|
192
|
+
assert_equal(result, @line.to_enum(:each).map {|*a| a})
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
it "fails if an item uses an invalid valign value" do
|
|
196
|
+
@line << setup_box(10, 10, :invalid)
|
|
197
|
+
assert_raises(HexaPDF::Error) { @line.each {} }
|
|
198
|
+
end
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
it "allows ignoring line justification" do
|
|
202
|
+
refute(@line.ignore_justification?)
|
|
203
|
+
@line.ignore_justification!
|
|
204
|
+
assert(@line.ignore_justification?)
|
|
205
|
+
end
|
|
206
|
+
end
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
|
2
|
+
|
|
3
|
+
require 'test_helper'
|
|
4
|
+
require 'hexapdf/layout/style'
|
|
5
|
+
require 'hexapdf/layout/text_box'
|
|
6
|
+
|
|
7
|
+
describe HexaPDF::Layout::Style::LineSpacing do
|
|
8
|
+
before do
|
|
9
|
+
@line1 = Object.new
|
|
10
|
+
@line1.define_singleton_method(:y_min) { - 1}
|
|
11
|
+
@line1.define_singleton_method(:y_max) { 2 }
|
|
12
|
+
@line2 = Object.new
|
|
13
|
+
@line2.define_singleton_method(:y_min) { -3 }
|
|
14
|
+
@line2.define_singleton_method(:y_max) { 4 }
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def line_spacing(type, value = nil)
|
|
18
|
+
HexaPDF::Layout::Style::LineSpacing.new(type, value: value)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
it "allows single line spacing" do
|
|
22
|
+
obj = line_spacing(:single)
|
|
23
|
+
assert_equal(:proportional, obj.type)
|
|
24
|
+
assert_equal(1, obj.value)
|
|
25
|
+
assert_equal(1 + 4, obj.baseline_distance(@line1, @line2))
|
|
26
|
+
assert_equal(0, obj.gap(@line1, @line2))
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
it "allows double line spacing" do
|
|
30
|
+
obj = line_spacing(:double)
|
|
31
|
+
assert_equal(:proportional, obj.type)
|
|
32
|
+
assert_equal(2, obj.value)
|
|
33
|
+
assert_equal((1 + 4) * 2, obj.baseline_distance(@line1, @line2))
|
|
34
|
+
assert_equal(1 + 4, obj.gap(@line1, @line2))
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
it "allows proportional line spacing" do
|
|
38
|
+
obj = line_spacing(:proportional, 1.5)
|
|
39
|
+
assert_equal(:proportional, obj.type)
|
|
40
|
+
assert_equal(1.5, obj.value)
|
|
41
|
+
assert_equal((1 + 4) * 1.5, obj.baseline_distance(@line1, @line2))
|
|
42
|
+
assert_equal((1 + 4) * 0.5, obj.gap(@line1, @line2))
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
it "allows fixed line spacing" do
|
|
46
|
+
obj = line_spacing(:fixed, 7)
|
|
47
|
+
assert_equal(:fixed, obj.type)
|
|
48
|
+
assert_equal(7, obj.value)
|
|
49
|
+
assert_equal(7, obj.baseline_distance(@line1, @line2))
|
|
50
|
+
assert_equal(7 - 1 - 4, obj.gap(@line1, @line2))
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
it "allows line spacing using a leading value" do
|
|
54
|
+
obj = line_spacing(:leading, 3)
|
|
55
|
+
assert_equal(:leading, obj.type)
|
|
56
|
+
assert_equal(3, obj.value)
|
|
57
|
+
assert_equal(1 + 4 + 3, obj.baseline_distance(@line1, @line2))
|
|
58
|
+
assert_equal(3, obj.gap(@line1, @line2))
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
it "allows using a LineSpacing object as type" do
|
|
62
|
+
obj = line_spacing(line_spacing(:single))
|
|
63
|
+
assert_equal(:proportional, obj.type)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
it "raises an error if a value is needed and none is provided" do
|
|
67
|
+
assert_raises(ArgumentError) { line_spacing(:proportional) }
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
it "raises an error if an invalid type is provided" do
|
|
71
|
+
assert_raises(ArgumentError) { line_spacing(:invalid) }
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
describe HexaPDF::Layout::Style do
|
|
76
|
+
before do
|
|
77
|
+
@style = HexaPDF::Layout::Style.new
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
it "can assign values on initialization" do
|
|
81
|
+
style = HexaPDF::Layout::Style.new(font_size: 10)
|
|
82
|
+
assert_equal(10, style.font_size)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
it "has several dynamically generated properties with default values" do
|
|
86
|
+
assert_raises(HexaPDF::Error) { @style.font }
|
|
87
|
+
assert_equal(10, @style.font_size)
|
|
88
|
+
assert_equal(0, @style.character_spacing)
|
|
89
|
+
assert_equal(0, @style.word_spacing)
|
|
90
|
+
assert_equal(100, @style.horizontal_scaling)
|
|
91
|
+
assert_equal(0, @style.text_rise)
|
|
92
|
+
assert_equal({}, @style.font_features)
|
|
93
|
+
assert_equal(:left, @style.align)
|
|
94
|
+
assert_equal(:top, @style.valign)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
it "can set and retrieve line spacing objects" do
|
|
98
|
+
assert_equal([:proportional, 1], [@style.line_spacing.type, @style.line_spacing.value])
|
|
99
|
+
@style.line_spacing = :double
|
|
100
|
+
assert_equal([:proportional, 2], [@style.line_spacing.type, @style.line_spacing.value])
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
it "can set and retrieve text segmentation algorithms" do
|
|
104
|
+
assert_equal(HexaPDF::Layout::TextBox::SimpleTextSegmentation,
|
|
105
|
+
@style.text_segmentation_algorithm)
|
|
106
|
+
block = proc { :y }
|
|
107
|
+
@style.text_segmentation_algorithm(&block)
|
|
108
|
+
assert_equal(block, @style.text_segmentation_algorithm)
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
it "can set and retrieve line wrapping algorithms" do
|
|
112
|
+
assert_equal(HexaPDF::Layout::TextBox::SimpleLineWrapping,
|
|
113
|
+
@style.text_line_wrapping_algorithm)
|
|
114
|
+
@style.text_line_wrapping_algorithm(:callable)
|
|
115
|
+
assert_equal(:callable, @style.text_line_wrapping_algorithm)
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
it "has methods for some derived and cached values" do
|
|
119
|
+
assert_equal(0.01, @style.scaled_font_size)
|
|
120
|
+
assert_equal(0, @style.scaled_character_spacing)
|
|
121
|
+
assert_equal(0, @style.scaled_word_spacing)
|
|
122
|
+
assert_equal(1, @style.scaled_horizontal_scaling)
|
|
123
|
+
|
|
124
|
+
wrapped_font = Object.new
|
|
125
|
+
wrapped_font.define_singleton_method(:ascender) { 600 }
|
|
126
|
+
wrapped_font.define_singleton_method(:descender) { -100 }
|
|
127
|
+
font = Object.new
|
|
128
|
+
font.define_singleton_method(:scaling_factor) { 1 }
|
|
129
|
+
font.define_singleton_method(:wrapped_font) { wrapped_font }
|
|
130
|
+
@style.font = font
|
|
131
|
+
|
|
132
|
+
assert_equal(6, @style.scaled_font_ascender)
|
|
133
|
+
assert_equal(-1, @style.scaled_font_descender)
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
it "can clear cached values" do
|
|
137
|
+
assert_equal(0.01, @style.scaled_font_size)
|
|
138
|
+
@style.font_size = 20
|
|
139
|
+
assert_equal(0.01, @style.scaled_font_size)
|
|
140
|
+
@style.clear_cache
|
|
141
|
+
assert_equal(0.02, @style.scaled_font_size)
|
|
142
|
+
end
|
|
143
|
+
end
|