prawn 2.0.2 → 2.3.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 +5 -5
- checksums.yaml.gz.sig +0 -0
- data.tar.gz.sig +0 -0
- data/GPLv2 +20 -21
- data/Gemfile +3 -9
- data/Rakefile +20 -23
- data/lib/prawn.rb +37 -49
- data/lib/prawn/document.rb +181 -133
- data/lib/prawn/document/bounding_box.rb +41 -29
- data/lib/prawn/document/column_box.rb +7 -7
- data/lib/prawn/document/internals.rb +18 -8
- data/lib/prawn/document/span.rb +21 -16
- data/lib/prawn/encoding.rb +69 -68
- data/lib/prawn/errors.rb +12 -7
- data/lib/prawn/font.rb +115 -69
- data/lib/prawn/font_metric_cache.rb +14 -8
- data/lib/prawn/{font → fonts}/afm.rb +102 -68
- data/lib/prawn/{font → fonts}/dfont.rb +5 -11
- data/lib/prawn/fonts/otf.rb +11 -0
- data/lib/prawn/fonts/ttc.rb +36 -0
- data/lib/prawn/{font → fonts}/ttf.rb +87 -68
- data/lib/prawn/graphics.rb +120 -80
- data/lib/prawn/graphics/blend_mode.rb +65 -0
- data/lib/prawn/graphics/cap_style.rb +3 -3
- data/lib/prawn/graphics/color.rb +27 -25
- data/lib/prawn/graphics/dash.rb +23 -11
- data/lib/prawn/graphics/join_style.rb +9 -3
- data/lib/prawn/graphics/patterns.rb +197 -67
- data/lib/prawn/graphics/transformation.rb +17 -8
- data/lib/prawn/graphics/transparency.rb +17 -13
- data/lib/prawn/grid.rb +48 -47
- data/lib/prawn/image_handler.rb +5 -5
- data/lib/prawn/images.rb +39 -30
- data/lib/prawn/images/image.rb +2 -1
- data/lib/prawn/images/jpg.rb +28 -22
- data/lib/prawn/images/png.rb +107 -66
- data/lib/prawn/measurement_extensions.rb +10 -9
- data/lib/prawn/measurements.rb +19 -15
- data/lib/prawn/outline.rb +97 -77
- data/lib/prawn/repeater.rb +14 -10
- data/lib/prawn/security.rb +81 -61
- data/lib/prawn/security/arcfour.rb +2 -2
- data/lib/prawn/soft_mask.rb +26 -26
- data/lib/prawn/stamp.rb +20 -13
- data/lib/prawn/text.rb +68 -52
- data/lib/prawn/text/box.rb +11 -8
- data/lib/prawn/text/formatted.rb +5 -5
- data/lib/prawn/text/formatted/arranger.rb +78 -49
- data/lib/prawn/text/formatted/box.rb +134 -100
- data/lib/prawn/text/formatted/fragment.rb +11 -14
- data/lib/prawn/text/formatted/line_wrap.rb +121 -63
- data/lib/prawn/text/formatted/parser.rb +139 -117
- data/lib/prawn/text/formatted/wrap.rb +43 -31
- data/lib/prawn/transformation_stack.rb +44 -0
- data/lib/prawn/utilities.rb +7 -22
- data/lib/prawn/version.rb +2 -2
- data/lib/prawn/view.rb +17 -7
- data/manual/basic_concepts/adding_pages.rb +6 -7
- data/manual/basic_concepts/basic_concepts.rb +31 -22
- data/manual/basic_concepts/creation.rb +10 -11
- data/manual/basic_concepts/cursor.rb +4 -5
- data/manual/basic_concepts/measurement.rb +6 -7
- data/manual/basic_concepts/origin.rb +5 -6
- data/manual/basic_concepts/other_cursor_helpers.rb +11 -12
- data/manual/basic_concepts/view.rb +22 -16
- data/manual/bounding_box/bounding_box.rb +29 -24
- data/manual/bounding_box/bounds.rb +11 -12
- data/manual/bounding_box/canvas.rb +4 -5
- data/manual/bounding_box/creation.rb +6 -7
- data/manual/bounding_box/indentation.rb +14 -15
- data/manual/bounding_box/nesting.rb +24 -17
- data/manual/bounding_box/russian_boxes.rb +14 -13
- data/manual/bounding_box/stretchy.rb +12 -13
- data/manual/contents.rb +28 -22
- data/manual/cover.rb +33 -28
- data/manual/document_and_page_options/background.rb +11 -13
- data/manual/document_and_page_options/document_and_page_options.rb +25 -20
- data/manual/document_and_page_options/metadata.rb +18 -16
- data/manual/document_and_page_options/page_margins.rb +18 -20
- data/manual/document_and_page_options/page_size.rb +13 -12
- data/manual/document_and_page_options/print_scaling.rb +17 -15
- data/manual/example_helper.rb +5 -4
- data/manual/graphics/blend_mode.rb +52 -0
- data/manual/graphics/circle_and_ellipse.rb +4 -5
- data/manual/graphics/color.rb +7 -9
- data/manual/graphics/common_lines.rb +7 -8
- data/manual/graphics/fill_and_stroke.rb +4 -5
- data/manual/graphics/fill_rules.rb +9 -10
- data/manual/graphics/gradients.rb +27 -21
- data/manual/graphics/graphics.rb +48 -39
- data/manual/graphics/helper.rb +12 -9
- data/manual/graphics/line_width.rb +8 -7
- data/manual/graphics/lines_and_curves.rb +7 -8
- data/manual/graphics/polygon.rb +6 -8
- data/manual/graphics/rectangle.rb +4 -5
- data/manual/graphics/rotate.rb +6 -7
- data/manual/graphics/scale.rb +14 -15
- data/manual/graphics/soft_masks.rb +4 -5
- data/manual/graphics/stroke_cap.rb +6 -7
- data/manual/graphics/stroke_dash.rb +11 -12
- data/manual/graphics/stroke_join.rb +5 -6
- data/manual/graphics/translate.rb +9 -10
- data/manual/graphics/transparency.rb +7 -8
- data/manual/how_to_read_this_manual.rb +6 -6
- data/manual/images/absolute_position.rb +6 -7
- data/manual/images/fit.rb +7 -8
- data/manual/images/horizontal.rb +9 -10
- data/manual/images/images.rb +28 -24
- data/manual/images/plain_image.rb +5 -6
- data/manual/images/scale.rb +9 -10
- data/manual/images/vertical.rb +13 -14
- data/manual/images/width_and_height.rb +10 -11
- data/manual/layout/boxes.rb +5 -6
- data/manual/layout/content.rb +7 -8
- data/manual/layout/layout.rb +18 -16
- data/manual/layout/simple_grid.rb +6 -7
- data/manual/outline/add_subsection_to.rb +20 -21
- data/manual/outline/insert_section_after.rb +15 -16
- data/manual/outline/outline.rb +21 -17
- data/manual/outline/sections_and_pages.rb +17 -18
- data/manual/repeatable_content/alternate_page_numbering.rb +21 -17
- data/manual/repeatable_content/page_numbering.rb +17 -16
- data/manual/repeatable_content/repeatable_content.rb +25 -19
- data/manual/repeatable_content/repeater.rb +14 -15
- data/manual/repeatable_content/stamp.rb +14 -15
- data/manual/security/encryption.rb +9 -10
- data/manual/security/permissions.rb +19 -14
- data/manual/security/security.rb +19 -16
- data/manual/table.rb +3 -3
- data/manual/text/alignment.rb +16 -17
- data/manual/text/color.rb +12 -11
- data/manual/text/column_box.rb +9 -10
- data/manual/text/fallback_fonts.rb +25 -21
- data/manual/text/font.rb +11 -12
- data/manual/text/font_size.rb +13 -14
- data/manual/text/font_style.rb +7 -8
- data/manual/text/formatted_callbacks.rb +25 -21
- data/manual/text/formatted_text.rb +33 -25
- data/manual/text/free_flowing_text.rb +20 -21
- data/manual/text/inline.rb +18 -19
- data/manual/text/kerning_and_character_spacing.rb +14 -15
- data/manual/text/leading.rb +7 -8
- data/manual/text/line_wrapping.rb +37 -18
- data/manual/text/paragraph_indentation.rb +13 -14
- data/manual/text/positioned_text.rb +15 -16
- data/manual/text/registering_families.rb +20 -21
- data/manual/text/rendering_and_color.rb +9 -10
- data/manual/text/right_to_left_text.rb +26 -19
- data/manual/text/rotation.rb +28 -23
- data/manual/text/single_usage.rb +8 -9
- data/manual/text/text.rb +57 -52
- data/manual/text/text_box_excess.rb +20 -17
- data/manual/text/text_box_extensions.rb +18 -15
- data/manual/text/text_box_overflow.rb +18 -19
- data/manual/text/utf8.rb +11 -12
- data/manual/text/win_ansi_charset.rb +21 -19
- data/prawn.gemspec +45 -33
- data/spec/extensions/encoding_helpers.rb +3 -3
- data/spec/prawn/document/bounding_box_spec.rb +546 -0
- data/spec/prawn/document/column_box_spec.rb +75 -0
- data/spec/prawn/document/security_spec.rb +176 -0
- data/spec/prawn/document_annotations_spec.rb +76 -0
- data/spec/prawn/document_destinations_spec.rb +15 -0
- data/spec/prawn/document_grid_spec.rb +99 -0
- data/spec/prawn/document_reference_spec.rb +27 -0
- data/spec/prawn/document_span_spec.rb +36 -0
- data/spec/prawn/document_spec.rb +802 -0
- data/spec/prawn/font_metric_cache_spec.rb +54 -0
- data/spec/prawn/font_spec.rb +542 -0
- data/spec/prawn/graphics/blend_mode_spec.rb +63 -0
- data/spec/prawn/graphics/transparency_spec.rb +81 -0
- data/spec/prawn/graphics_spec.rb +837 -0
- data/spec/prawn/graphics_stroke_styles_spec.rb +229 -0
- data/spec/prawn/image_handler_spec.rb +53 -0
- data/spec/prawn/images/jpg_spec.rb +20 -0
- data/spec/prawn/images/png_spec.rb +283 -0
- data/spec/prawn/images_spec.rb +224 -0
- data/spec/prawn/measurements_extensions_spec.rb +24 -0
- data/spec/prawn/outline_spec.rb +412 -0
- data/spec/prawn/repeater_spec.rb +165 -0
- data/spec/prawn/soft_mask_spec.rb +74 -0
- data/spec/prawn/stamp_spec.rb +172 -0
- data/spec/prawn/text/box_spec.rb +1112 -0
- data/spec/prawn/text/formatted/arranger_spec.rb +466 -0
- data/spec/prawn/text/formatted/box_spec.rb +846 -0
- data/spec/prawn/text/formatted/fragment_spec.rb +343 -0
- data/spec/prawn/text/formatted/line_wrap_spec.rb +494 -0
- data/spec/prawn/text/formatted/parser_spec.rb +697 -0
- data/spec/prawn/text_draw_text_spec.rb +149 -0
- data/spec/prawn/text_rendering_mode_spec.rb +48 -0
- data/spec/prawn/text_spacing_spec.rb +95 -0
- data/spec/prawn/text_spec.rb +603 -0
- data/spec/prawn/text_with_inline_formatting_spec.rb +35 -0
- data/spec/prawn/transformation_stack_spec.rb +66 -0
- data/spec/prawn/view_spec.rb +63 -0
- data/spec/prawn_manual_spec.rb +35 -0
- data/spec/spec_helper.rb +19 -23
- metadata +145 -185
- metadata.gz.sig +4 -0
- data/data/images/16bit.alpha +0 -0
- data/data/images/16bit.color +0 -0
- data/data/images/16bit.png +0 -0
- data/data/images/arrow.png +0 -0
- data/data/images/arrow2.png +0 -0
- data/data/images/dice.alpha +0 -0
- data/data/images/dice.color +0 -0
- data/data/images/dice.png +0 -0
- data/data/images/dice_interlaced.png +0 -0
- data/data/images/fractal.jpg +0 -0
- data/data/images/indexed_color.dat +0 -0
- data/data/images/indexed_color.png +0 -0
- data/data/images/letterhead.jpg +0 -0
- data/data/images/license.md +0 -8
- data/data/images/page_white_text.alpha +0 -0
- data/data/images/page_white_text.color +0 -0
- data/data/images/page_white_text.png +0 -0
- data/data/images/pal_bk.png +0 -0
- data/data/images/pigs.jpg +0 -0
- data/data/images/prawn.png +0 -0
- data/data/images/ruport.png +0 -0
- data/data/images/ruport_data.dat +0 -0
- data/data/images/ruport_transparent.png +0 -0
- data/data/images/ruport_type0.png +0 -0
- data/data/images/stef.jpg +0 -0
- data/data/images/tru256.bmp +0 -0
- data/data/images/web-links.dat +0 -1
- data/data/images/web-links.png +0 -0
- data/data/pdfs/complex_template.pdf +0 -0
- data/data/pdfs/contains_ttf_font.pdf +0 -0
- data/data/pdfs/encrypted.pdf +0 -0
- data/data/pdfs/form.pdf +1 -819
- data/data/pdfs/hexagon.pdf +0 -61
- data/data/pdfs/indirect_reference.pdf +0 -86
- data/data/pdfs/multipage_template.pdf +0 -127
- data/data/pdfs/nested_pages.pdf +0 -118
- data/data/pdfs/page_without_mediabox.pdf +0 -193
- data/data/pdfs/resources_as_indirect_object.pdf +0 -83
- data/data/pdfs/two_hexagons.pdf +0 -90
- data/data/pdfs/version_1_6.pdf +0 -61
- data/data/shift_jis_text.txt +0 -1
- data/spec/acceptance/png.rb +0 -24
- data/spec/annotations_spec.rb +0 -67
- data/spec/bounding_box_spec.rb +0 -501
- data/spec/column_box_spec.rb +0 -59
- data/spec/destinations_spec.rb +0 -13
- data/spec/document_spec.rb +0 -742
- data/spec/extensions/mocha.rb +0 -45
- data/spec/font_metric_cache_spec.rb +0 -52
- data/spec/font_spec.rb +0 -475
- data/spec/formatted_text_arranger_spec.rb +0 -423
- data/spec/formatted_text_box_spec.rb +0 -716
- data/spec/formatted_text_fragment_spec.rb +0 -299
- data/spec/graphics_spec.rb +0 -666
- data/spec/grid_spec.rb +0 -95
- data/spec/image_handler_spec.rb +0 -53
- data/spec/images_spec.rb +0 -167
- data/spec/inline_formatted_text_parser_spec.rb +0 -568
- data/spec/jpg_spec.rb +0 -23
- data/spec/line_wrap_spec.rb +0 -366
- data/spec/measurement_units_spec.rb +0 -22
- data/spec/outline_spec.rb +0 -409
- data/spec/png_spec.rb +0 -235
- data/spec/reference_spec.rb +0 -25
- data/spec/repeater_spec.rb +0 -154
- data/spec/security_spec.rb +0 -151
- data/spec/soft_mask_spec.rb +0 -78
- data/spec/span_spec.rb +0 -43
- data/spec/stamp_spec.rb +0 -179
- data/spec/stroke_styles_spec.rb +0 -208
- data/spec/text_at_spec.rb +0 -142
- data/spec/text_box_spec.rb +0 -1038
- data/spec/text_rendering_mode_spec.rb +0 -45
- data/spec/text_spacing_spec.rb +0 -93
- data/spec/text_spec.rb +0 -549
- data/spec/text_with_inline_formatting_spec.rb +0 -35
- data/spec/transparency_spec.rb +0 -91
- data/spec/view_spec.rb +0 -42
@@ -0,0 +1,165 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
describe Prawn::Repeater do
|
6
|
+
it 'creates a stamp and increments Prawn::Repeater.count on initialize' do
|
7
|
+
orig_count = described_class.count
|
8
|
+
|
9
|
+
doc = sample_document
|
10
|
+
allow(doc).to receive(:create_stamp).with("prawn_repeater(#{orig_count})")
|
11
|
+
|
12
|
+
repeater(doc, :all) { :do_nothing }
|
13
|
+
|
14
|
+
expect(doc).to have_received(:create_stamp)
|
15
|
+
.with("prawn_repeater(#{orig_count})")
|
16
|
+
|
17
|
+
expect(described_class.count).to eq(orig_count + 1)
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'must provide an :all filter' do
|
21
|
+
doc = sample_document
|
22
|
+
r = repeater(doc, :all) { :do_nothing }
|
23
|
+
|
24
|
+
expect((1..doc.page_count).all? { |i| r.match?(i) }).to eq true
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'must provide an :odd filter' do
|
28
|
+
doc = sample_document
|
29
|
+
r = repeater(doc, :odd) { :do_nothing }
|
30
|
+
|
31
|
+
odd, even = (1..doc.page_count).partition(&:odd?)
|
32
|
+
|
33
|
+
expect(odd.all? { |i| r.match?(i) }).to eq true
|
34
|
+
expect(even.any? { |i| r.match?(i) }).to eq false
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'must be able to filter by an array of page numbers' do
|
38
|
+
doc = sample_document
|
39
|
+
r = repeater(doc, [1, 2, 7]) { :do_nothing }
|
40
|
+
|
41
|
+
expect((1..10).select { |i| r.match?(i) }).to eq([1, 2, 7])
|
42
|
+
end
|
43
|
+
|
44
|
+
it 'must be able to filter by a range of page numbers' do
|
45
|
+
doc = sample_document
|
46
|
+
r = repeater(doc, 2..4) { :do_nothing }
|
47
|
+
|
48
|
+
expect((1..10).select { |i| r.match?(i) }).to eq([2, 3, 4])
|
49
|
+
end
|
50
|
+
|
51
|
+
it 'must be able to filter by an arbitrary proc' do
|
52
|
+
doc = sample_document
|
53
|
+
r = repeater(doc, ->(x) { x == 1 || x % 3 == 0 })
|
54
|
+
|
55
|
+
expect((1..10).select { |i| r.match?(i) }).to eq([1, 3, 6, 9])
|
56
|
+
end
|
57
|
+
|
58
|
+
it 'must try to run a stamp if the page number matches' do
|
59
|
+
doc = sample_document
|
60
|
+
allow(doc).to receive(:stamp)
|
61
|
+
|
62
|
+
repeater(doc, :odd).run(3)
|
63
|
+
expect(doc).to have_received(:stamp)
|
64
|
+
end
|
65
|
+
|
66
|
+
it 'must not try to run a stamp unless the page number matches' do
|
67
|
+
doc = sample_document
|
68
|
+
|
69
|
+
allow(doc).to receive(:stamp)
|
70
|
+
repeater(doc, :odd).run(2)
|
71
|
+
expect(doc).to_not have_received(:stamp)
|
72
|
+
end
|
73
|
+
|
74
|
+
it 'must not try to run a stamp if dynamic is selected' do
|
75
|
+
doc = sample_document
|
76
|
+
|
77
|
+
allow(doc).to receive(:stamp)
|
78
|
+
(1..10).each { |p| repeater(doc, :all, true) { :do_nothing }.run(p) }
|
79
|
+
expect(doc).to_not have_received(:stamp)
|
80
|
+
end
|
81
|
+
|
82
|
+
it 'must try to run a block if the page number matches' do
|
83
|
+
doc = sample_document
|
84
|
+
|
85
|
+
allow(doc).to receive(:draw_text)
|
86
|
+
(1..10).each do |p|
|
87
|
+
repeater(doc, [1, 2], true) { doc.draw_text 'foo' }.run(p)
|
88
|
+
end
|
89
|
+
expect(doc).to have_received(:draw_text).twice
|
90
|
+
end
|
91
|
+
|
92
|
+
it 'must not try to run a block unless the page number matches' do
|
93
|
+
doc = sample_document
|
94
|
+
|
95
|
+
allow(doc).to receive(:draw_text)
|
96
|
+
repeater(doc, :odd, true) { doc.draw_text 'foo' }.run(2)
|
97
|
+
expect(doc).to_not have_received(:draw_text)
|
98
|
+
end
|
99
|
+
|
100
|
+
it 'must treat any block as a closure' do
|
101
|
+
doc = sample_document
|
102
|
+
|
103
|
+
page = 'Page' # ensure access to ivars
|
104
|
+
doc.repeat(:all, dynamic: true) do
|
105
|
+
doc.draw_text "#{page} #{doc.page_number}", at: [500, 0]
|
106
|
+
end
|
107
|
+
|
108
|
+
text = PDF::Inspector::Text.analyze(doc.render)
|
109
|
+
expect(text.strings).to eq((1..10).to_a.map { |p| "Page #{p}" })
|
110
|
+
end
|
111
|
+
|
112
|
+
it 'must treat any block as a closure (Document.new instance_eval form)' do
|
113
|
+
doc = Prawn::Document.new(skip_page_creation: true) do
|
114
|
+
10.times { start_new_page }
|
115
|
+
|
116
|
+
page = 'Page'
|
117
|
+
repeat(:all, dynamic: true) do
|
118
|
+
# ensure self is accessible here
|
119
|
+
draw_text "#{page} #{page_number}", at: [500, 0]
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
text = PDF::Inspector::Text.analyze(doc.render)
|
124
|
+
expect(text.strings).to eq((1..10).to_a.map { |p| "Page #{p}" })
|
125
|
+
end
|
126
|
+
|
127
|
+
def sample_document
|
128
|
+
doc = Prawn::Document.new(skip_page_creation: true)
|
129
|
+
10.times { |_e| doc.start_new_page }
|
130
|
+
doc
|
131
|
+
end
|
132
|
+
|
133
|
+
def repeater(*args, &block)
|
134
|
+
Prawn::Repeater.new(*args, &block)
|
135
|
+
end
|
136
|
+
|
137
|
+
describe 'graphic state' do
|
138
|
+
let(:pdf) { create_pdf }
|
139
|
+
|
140
|
+
it 'does not alter the graphic state stack color space' do
|
141
|
+
starting_color_space = pdf.state.page.graphic_state.color_space.dup
|
142
|
+
pdf.repeat :all do
|
143
|
+
pdf.text 'Testing', size: 24, style: :bold
|
144
|
+
end
|
145
|
+
expect(pdf.state.page.graphic_state.color_space)
|
146
|
+
.to eq(starting_color_space)
|
147
|
+
end
|
148
|
+
|
149
|
+
context 'with dynamic repeaters' do
|
150
|
+
it 'preserves the graphic state at creation time' do
|
151
|
+
pdf.repeat :all, dynamic: true do
|
152
|
+
pdf.text "fill_color: #{pdf.graphic_state.fill_color}"
|
153
|
+
pdf.text "cap_style: #{pdf.graphic_state.cap_style}"
|
154
|
+
end
|
155
|
+
pdf.fill_color '666666'
|
156
|
+
pdf.cap_style :round
|
157
|
+
text = PDF::Inspector::Text.analyze(pdf.render)
|
158
|
+
expect(text.strings.include?('fill_color: 666666')).to eq(false)
|
159
|
+
expect(text.strings.include?('fill_color: 000000')).to eq(true)
|
160
|
+
expect(text.strings.include?('cap_style: round')).to eq(false)
|
161
|
+
expect(text.strings.include?('cap_style: butt')).to eq(true)
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
describe Prawn::SoftMask do
|
6
|
+
let(:pdf) { create_pdf }
|
7
|
+
|
8
|
+
def make_soft_mask
|
9
|
+
pdf.save_graphics_state do
|
10
|
+
pdf.soft_mask do
|
11
|
+
if block_given?
|
12
|
+
yield
|
13
|
+
else
|
14
|
+
pdf.fill_color '808080'
|
15
|
+
pdf.fill_rectangle [100, 100], 200, 200
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
pdf.fill_color '000000'
|
20
|
+
pdf.fill_rectangle [0, 0], 200, 200
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'has PDF version at least 1.4' do
|
25
|
+
make_soft_mask
|
26
|
+
str = pdf.render
|
27
|
+
expect(str[0, 8]).to eq('%PDF-1.4')
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'creates a new extended graphics state for each unique soft mask' do
|
31
|
+
make_soft_mask do
|
32
|
+
pdf.fill_color '808080'
|
33
|
+
pdf.fill_rectangle [100, 100], 200, 200
|
34
|
+
end
|
35
|
+
|
36
|
+
make_soft_mask do
|
37
|
+
pdf.fill_color '808080'
|
38
|
+
pdf.fill_rectangle [10, 10], 200, 200
|
39
|
+
end
|
40
|
+
|
41
|
+
extgstates = PDF::Inspector::ExtGState.analyze(pdf.render).extgstates
|
42
|
+
expect(extgstates.length).to eq(2)
|
43
|
+
end
|
44
|
+
|
45
|
+
it 'a new extended graphics state contains soft mask with drawing '\
|
46
|
+
'instructions' do
|
47
|
+
make_soft_mask do
|
48
|
+
pdf.fill_color '808080'
|
49
|
+
pdf.fill_rectangle [100, 100], 200, 200
|
50
|
+
end
|
51
|
+
|
52
|
+
extgstate = PDF::Inspector::ExtGState.analyze(pdf.render).extgstates.first
|
53
|
+
expect(extgstate[:soft_mask][:G].data).to eq(
|
54
|
+
"q\n/DeviceRGB cs\n0.0 0.0 0.0 scn\n/DeviceRGB CS\n0.0 0.0 0.0 SCN\n"\
|
55
|
+
"1 w\n0 J\n0 j\n[] 0 d\n/DeviceRGB cs\n0.502 0.502 0.502 scn\n"\
|
56
|
+
"100.0 -100.0 200.0 200.0 re\nf\nQ\n"
|
57
|
+
)
|
58
|
+
end
|
59
|
+
|
60
|
+
it 'does not create duplicate extended graphics states' do
|
61
|
+
make_soft_mask do
|
62
|
+
pdf.fill_color '808080'
|
63
|
+
pdf.fill_rectangle [100, 100], 200, 200
|
64
|
+
end
|
65
|
+
|
66
|
+
make_soft_mask do
|
67
|
+
pdf.fill_color '808080'
|
68
|
+
pdf.fill_rectangle [100, 100], 200, 200
|
69
|
+
end
|
70
|
+
|
71
|
+
extgstates = PDF::Inspector::ExtGState.analyze(pdf.render).extgstates
|
72
|
+
expect(extgstates.length).to eq(1)
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,172 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
describe Prawn::Stamp do
|
6
|
+
describe 'create_stamp before any page is added' do
|
7
|
+
let(:pdf) { Prawn::Document.new(skip_page_creation: true) }
|
8
|
+
|
9
|
+
it 'works with the font class' do
|
10
|
+
# If anything goes wrong, Prawn::Errors::NotOnPage will be raised
|
11
|
+
pdf.create_stamp('my_stamp') do
|
12
|
+
pdf.font.height
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'works with setting color' do
|
17
|
+
# If anything goes wrong, Prawn::Errors::NotOnPage will be raised
|
18
|
+
pdf.create_stamp('my_stamp') do
|
19
|
+
pdf.fill_color = 'ff0000'
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
describe '#stamp_at' do
|
25
|
+
let(:pdf) { create_pdf }
|
26
|
+
|
27
|
+
it 'works' do
|
28
|
+
pdf.create_stamp('MyStamp')
|
29
|
+
pdf.stamp_at('MyStamp', [100, 200])
|
30
|
+
# I had modified PDF::Inspector::XObject to receive the
|
31
|
+
# invoke_xobject message and count the number of times it was
|
32
|
+
# called, but it was only called once, so I reverted checking the
|
33
|
+
# output with a regular expression
|
34
|
+
expect(pdf.render).to match(%r{/Stamp1 Do.*?}m)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
describe 'Document with a stamp' do
|
39
|
+
let(:pdf) { create_pdf }
|
40
|
+
|
41
|
+
it 'raises NameTaken error when attempt to create stamp with '\
|
42
|
+
'same name as an existing stamp' do
|
43
|
+
pdf.create_stamp('MyStamp')
|
44
|
+
expect do
|
45
|
+
pdf.create_stamp('MyStamp')
|
46
|
+
end.to raise_error(Prawn::Errors::NameTaken)
|
47
|
+
end
|
48
|
+
|
49
|
+
it 'raises InvalidName error when attempt to create stamp with '\
|
50
|
+
'a blank name' do
|
51
|
+
expect do
|
52
|
+
pdf.create_stamp('')
|
53
|
+
end.to raise_error(Prawn::Errors::InvalidName)
|
54
|
+
end
|
55
|
+
|
56
|
+
it 'a new XObject should be defined for each stamp created' do
|
57
|
+
pdf.create_stamp('MyStamp')
|
58
|
+
pdf.create_stamp('AnotherStamp')
|
59
|
+
pdf.stamp('MyStamp')
|
60
|
+
pdf.stamp('AnotherStamp')
|
61
|
+
|
62
|
+
inspector = PDF::Inspector::XObject.analyze(pdf.render)
|
63
|
+
xobjects = inspector.page_xobjects.last
|
64
|
+
expect(xobjects.length).to eq(2)
|
65
|
+
end
|
66
|
+
|
67
|
+
it 'calling stamp with a name that does not match an existing stamp ' \
|
68
|
+
'should raise_error UndefinedObjectName' do
|
69
|
+
pdf.create_stamp('MyStamp')
|
70
|
+
expect do
|
71
|
+
pdf.stamp('OtherStamp')
|
72
|
+
end.to raise_error(Prawn::Errors::UndefinedObjectName)
|
73
|
+
end
|
74
|
+
|
75
|
+
it 'stamp should be drawn into the document each time stamp is called' do
|
76
|
+
pdf.create_stamp('MyStamp')
|
77
|
+
pdf.stamp('MyStamp')
|
78
|
+
pdf.stamp('MyStamp')
|
79
|
+
pdf.stamp('MyStamp')
|
80
|
+
# I had modified PDF::Inspector::XObject to receive the
|
81
|
+
# invoke_xobject message and count the number of times it was
|
82
|
+
# called, but it was only called once, so I reverted checking the
|
83
|
+
# output with a regular expression
|
84
|
+
expect(pdf.render).to match(%r{(/Stamp1 Do.*?){3}}m)
|
85
|
+
end
|
86
|
+
|
87
|
+
it 'stamp should render clickable links' do
|
88
|
+
pdf.create_stamp 'bar' do
|
89
|
+
pdf.text '<b>Prawn</b> <link href="http://github.com">GitHub</link>',
|
90
|
+
inline_format: true
|
91
|
+
end
|
92
|
+
pdf.stamp 'bar'
|
93
|
+
|
94
|
+
output = pdf.render
|
95
|
+
objects = output.split('endobj')
|
96
|
+
|
97
|
+
objects.each do |obj|
|
98
|
+
next unless %r{/Type /Page$}.match?(obj)
|
99
|
+
|
100
|
+
# The page object must contain the annotation reference
|
101
|
+
# to render a clickable link
|
102
|
+
expect(obj).to match(%r{^/Annots \[\d \d .\]$})
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
it 'resources added during stamp creation should be added to the ' \
|
107
|
+
'stamp XObject, not the page' do
|
108
|
+
pdf.create_stamp('MyStamp') do
|
109
|
+
pdf.transparent(0.5) { pdf.circle([100, 100], 10) }
|
110
|
+
end
|
111
|
+
pdf.stamp('MyStamp')
|
112
|
+
|
113
|
+
# Inspector::XObject does not give information about resources, so
|
114
|
+
# resorting to string matching
|
115
|
+
|
116
|
+
output = pdf.render
|
117
|
+
objects = output.split('endobj')
|
118
|
+
objects.each do |object|
|
119
|
+
if %r{/Type /Page$}.match?(object)
|
120
|
+
expect(object).to_not match(%r{/ExtGState})
|
121
|
+
elsif %r{/Type /XObject$}.match?(object)
|
122
|
+
expect(object).to match(%r{/ExtGState})
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
it 'stamp stream should be wrapped in a graphic state' do
|
128
|
+
pdf.create_stamp('MyStamp') do
|
129
|
+
pdf.text "This should have a 'q' before it and a 'Q' after it"
|
130
|
+
end
|
131
|
+
pdf.stamp('MyStamp')
|
132
|
+
stamps = PDF::Inspector::XObject.analyze(pdf.render)
|
133
|
+
expect(stamps.xobject_streams[:Stamp1].data.chomp).to match(/q(.|\s)*Q\Z/)
|
134
|
+
end
|
135
|
+
|
136
|
+
it 'does not add to the page graphic state stack' do
|
137
|
+
expect(pdf.state.page.stack.stack.size).to eq(1)
|
138
|
+
|
139
|
+
pdf.create_stamp('MyStamp') do
|
140
|
+
pdf.save_graphics_state
|
141
|
+
pdf.save_graphics_state
|
142
|
+
pdf.save_graphics_state
|
143
|
+
pdf.text "This should have a 'q' before it and a 'Q' after it"
|
144
|
+
pdf.restore_graphics_state
|
145
|
+
end
|
146
|
+
expect(pdf.state.page.stack.stack.size).to eq(1)
|
147
|
+
end
|
148
|
+
|
149
|
+
it 'is able to change fill and stroke colors within the stamp stream' do
|
150
|
+
pdf.create_stamp('MyStamp') do
|
151
|
+
pdf.fill_color(100, 100, 20, 0)
|
152
|
+
pdf.stroke_color(100, 100, 20, 0)
|
153
|
+
end
|
154
|
+
pdf.stamp('MyStamp')
|
155
|
+
stamps = PDF::Inspector::XObject.analyze(pdf.render)
|
156
|
+
stamp_stream = stamps.xobject_streams[:Stamp1].data
|
157
|
+
expect(stamp_stream).to include("/DeviceCMYK cs\n1.0 1.0 0.2 0.0 scn")
|
158
|
+
expect(stamp_stream).to include("/DeviceCMYK CS\n1.0 1.0 0.2 0.0 SCN")
|
159
|
+
end
|
160
|
+
|
161
|
+
it 'saves the color space even when same as current page color space' do
|
162
|
+
pdf.stroke_color(100, 100, 20, 0)
|
163
|
+
pdf.create_stamp('MyStamp') do
|
164
|
+
pdf.stroke_color(100, 100, 20, 0)
|
165
|
+
end
|
166
|
+
pdf.stamp('MyStamp')
|
167
|
+
stamps = PDF::Inspector::XObject.analyze(pdf.render)
|
168
|
+
stamp_stream = stamps.xobject_streams[:Stamp1].data
|
169
|
+
expect(stamp_stream).to include("/DeviceCMYK CS\n1.0 1.0 0.2 0.0 SCN")
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
@@ -0,0 +1,1112 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
describe Prawn::Text::Box do
|
6
|
+
let(:pdf) { create_pdf }
|
7
|
+
|
8
|
+
it 'is able to set leading document-wide' do
|
9
|
+
pdf.default_leading(7)
|
10
|
+
pdf.default_leading = 7
|
11
|
+
text_box = described_class.new('hello world', document: pdf)
|
12
|
+
expect(text_box.leading).to eq(7)
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'option should be able to override document-wide leading' do
|
16
|
+
pdf.default_leading = 7
|
17
|
+
text_box = described_class.new(
|
18
|
+
'hello world',
|
19
|
+
document: pdf,
|
20
|
+
leading: 20
|
21
|
+
)
|
22
|
+
expect(text_box.leading).to eq(20)
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'is able to set text direction document-wide' do
|
26
|
+
pdf.text_direction(:rtl)
|
27
|
+
pdf.text_direction = :rtl
|
28
|
+
string = "Hello world, how are you?\nI'm fine, thank you."
|
29
|
+
text_box = described_class.new(string, document: pdf)
|
30
|
+
text_box.render
|
31
|
+
text = PDF::Inspector::Text.analyze(pdf.render)
|
32
|
+
expect(text.strings[0]).to eq('?uoy era woh ,dlrow olleH')
|
33
|
+
expect(text.strings[1]).to eq(".uoy knaht ,enif m'I")
|
34
|
+
end
|
35
|
+
|
36
|
+
it 'is able to reverse multi-byte text' do
|
37
|
+
pdf.text_direction(:rtl)
|
38
|
+
pdf.text_direction = :rtl
|
39
|
+
pdf.text_direction = :rtl
|
40
|
+
pdf.font("#{Prawn::DATADIR}/fonts/gkai00mp.ttf", size: 16) do
|
41
|
+
pdf.text '写个小'
|
42
|
+
end
|
43
|
+
text = PDF::Inspector::Text.analyze(pdf.render)
|
44
|
+
expect(text.strings[0]).to eq('小个写')
|
45
|
+
end
|
46
|
+
|
47
|
+
it 'option should be able to override document-wide text direction' do
|
48
|
+
pdf.text_direction = :rtl
|
49
|
+
string = "Hello world, how are you?\nI'm fine, thank you."
|
50
|
+
text_box = described_class.new(
|
51
|
+
string,
|
52
|
+
document: pdf,
|
53
|
+
direction: :ltr
|
54
|
+
)
|
55
|
+
text_box.render
|
56
|
+
text = PDF::Inspector::Text.analyze(pdf.render)
|
57
|
+
expect(text.strings[0]).to eq('Hello world, how are you?')
|
58
|
+
expect(text.strings[1]).to eq("I'm fine, thank you.")
|
59
|
+
end
|
60
|
+
|
61
|
+
it 'only requires enough space for the descender and the ascender '\
|
62
|
+
'when determining whether a line can fit' do
|
63
|
+
text = 'Oh hai text rect'
|
64
|
+
options = {
|
65
|
+
document: pdf,
|
66
|
+
height: pdf.font.ascender + pdf.font.descender
|
67
|
+
}
|
68
|
+
text_box = described_class.new(text, options)
|
69
|
+
text_box.render
|
70
|
+
expect(text_box.text).to eq('Oh hai text rect')
|
71
|
+
|
72
|
+
text = "Oh hai text rect\nOh hai text rect"
|
73
|
+
options = {
|
74
|
+
document: pdf,
|
75
|
+
height: pdf.font.height + pdf.font.ascender + pdf.font.descender
|
76
|
+
}
|
77
|
+
text_box = described_class.new(text, options)
|
78
|
+
text_box.render
|
79
|
+
expect(text_box.text).to eq("Oh hai text rect\nOh hai text rect")
|
80
|
+
end
|
81
|
+
|
82
|
+
describe '#nothing_printed?' do
|
83
|
+
it 'returns true when nothing printed' do
|
84
|
+
string = "Hello world, how are you?\nI'm fine, thank you."
|
85
|
+
text_box = described_class.new(string, height: 2, document: pdf)
|
86
|
+
text_box.render
|
87
|
+
expect(text_box.nothing_printed?).to eq true
|
88
|
+
end
|
89
|
+
|
90
|
+
it 'returns false when something printed' do
|
91
|
+
string = "Hello world, how are you?\nI'm fine, thank you."
|
92
|
+
text_box = described_class.new(string, height: 14, document: pdf)
|
93
|
+
text_box.render
|
94
|
+
expect(text_box.nothing_printed?).to eq false
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
describe '#everything_printed?' do
|
99
|
+
it 'returns false when not everything printed' do
|
100
|
+
string = "Hello world, how are you?\nI'm fine, thank you."
|
101
|
+
text_box = described_class.new(string, height: 14, document: pdf)
|
102
|
+
text_box.render
|
103
|
+
expect(text_box.everything_printed?).to eq false
|
104
|
+
end
|
105
|
+
|
106
|
+
it 'returns true when everything printed' do
|
107
|
+
string = "Hello world, how are you?\nI'm fine, thank you."
|
108
|
+
text_box = described_class.new(string, document: pdf)
|
109
|
+
text_box.render
|
110
|
+
expect(text_box.everything_printed?).to eq true
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
describe '#line_gap' do
|
115
|
+
it '==S the line gap of the font when using a single font and font size' do
|
116
|
+
string = "Hello world, how are you?\nI'm fine, thank you."
|
117
|
+
text_box = described_class.new(string, document: pdf)
|
118
|
+
text_box.render
|
119
|
+
expect(text_box.line_gap).to be_within(0.0001).of(pdf.font.line_gap)
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
describe '#render with :align => :justify' do
|
124
|
+
it 'draws the word spacing to the document' do
|
125
|
+
string = 'hello world ' * 20
|
126
|
+
options = { document: pdf, align: :justify }
|
127
|
+
text_box = described_class.new(string, options)
|
128
|
+
text_box.render
|
129
|
+
contents = PDF::Inspector::Text.analyze(pdf.render)
|
130
|
+
expect(contents.word_spacing[0]).to be > 0
|
131
|
+
end
|
132
|
+
|
133
|
+
it 'does not justify the last line of a paragraph' do
|
134
|
+
string = 'hello world '
|
135
|
+
options = { document: pdf, align: :justify }
|
136
|
+
text_box = described_class.new(string, options)
|
137
|
+
text_box.render
|
138
|
+
contents = PDF::Inspector::Text.analyze(pdf.render)
|
139
|
+
expect(contents.word_spacing).to be_empty
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
describe '#height without leading' do
|
144
|
+
it 'is the sum of the height of each line, not including the space below '\
|
145
|
+
'the last line' do
|
146
|
+
text = "Oh hai text rect.\nOh hai text rect."
|
147
|
+
options = { document: pdf }
|
148
|
+
text_box = described_class.new(text, options)
|
149
|
+
text_box.render
|
150
|
+
expect(text_box.height).to be_within(0.001)
|
151
|
+
.of(pdf.font.height * 2 - pdf.font.line_gap)
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
describe '#height with leading' do
|
156
|
+
it 'is the sum of the height of each line plus leading, but not including '\
|
157
|
+
'the space below the last line' do
|
158
|
+
text = "Oh hai text rect.\nOh hai text rect."
|
159
|
+
leading = 12
|
160
|
+
options = { document: pdf, leading: leading }
|
161
|
+
text_box = described_class.new(text, options)
|
162
|
+
text_box.render
|
163
|
+
expect(text_box.height).to be_within(0.001).of(
|
164
|
+
(pdf.font.height + leading) * 2 - pdf.font.line_gap - leading
|
165
|
+
)
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
context 'with :draw_text_callback' do
|
170
|
+
it 'hits the callback whenever text is drawn' do
|
171
|
+
draw_block = instance_spy('Draw block')
|
172
|
+
|
173
|
+
pdf.text_box 'this text is long enough to span two lines',
|
174
|
+
width: 150,
|
175
|
+
draw_text_callback: ->(text, _) { draw_block.kick(text) }
|
176
|
+
|
177
|
+
expect(draw_block).to have_received(:kick)
|
178
|
+
.with('this text is long enough to')
|
179
|
+
expect(draw_block).to have_received(:kick).with('span two lines')
|
180
|
+
end
|
181
|
+
|
182
|
+
it 'hits the callback once per fragment for :inline_format' do
|
183
|
+
draw_block = instance_spy('Draw block')
|
184
|
+
|
185
|
+
pdf.text_box 'this text has <b>fancy</b> formatting',
|
186
|
+
inline_format: true, width: 500,
|
187
|
+
draw_text_callback: ->(text, _) { draw_block.kick(text) }
|
188
|
+
|
189
|
+
expect(draw_block).to have_received(:kick).with('this text has ')
|
190
|
+
expect(draw_block).to have_received(:kick).with('fancy')
|
191
|
+
expect(draw_block).to have_received(:kick).with(' formatting')
|
192
|
+
end
|
193
|
+
|
194
|
+
it 'does not call #draw_text!' do
|
195
|
+
allow(pdf).to receive(:draw_text!)
|
196
|
+
pdf.text_box 'some text', width: 500,
|
197
|
+
draw_text_callback: ->(_, _) {}
|
198
|
+
expect(pdf).to_not have_received(:draw_text!)
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
describe '#valid_options' do
|
203
|
+
it 'returns an array' do
|
204
|
+
text_box = described_class.new('', document: pdf)
|
205
|
+
expect(text_box.valid_options).to be_a_kind_of(Array)
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
describe '#render' do
|
210
|
+
it 'does not fail if height is smaller than 1 line' do
|
211
|
+
text = 'Oh hai text rect. ' * 10
|
212
|
+
options = {
|
213
|
+
height: pdf.font.height * 0.5,
|
214
|
+
document: pdf
|
215
|
+
}
|
216
|
+
text_box = described_class.new(text, options)
|
217
|
+
text_box.render
|
218
|
+
expect(text_box.text).to eq('')
|
219
|
+
end
|
220
|
+
|
221
|
+
it 'draws content to the page' do
|
222
|
+
text = 'Oh hai text rect. ' * 10
|
223
|
+
options = { document: pdf }
|
224
|
+
text_box = described_class.new(text, options)
|
225
|
+
text_box.render
|
226
|
+
text = PDF::Inspector::Text.analyze(pdf.render)
|
227
|
+
expect(text.strings).to_not be_empty
|
228
|
+
end
|
229
|
+
|
230
|
+
it 'does not draw a transformation matrix' do
|
231
|
+
text = 'Oh hai text rect. ' * 10
|
232
|
+
options = { document: pdf }
|
233
|
+
text_box = described_class.new(text, options)
|
234
|
+
text_box.render
|
235
|
+
matrices = PDF::Inspector::Graphics::Matrix.analyze(pdf.render)
|
236
|
+
expect(matrices.matrices.length).to eq(0)
|
237
|
+
end
|
238
|
+
end
|
239
|
+
|
240
|
+
describe '#render(:single_line => true)' do
|
241
|
+
it 'draws only one line to the page' do
|
242
|
+
text = 'Oh hai text rect. ' * 10
|
243
|
+
options = {
|
244
|
+
document: pdf,
|
245
|
+
single_line: true
|
246
|
+
}
|
247
|
+
text_box = described_class.new(text, options)
|
248
|
+
text_box.render
|
249
|
+
text = PDF::Inspector::Text.analyze(pdf.render)
|
250
|
+
expect(text.strings.length).to eq(1)
|
251
|
+
end
|
252
|
+
end
|
253
|
+
|
254
|
+
describe '#render(:dry_run => true)' do
|
255
|
+
it 'does not draw any content to the page' do
|
256
|
+
text = 'Oh hai text rect. ' * 10
|
257
|
+
options = { document: pdf }
|
258
|
+
text_box = described_class.new(text, options)
|
259
|
+
text_box.render(dry_run: true)
|
260
|
+
text = PDF::Inspector::Text.analyze(pdf.render)
|
261
|
+
expect(text.strings).to be_empty
|
262
|
+
end
|
263
|
+
|
264
|
+
it 'subsequent calls to render do not raise an ArgumentError exception' do
|
265
|
+
text = '™©'
|
266
|
+
options = { document: pdf }
|
267
|
+
text_box = described_class.new(text, options)
|
268
|
+
text_box.render(dry_run: true)
|
269
|
+
|
270
|
+
expect do
|
271
|
+
text_box.render
|
272
|
+
end.to_not raise_exception
|
273
|
+
end
|
274
|
+
end
|
275
|
+
|
276
|
+
describe '#render(:valign => :bottom)' do
|
277
|
+
it '#at should be the same from one dry run to the next' do
|
278
|
+
text = 'this is center text ' * 12
|
279
|
+
options = {
|
280
|
+
width: 162,
|
281
|
+
valign: :bottom,
|
282
|
+
document: pdf
|
283
|
+
}
|
284
|
+
text_box = described_class.new(text, options)
|
285
|
+
|
286
|
+
text_box.render(dry_run: true)
|
287
|
+
original_at = text_box.at.dup
|
288
|
+
|
289
|
+
text_box.render(dry_run: true)
|
290
|
+
expect(text_box.at).to eq(original_at)
|
291
|
+
end
|
292
|
+
end
|
293
|
+
|
294
|
+
describe '#render(:valign => :center)' do
|
295
|
+
it '#at should be the same from one dry run to the next' do
|
296
|
+
text = 'this is center text ' * 12
|
297
|
+
options = {
|
298
|
+
width: 162,
|
299
|
+
valign: :center,
|
300
|
+
document: pdf
|
301
|
+
}
|
302
|
+
text_box = described_class.new(text, options)
|
303
|
+
|
304
|
+
text_box.render(dry_run: true)
|
305
|
+
original_at = text_box.at.dup
|
306
|
+
|
307
|
+
text_box.render(dry_run: true)
|
308
|
+
expect(text_box.at).to eq(original_at)
|
309
|
+
end
|
310
|
+
end
|
311
|
+
|
312
|
+
describe '#render with :rotate option of 30)' do
|
313
|
+
let(:angle) { 30 }
|
314
|
+
let(:x) { 300 }
|
315
|
+
let(:y) { 70 }
|
316
|
+
let(:width) { 100 }
|
317
|
+
let(:height) { 50 }
|
318
|
+
let(:cos) { Math.cos(angle * Math::PI / 180) }
|
319
|
+
let(:sin) { Math.sin(angle * Math::PI / 180) }
|
320
|
+
let(:text) { 'Oh hai text rect. ' * 10 }
|
321
|
+
let(:options) do
|
322
|
+
{
|
323
|
+
document: pdf,
|
324
|
+
rotate: angle,
|
325
|
+
at: [x, y],
|
326
|
+
width: width,
|
327
|
+
height: height
|
328
|
+
}
|
329
|
+
end
|
330
|
+
|
331
|
+
context 'with :rotate_around option of :center' do
|
332
|
+
it 'draws content to the page rotated about the center of the text' do
|
333
|
+
options[:rotate_around] = :center
|
334
|
+
text_box = described_class.new(text, options)
|
335
|
+
text_box.render
|
336
|
+
|
337
|
+
matrices = PDF::Inspector::Graphics::Matrix.analyze(pdf.render)
|
338
|
+
x_ = x + width / 2
|
339
|
+
y_ = y - height / 2
|
340
|
+
x_prime = x_ * cos - y_ * sin
|
341
|
+
y_prime = x_ * sin + y_ * cos
|
342
|
+
expect(matrices.matrices[0]).to eq([
|
343
|
+
1, 0, 0, 1,
|
344
|
+
reduce_precision(x_ - x_prime),
|
345
|
+
reduce_precision(y_ - y_prime)
|
346
|
+
])
|
347
|
+
expect(matrices.matrices[1]).to eq([
|
348
|
+
reduce_precision(cos),
|
349
|
+
reduce_precision(sin),
|
350
|
+
reduce_precision(-sin),
|
351
|
+
reduce_precision(cos),
|
352
|
+
0, 0
|
353
|
+
])
|
354
|
+
|
355
|
+
text = PDF::Inspector::Text.analyze(pdf.render)
|
356
|
+
expect(text.strings).to_not be_empty
|
357
|
+
end
|
358
|
+
end
|
359
|
+
|
360
|
+
context 'with :rotate_around option of :upper_left' do
|
361
|
+
it 'draws content to the page rotated about the upper left corner of '\
|
362
|
+
'the text' do
|
363
|
+
options[:rotate_around] = :upper_left
|
364
|
+
text_box = described_class.new(text, options)
|
365
|
+
text_box.render
|
366
|
+
|
367
|
+
matrices = PDF::Inspector::Graphics::Matrix.analyze(pdf.render)
|
368
|
+
x_prime = x * cos - y * sin
|
369
|
+
y_prime = x * sin + y * cos
|
370
|
+
expect(matrices.matrices[0]).to eq([
|
371
|
+
1, 0, 0, 1,
|
372
|
+
reduce_precision(x - x_prime),
|
373
|
+
reduce_precision(y - y_prime)
|
374
|
+
])
|
375
|
+
expect(matrices.matrices[1]).to eq([
|
376
|
+
reduce_precision(cos),
|
377
|
+
reduce_precision(sin),
|
378
|
+
reduce_precision(-sin),
|
379
|
+
reduce_precision(cos),
|
380
|
+
0, 0
|
381
|
+
])
|
382
|
+
|
383
|
+
text = PDF::Inspector::Text.analyze(pdf.render)
|
384
|
+
expect(text.strings).to_not be_empty
|
385
|
+
end
|
386
|
+
end
|
387
|
+
|
388
|
+
context 'with default :rotate_around' do
|
389
|
+
it 'draws content to the page rotated about the upper left corner of '\
|
390
|
+
'the text' do
|
391
|
+
text_box = described_class.new(text, options)
|
392
|
+
text_box.render
|
393
|
+
|
394
|
+
matrices = PDF::Inspector::Graphics::Matrix.analyze(pdf.render)
|
395
|
+
x_prime = x * cos - y * sin
|
396
|
+
y_prime = x * sin + y * cos
|
397
|
+
expect(matrices.matrices[0]).to eq([
|
398
|
+
1, 0, 0, 1,
|
399
|
+
reduce_precision(x - x_prime),
|
400
|
+
reduce_precision(y - y_prime)
|
401
|
+
])
|
402
|
+
expect(matrices.matrices[1]).to eq([
|
403
|
+
reduce_precision(cos),
|
404
|
+
reduce_precision(sin),
|
405
|
+
reduce_precision(-sin),
|
406
|
+
reduce_precision(cos),
|
407
|
+
0, 0
|
408
|
+
])
|
409
|
+
|
410
|
+
text = PDF::Inspector::Text.analyze(pdf.render)
|
411
|
+
expect(text.strings).to_not be_empty
|
412
|
+
end
|
413
|
+
end
|
414
|
+
|
415
|
+
context 'with :rotate_around option of :upper_right' do
|
416
|
+
it 'draws content to the page rotated about the upper right corner of '\
|
417
|
+
'the text' do
|
418
|
+
options[:rotate_around] = :upper_right
|
419
|
+
text_box = described_class.new(text, options)
|
420
|
+
text_box.render
|
421
|
+
|
422
|
+
matrices = PDF::Inspector::Graphics::Matrix.analyze(pdf.render)
|
423
|
+
x_ = x + width
|
424
|
+
y_ = y
|
425
|
+
x_prime = x_ * cos - y_ * sin
|
426
|
+
y_prime = x_ * sin + y_ * cos
|
427
|
+
expect(matrices.matrices[0]).to eq([
|
428
|
+
1, 0, 0, 1,
|
429
|
+
reduce_precision(x_ - x_prime),
|
430
|
+
reduce_precision(y_ - y_prime)
|
431
|
+
])
|
432
|
+
expect(matrices.matrices[1]).to eq([
|
433
|
+
reduce_precision(cos),
|
434
|
+
reduce_precision(sin),
|
435
|
+
reduce_precision(-sin),
|
436
|
+
reduce_precision(cos),
|
437
|
+
0, 0
|
438
|
+
])
|
439
|
+
|
440
|
+
text = PDF::Inspector::Text.analyze(pdf.render)
|
441
|
+
expect(text.strings).to_not be_empty
|
442
|
+
end
|
443
|
+
end
|
444
|
+
|
445
|
+
context 'with :rotate_around option of :lower_right' do
|
446
|
+
it 'draws content to the page rotated about the lower right corner of '\
|
447
|
+
'the text' do
|
448
|
+
options[:rotate_around] = :lower_right
|
449
|
+
text_box = described_class.new(text, options)
|
450
|
+
text_box.render
|
451
|
+
|
452
|
+
matrices = PDF::Inspector::Graphics::Matrix.analyze(pdf.render)
|
453
|
+
x_ = x + width
|
454
|
+
y_ = y - height
|
455
|
+
x_prime = x_ * cos - y_ * sin
|
456
|
+
y_prime = x_ * sin + y_ * cos
|
457
|
+
expect(matrices.matrices[0]).to eq([
|
458
|
+
1, 0, 0, 1,
|
459
|
+
reduce_precision(x_ - x_prime),
|
460
|
+
reduce_precision(y_ - y_prime)
|
461
|
+
])
|
462
|
+
expect(matrices.matrices[1]).to eq([
|
463
|
+
reduce_precision(cos),
|
464
|
+
reduce_precision(sin),
|
465
|
+
reduce_precision(-sin),
|
466
|
+
reduce_precision(cos),
|
467
|
+
0, 0
|
468
|
+
])
|
469
|
+
|
470
|
+
text = PDF::Inspector::Text.analyze(pdf.render)
|
471
|
+
expect(text.strings).to_not be_empty
|
472
|
+
end
|
473
|
+
end
|
474
|
+
|
475
|
+
context 'with :rotate_around option of :lower_left' do
|
476
|
+
it 'draws content to the page rotated about the lower left corner of '\
|
477
|
+
'the text' do
|
478
|
+
options[:rotate_around] = :lower_left
|
479
|
+
text_box = described_class.new(text, options)
|
480
|
+
text_box.render
|
481
|
+
|
482
|
+
matrices = PDF::Inspector::Graphics::Matrix.analyze(pdf.render)
|
483
|
+
x_ = x
|
484
|
+
y_ = y - height
|
485
|
+
x_prime = x_ * cos - y_ * sin
|
486
|
+
y_prime = x_ * sin + y_ * cos
|
487
|
+
expect(matrices.matrices[0]).to eq([
|
488
|
+
1, 0, 0, 1,
|
489
|
+
reduce_precision(x_ - x_prime),
|
490
|
+
reduce_precision(y_ - y_prime)
|
491
|
+
])
|
492
|
+
expect(matrices.matrices[1]).to eq([
|
493
|
+
reduce_precision(cos),
|
494
|
+
reduce_precision(sin),
|
495
|
+
reduce_precision(-sin),
|
496
|
+
reduce_precision(cos),
|
497
|
+
0, 0
|
498
|
+
])
|
499
|
+
|
500
|
+
text = PDF::Inspector::Text.analyze(pdf.render)
|
501
|
+
expect(text.strings).to_not be_empty
|
502
|
+
end
|
503
|
+
end
|
504
|
+
end
|
505
|
+
|
506
|
+
describe 'default height' do
|
507
|
+
it 'is the height from the bottom bound to document.y' do
|
508
|
+
target_height = pdf.y - pdf.bounds.bottom
|
509
|
+
text = "Oh hai\n" * 60
|
510
|
+
text_box = described_class.new(text, document: pdf)
|
511
|
+
text_box.render
|
512
|
+
expect(text_box.height).to be_within(pdf.font.height).of(target_height)
|
513
|
+
end
|
514
|
+
|
515
|
+
it 'uses the margin-box bottom if only in a stretchy bbox' do
|
516
|
+
pdf.bounding_box([0, pdf.cursor], width: pdf.bounds.width) do
|
517
|
+
target_height = pdf.y - pdf.bounds.bottom
|
518
|
+
text = "Oh hai\n" * 60
|
519
|
+
text_box = described_class.new(text, document: pdf)
|
520
|
+
text_box.render
|
521
|
+
expect(text_box.height).to be_within(pdf.font.height).of(target_height)
|
522
|
+
end
|
523
|
+
end
|
524
|
+
|
525
|
+
it 'uses the parent-box bottom if in a stretchy bbox and overflow is '\
|
526
|
+
':expand, even with an explicit height' do
|
527
|
+
pdf.bounding_box([0, pdf.cursor], width: pdf.bounds.width) do
|
528
|
+
target_height = pdf.y - pdf.bounds.bottom
|
529
|
+
text = "Oh hai\n" * 60
|
530
|
+
text_box = described_class.new(
|
531
|
+
text,
|
532
|
+
document: pdf,
|
533
|
+
height: 100,
|
534
|
+
overflow: :expand
|
535
|
+
)
|
536
|
+
text_box.render
|
537
|
+
expect(text_box.height).to be_within(pdf.font.height).of(target_height)
|
538
|
+
end
|
539
|
+
end
|
540
|
+
|
541
|
+
it 'uses the innermost non-stretchy bbox, not the margin box' do
|
542
|
+
pdf.bounding_box(
|
543
|
+
[0, pdf.cursor],
|
544
|
+
width: pdf.bounds.width,
|
545
|
+
height: 200
|
546
|
+
) do
|
547
|
+
pdf.bounding_box([0, pdf.cursor], width: pdf.bounds.width) do
|
548
|
+
text = "Oh hai\n" * 60
|
549
|
+
text_box = described_class.new(text, document: pdf)
|
550
|
+
text_box.render
|
551
|
+
expect(text_box.height).to be_within(pdf.font.height).of(200)
|
552
|
+
end
|
553
|
+
end
|
554
|
+
end
|
555
|
+
end
|
556
|
+
|
557
|
+
describe 'default at' do
|
558
|
+
it 'is the left corner of the bounds, and the current document.y' do
|
559
|
+
target_at = [pdf.bounds.left, pdf.y]
|
560
|
+
text = 'Oh hai text rect. ' * 100
|
561
|
+
options = { document: pdf }
|
562
|
+
text_box = described_class.new(text, options)
|
563
|
+
text_box.render
|
564
|
+
expect(text_box.at).to eq(target_at)
|
565
|
+
end
|
566
|
+
end
|
567
|
+
|
568
|
+
context 'with text than can fit in the box' do
|
569
|
+
let(:text) { 'Oh hai text rect. ' * 10 }
|
570
|
+
let(:options) do
|
571
|
+
{
|
572
|
+
width: 162.0,
|
573
|
+
height: 162.0,
|
574
|
+
document: pdf
|
575
|
+
}
|
576
|
+
end
|
577
|
+
|
578
|
+
it 'printed text should match requested text, except that preceding and ' \
|
579
|
+
'trailing white space will be stripped from each line, and newlines ' \
|
580
|
+
'may be inserted' do
|
581
|
+
text_box = described_class.new(' ' + text, options)
|
582
|
+
text_box.render
|
583
|
+
expect(text_box.text.tr("\n", ' ')).to eq(text.strip)
|
584
|
+
end
|
585
|
+
|
586
|
+
it 'render returns an empty string because no text remains unprinted' do
|
587
|
+
text_box = described_class.new(text, options)
|
588
|
+
expect(text_box.render).to eq('')
|
589
|
+
end
|
590
|
+
|
591
|
+
it 'is truncated when the leading is set high enough to prevent all the '\
|
592
|
+
'lines from being printed' do
|
593
|
+
options[:leading] = 40
|
594
|
+
text_box = described_class.new(text, options)
|
595
|
+
text_box.render
|
596
|
+
expect(text_box.text.tr("\n", ' ')).to_not eq(text.strip)
|
597
|
+
end
|
598
|
+
end
|
599
|
+
|
600
|
+
context 'with text that fits exactly in the box' do
|
601
|
+
let(:lines) { 3 }
|
602
|
+
let(:interlines) { lines - 1 }
|
603
|
+
let(:text) { (1..lines).to_a.join("\n") }
|
604
|
+
let(:options) do
|
605
|
+
{
|
606
|
+
width: 162.0,
|
607
|
+
height: pdf.font.ascender + pdf.font.height * interlines +
|
608
|
+
pdf.font.descender,
|
609
|
+
document: pdf
|
610
|
+
}
|
611
|
+
end
|
612
|
+
|
613
|
+
it 'has the expected height' do
|
614
|
+
expected_height = options.delete(:height)
|
615
|
+
text_box = described_class.new(text, options)
|
616
|
+
text_box.render
|
617
|
+
expect(text_box.height).to be_within(0.0001).of(expected_height)
|
618
|
+
end
|
619
|
+
|
620
|
+
it 'prints everything' do
|
621
|
+
text_box = described_class.new(text, options)
|
622
|
+
text_box.render
|
623
|
+
expect(text_box.text).to eq(text)
|
624
|
+
end
|
625
|
+
|
626
|
+
describe 'with leading' do
|
627
|
+
before do
|
628
|
+
options[:leading] = 15
|
629
|
+
end
|
630
|
+
|
631
|
+
it 'does not overflow when enough height is added' do
|
632
|
+
options[:height] += options[:leading] * interlines
|
633
|
+
text_box = described_class.new(text, options)
|
634
|
+
text_box.render
|
635
|
+
expect(text_box.text).to eq(text)
|
636
|
+
end
|
637
|
+
|
638
|
+
it 'overflows when insufficient height is added' do
|
639
|
+
options[:height] += options[:leading] * interlines - 1
|
640
|
+
text_box = described_class.new(text, options)
|
641
|
+
text_box.render
|
642
|
+
expect(text_box.text).to_not eq(text)
|
643
|
+
end
|
644
|
+
end
|
645
|
+
|
646
|
+
context 'with negative leading' do
|
647
|
+
before do
|
648
|
+
options[:leading] = -4
|
649
|
+
end
|
650
|
+
|
651
|
+
it 'does not overflow when enough height is removed' do
|
652
|
+
options[:height] += options[:leading] * interlines
|
653
|
+
text_box = described_class.new(text, options)
|
654
|
+
text_box.render
|
655
|
+
expect(text_box.text).to eq(text)
|
656
|
+
end
|
657
|
+
|
658
|
+
it 'overflows when too much height is removed' do
|
659
|
+
options[:height] += options[:leading] * interlines - 1
|
660
|
+
text_box = described_class.new(text, options)
|
661
|
+
text_box.render
|
662
|
+
expect(text_box.text).to_not eq(text)
|
663
|
+
end
|
664
|
+
end
|
665
|
+
end
|
666
|
+
|
667
|
+
context 'when printing UTF-8 string with higher bit characters' do
|
668
|
+
let(:text) { '©' }
|
669
|
+
|
670
|
+
let(:text_box) do
|
671
|
+
# not enough height to print any text, so we can directly compare against
|
672
|
+
# the input string
|
673
|
+
bounding_height = 1.0
|
674
|
+
options = {
|
675
|
+
height: bounding_height,
|
676
|
+
document: pdf
|
677
|
+
}
|
678
|
+
described_class.new(text, options)
|
679
|
+
end
|
680
|
+
|
681
|
+
before do
|
682
|
+
file = "#{Prawn::DATADIR}/fonts/Panic+Sans.dfont"
|
683
|
+
pdf.font_families['Panic Sans'] = {
|
684
|
+
normal: { file: file, font: 'PanicSans' },
|
685
|
+
italic: { file: file, font: 'PanicSans-Italic' },
|
686
|
+
bold: { file: file, font: 'PanicSans-Bold' },
|
687
|
+
bold_italic: { file: file, font: 'PanicSans-BoldItalic' }
|
688
|
+
}
|
689
|
+
end
|
690
|
+
|
691
|
+
describe 'when using a TTF font' do
|
692
|
+
it 'unprinted text should be in UTF-8 encoding' do
|
693
|
+
pdf.font('Panic Sans')
|
694
|
+
remaining_text = text_box.render
|
695
|
+
expect(remaining_text).to eq(text)
|
696
|
+
end
|
697
|
+
end
|
698
|
+
|
699
|
+
describe 'when using an AFM font' do
|
700
|
+
it 'unprinted text should be in UTF-8 encoding' do
|
701
|
+
remaining_text = text_box.render
|
702
|
+
expect(remaining_text).to eq(text)
|
703
|
+
end
|
704
|
+
end
|
705
|
+
end
|
706
|
+
|
707
|
+
context 'with more text than can fit in the box' do
|
708
|
+
let(:text) { 'Oh hai text rect. ' * 30 }
|
709
|
+
let(:bounding_height) { 162.0 }
|
710
|
+
let(:options) do
|
711
|
+
{
|
712
|
+
width: 162.0,
|
713
|
+
height: bounding_height,
|
714
|
+
document: pdf
|
715
|
+
}
|
716
|
+
end
|
717
|
+
|
718
|
+
context 'when truncated overflow' do
|
719
|
+
let(:text_box) do
|
720
|
+
described_class.new(text, options.merge(overflow: :truncate))
|
721
|
+
end
|
722
|
+
|
723
|
+
it 'is truncated' do
|
724
|
+
text_box.render
|
725
|
+
expect(text_box.text.tr("\n", ' ')).to_not eq(text.strip)
|
726
|
+
end
|
727
|
+
|
728
|
+
it 'render does not return an empty string because some text remains '\
|
729
|
+
'unprinted' do
|
730
|
+
expect(text_box.render).to_not be_empty
|
731
|
+
end
|
732
|
+
|
733
|
+
it '#height should be no taller than the specified height' do
|
734
|
+
text_box.render
|
735
|
+
expect(text_box.height).to be <= bounding_height
|
736
|
+
end
|
737
|
+
|
738
|
+
it '#height should be within one font height of the specified height' do
|
739
|
+
text_box.render
|
740
|
+
expect(bounding_height).to be_within(pdf.font.height)
|
741
|
+
.of(text_box.height)
|
742
|
+
end
|
743
|
+
|
744
|
+
context 'with :rotate option' do
|
745
|
+
it 'unrendered text should be the same as when not rotated' do
|
746
|
+
remaining_text = text_box.render
|
747
|
+
|
748
|
+
rotate = 30
|
749
|
+
x = 300
|
750
|
+
y = 70
|
751
|
+
options[:document] = pdf
|
752
|
+
options[:rotate] = rotate
|
753
|
+
options[:at] = [x, y]
|
754
|
+
rotated_text_box = described_class.new(text, options)
|
755
|
+
expect(rotated_text_box.render).to eq(remaining_text)
|
756
|
+
end
|
757
|
+
end
|
758
|
+
end
|
759
|
+
|
760
|
+
context 'when truncated with text and size taken from the manual' do
|
761
|
+
it 'returns the right text' do
|
762
|
+
text = 'This is the beginning of the text. It will be cut somewhere ' \
|
763
|
+
'and the rest of the text will procede to be rendered this time by '\
|
764
|
+
'calling another method.' + ' . ' * 50
|
765
|
+
options[:width] = 300
|
766
|
+
options[:height] = 50
|
767
|
+
options[:size] = 18
|
768
|
+
text_box = described_class.new(text, options)
|
769
|
+
remaining_text = text_box.render
|
770
|
+
expect(remaining_text).to eq(
|
771
|
+
'text will procede to be rendered this time by calling another ' \
|
772
|
+
'method. . . . . . . . . . . . . . . . . . . . ' \
|
773
|
+
'. . . . . . . . . . . . . . . . . . . . . . ' \
|
774
|
+
'. . . . . . . . . '
|
775
|
+
)
|
776
|
+
end
|
777
|
+
end
|
778
|
+
|
779
|
+
context 'when expand overflow' do
|
780
|
+
let(:text_box) do
|
781
|
+
described_class.new(text, options.merge(overflow: :expand))
|
782
|
+
end
|
783
|
+
|
784
|
+
it 'height expands to encompass all the text '\
|
785
|
+
'(but not exceed the height of the page)' do
|
786
|
+
text_box.render
|
787
|
+
expect(text_box.height).to be > bounding_height
|
788
|
+
end
|
789
|
+
|
790
|
+
it 'displays the entire string (as long as there was space remaining on '\
|
791
|
+
'the page to print all the text)' do
|
792
|
+
text_box.render
|
793
|
+
expect(text_box.text.tr("\n", ' ')).to eq(text.strip)
|
794
|
+
end
|
795
|
+
|
796
|
+
it 'render returns an empty string because no text remains unprinted '\
|
797
|
+
'(as long as there was space remaining on the page to print all '\
|
798
|
+
'the text)' do
|
799
|
+
expect(text_box.render).to eq('')
|
800
|
+
end
|
801
|
+
end
|
802
|
+
|
803
|
+
context 'when shrink_to_fit overflow' do
|
804
|
+
let(:text_box) do
|
805
|
+
described_class.new(
|
806
|
+
text,
|
807
|
+
options.merge(
|
808
|
+
overflow: :shrink_to_fit,
|
809
|
+
min_font_size: 2
|
810
|
+
)
|
811
|
+
)
|
812
|
+
end
|
813
|
+
|
814
|
+
it 'displays the entire text' do
|
815
|
+
text_box.render
|
816
|
+
expect(text_box.text.tr("\n", ' ')).to eq(text.strip)
|
817
|
+
end
|
818
|
+
|
819
|
+
it 'render returns an empty string because no text remains unprinted' do
|
820
|
+
expect(text_box.render).to eq('')
|
821
|
+
end
|
822
|
+
|
823
|
+
it 'does not drop below the minimum font size' do
|
824
|
+
options[:overflow] = :shrink_to_fit
|
825
|
+
options[:min_font_size] = 10.1
|
826
|
+
text_box = described_class.new(text, options)
|
827
|
+
text_box.render
|
828
|
+
|
829
|
+
actual_text = PDF::Inspector::Text.analyze(pdf.render)
|
830
|
+
expect(actual_text.font_settings[0][:size]).to eq(10.1)
|
831
|
+
end
|
832
|
+
end
|
833
|
+
end
|
834
|
+
|
835
|
+
context 'with enough space to fit the text but using the ' \
|
836
|
+
'shrink_to_fit overflow' do
|
837
|
+
it 'does not shrink the text when there is no need to' do
|
838
|
+
bounding_height = 162.0
|
839
|
+
options = {
|
840
|
+
width: 162.0,
|
841
|
+
height: bounding_height,
|
842
|
+
overflow: :shrink_to_fit,
|
843
|
+
min_font_size: 5,
|
844
|
+
document: pdf
|
845
|
+
}
|
846
|
+
text_box = described_class.new("hello\nworld", options)
|
847
|
+
text_box.render
|
848
|
+
|
849
|
+
text = PDF::Inspector::Text.analyze(pdf.render)
|
850
|
+
expect(text.font_settings[0][:size]).to eq(12)
|
851
|
+
end
|
852
|
+
end
|
853
|
+
|
854
|
+
context 'with a solid block of Chinese characters' do
|
855
|
+
it 'printed text should match requested text, except for newlines' do
|
856
|
+
text = '写中国字' * 10
|
857
|
+
options = {
|
858
|
+
width: 162.0,
|
859
|
+
height: 162.0,
|
860
|
+
document: pdf,
|
861
|
+
overflow: :truncate
|
862
|
+
}
|
863
|
+
pdf.font "#{Prawn::DATADIR}/fonts/gkai00mp.ttf"
|
864
|
+
text_box = described_class.new(text, options)
|
865
|
+
text_box.render
|
866
|
+
expect(text_box.text.delete("\n")).to eq(text)
|
867
|
+
end
|
868
|
+
end
|
869
|
+
|
870
|
+
describe 'drawing bounding boxes' do
|
871
|
+
it 'restores the margin box when bounding box exits' do
|
872
|
+
margin_box = pdf.bounds
|
873
|
+
|
874
|
+
pdf.text_box 'Oh hai text box. ' * 11, height: pdf.font.height * 10
|
875
|
+
|
876
|
+
expect(pdf.bounds).to eq(margin_box)
|
877
|
+
end
|
878
|
+
end
|
879
|
+
|
880
|
+
describe '#render with :character_spacing option' do
|
881
|
+
it 'draws the character spacing to the document' do
|
882
|
+
string = 'hello world'
|
883
|
+
options = { document: pdf, character_spacing: 10 }
|
884
|
+
text_box = described_class.new(string, options)
|
885
|
+
text_box.render
|
886
|
+
contents = PDF::Inspector::Text.analyze(pdf.render)
|
887
|
+
expect(contents.character_spacing[0]).to eq(10)
|
888
|
+
end
|
889
|
+
|
890
|
+
it 'takes character spacing into account when wrapping' do
|
891
|
+
pdf.font 'Courier'
|
892
|
+
text_box = described_class.new(
|
893
|
+
'hello world',
|
894
|
+
width: 100,
|
895
|
+
overflow: :expand,
|
896
|
+
character_spacing: 10,
|
897
|
+
document: pdf
|
898
|
+
)
|
899
|
+
text_box.render
|
900
|
+
expect(text_box.text).to eq("hello\nworld")
|
901
|
+
end
|
902
|
+
end
|
903
|
+
|
904
|
+
describe 'wrapping' do
|
905
|
+
it 'wraps text' do
|
906
|
+
text = 'Please wrap this text about HERE. ' \
|
907
|
+
'More text that should be wrapped'
|
908
|
+
expect = "Please wrap this text about\n"\
|
909
|
+
"HERE. More text that should be\nwrapped"
|
910
|
+
|
911
|
+
pdf.font 'Courier'
|
912
|
+
text_box = described_class.new(
|
913
|
+
text,
|
914
|
+
width: 220,
|
915
|
+
overflow: :expand,
|
916
|
+
document: pdf
|
917
|
+
)
|
918
|
+
text_box.render
|
919
|
+
expect(text_box.text).to eq(expect)
|
920
|
+
end
|
921
|
+
|
922
|
+
# white space was being stripped after the entire line was generated,
|
923
|
+
# meaning that leading white space characters reduced the amount of space on
|
924
|
+
# the line for other characters, so wrapping "hello hello" resulted in
|
925
|
+
# "hello\n\nhello", rather than "hello\nhello"
|
926
|
+
#
|
927
|
+
it 'white space at beginning of line should not be taken into account ' \
|
928
|
+
'when computing line width' do
|
929
|
+
text = 'hello hello'
|
930
|
+
expect = "hello\nhello"
|
931
|
+
|
932
|
+
pdf.font 'Courier'
|
933
|
+
text_box = described_class.new(
|
934
|
+
text,
|
935
|
+
width: 40,
|
936
|
+
overflow: :expand,
|
937
|
+
document: pdf
|
938
|
+
)
|
939
|
+
text_box.render
|
940
|
+
expect(text_box.text).to eq(expect)
|
941
|
+
end
|
942
|
+
|
943
|
+
it 'respects end of line when wrapping text' do
|
944
|
+
text = "Please wrap only before\nTHIS word. Don't wrap this"
|
945
|
+
expect = text
|
946
|
+
|
947
|
+
pdf.font 'Courier'
|
948
|
+
text_box = described_class.new(
|
949
|
+
text,
|
950
|
+
width: 220,
|
951
|
+
overflow: :expand,
|
952
|
+
document: pdf
|
953
|
+
)
|
954
|
+
text_box.render
|
955
|
+
expect(text_box.text).to eq(expect)
|
956
|
+
end
|
957
|
+
|
958
|
+
it 'respects multiple newlines when wrapping text' do
|
959
|
+
text = "Please wrap only before THIS\n\nword. Don't wrap this"
|
960
|
+
expect = "Please wrap only before\nTHIS\n\nword. Don't wrap this"
|
961
|
+
|
962
|
+
pdf.font 'Courier'
|
963
|
+
text_box = described_class.new(
|
964
|
+
text,
|
965
|
+
width: 200,
|
966
|
+
overflow: :expand,
|
967
|
+
document: pdf
|
968
|
+
)
|
969
|
+
text_box.render
|
970
|
+
expect(text_box.text).to eq(expect)
|
971
|
+
end
|
972
|
+
|
973
|
+
it 'respects multiple newlines when wrapping text when those newlines '\
|
974
|
+
'coincide with a line break' do
|
975
|
+
text = "Please wrap only before\n\nTHIS word. Don't wrap this"
|
976
|
+
expect = text
|
977
|
+
|
978
|
+
pdf.font 'Courier'
|
979
|
+
text_box = described_class.new(
|
980
|
+
text,
|
981
|
+
width: 220,
|
982
|
+
overflow: :expand,
|
983
|
+
document: pdf
|
984
|
+
)
|
985
|
+
text_box.render
|
986
|
+
expect(text_box.text).to eq(expect)
|
987
|
+
end
|
988
|
+
|
989
|
+
it 'respects initial newlines' do
|
990
|
+
text = "\nThis should be on line 2"
|
991
|
+
expect = text
|
992
|
+
|
993
|
+
pdf.font 'Courier'
|
994
|
+
text_box = described_class.new(
|
995
|
+
text,
|
996
|
+
width: 220,
|
997
|
+
overflow: :expand,
|
998
|
+
document: pdf
|
999
|
+
)
|
1000
|
+
text_box.render
|
1001
|
+
expect(text_box.text).to eq(expect)
|
1002
|
+
end
|
1003
|
+
|
1004
|
+
it 'wraps lines comprised of a single word of the bounds when '\
|
1005
|
+
'wrapping text' do
|
1006
|
+
text = 'You_can_wrap_this_text_HERE'
|
1007
|
+
expect = "You_can_wrap_this_text_HE\nRE"
|
1008
|
+
|
1009
|
+
pdf.font 'Courier'
|
1010
|
+
text_box = described_class.new(
|
1011
|
+
text,
|
1012
|
+
width: 180,
|
1013
|
+
overflow: :expand,
|
1014
|
+
document: pdf
|
1015
|
+
)
|
1016
|
+
text_box.render
|
1017
|
+
expect(text_box.text).to eq(expect)
|
1018
|
+
end
|
1019
|
+
|
1020
|
+
it 'wraps lines comprised of a single non-alpha word of the bounds when '\
|
1021
|
+
'wrapping text' do
|
1022
|
+
text = '©' * 30
|
1023
|
+
|
1024
|
+
pdf.font 'Courier'
|
1025
|
+
text_box = described_class.new(
|
1026
|
+
text, width: 180,
|
1027
|
+
overflow: :expand,
|
1028
|
+
document: pdf
|
1029
|
+
)
|
1030
|
+
|
1031
|
+
text_box.render
|
1032
|
+
|
1033
|
+
expected = +'©' * 25 + "\n" + '©' * 5
|
1034
|
+
expected = pdf.font.normalize_encoding(expected)
|
1035
|
+
expected = expected.force_encoding(Encoding::UTF_8)
|
1036
|
+
expect(text_box.text).to eq(expected)
|
1037
|
+
end
|
1038
|
+
|
1039
|
+
it 'wraps non-unicode strings using single-byte word-wrapping' do
|
1040
|
+
text = 'continúa esforzandote ' * 5
|
1041
|
+
text_box = described_class.new(
|
1042
|
+
text, width: 180,
|
1043
|
+
document: pdf
|
1044
|
+
)
|
1045
|
+
text_box.render
|
1046
|
+
results_with_accent = text_box.text
|
1047
|
+
|
1048
|
+
text = 'continua esforzandote ' * 5
|
1049
|
+
text_box = described_class.new(
|
1050
|
+
text, width: 180,
|
1051
|
+
document: pdf
|
1052
|
+
)
|
1053
|
+
text_box.render
|
1054
|
+
results_without_accent = text_box.text
|
1055
|
+
|
1056
|
+
expect(first_line(results_with_accent).length)
|
1057
|
+
.to eq(first_line(results_without_accent).length)
|
1058
|
+
end
|
1059
|
+
|
1060
|
+
it 'allows you to disable wrapping by char' do
|
1061
|
+
text = 'You_cannot_wrap_this_text_at_all_because_we_are_disabling_' \
|
1062
|
+
'wrapping_by_char_and_there_are_no_word_breaks'
|
1063
|
+
|
1064
|
+
pdf.font 'Courier'
|
1065
|
+
text_box = described_class.new(
|
1066
|
+
text,
|
1067
|
+
width: 180,
|
1068
|
+
overflow: :shrink_to_fit,
|
1069
|
+
disable_wrap_by_char: true,
|
1070
|
+
document: pdf
|
1071
|
+
)
|
1072
|
+
expect { text_box.render }.to raise_error(Prawn::Errors::CannotFit)
|
1073
|
+
end
|
1074
|
+
|
1075
|
+
it 'retains full words with :shrink_to_fit if char wrapping is disabled' do
|
1076
|
+
text = 'Wrapped_words'
|
1077
|
+
expect = 'Wrapped_words'
|
1078
|
+
|
1079
|
+
pdf.font 'Courier'
|
1080
|
+
text_box = described_class.new(
|
1081
|
+
text,
|
1082
|
+
width: 50,
|
1083
|
+
height: 50,
|
1084
|
+
size: 50,
|
1085
|
+
overflow: :shrink_to_fit,
|
1086
|
+
disable_wrap_by_char: true,
|
1087
|
+
document: pdf
|
1088
|
+
)
|
1089
|
+
text_box.render
|
1090
|
+
expect(text_box.text).to eq(expect)
|
1091
|
+
end
|
1092
|
+
end
|
1093
|
+
|
1094
|
+
describe 'Text::Box#render with :mode option' do
|
1095
|
+
it 'alters the text rendering mode of the document' do
|
1096
|
+
string = 'hello world'
|
1097
|
+
options = { document: pdf, mode: :fill_stroke }
|
1098
|
+
text_box = described_class.new(string, options)
|
1099
|
+
text_box.render
|
1100
|
+
contents = PDF::Inspector::Text.analyze(pdf.render)
|
1101
|
+
expect(contents.text_rendering_mode).to eq([2, 0])
|
1102
|
+
end
|
1103
|
+
end
|
1104
|
+
|
1105
|
+
def reduce_precision(float)
|
1106
|
+
float.round(5)
|
1107
|
+
end
|
1108
|
+
|
1109
|
+
def first_line(str)
|
1110
|
+
str.each_line { |line| return line }
|
1111
|
+
end
|
1112
|
+
end
|