hexapdf 0.5.0 → 0.6.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 +76 -2
- data/CONTRIBUTERS +1 -1
- data/Rakefile +1 -1
- data/VERSION +1 -1
- data/examples/boxes.rb +68 -0
- data/examples/graphics.rb +12 -12
- data/examples/{text_box_alignment.rb → text_layouter_alignment.rb} +14 -14
- data/examples/text_layouter_inline_boxes.rb +66 -0
- data/examples/{text_box_line_wrapping.rb → text_layouter_line_wrapping.rb} +9 -10
- data/examples/{text_box_shapes.rb → text_layouter_shapes.rb} +58 -54
- data/examples/text_layouter_styling.rb +125 -0
- data/examples/truetype.rb +5 -7
- data/lib/hexapdf/cli/command.rb +1 -0
- data/lib/hexapdf/configuration.rb +170 -106
- data/lib/hexapdf/content/canvas.rb +41 -36
- data/lib/hexapdf/content/graphics_state.rb +15 -0
- data/lib/hexapdf/content/operator.rb +1 -1
- data/lib/hexapdf/dictionary.rb +20 -8
- data/lib/hexapdf/dictionary_fields.rb +8 -6
- data/lib/hexapdf/document.rb +25 -26
- data/lib/hexapdf/document/fonts.rb +4 -4
- data/lib/hexapdf/document/images.rb +2 -2
- data/lib/hexapdf/document/pages.rb +16 -16
- data/lib/hexapdf/encryption/security_handler.rb +41 -9
- data/lib/hexapdf/filter/flate_decode.rb +1 -1
- data/lib/hexapdf/filter/lzw_decode.rb +1 -1
- data/lib/hexapdf/filter/predictor.rb +7 -1
- data/lib/hexapdf/font/true_type/font.rb +20 -0
- data/lib/hexapdf/font/type1/font.rb +23 -0
- data/lib/hexapdf/font_loader.rb +1 -0
- data/lib/hexapdf/font_loader/from_configuration.rb +2 -3
- data/lib/hexapdf/font_loader/from_file.rb +65 -0
- data/lib/hexapdf/image_loader/png.rb +2 -2
- data/lib/hexapdf/layout.rb +3 -2
- data/lib/hexapdf/layout/box.rb +146 -0
- data/lib/hexapdf/layout/inline_box.rb +40 -31
- data/lib/hexapdf/layout/{line_fragment.rb → line.rb} +12 -13
- data/lib/hexapdf/layout/style.rb +630 -41
- data/lib/hexapdf/layout/text_fragment.rb +80 -12
- data/lib/hexapdf/layout/{text_box.rb → text_layouter.rb} +164 -109
- data/lib/hexapdf/number_tree_node.rb +1 -1
- data/lib/hexapdf/parser.rb +4 -1
- data/lib/hexapdf/revisions.rb +11 -4
- data/lib/hexapdf/stream.rb +8 -9
- data/lib/hexapdf/tokenizer.rb +5 -3
- data/lib/hexapdf/type.rb +3 -0
- data/lib/hexapdf/type/action.rb +56 -0
- data/lib/hexapdf/type/actions.rb +52 -0
- data/lib/hexapdf/type/actions/go_to.rb +52 -0
- data/lib/hexapdf/type/actions/go_to_r.rb +54 -0
- data/lib/hexapdf/type/actions/launch.rb +73 -0
- data/lib/hexapdf/type/actions/uri.rb +65 -0
- data/lib/hexapdf/type/annotation.rb +85 -0
- data/lib/hexapdf/type/annotations.rb +51 -0
- data/lib/hexapdf/type/annotations/link.rb +70 -0
- data/lib/hexapdf/type/annotations/markup_annotation.rb +70 -0
- data/lib/hexapdf/type/annotations/text.rb +81 -0
- data/lib/hexapdf/type/catalog.rb +3 -1
- data/lib/hexapdf/type/embedded_file.rb +6 -11
- data/lib/hexapdf/type/file_specification.rb +4 -6
- data/lib/hexapdf/type/font.rb +3 -1
- data/lib/hexapdf/type/font_descriptor.rb +18 -16
- data/lib/hexapdf/type/form.rb +3 -1
- data/lib/hexapdf/type/graphics_state_parameter.rb +3 -1
- data/lib/hexapdf/type/image.rb +4 -2
- data/lib/hexapdf/type/info.rb +2 -5
- data/lib/hexapdf/type/names.rb +2 -5
- data/lib/hexapdf/type/object_stream.rb +2 -1
- data/lib/hexapdf/type/page.rb +14 -1
- data/lib/hexapdf/type/page_tree_node.rb +9 -6
- data/lib/hexapdf/type/resources.rb +2 -5
- data/lib/hexapdf/type/trailer.rb +2 -5
- data/lib/hexapdf/type/viewer_preferences.rb +2 -5
- data/lib/hexapdf/type/xref_stream.rb +3 -1
- data/lib/hexapdf/version.rb +1 -1
- data/test/hexapdf/common_tokenizer_tests.rb +3 -1
- data/test/hexapdf/content/test_canvas.rb +29 -3
- data/test/hexapdf/content/test_graphics_state.rb +11 -0
- data/test/hexapdf/content/test_operator.rb +3 -2
- data/test/hexapdf/document/test_fonts.rb +8 -8
- data/test/hexapdf/document/test_images.rb +4 -12
- data/test/hexapdf/document/test_pages.rb +7 -7
- data/test/hexapdf/encryption/test_security_handler.rb +1 -5
- data/test/hexapdf/filter/test_predictor.rb +40 -12
- data/test/hexapdf/font/true_type/test_font.rb +16 -0
- data/test/hexapdf/font/type1/test_font.rb +30 -0
- data/test/hexapdf/font_loader/test_from_file.rb +29 -0
- data/test/hexapdf/font_loader/test_standard14.rb +4 -3
- data/test/hexapdf/layout/test_box.rb +104 -0
- data/test/hexapdf/layout/test_inline_box.rb +24 -10
- data/test/hexapdf/layout/{test_line_fragment.rb → test_line.rb} +9 -9
- data/test/hexapdf/layout/test_style.rb +519 -31
- data/test/hexapdf/layout/test_text_fragment.rb +136 -15
- data/test/hexapdf/layout/{test_text_box.rb → test_text_layouter.rb} +224 -144
- data/test/hexapdf/layout/test_text_shaper.rb +1 -1
- data/test/hexapdf/test_configuration.rb +12 -6
- data/test/hexapdf/test_dictionary.rb +27 -2
- data/test/hexapdf/test_dictionary_fields.rb +10 -1
- data/test/hexapdf/test_document.rb +14 -13
- data/test/hexapdf/test_parser.rb +12 -0
- data/test/hexapdf/test_revisions.rb +34 -0
- data/test/hexapdf/test_stream.rb +1 -1
- data/test/hexapdf/test_type.rb +18 -0
- data/test/hexapdf/test_writer.rb +2 -2
- data/test/hexapdf/type/actions/test_launch.rb +24 -0
- data/test/hexapdf/type/actions/test_uri.rb +23 -0
- data/test/hexapdf/type/annotations/test_link.rb +19 -0
- data/test/hexapdf/type/annotations/test_markup_annotation.rb +22 -0
- data/test/hexapdf/type/annotations/test_text.rb +38 -0
- data/test/hexapdf/type/test_annotation.rb +38 -0
- data/test/hexapdf/type/test_file_specification.rb +0 -7
- data/test/hexapdf/type/test_info.rb +0 -5
- data/test/hexapdf/type/test_page.rb +14 -0
- data/test/hexapdf/type/test_page_tree_node.rb +4 -1
- data/test/hexapdf/type/test_trailer.rb +0 -4
- data/test/test_helper.rb +6 -3
- metadata +36 -15
- data/examples/text_box_inline_boxes.rb +0 -56
- data/examples/text_box_styling.rb +0 -72
- data/test/hexapdf/type/test_embedded_file.rb +0 -16
- data/test/hexapdf/type/test_names.rb +0 -9
@@ -1,8 +1,11 @@
|
|
1
1
|
# -*- encoding: utf-8 -*-
|
2
2
|
|
3
3
|
require 'test_helper'
|
4
|
+
require_relative '../content/common'
|
5
|
+
require 'hexapdf/document'
|
4
6
|
require 'hexapdf/layout/style'
|
5
|
-
require 'hexapdf/layout/
|
7
|
+
require 'hexapdf/layout/text_layouter'
|
8
|
+
require 'hexapdf/layout/box'
|
6
9
|
|
7
10
|
describe HexaPDF::Layout::Style::LineSpacing do
|
8
11
|
before do
|
@@ -47,7 +50,7 @@ describe HexaPDF::Layout::Style::LineSpacing do
|
|
47
50
|
assert_equal(:fixed, obj.type)
|
48
51
|
assert_equal(7, obj.value)
|
49
52
|
assert_equal(7, obj.baseline_distance(@line1, @line2))
|
50
|
-
assert_equal(7 - 1 -
|
53
|
+
assert_equal(7 - 1 - 4, obj.gap(@line1, @line2))
|
51
54
|
end
|
52
55
|
|
53
56
|
it "allows line spacing using a leading value" do
|
@@ -72,6 +75,412 @@ describe HexaPDF::Layout::Style::LineSpacing do
|
|
72
75
|
end
|
73
76
|
end
|
74
77
|
|
78
|
+
describe HexaPDF::Layout::Style::Quad do
|
79
|
+
def create_quad(val)
|
80
|
+
HexaPDF::Layout::Style::Quad.new(val)
|
81
|
+
end
|
82
|
+
|
83
|
+
describe "initialize" do
|
84
|
+
it "works with a single value" do
|
85
|
+
quad = create_quad(5)
|
86
|
+
assert_equal(5, quad.top)
|
87
|
+
assert_equal(5, quad.right)
|
88
|
+
assert_equal(5, quad.bottom)
|
89
|
+
assert_equal(5, quad.left)
|
90
|
+
end
|
91
|
+
|
92
|
+
it "works with two values" do
|
93
|
+
quad = create_quad([5, 2])
|
94
|
+
assert_equal(5, quad.top)
|
95
|
+
assert_equal(2, quad.right)
|
96
|
+
assert_equal(5, quad.bottom)
|
97
|
+
assert_equal(2, quad.left)
|
98
|
+
end
|
99
|
+
|
100
|
+
it "works with three values" do
|
101
|
+
quad = create_quad([5, 2, 7])
|
102
|
+
assert_equal(5, quad.top)
|
103
|
+
assert_equal(2, quad.right)
|
104
|
+
assert_equal(7, quad.bottom)
|
105
|
+
assert_equal(2, quad.left)
|
106
|
+
end
|
107
|
+
|
108
|
+
it "works with four or more values" do
|
109
|
+
quad = create_quad([5, 2, 7, 1, 9])
|
110
|
+
assert_equal(5, quad.top)
|
111
|
+
assert_equal(2, quad.right)
|
112
|
+
assert_equal(7, quad.bottom)
|
113
|
+
assert_equal(1, quad.left)
|
114
|
+
end
|
115
|
+
|
116
|
+
it "works with a Quad as value" do
|
117
|
+
quad = create_quad([5, 2, 7, 1])
|
118
|
+
new_quad = create_quad(quad)
|
119
|
+
assert_equal(new_quad.top, quad.top)
|
120
|
+
assert_equal(new_quad.right, quad.right)
|
121
|
+
assert_equal(new_quad.bottom, quad.bottom)
|
122
|
+
assert_equal(new_quad.left, quad.left)
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
it "can be asked if it contains only a single value" do
|
127
|
+
assert(create_quad(5).simple?)
|
128
|
+
refute(create_quad([5, 2]).simple?)
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
describe HexaPDF::Layout::Style::Border do
|
133
|
+
def create_border(*args)
|
134
|
+
HexaPDF::Layout::Style::Border.new(*args)
|
135
|
+
end
|
136
|
+
|
137
|
+
it "has accessors for with, color and style that return Quads" do
|
138
|
+
border = create_border
|
139
|
+
assert_kind_of(HexaPDF::Layout::Style::Quad, border.width)
|
140
|
+
assert_kind_of(HexaPDF::Layout::Style::Quad, border.color)
|
141
|
+
assert_kind_of(HexaPDF::Layout::Style::Quad, border.style)
|
142
|
+
end
|
143
|
+
|
144
|
+
it "can be asked whether a border is defined" do
|
145
|
+
assert(create_border.none?)
|
146
|
+
refute(create_border(width: 5).none?)
|
147
|
+
end
|
148
|
+
|
149
|
+
describe "draw" do
|
150
|
+
before do
|
151
|
+
@canvas = HexaPDF::Document.new.pages.add.canvas
|
152
|
+
end
|
153
|
+
|
154
|
+
describe "simple - same width, color and style on all sides" do
|
155
|
+
it "works with style solid" do
|
156
|
+
border = create_border(width: 10, color: 0.5, style: :solid)
|
157
|
+
border.draw(@canvas, 0, 0, 100, 100)
|
158
|
+
assert_operators(@canvas.contents, [[:save_graphics_state],
|
159
|
+
[:set_device_gray_stroking_color, [0.5]],
|
160
|
+
[:set_line_width, [10]],
|
161
|
+
[:append_rectangle, [5, 5, 90, 90]],
|
162
|
+
[:stroke_path],
|
163
|
+
[:restore_graphics_state]])
|
164
|
+
end
|
165
|
+
|
166
|
+
it "works with style dashed" do
|
167
|
+
border = create_border(width: 10, color: 0.5, style: :dashed)
|
168
|
+
border.draw(@canvas, 0, 0, 200, 300)
|
169
|
+
ops = [[:save_graphics_state],
|
170
|
+
[:set_device_gray_stroking_color, [0.5]],
|
171
|
+
[:set_line_width, [10]],
|
172
|
+
[:set_line_cap_style, [2]],
|
173
|
+
[:append_rectangle, [0, 0, 200, 300]],
|
174
|
+
[:clip_path_non_zero], [:end_path],
|
175
|
+
[:set_line_dash_pattern, [[10, 20], 25]],
|
176
|
+
[:move_to, [0, 295]], [:line_to, [200, 295]],
|
177
|
+
[:move_to, [200, 5]], [:line_to, [0, 5]],
|
178
|
+
[:stroke_path],
|
179
|
+
[:set_line_dash_pattern, [[10, 18], 23]],
|
180
|
+
[:move_to, [195, 300]], [:line_to, [195, 0]],
|
181
|
+
[:move_to, [5, 0]], [:line_to, [5, 300]],
|
182
|
+
[:stroke_path],
|
183
|
+
[:restore_graphics_state]]
|
184
|
+
assert_operators(@canvas.contents, ops)
|
185
|
+
end
|
186
|
+
|
187
|
+
it "works with style dashed_round" do
|
188
|
+
border = create_border(width: 10, color: 0.5, style: :dashed_round)
|
189
|
+
border.draw(@canvas, 0, 0, 200, 300)
|
190
|
+
ops = [[:save_graphics_state],
|
191
|
+
[:set_device_gray_stroking_color, [0.5]],
|
192
|
+
[:set_line_width, [10]],
|
193
|
+
[:set_line_cap_style, [1]],
|
194
|
+
[:append_rectangle, [0, 0, 200, 300]],
|
195
|
+
[:clip_path_non_zero], [:end_path],
|
196
|
+
[:set_line_dash_pattern, [[10, 20], 25]],
|
197
|
+
[:move_to, [0, 295]], [:line_to, [200, 295]],
|
198
|
+
[:move_to, [200, 5]], [:line_to, [0, 5]],
|
199
|
+
[:stroke_path],
|
200
|
+
[:set_line_dash_pattern, [[10, 18], 23]],
|
201
|
+
[:move_to, [195, 300]], [:line_to, [195, 0]],
|
202
|
+
[:move_to, [5, 0]], [:line_to, [5, 300]],
|
203
|
+
[:stroke_path],
|
204
|
+
[:restore_graphics_state]]
|
205
|
+
assert_operators(@canvas.contents, ops)
|
206
|
+
end
|
207
|
+
|
208
|
+
it "works with style dotted" do
|
209
|
+
border = create_border(width: 10, color: 0.5, style: :dotted)
|
210
|
+
border.draw(@canvas, 0, 0, 100, 200)
|
211
|
+
ops = [[:save_graphics_state],
|
212
|
+
[:set_device_gray_stroking_color, [0.5]],
|
213
|
+
[:set_line_width, [10]],
|
214
|
+
[:set_line_cap_style, [1]],
|
215
|
+
[:append_rectangle, [0, 0, 100, 200]],
|
216
|
+
[:clip_path_non_zero], [:end_path],
|
217
|
+
[:set_line_dash_pattern, [[0, 18], 13]],
|
218
|
+
[:move_to, [0, 195]], [:line_to, [100, 195]],
|
219
|
+
[:move_to, [100, 5]], [:line_to, [0, 5]],
|
220
|
+
[:stroke_path],
|
221
|
+
[:set_line_dash_pattern, [[0, 19], 14]],
|
222
|
+
[:move_to, [95, 200]], [:line_to, [95, 0]],
|
223
|
+
[:move_to, [5, 0]], [:line_to, [5, 200]],
|
224
|
+
[:stroke_path],
|
225
|
+
[:restore_graphics_state]]
|
226
|
+
assert_operators(@canvas.contents, ops)
|
227
|
+
end
|
228
|
+
end
|
229
|
+
|
230
|
+
describe "complex borders where edges have different width/color/style values" do
|
231
|
+
it "works correctly for the top border" do
|
232
|
+
border = create_border(width: [10, 0, 0, 0], color: 0.5, style: :dashed)
|
233
|
+
border.draw(@canvas, 0, 0, 200, 300)
|
234
|
+
ops = [[:save_graphics_state],
|
235
|
+
[:save_graphics_state],
|
236
|
+
[:move_to, [0, 300]], [:line_to, [200, 300]],
|
237
|
+
[:line_to, [200, 290]], [:line_to, [0, 290]],
|
238
|
+
[:clip_path_non_zero], [:end_path],
|
239
|
+
[:set_device_gray_stroking_color, [0.5]],
|
240
|
+
[:set_line_width, [10]],
|
241
|
+
[:set_line_cap_style, [2]],
|
242
|
+
[:set_line_dash_pattern, [[10, 20], 25]],
|
243
|
+
[:move_to, [0, 295]], [:line_to, [200, 295]],
|
244
|
+
[:stroke_path],
|
245
|
+
[:restore_graphics_state],
|
246
|
+
[:restore_graphics_state]]
|
247
|
+
assert_operators(@canvas.contents, ops)
|
248
|
+
end
|
249
|
+
|
250
|
+
it "works correctly for the right border" do
|
251
|
+
border = create_border(width: [0, 10, 0, 0], color: 0.5, style: :dashed)
|
252
|
+
border.draw(@canvas, 0, 0, 200, 300)
|
253
|
+
ops = [[:save_graphics_state],
|
254
|
+
[:save_graphics_state],
|
255
|
+
[:move_to, [200, 300]], [:line_to, [200, 0]],
|
256
|
+
[:line_to, [190, 0]], [:line_to, [190, 300]],
|
257
|
+
[:clip_path_non_zero], [:end_path],
|
258
|
+
[:set_device_gray_stroking_color, [0.5]],
|
259
|
+
[:set_line_width, [10]],
|
260
|
+
[:set_line_cap_style, [2]],
|
261
|
+
[:set_line_dash_pattern, [[10, 18], 23]],
|
262
|
+
[:move_to, [195, 300]], [:line_to, [195, 0]],
|
263
|
+
[:stroke_path],
|
264
|
+
[:restore_graphics_state],
|
265
|
+
[:restore_graphics_state]]
|
266
|
+
assert_operators(@canvas.contents, ops)
|
267
|
+
end
|
268
|
+
|
269
|
+
it "works correctly for the bottom border" do
|
270
|
+
border = create_border(width: [0, 0, 10, 0], color: 0.5, style: :dashed)
|
271
|
+
border.draw(@canvas, 0, 0, 200, 300)
|
272
|
+
ops = [[:save_graphics_state],
|
273
|
+
[:save_graphics_state],
|
274
|
+
[:move_to, [200, 0]], [:line_to, [0, 0]],
|
275
|
+
[:line_to, [0, 10]], [:line_to, [200, 10]],
|
276
|
+
[:clip_path_non_zero], [:end_path],
|
277
|
+
[:set_device_gray_stroking_color, [0.5]],
|
278
|
+
[:set_line_width, [10]],
|
279
|
+
[:set_line_cap_style, [2]],
|
280
|
+
[:set_line_dash_pattern, [[10, 20], 25]],
|
281
|
+
[:move_to, [200, 5]], [:line_to, [0, 5]],
|
282
|
+
[:stroke_path],
|
283
|
+
[:restore_graphics_state],
|
284
|
+
[:restore_graphics_state]]
|
285
|
+
assert_operators(@canvas.contents, ops)
|
286
|
+
end
|
287
|
+
|
288
|
+
it "works correctly for the left border" do
|
289
|
+
border = create_border(width: [0, 0, 0, 10], color: 0.5, style: :dashed)
|
290
|
+
border.draw(@canvas, 0, 0, 200, 300)
|
291
|
+
ops = [[:save_graphics_state],
|
292
|
+
[:save_graphics_state],
|
293
|
+
[:move_to, [0, 0]], [:line_to, [0, 300]],
|
294
|
+
[:line_to, [10, 300]], [:line_to, [10, 0]],
|
295
|
+
[:clip_path_non_zero], [:end_path],
|
296
|
+
[:set_device_gray_stroking_color, [0.5]],
|
297
|
+
[:set_line_width, [10]],
|
298
|
+
[:set_line_cap_style, [2]],
|
299
|
+
[:set_line_dash_pattern, [[10, 18], 23]],
|
300
|
+
[:move_to, [5, 0]], [:line_to, [5, 300]],
|
301
|
+
[:stroke_path],
|
302
|
+
[:restore_graphics_state],
|
303
|
+
[:restore_graphics_state]]
|
304
|
+
assert_operators(@canvas.contents, ops)
|
305
|
+
end
|
306
|
+
|
307
|
+
it "works with all values combined" do
|
308
|
+
border = create_border(width: [20, 10, 40, 30], color: [0, 0.25, 0.5, 0.75],
|
309
|
+
style: [:solid, :dashed, :dashed_round, :dotted])
|
310
|
+
border.draw(@canvas, 0, 0, 100, 200)
|
311
|
+
ops = [[:save_graphics_state],
|
312
|
+
[:save_graphics_state],
|
313
|
+
[:move_to, [0, 200]], [:line_to, [100, 200]],
|
314
|
+
[:line_to, [90, 180]], [:line_to, [30, 180]], [:clip_path_non_zero], [:end_path],
|
315
|
+
[:set_line_width, [20]],
|
316
|
+
[:move_to, [0, 190]], [:line_to, [100, 190]], [:stroke_path],
|
317
|
+
[:restore_graphics_state], [:save_graphics_state],
|
318
|
+
[:move_to, [100, 200]], [:line_to, [100, 0]],
|
319
|
+
[:line_to, [90, 40]], [:line_to, [90, 180]], [:clip_path_non_zero], [:end_path],
|
320
|
+
[:set_device_gray_stroking_color, [0.25]], [:set_line_width, [10]],
|
321
|
+
[:set_line_cap_style, [2]], [:set_line_dash_pattern, [[10, 20], 25]],
|
322
|
+
[:move_to, [95, 200]], [:line_to, [95, 0]], [:stroke_path],
|
323
|
+
[:restore_graphics_state], [:save_graphics_state],
|
324
|
+
[:move_to, [100, 0]], [:line_to, [0, 0]],
|
325
|
+
[:line_to, [30, 40]], [:line_to, [90, 40]], [:clip_path_non_zero], [:end_path],
|
326
|
+
[:set_device_gray_stroking_color, [0.5]], [:set_line_width, [40]],
|
327
|
+
[:set_line_cap_style, [1]], [:set_line_dash_pattern, [[40, 0], 20]],
|
328
|
+
[:move_to, [100, 20]], [:line_to, [0, 20]], [:stroke_path],
|
329
|
+
[:restore_graphics_state], [:save_graphics_state],
|
330
|
+
[:move_to, [0, 0]], [:line_to, [0, 200]],
|
331
|
+
[:line_to, [30, 180]], [:line_to, [30, 40]], [:clip_path_non_zero], [:end_path],
|
332
|
+
[:set_device_gray_stroking_color, [0.75]], [:set_line_width, [30]],
|
333
|
+
[:set_line_cap_style, [1]], [:set_line_dash_pattern, [[0, 42.5], 27.5]],
|
334
|
+
[:move_to, [15, 0]], [:line_to, [15, 200]], [:stroke_path],
|
335
|
+
[:restore_graphics_state], [:restore_graphics_state]]
|
336
|
+
assert_operators(@canvas.contents, ops)
|
337
|
+
end
|
338
|
+
end
|
339
|
+
|
340
|
+
it "raises an error if an invalid style is provided" do
|
341
|
+
assert_raises(ArgumentError) do
|
342
|
+
create_border(width: 1, color: 0, style: :unknown).draw(@canvas, 0, 0, 10, 10)
|
343
|
+
end
|
344
|
+
end
|
345
|
+
end
|
346
|
+
end
|
347
|
+
|
348
|
+
describe HexaPDF::Layout::Style::Layers do
|
349
|
+
before do
|
350
|
+
@layers = HexaPDF::Layout::Style::Layers.new
|
351
|
+
end
|
352
|
+
|
353
|
+
it "can be initialized with an array of layers" do
|
354
|
+
data = [-> {}]
|
355
|
+
layers = HexaPDF::Layout::Style::Layers.new(data)
|
356
|
+
assert_equal(data, layers.enum_for(:each, {}).to_a)
|
357
|
+
end
|
358
|
+
|
359
|
+
describe "add and each" do
|
360
|
+
it "can use a given block" do
|
361
|
+
block = proc { true}
|
362
|
+
@layers.add(&block)
|
363
|
+
assert_equal([block], @layers.enum_for(:each, {}).to_a)
|
364
|
+
end
|
365
|
+
|
366
|
+
it "can store a reference" do
|
367
|
+
@layers.add(:link, option: :value)
|
368
|
+
value = Object.new
|
369
|
+
value.define_singleton_method(:new) {|*args| :new }
|
370
|
+
config = Object.new
|
371
|
+
config.define_singleton_method(:constantize) {|*args| value }
|
372
|
+
assert_equal([:new], @layers.enum_for(:each, config).to_a)
|
373
|
+
end
|
374
|
+
|
375
|
+
it "fails if neither a block nor a name is given when adding a layer" do
|
376
|
+
assert_raises(ArgumentError) { @layers.add }
|
377
|
+
end
|
378
|
+
end
|
379
|
+
|
380
|
+
it "can determine whether layers are defined" do
|
381
|
+
assert(@layers.none?)
|
382
|
+
@layers.add {}
|
383
|
+
refute(@layers.none?)
|
384
|
+
end
|
385
|
+
|
386
|
+
it "draws the layers onto a canvas" do
|
387
|
+
box = Object.new
|
388
|
+
value = nil
|
389
|
+
klass = Class.new
|
390
|
+
klass.send(:define_method, :initialize) {|**args| @args = args }
|
391
|
+
klass.send(:define_method, :call) do |canvas, _|
|
392
|
+
value = @args
|
393
|
+
canvas.line_width(5)
|
394
|
+
end
|
395
|
+
canvas = HexaPDF::Document.new.pages.add.canvas
|
396
|
+
canvas.context.document.config['style.layers_map'][:test] = klass
|
397
|
+
|
398
|
+
@layers.add {|canv, ibox| assert_equal(box, ibox); canv.line_width(10)}
|
399
|
+
@layers.add(:test, option: :value)
|
400
|
+
@layers.draw(canvas, 10, 15, box)
|
401
|
+
ops = [[:save_graphics_state],
|
402
|
+
[:concatenate_matrix, [1, 0, 0, 1, 10, 15]],
|
403
|
+
[:save_graphics_state],
|
404
|
+
[:set_line_width, [10]],
|
405
|
+
[:restore_graphics_state],
|
406
|
+
[:save_graphics_state],
|
407
|
+
[:set_line_width, [5]],
|
408
|
+
[:restore_graphics_state],
|
409
|
+
[:restore_graphics_state]]
|
410
|
+
assert_operators(canvas.contents, ops)
|
411
|
+
assert_equal({option: :value}, value)
|
412
|
+
end
|
413
|
+
end
|
414
|
+
|
415
|
+
describe HexaPDF::Layout::Style::LinkLayer do
|
416
|
+
describe "initialize" do
|
417
|
+
it "fails if more than one possible target is chosen" do
|
418
|
+
assert_raises(ArgumentError) { HexaPDF::Layout::Style::LinkLayer.new(dest: true, uri: true) }
|
419
|
+
assert_raises(ArgumentError) { HexaPDF::Layout::Style::LinkLayer.new(dest: true, file: true) }
|
420
|
+
assert_raises(ArgumentError) { HexaPDF::Layout::Style::LinkLayer.new(uri: true, file: true) }
|
421
|
+
end
|
422
|
+
|
423
|
+
it "fails if an invalid border is provided" do
|
424
|
+
assert_raises(ArgumentError) { HexaPDF::Layout::Style::LinkLayer.new(border: 5) }
|
425
|
+
end
|
426
|
+
end
|
427
|
+
|
428
|
+
describe "call" do
|
429
|
+
before do
|
430
|
+
@canvas = HexaPDF::Document.new.pages.add.canvas
|
431
|
+
@canvas.translate(10, 10)
|
432
|
+
@box = HexaPDF::Layout::Box.new(width: 15, height: 10)
|
433
|
+
end
|
434
|
+
|
435
|
+
def call_link(hash)
|
436
|
+
link = HexaPDF::Layout::Style::LinkLayer.new(hash)
|
437
|
+
link.call(@canvas, @box)
|
438
|
+
@canvas.context[:Annots][0]
|
439
|
+
end
|
440
|
+
|
441
|
+
it "sets general values like /Rect and /QuadPoints" do
|
442
|
+
annot = call_link(dest: true)
|
443
|
+
assert_equal(:Link, annot[:Subtype])
|
444
|
+
assert_equal([10, 10, 25, 20], annot[:Rect].value)
|
445
|
+
assert_equal([10, 10, 25, 10, 25, 20, 10, 20], annot[:QuadPoints])
|
446
|
+
end
|
447
|
+
|
448
|
+
it "removes the border by default" do
|
449
|
+
annot = call_link(dest: true)
|
450
|
+
assert_equal([0, 0, 0], annot[:Border])
|
451
|
+
end
|
452
|
+
|
453
|
+
it "uses a default border if no specific border style is specified" do
|
454
|
+
annot = call_link(dest: true, border: true)
|
455
|
+
assert_equal([0, 0, 1], annot[:Border])
|
456
|
+
end
|
457
|
+
|
458
|
+
it "uses the specified border and border color" do
|
459
|
+
annot = call_link(dest: true, border: [10, 10, 2], border_color: [255])
|
460
|
+
assert_equal([10, 10, 2], annot[:Border])
|
461
|
+
assert_equal([1.0], annot[:C])
|
462
|
+
end
|
463
|
+
|
464
|
+
it "works for simple destinations" do
|
465
|
+
annot = call_link(dest: [@canvas.context, :FitH])
|
466
|
+
assert_equal([@canvas.context, :FitH], annot[:Dest])
|
467
|
+
assert_nil(annot[:A])
|
468
|
+
end
|
469
|
+
|
470
|
+
it "works for URIs" do
|
471
|
+
annot = call_link(uri: "test.html")
|
472
|
+
assert_equal({S: :URI, URI: "test.html"}, annot[:A].value)
|
473
|
+
assert_nil(annot[:Dest])
|
474
|
+
end
|
475
|
+
|
476
|
+
it "works for files" do
|
477
|
+
annot = call_link(file: "local-file.pdf")
|
478
|
+
assert_equal({S: :Launch, F: "local-file.pdf", NewWindow: true}, annot[:A].value)
|
479
|
+
assert_nil(annot[:Dest])
|
480
|
+
end
|
481
|
+
end
|
482
|
+
end
|
483
|
+
|
75
484
|
describe HexaPDF::Layout::Style do
|
76
485
|
before do
|
77
486
|
@style = HexaPDF::Layout::Style.new
|
@@ -82,7 +491,7 @@ describe HexaPDF::Layout::Style do
|
|
82
491
|
assert_equal(10, style.font_size)
|
83
492
|
end
|
84
493
|
|
85
|
-
it "has several dynamically generated properties with default values" do
|
494
|
+
it "has several simple and dynamically generated properties with default values" do
|
86
495
|
assert_raises(HexaPDF::Error) { @style.font }
|
87
496
|
assert_equal(10, @style.font_size)
|
88
497
|
assert_equal(0, @style.character_spacing)
|
@@ -90,47 +499,126 @@ describe HexaPDF::Layout::Style do
|
|
90
499
|
assert_equal(100, @style.horizontal_scaling)
|
91
500
|
assert_equal(0, @style.text_rise)
|
92
501
|
assert_equal({}, @style.font_features)
|
502
|
+
assert_equal(:fill, @style.text_rendering_mode)
|
503
|
+
assert_equal([0], @style.fill_color.components)
|
504
|
+
assert_equal(1, @style.fill_alpha)
|
505
|
+
assert_equal([0], @style.stroke_color.components)
|
506
|
+
assert_equal(1, @style.stroke_alpha)
|
507
|
+
assert_equal(1, @style.stroke_width)
|
508
|
+
assert_equal(:butt, @style.stroke_cap_style)
|
509
|
+
assert_equal(:miter, @style.stroke_join_style)
|
510
|
+
assert_equal(10.0, @style.stroke_miter_limit)
|
93
511
|
assert_equal(:left, @style.align)
|
94
512
|
assert_equal(:top, @style.valign)
|
513
|
+
assert_equal(0, @style.text_indent)
|
514
|
+
assert_nil(@style.background_color)
|
515
|
+
assert(@style.padding.simple?)
|
516
|
+
assert_equal(0, @style.padding.top)
|
517
|
+
assert(@style.margin.simple?)
|
518
|
+
assert_equal(0, @style.margin.top)
|
519
|
+
assert(@style.border.none?)
|
520
|
+
assert_equal([[], 0], @style.stroke_dash_pattern.to_operands)
|
521
|
+
assert_equal([:proportional, 1], [@style.line_spacing.type, @style.line_spacing.value])
|
522
|
+
refute(@style.subscript)
|
523
|
+
refute(@style.superscript)
|
524
|
+
assert_kind_of(HexaPDF::Layout::Style::Layers, @style.underlays)
|
525
|
+
assert_kind_of(HexaPDF::Layout::Style::Layers, @style.overlays)
|
95
526
|
end
|
96
527
|
|
97
|
-
it "
|
98
|
-
|
99
|
-
@style.
|
100
|
-
assert_equal(
|
528
|
+
it "allows using a non-standard setter for generated properties" do
|
529
|
+
@style.padding = [5, 3]
|
530
|
+
assert_equal(5, @style.padding.top)
|
531
|
+
assert_equal(3, @style.padding.left)
|
532
|
+
|
533
|
+
@style.stroke_dash_pattern(5, 2)
|
534
|
+
assert_equal([[5], 2], @style.stroke_dash_pattern.to_operands)
|
535
|
+
end
|
536
|
+
|
537
|
+
it "allows checking whether a property has been set or accessed" do
|
538
|
+
refute(@style.align?)
|
539
|
+
assert_equal(:left, @style.align)
|
540
|
+
assert(@style.align?)
|
541
|
+
|
542
|
+
refute(@style.valign?)
|
543
|
+
@style.valign = :bottom
|
544
|
+
assert(@style.valign?)
|
101
545
|
end
|
102
546
|
|
103
|
-
it "
|
104
|
-
assert_equal(HexaPDF::Layout::
|
547
|
+
it "has several dynamically generated properties with default values that take blocks" do
|
548
|
+
assert_equal(HexaPDF::Layout::TextLayouter::SimpleTextSegmentation,
|
105
549
|
@style.text_segmentation_algorithm)
|
550
|
+
assert_equal(HexaPDF::Layout::TextLayouter::SimpleLineWrapping,
|
551
|
+
@style.text_line_wrapping_algorithm)
|
552
|
+
|
106
553
|
block = proc { :y }
|
107
554
|
@style.text_segmentation_algorithm(&block)
|
108
555
|
assert_equal(block, @style.text_segmentation_algorithm)
|
109
|
-
end
|
110
556
|
|
111
|
-
|
112
|
-
assert_equal(
|
113
|
-
@style.text_line_wrapping_algorithm)
|
114
|
-
@style.text_line_wrapping_algorithm(:callable)
|
115
|
-
assert_equal(:callable, @style.text_line_wrapping_algorithm)
|
557
|
+
@style.text_segmentation_algorithm(:callable)
|
558
|
+
assert_equal(:callable, @style.text_segmentation_algorithm)
|
116
559
|
end
|
117
560
|
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
561
|
+
describe "methods for some derived and cached values" do
|
562
|
+
before do
|
563
|
+
wrapped_font = Object.new
|
564
|
+
wrapped_font.define_singleton_method(:ascender) { 600 }
|
565
|
+
wrapped_font.define_singleton_method(:descender) { -100 }
|
566
|
+
font = Object.new
|
567
|
+
font.define_singleton_method(:scaling_factor) { 1 }
|
568
|
+
font.define_singleton_method(:wrapped_font) { wrapped_font }
|
569
|
+
@style.font = font
|
570
|
+
end
|
571
|
+
|
572
|
+
it "computes them correctly" do
|
573
|
+
@style.horizontal_scaling(200).character_spacing(1).word_spacing(2)
|
574
|
+
assert_equal(0.02, @style.scaled_font_size)
|
575
|
+
assert_equal(2, @style.scaled_character_spacing)
|
576
|
+
assert_equal(4, @style.scaled_word_spacing)
|
577
|
+
assert_equal(2, @style.scaled_horizontal_scaling)
|
578
|
+
|
579
|
+
assert_equal(6, @style.scaled_font_ascender)
|
580
|
+
assert_equal(-1, @style.scaled_font_descender)
|
581
|
+
end
|
582
|
+
|
583
|
+
it "computes item widths correctly" do
|
584
|
+
@style.horizontal_scaling(200).character_spacing(1).word_spacing(2)
|
585
|
+
|
586
|
+
assert_equal(-1.0, @style.scaled_item_width(50))
|
587
|
+
|
588
|
+
obj = Object.new
|
589
|
+
obj.define_singleton_method(:width) { 100 }
|
590
|
+
obj.define_singleton_method(:apply_word_spacing?) { true }
|
591
|
+
assert_equal(8, @style.scaled_item_width(obj))
|
592
|
+
end
|
593
|
+
|
594
|
+
it "handles subscript" do
|
595
|
+
@style.subscript = true
|
596
|
+
assert_in_delta(5.83, @style.calculated_font_size)
|
597
|
+
assert_in_delta(0.00583, @style.scaled_font_size, 0.000001)
|
598
|
+
assert_in_delta(-2.00, @style.calculated_text_rise)
|
599
|
+
end
|
600
|
+
|
601
|
+
it "handles superscript" do
|
602
|
+
@style.superscript = true
|
603
|
+
assert_in_delta(5.83, @style.calculated_font_size)
|
604
|
+
assert_in_delta(3.30, @style.calculated_text_rise)
|
605
|
+
end
|
606
|
+
|
607
|
+
it "handles underline" do
|
608
|
+
@style.font.wrapped_font.define_singleton_method(:underline_position) { -100 }
|
609
|
+
@style.font.wrapped_font.define_singleton_method(:underline_thickness) { 10 }
|
610
|
+
@style.text_rise = 10
|
611
|
+
assert_in_delta(-1.05 + 10, @style.calculated_underline_position)
|
612
|
+
assert_equal(0.1, @style.calculated_underline_thickness)
|
613
|
+
end
|
614
|
+
|
615
|
+
it "handles strikeout" do
|
616
|
+
@style.font.wrapped_font.define_singleton_method(:strikeout_position) { 300 }
|
617
|
+
@style.font.wrapped_font.define_singleton_method(:strikeout_thickness) { 10 }
|
618
|
+
@style.text_rise = 10
|
619
|
+
assert_in_delta(2.95 + 10, @style.calculated_strikeout_position)
|
620
|
+
assert_equal(0.1, @style.calculated_strikeout_thickness)
|
621
|
+
end
|
134
622
|
end
|
135
623
|
|
136
624
|
it "can clear cached values" do
|