asciidoctor-pdf 1.6.1 → 2.0.0.alpha.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.yardopts +1 -1
- data/CHANGELOG.adoc +273 -31
- data/NOTICE.adoc +16 -4
- data/README.adoc +208 -68
- data/asciidoctor-pdf.gemspec +3 -7
- data/data/fonts/ABOUT-mplus1mn-subset +1 -1
- data/data/fonts/ABOUT-mplus1p-subset +2 -2
- data/data/fonts/ABOUT-notosans-subset +26 -0
- data/data/fonts/ABOUT-notoserif-subset +1 -1
- data/data/fonts/{LICENSE-notoserif → LICENSE-noto} +0 -0
- data/data/fonts/mplus1mn-bold-subset.ttf +0 -0
- data/data/fonts/mplus1mn-bold_italic-subset.ttf +0 -0
- data/data/fonts/mplus1mn-italic-subset.ttf +0 -0
- data/data/fonts/mplus1mn-regular-subset.ttf +0 -0
- data/data/fonts/mplus1p-regular-fallback.ttf +0 -0
- data/data/fonts/notoemoji-subset.ttf +0 -0
- data/data/fonts/notosans-bold-subset.ttf +0 -0
- data/data/fonts/notosans-bold_italic-subset.ttf +0 -0
- data/data/fonts/notosans-italic-subset.ttf +0 -0
- data/data/fonts/notosans-regular-subset.ttf +0 -0
- data/data/fonts/notoserif-bold-subset.ttf +0 -0
- data/data/fonts/notoserif-bold_italic-subset.ttf +0 -0
- data/data/fonts/notoserif-italic-subset.ttf +0 -0
- data/data/fonts/notoserif-regular-subset.ttf +0 -0
- data/data/themes/base-theme.yml +21 -24
- data/data/themes/default-for-print-theme.yml +24 -0
- data/data/themes/default-for-print-with-fallback-font-theme.yml +3 -0
- data/data/themes/default-theme.yml +55 -59
- data/data/themes/default-with-fallback-font-theme.yml +2 -2
- data/data/themes/sans-with-fallback-font-theme.yml +10 -0
- data/docs/theming-guide.adoc +977 -352
- data/lib/asciidoctor/pdf/converter.rb +1853 -1566
- data/lib/asciidoctor/pdf/ext/asciidoctor/document.rb +22 -1
- data/lib/asciidoctor/pdf/ext/asciidoctor/image.rb +9 -15
- data/lib/asciidoctor/pdf/ext/asciidoctor/list.rb +6 -13
- data/lib/asciidoctor/pdf/ext/asciidoctor/section.rb +3 -16
- data/lib/asciidoctor/pdf/ext/asciidoctor.rb +1 -5
- data/lib/asciidoctor/pdf/ext/core/file.rb +1 -1
- data/lib/asciidoctor/pdf/ext/core/quantifiable_stdout.rb +1 -4
- data/lib/asciidoctor/pdf/ext/core/string.rb +2 -2
- data/lib/asciidoctor/pdf/ext/core.rb +1 -4
- data/lib/asciidoctor/pdf/ext/pdf-core/page.rb +8 -33
- data/lib/asciidoctor/pdf/ext/pdf-core.rb +0 -16
- data/lib/asciidoctor/pdf/ext/prawn/coderay_encoder.rb +5 -7
- data/lib/asciidoctor/pdf/ext/prawn/extensions.rb +489 -331
- data/lib/asciidoctor/pdf/ext/prawn/font/afm.rb +0 -4
- data/lib/asciidoctor/pdf/ext/prawn/font_metric_cache.rb +1 -1
- data/lib/asciidoctor/pdf/ext/prawn/formatted_text/arranger.rb +33 -3
- data/lib/asciidoctor/pdf/ext/prawn/formatted_text/box.rb +25 -14
- data/lib/asciidoctor/pdf/ext/prawn/formatted_text/fragment.rb +9 -3
- data/lib/asciidoctor/pdf/ext/prawn/formatted_text/protect_bottom_gutter.rb +13 -0
- data/lib/asciidoctor/pdf/ext/prawn/images.rb +20 -18
- data/lib/asciidoctor/pdf/ext/prawn-svg/loaders/data.rb +6 -0
- data/lib/asciidoctor/pdf/ext/prawn-svg/loaders/web.rb +22 -0
- data/lib/asciidoctor/pdf/ext/prawn-svg/url_loader.rb +13 -0
- data/lib/asciidoctor/pdf/ext/prawn-svg.rb +5 -2
- data/lib/asciidoctor/pdf/ext/prawn-table/cell/asciidoc.rb +76 -20
- data/lib/asciidoctor/pdf/ext/prawn-table/cell/text.rb +39 -1
- data/lib/asciidoctor/pdf/ext/prawn-table/cell.rb +21 -15
- data/lib/asciidoctor/pdf/ext/prawn-table.rb +1 -1
- data/lib/asciidoctor/pdf/ext/prawn.rb +1 -0
- data/lib/asciidoctor/pdf/ext/pygments.rb +2 -2
- data/lib/asciidoctor/pdf/ext/rouge/formatters/prawn.rb +17 -20
- data/lib/asciidoctor/pdf/ext/rouge/themes/asciidoctor_pdf_default.rb +1 -0
- data/lib/asciidoctor/pdf/ext/rouge.rb +0 -1
- data/lib/asciidoctor/pdf/formatted_text/formatter.rb +2 -2
- data/lib/asciidoctor/pdf/formatted_text/inline_destination_marker.rb +8 -10
- data/lib/asciidoctor/pdf/formatted_text/inline_image_arranger.rb +69 -78
- data/lib/asciidoctor/pdf/formatted_text/inline_image_renderer.rb +7 -10
- data/lib/asciidoctor/pdf/formatted_text/inline_text_aligner.rb +2 -4
- data/lib/asciidoctor/pdf/formatted_text/parser.rb +53 -47
- data/lib/asciidoctor/pdf/formatted_text/parser.treetop +5 -7
- data/lib/asciidoctor/pdf/formatted_text/source_wrap.rb +14 -14
- data/lib/asciidoctor/pdf/formatted_text/text_background_and_border_renderer.rb +4 -7
- data/lib/asciidoctor/pdf/formatted_text/transform.rb +122 -110
- data/lib/asciidoctor/pdf/formatted_text.rb +0 -1
- data/lib/asciidoctor/pdf/index_catalog.rb +7 -11
- data/lib/asciidoctor/pdf/nogmagick.rb +6 -0
- data/lib/asciidoctor/pdf/optimizer.rb +3 -5
- data/lib/asciidoctor/pdf/pdfmark.rb +16 -8
- data/lib/asciidoctor/pdf/roman_numeral.rb +4 -22
- data/lib/asciidoctor/pdf/sanitizer.rb +18 -13
- data/lib/asciidoctor/pdf/section_info_by_page.rb +24 -0
- data/lib/asciidoctor/pdf/theme_loader.rb +100 -80
- data/lib/asciidoctor/pdf/version.rb +1 -2
- data/lib/asciidoctor/pdf.rb +5 -2
- metadata +36 -64
- data/data/fonts/mplus1mn-bold-ascii.ttf +0 -0
- data/data/fonts/mplus1mn-bold_italic-ascii.ttf +0 -0
- data/data/fonts/mplus1mn-italic-ascii.ttf +0 -0
- data/data/fonts/mplus1mn-regular-ascii-conums.ttf +0 -0
- data/lib/asciidoctor/pdf/ext/asciidoctor/abstract_block.rb +0 -7
- data/lib/asciidoctor/pdf/ext/asciidoctor/abstract_node.rb +0 -7
- data/lib/asciidoctor/pdf/ext/asciidoctor/list_item.rb +0 -18
- data/lib/asciidoctor/pdf/ext/asciidoctor/logging_shim.rb +0 -33
- data/lib/asciidoctor/pdf/ext/core/array.rb +0 -11
- data/lib/asciidoctor/pdf/ext/core/hash.rb +0 -7
- data/lib/asciidoctor/pdf/ext/core/regexp.rb +0 -5
- data/lib/asciidoctor/pdf/ext/pdf-core/pdf_object.rb +0 -8
- data/lib/asciidoctor-pdf/converter.rb +0 -3
- data/lib/asciidoctor-pdf/version.rb +0 -3
@@ -4,7 +4,7 @@ Prawn::Font::AFM.instance_variable_set :@hide_m17n_warning, true
|
|
4
4
|
|
5
5
|
require 'prawn/icon'
|
6
6
|
|
7
|
-
Prawn::Icon::Compatibility.send :prepend, (::Module.new { def warning *
|
7
|
+
Prawn::Icon::Compatibility.send :prepend, (::Module.new { def warning *_args; end })
|
8
8
|
|
9
9
|
module Asciidoctor
|
10
10
|
module Prawn
|
@@ -22,7 +22,7 @@ module Asciidoctor
|
|
22
22
|
italic: [:italic].to_set,
|
23
23
|
bold_italic: [:bold, :italic].to_set,
|
24
24
|
}).default = ::Set.new
|
25
|
-
# NOTE must use a visible char for placeholder or else Prawn won't reserve space for the fragment
|
25
|
+
# NOTE: must use a visible char for placeholder or else Prawn won't reserve space for the fragment
|
26
26
|
PlaceholderChar = ?\u2063
|
27
27
|
|
28
28
|
# - :height is the height of a line
|
@@ -32,6 +32,91 @@ module Asciidoctor
|
|
32
32
|
# - :final_gap determines whether a gap is added below the last line
|
33
33
|
LineMetrics = ::Struct.new :height, :leading, :padding_top, :padding_bottom, :final_gap
|
34
34
|
|
35
|
+
Position = ::Struct.new :page, :cursor
|
36
|
+
|
37
|
+
Extent = ::Struct.new :current, :from, :to do
|
38
|
+
def initialize current_page, current_cursor, start_page, start_cursor, end_page, end_cursor
|
39
|
+
self.from = self.current = Position.new current_page, current_cursor
|
40
|
+
self.from = Position.new start_page, start_cursor unless start_page == current_page && start_cursor == current_cursor
|
41
|
+
self.to = Position.new end_page, end_cursor
|
42
|
+
end
|
43
|
+
|
44
|
+
def each_page
|
45
|
+
from.page.upto to.page do |pgnum|
|
46
|
+
yield pgnum == from.page && from, pgnum == to.page && to, pgnum
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def single_page?
|
51
|
+
from.page == to.page
|
52
|
+
end
|
53
|
+
|
54
|
+
def single_page_height
|
55
|
+
single_page? ? from.cursor - to.cursor : nil
|
56
|
+
end
|
57
|
+
|
58
|
+
def page_range
|
59
|
+
(from.page..to.page)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
ScratchExtent = ::Struct.new :from, :to do
|
64
|
+
def initialize start_page, start_cursor, end_page, end_cursor
|
65
|
+
self.from = Position.new start_page, start_cursor
|
66
|
+
self.to = Position.new end_page, end_cursor
|
67
|
+
end
|
68
|
+
|
69
|
+
def position_onto pdf, keep_together = nil
|
70
|
+
current_page = pdf.page_number
|
71
|
+
current_cursor = pdf.cursor
|
72
|
+
from_page = current_page + (advance_by = from.page - 1)
|
73
|
+
to_page = current_page + (to.page - 1)
|
74
|
+
if advance_by > 0
|
75
|
+
advance_by.times { pdf.advance_page }
|
76
|
+
elsif keep_together && single_page? && !(try_to_fit_on_previous current_cursor)
|
77
|
+
pdf.advance_page
|
78
|
+
from_page += 1
|
79
|
+
to_page += 1
|
80
|
+
end
|
81
|
+
Extent.new current_page, current_cursor, from_page, from.cursor, to_page, to.cursor
|
82
|
+
end
|
83
|
+
|
84
|
+
def single_page?
|
85
|
+
from.page == to.page
|
86
|
+
end
|
87
|
+
|
88
|
+
def single_page_height
|
89
|
+
single_page? ? from.cursor - to.cursor : nil
|
90
|
+
end
|
91
|
+
|
92
|
+
def try_to_fit_on_previous reference_cursor
|
93
|
+
if (height = from.cursor - to.cursor) <= reference_cursor
|
94
|
+
from.cursor = reference_cursor
|
95
|
+
to.cursor = reference_cursor - height
|
96
|
+
true
|
97
|
+
else
|
98
|
+
false
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
NewPageRequiredError = ::Class.new ::StopIteration
|
104
|
+
|
105
|
+
InhibitNewPageProc = proc do |pdf|
|
106
|
+
pdf.delete_page
|
107
|
+
raise NewPageRequiredError
|
108
|
+
end
|
109
|
+
|
110
|
+
DetectEmptyFirstPage = ::Module.new
|
111
|
+
|
112
|
+
DetectEmptyFirstPageProc = proc do |delegate, pdf|
|
113
|
+
if pdf.state.pages[pdf.page_number - 2].empty?
|
114
|
+
pdf.delete_page
|
115
|
+
raise NewPageRequiredError
|
116
|
+
end
|
117
|
+
delegate.call pdf if (pdf.state.on_page_create_callback = delegate)
|
118
|
+
end
|
119
|
+
|
35
120
|
# Core
|
36
121
|
|
37
122
|
# Retrieves the catalog reference data for the PDF.
|
@@ -54,14 +139,6 @@ module Asciidoctor
|
|
54
139
|
page.dimensions[2]
|
55
140
|
end
|
56
141
|
|
57
|
-
# Returns the effective (writable) width of the page
|
58
|
-
#
|
59
|
-
# If inside a bounding box, returns width of box.
|
60
|
-
#
|
61
|
-
def effective_page_width
|
62
|
-
reference_bounds.width
|
63
|
-
end
|
64
|
-
|
65
142
|
# Returns the height of the current page from edge-to-edge
|
66
143
|
#
|
67
144
|
def page_height
|
@@ -70,13 +147,13 @@ module Asciidoctor
|
|
70
147
|
|
71
148
|
# Returns the effective (writable) height of the page
|
72
149
|
#
|
73
|
-
# If inside a fixed-height bounding box, returns
|
150
|
+
# If inside a fixed-height bounding box, returns height of box.
|
74
151
|
#
|
75
152
|
def effective_page_height
|
76
153
|
reference_bounds.height
|
77
154
|
end
|
78
155
|
|
79
|
-
#
|
156
|
+
# remove once fixed upstream; see https://github.com/prawnpdf/prawn/pull/1122
|
80
157
|
def generate_margin_box
|
81
158
|
page_w, page_h = (page = state.page).dimensions.slice 2, 2
|
82
159
|
page_m = page.margins
|
@@ -108,7 +185,7 @@ module Asciidoctor
|
|
108
185
|
# Returns the margins for the current page as a 4 element array (top, right, bottom, left)
|
109
186
|
#
|
110
187
|
def page_margin
|
111
|
-
[
|
188
|
+
[page_margin_top, page_margin_right, page_margin_bottom, page_margin_left]
|
112
189
|
end
|
113
190
|
|
114
191
|
# Returns the width of the left margin for the current page
|
@@ -116,16 +193,12 @@ module Asciidoctor
|
|
116
193
|
def page_margin_left
|
117
194
|
page.margins[:left]
|
118
195
|
end
|
119
|
-
# deprecated
|
120
|
-
alias left_margin page_margin_left
|
121
196
|
|
122
197
|
# Returns the width of the right margin for the current page
|
123
198
|
#
|
124
199
|
def page_margin_right
|
125
200
|
page.margins[:right]
|
126
201
|
end
|
127
|
-
# deprecated
|
128
|
-
alias right_margin page_margin_right
|
129
202
|
|
130
203
|
# Returns the width of the top margin for the current page
|
131
204
|
#
|
@@ -157,7 +230,7 @@ module Asciidoctor
|
|
157
230
|
if invert
|
158
231
|
(recto_page? pgnum) ? :verso : :recto
|
159
232
|
else
|
160
|
-
(
|
233
|
+
(verso_page? pgnum) ? :verso : :recto
|
161
234
|
end
|
162
235
|
end
|
163
236
|
|
@@ -243,16 +316,10 @@ module Asciidoctor
|
|
243
316
|
{ family: font.options[:family], style: (font.options[:style] || :normal), size: @font_size }
|
244
317
|
end
|
245
318
|
|
246
|
-
#
|
247
|
-
# yields. If the style is nil and no block is given, return the current
|
248
|
-
# font style.
|
319
|
+
# Set the font style on the document, if a style is given, otherwise return the current font style.
|
249
320
|
#
|
250
321
|
def font_style style = nil
|
251
|
-
if
|
252
|
-
font font.options[:family], style: style do
|
253
|
-
yield
|
254
|
-
end
|
255
|
-
elsif style
|
322
|
+
if style
|
256
323
|
font font.options[:family], style: style
|
257
324
|
else
|
258
325
|
font.options[:style] || :normal
|
@@ -264,12 +331,10 @@ module Asciidoctor
|
|
264
331
|
# implementation to carry out the built-in functionality.
|
265
332
|
#
|
266
333
|
#--
|
267
|
-
# QUESTION should we round the result?
|
334
|
+
# QUESTION: should we round the result?
|
268
335
|
def font_size points = nil
|
269
336
|
return @font_size unless points
|
270
|
-
if
|
271
|
-
super @font_size
|
272
|
-
elsif String === points
|
337
|
+
if ::String === points
|
273
338
|
if points.end_with? 'rem'
|
274
339
|
super @root_font_size * points.to_f
|
275
340
|
elsif points.end_with? 'em'
|
@@ -279,8 +344,8 @@ module Asciidoctor
|
|
279
344
|
else
|
280
345
|
super points.to_f
|
281
346
|
end
|
282
|
-
#
|
283
|
-
elsif points
|
347
|
+
# NOTE: assume em value (since a font size of 1 is extremely unlikely)
|
348
|
+
elsif points <= 1
|
284
349
|
super @font_size * points
|
285
350
|
else
|
286
351
|
super points
|
@@ -304,37 +369,9 @@ module Asciidoctor
|
|
304
369
|
FontStyleToSet[style].dup
|
305
370
|
end
|
306
371
|
|
307
|
-
# Apply the font settings (family, size, styles and character spacing) from
|
308
|
-
# the fragment to the document, then yield to the block.
|
309
|
-
#
|
310
|
-
# The original font settings are restored before this method returns.
|
311
|
-
#
|
312
|
-
def fragment_font fragment
|
313
|
-
f_info = font_info
|
314
|
-
f_family = fragment[:font] || f_info[:family]
|
315
|
-
f_size = fragment[:size] || f_info[:size]
|
316
|
-
if (f_styles = fragment[:styles])
|
317
|
-
f_style = resolve_font_style f_styles
|
318
|
-
else
|
319
|
-
f_style = :normal
|
320
|
-
end
|
321
|
-
|
322
|
-
if (c_spacing = fragment[:character_spacing])
|
323
|
-
character_spacing c_spacing do
|
324
|
-
font f_family, size: f_size, style: f_style do
|
325
|
-
yield
|
326
|
-
end
|
327
|
-
end
|
328
|
-
else
|
329
|
-
font f_family, size: f_size, style: f_style do
|
330
|
-
yield
|
331
|
-
end
|
332
|
-
end
|
333
|
-
end
|
334
|
-
|
335
372
|
# Override width of string to check for placeholder char, which uses character spacing to control width
|
336
373
|
#
|
337
|
-
def width_of_string string, options
|
374
|
+
def width_of_string string, options
|
338
375
|
string == PlaceholderChar ? @character_spacing : super
|
339
376
|
end
|
340
377
|
|
@@ -346,7 +383,7 @@ module Asciidoctor
|
|
346
383
|
::Prawn::Icon::Compatibility::SHIMS[%(fa-#{name})]
|
347
384
|
end
|
348
385
|
|
349
|
-
def calc_line_metrics line_height
|
386
|
+
def calc_line_metrics line_height, font = self.font, font_size = self.font_size
|
350
387
|
line_height_length = line_height * font_size
|
351
388
|
leading = line_height_length - font_size
|
352
389
|
half_leading = leading / 2
|
@@ -355,20 +392,6 @@ module Asciidoctor
|
|
355
392
|
LineMetrics.new line_height_length, leading, padding_top, padding_bottom, false
|
356
393
|
end
|
357
394
|
|
358
|
-
=begin
|
359
|
-
# these line metrics attempted to figure out a correction based on the reported height and the font_size
|
360
|
-
# however, it only works for some fonts, and breaks down for fonts like Noto Serif
|
361
|
-
def calc_line_metrics line_height = 1, font = self.font, font_size = self.font_size
|
362
|
-
line_height_length = font_size * line_height
|
363
|
-
line_gap = line_height_length - font_size
|
364
|
-
correction = font.height - font_size
|
365
|
-
leading = line_gap - correction
|
366
|
-
shift = (font.line_gap + correction + line_gap) / 2
|
367
|
-
final_gap = font.line_gap != 0
|
368
|
-
LineMetrics.new line_height_length, leading, shift, shift, final_gap
|
369
|
-
end
|
370
|
-
=end
|
371
|
-
|
372
395
|
# Parse the text into an array of fragments using the text formatter.
|
373
396
|
def parse_text string, options = {}
|
374
397
|
return [] if string.nil?
|
@@ -383,22 +406,22 @@ module Asciidoctor
|
|
383
406
|
|
384
407
|
if (color = options.delete :color)
|
385
408
|
fragments.map do |fragment|
|
386
|
-
fragment[:color] ? fragment : fragment.merge
|
409
|
+
fragment[:color] ? fragment : (fragment.merge color: color)
|
387
410
|
end
|
388
411
|
else
|
389
412
|
fragments
|
390
413
|
end
|
391
414
|
end
|
392
415
|
|
393
|
-
# NOTE override built-in fill_formatted_text_box to insert leading before second line when :first_line is true
|
394
|
-
def fill_formatted_text_box text,
|
395
|
-
merge_text_box_positioning_options
|
396
|
-
box = ::Prawn::Text::Formatted::Box.new text,
|
416
|
+
# NOTE: override built-in fill_formatted_text_box to insert leading before second line when :first_line is true
|
417
|
+
def fill_formatted_text_box text, options
|
418
|
+
merge_text_box_positioning_options options
|
419
|
+
box = ::Prawn::Text::Formatted::Box.new text, options
|
397
420
|
remaining_text = box.render
|
398
421
|
@no_text_printed = box.nothing_printed?
|
399
422
|
@all_text_printed = box.everything_printed?
|
400
423
|
|
401
|
-
if @final_gap || (
|
424
|
+
if ((defined? @final_gap) && @final_gap) || (options[:first_line] && !(@no_text_printed || @all_text_printed))
|
402
425
|
self.y -= box.height + box.line_gap + box.leading
|
403
426
|
else
|
404
427
|
self.y -= box.height
|
@@ -407,51 +430,52 @@ module Asciidoctor
|
|
407
430
|
remaining_text
|
408
431
|
end
|
409
432
|
|
410
|
-
# NOTE override built-in draw_indented_formatted_line to set first_line flag
|
411
|
-
def draw_indented_formatted_line string,
|
412
|
-
super string, (
|
433
|
+
# NOTE: override built-in draw_indented_formatted_line to set first_line flag
|
434
|
+
def draw_indented_formatted_line string, options
|
435
|
+
super string, (options.merge first_line: true)
|
413
436
|
end
|
414
437
|
|
415
|
-
# Performs the same work as Prawn::Text.text except that the
|
438
|
+
# Performs the same work as Prawn::Text.text except that the first_line_options are applied to the first line of text
|
416
439
|
# renderered. It's necessary to use low-level APIs in this method so we only style the first line and not the
|
417
440
|
# remaining lines (which is the default behavior in Prawn).
|
418
|
-
def text_with_formatted_first_line string,
|
419
|
-
color =
|
420
|
-
fragments = parse_text string,
|
421
|
-
# NOTE the low-level APIs we're using don't recognize the :styles option, so we must resolve
|
422
|
-
|
423
|
-
|
424
|
-
|
425
|
-
|
426
|
-
|
427
|
-
|
428
|
-
|
429
|
-
|
430
|
-
|
431
|
-
|
432
|
-
|
433
|
-
|
434
|
-
|
441
|
+
def text_with_formatted_first_line string, first_line_options, options
|
442
|
+
color = options.delete :color
|
443
|
+
fragments = parse_text string, options
|
444
|
+
# NOTE: the low-level APIs we're using don't recognize the :styles option, so we must resolve
|
445
|
+
# NOTE: disabled until we have a need for it
|
446
|
+
#if (styles = options.delete :styles)
|
447
|
+
# options[:style] = resolve_font_style styles
|
448
|
+
#end
|
449
|
+
if (first_line_styles = first_line_options.delete :styles)
|
450
|
+
first_line_options[:style] = resolve_font_style first_line_styles
|
451
|
+
end
|
452
|
+
first_line_color = (first_line_options.delete :color) || color
|
453
|
+
options = options.merge document: self
|
454
|
+
# QUESTION: should we merge more carefully here? (hand-select keys?)
|
455
|
+
first_line_options = (options.merge first_line_options).merge single_line: true, first_line: true
|
456
|
+
box = ::Prawn::Text::Formatted::Box.new fragments, first_line_options
|
457
|
+
# NOTE: get remaining_fragments before we add color to fragments on first line
|
458
|
+
if (text_indent = options.delete :indent_paragraphs)
|
435
459
|
remaining_fragments = indent text_indent do
|
436
460
|
box.render dry_run: true
|
437
461
|
end
|
438
462
|
else
|
439
463
|
remaining_fragments = box.render dry_run: true
|
440
464
|
end
|
441
|
-
# NOTE color must be applied per-fragment
|
442
|
-
fragments.each {|fragment| fragment[:color] ||= first_line_color }
|
465
|
+
# NOTE: color must be applied per-fragment
|
466
|
+
fragments.each {|fragment| fragment[:color] ||= first_line_color }
|
443
467
|
if text_indent
|
444
468
|
indent text_indent do
|
445
|
-
fill_formatted_text_box fragments,
|
469
|
+
fill_formatted_text_box fragments, first_line_options
|
446
470
|
end
|
447
471
|
else
|
448
|
-
fill_formatted_text_box fragments,
|
472
|
+
fill_formatted_text_box fragments, first_line_options
|
449
473
|
end
|
450
474
|
unless remaining_fragments.empty?
|
451
|
-
# NOTE color must be applied per-fragment
|
452
|
-
remaining_fragments.each {|fragment| fragment[:color] ||= color }
|
453
|
-
remaining_fragments = fill_formatted_text_box remaining_fragments,
|
454
|
-
draw_remaining_formatted_text_on_new_pages remaining_fragments,
|
475
|
+
# NOTE: color must be applied per-fragment
|
476
|
+
remaining_fragments.each {|fragment| fragment[:color] ||= color }
|
477
|
+
remaining_fragments = fill_formatted_text_box remaining_fragments, options
|
478
|
+
draw_remaining_formatted_text_on_new_pages remaining_fragments, options
|
455
479
|
end
|
456
480
|
end
|
457
481
|
|
@@ -492,7 +516,7 @@ module Asciidoctor
|
|
492
516
|
# Override built-in move_text_position method to prevent Prawn from advancing
|
493
517
|
# to next page if image doesn't fit before rendering image.
|
494
518
|
#--
|
495
|
-
# NOTE could use :at option when calling image/embed_image instead
|
519
|
+
# NOTE: could use :at option when calling image/embed_image instead
|
496
520
|
def move_text_position h; end
|
497
521
|
|
498
522
|
# Short-circuits the call to the built-in move_down operation
|
@@ -504,21 +528,7 @@ module Asciidoctor
|
|
504
528
|
|
505
529
|
# Bounds
|
506
530
|
|
507
|
-
#
|
508
|
-
#
|
509
|
-
# Example:
|
510
|
-
#
|
511
|
-
# pad 20, 10 do
|
512
|
-
# text 'A paragraph with twice as much top padding as bottom padding.'
|
513
|
-
# end
|
514
|
-
#
|
515
|
-
def pad top, bottom = nil
|
516
|
-
move_down top
|
517
|
-
yield
|
518
|
-
move_down(bottom || top)
|
519
|
-
end
|
520
|
-
|
521
|
-
# Combines the built-in pad and indent operations into a single method.
|
531
|
+
# Augments the built-in pad method by adding support for specifying padding on all four sizes.
|
522
532
|
#
|
523
533
|
# Padding may be specified as an array of four values, or as a single value.
|
524
534
|
# The single value is used as the padding around all four sides of the box.
|
@@ -535,55 +545,102 @@ module Asciidoctor
|
|
535
545
|
# text 'An indented paragraph inside a box with equal padding on all sides.'
|
536
546
|
# end
|
537
547
|
#
|
538
|
-
def pad_box padding
|
548
|
+
def pad_box padding, node = nil
|
539
549
|
if padding
|
540
550
|
# TODO: implement shorthand combinations like in CSS
|
541
551
|
p_top, p_right, p_bottom, p_left = ::Array === padding ? padding : (::Array.new 4, padding)
|
552
|
+
# logic is intentionally inlined
|
542
553
|
begin
|
543
|
-
|
554
|
+
if node && ((last_block = node).content_model != :compound || (last_block = node.blocks[-1])&.context == :paragraph)
|
555
|
+
@bottom_gutters << { last_block => p_bottom }
|
556
|
+
else
|
557
|
+
@bottom_gutters << {}
|
558
|
+
end
|
544
559
|
move_down p_top
|
545
560
|
bounds.add_left_padding p_left
|
546
561
|
bounds.add_right_padding p_right
|
547
562
|
yield
|
548
|
-
|
549
|
-
if p_bottom < 0
|
550
|
-
# QUESTION should we return to previous page if top of page is reached?
|
551
|
-
p_bottom < cursor - reference_bounds.top ? (move_cursor_to reference_bounds.top) : (move_down p_bottom)
|
552
|
-
else
|
553
|
-
p_bottom < cursor ? (move_down p_bottom) : reference_bounds.move_past_bottom
|
554
|
-
end
|
563
|
+
cursor > p_bottom ? (move_down p_bottom) : reference_bounds.move_past_bottom unless at_page_top?
|
555
564
|
ensure
|
565
|
+
@bottom_gutters.pop
|
556
566
|
bounds.subtract_left_padding p_left
|
557
567
|
bounds.subtract_right_padding p_right
|
558
568
|
end
|
559
569
|
else
|
560
570
|
yield
|
561
571
|
end
|
562
|
-
|
563
|
-
# alternate, delegated logic
|
564
|
-
#pad padding[0], padding[2] do
|
565
|
-
# indent padding[1], padding[3] do
|
566
|
-
# yield
|
567
|
-
# end
|
568
|
-
#end
|
569
572
|
end
|
570
573
|
|
571
|
-
def
|
574
|
+
def expand_indent_value value
|
572
575
|
(::Array === value ? (value.slice 0, 2) : (::Array.new 2, value)).map(&:to_f)
|
573
576
|
end
|
574
577
|
|
575
|
-
|
576
|
-
|
577
|
-
|
578
|
-
|
579
|
-
|
580
|
-
|
581
|
-
|
582
|
-
|
583
|
-
|
584
|
-
|
578
|
+
def expand_padding_value shorthand
|
579
|
+
unless (padding = (@side_area_shorthand_cache ||= {})[shorthand])
|
580
|
+
if ::Array === shorthand
|
581
|
+
case shorthand.size
|
582
|
+
when 1
|
583
|
+
padding = [shorthand[0], shorthand[0], shorthand[0], shorthand[0]]
|
584
|
+
when 2
|
585
|
+
padding = [shorthand[0], shorthand[1], shorthand[0], shorthand[1]]
|
586
|
+
when 3
|
587
|
+
padding = [shorthand[0], shorthand[1], shorthand[2], shorthand[1]]
|
588
|
+
when 4
|
589
|
+
padding = shorthand
|
590
|
+
else
|
591
|
+
padding = shorthand.slice 0, 4
|
592
|
+
end
|
593
|
+
else
|
594
|
+
padding = ::Array.new 4, (shorthand || 0)
|
595
|
+
end
|
596
|
+
@side_area_shorthand_cache[shorthand] = padding
|
597
|
+
end
|
598
|
+
padding.dup
|
599
|
+
end
|
600
|
+
|
601
|
+
alias expand_margin_value expand_padding_value
|
602
|
+
|
603
|
+
def expand_grid_values shorthand, default = nil
|
604
|
+
if ::Array === shorthand
|
605
|
+
case shorthand.size
|
606
|
+
when 1
|
607
|
+
[(value0 = shorthand[0] || default), value0]
|
608
|
+
when 2
|
609
|
+
shorthand.map {|it| it || default }
|
610
|
+
when 4
|
611
|
+
if Asciidoctor::PDF::ThemeLoader::CMYKColorValue === shorthand
|
612
|
+
[shorthand, shorthand]
|
613
|
+
else
|
614
|
+
(shorthand.slice 0, 2).map {|it| it || default }
|
615
|
+
end
|
616
|
+
else
|
617
|
+
(shorthand.slice 0, 2).map {|it| it || default }
|
618
|
+
end
|
585
619
|
else
|
586
|
-
|
620
|
+
[(value0 = shorthand || default), value0]
|
621
|
+
end
|
622
|
+
end
|
623
|
+
|
624
|
+
def expand_rect_values shorthand, default = nil
|
625
|
+
if ::Array === shorthand
|
626
|
+
case shorthand.size
|
627
|
+
when 1
|
628
|
+
[(value0 = shorthand[0] || default), value0, value0, value0]
|
629
|
+
when 2
|
630
|
+
[(value0 = shorthand[0] || default), (value1 = shorthand[1] || default), value0, value1]
|
631
|
+
when 3
|
632
|
+
[shorthand[0] || default, (value1 = shorthand[1] || default), shorthand[2] || default, value1]
|
633
|
+
when 4
|
634
|
+
if Asciidoctor::PDF::ThemeLoader::CMYKColorValue === shorthand
|
635
|
+
[shorthand, shorthand, shorthand, shorthand]
|
636
|
+
else
|
637
|
+
shorthand.map {|it| it || default }
|
638
|
+
end
|
639
|
+
else
|
640
|
+
(shorthand.slice 0, 4).map {|it| it || default }
|
641
|
+
end
|
642
|
+
else
|
643
|
+
[(value0 = shorthand || default), value0, value0, value0]
|
587
644
|
end
|
588
645
|
end
|
589
646
|
|
@@ -601,16 +658,15 @@ module Asciidoctor
|
|
601
658
|
end
|
602
659
|
end
|
603
660
|
|
604
|
-
# A flowing version of
|
605
|
-
#
|
606
|
-
#
|
661
|
+
# A flowing version of bounding_box. If the content runs to another page, the cursor starts at
|
662
|
+
# the top of the page instead of from the original cursor position. Similar to span, except
|
663
|
+
# the :position option is limited to a numeric value and additional options are passed through
|
664
|
+
# to bounding_box.
|
607
665
|
#
|
608
|
-
def flow_bounding_box
|
609
|
-
original_y = y
|
610
|
-
# QUESTION should preserving original_x be an option?
|
611
|
-
original_x = bounds.absolute_left - margin_box.absolute_left
|
666
|
+
def flow_bounding_box options = {}
|
667
|
+
original_y, original_x = y, bounds.absolute_left
|
612
668
|
canvas do
|
613
|
-
bounding_box [
|
669
|
+
bounding_box [original_x + (options.delete :position).to_f, @margin_box.absolute_top], options do
|
614
670
|
self.y = original_y
|
615
671
|
yield
|
616
672
|
end
|
@@ -622,19 +678,17 @@ module Asciidoctor
|
|
622
678
|
# Fills the current bounding box with the specified fill color. Before
|
623
679
|
# returning from this method, the original fill color on the document is
|
624
680
|
# restored.
|
625
|
-
def fill_bounds f_color
|
626
|
-
|
627
|
-
|
628
|
-
|
629
|
-
|
630
|
-
fill_color prev_fill_color
|
631
|
-
end
|
681
|
+
def fill_bounds f_color
|
682
|
+
prev_fill_color = fill_color
|
683
|
+
fill_color f_color
|
684
|
+
fill_rectangle bounds.top_left, bounds.width, bounds.height
|
685
|
+
fill_color prev_fill_color
|
632
686
|
end
|
633
687
|
|
634
688
|
# Fills the absolute bounding box with the specified fill color. Before
|
635
689
|
# returning from this method, the original fill color on the document is
|
636
690
|
# restored.
|
637
|
-
def fill_absolute_bounds f_color
|
691
|
+
def fill_absolute_bounds f_color
|
638
692
|
canvas { fill_bounds f_color }
|
639
693
|
end
|
640
694
|
|
@@ -645,53 +699,58 @@ module Asciidoctor
|
|
645
699
|
#
|
646
700
|
def fill_and_stroke_bounds f_color = fill_color, s_color = stroke_color, options = {}
|
647
701
|
no_fill = !f_color || f_color == 'transparent'
|
648
|
-
|
702
|
+
if ::Array === (s_width = options[:line_width] || 0)
|
703
|
+
s_width_max = s_width.map(&:to_i).max
|
704
|
+
radius = 0
|
705
|
+
else
|
706
|
+
radius = options[:radius] || 0
|
707
|
+
end
|
708
|
+
no_stroke = !s_color || s_color == 'transparent' || (s_width_max || s_width) == 0
|
649
709
|
return if no_fill && no_stroke
|
650
710
|
save_graphics_state do
|
651
|
-
radius = options[:radius] || 0
|
652
|
-
|
653
711
|
# fill
|
654
712
|
unless no_fill
|
655
713
|
fill_color f_color
|
656
714
|
fill_rounded_rectangle bounds.top_left, bounds.width, bounds.height, radius
|
657
715
|
end
|
658
716
|
|
717
|
+
next if no_stroke
|
718
|
+
|
659
719
|
# stroke
|
660
|
-
|
720
|
+
if s_width_max
|
721
|
+
if (s_width_end = s_width[0] || 0) > 0
|
722
|
+
stroke_horizontal_rule s_color, line_width: s_width_end, line_style: options[:line_style]
|
723
|
+
stroke_horizontal_rule s_color, line_width: s_width_end, line_style: options[:line_style], at: bounds.height
|
724
|
+
end
|
725
|
+
if (s_width_side = s_width[1] || 0) > 0
|
726
|
+
stroke_vertical_rule s_color, line_width: s_width_side, line_style: options[:line_style]
|
727
|
+
stroke_vertical_rule s_color, line_width: s_width_side, line_style: options[:line_style], at: bounds.width
|
728
|
+
end
|
729
|
+
else
|
661
730
|
stroke_color s_color
|
662
|
-
|
663
|
-
|
664
|
-
|
665
|
-
|
666
|
-
|
731
|
+
case options[:line_style]
|
732
|
+
when :dashed
|
733
|
+
line_width s_width
|
734
|
+
dash s_width * 4
|
735
|
+
when :dotted
|
736
|
+
line_width s_width
|
737
|
+
dash s_width
|
738
|
+
when :double
|
739
|
+
single_line_width = s_width / 3.0
|
740
|
+
line_width single_line_width
|
741
|
+
inner_line_offset = single_line_width * 2
|
742
|
+
inner_top_left = [bounds.left + inner_line_offset, bounds.top - inner_line_offset]
|
743
|
+
stroke_rounded_rectangle bounds.top_left, bounds.width, bounds.height, radius
|
744
|
+
stroke_rounded_rectangle inner_top_left, bounds.width - (inner_line_offset * 2), bounds.height - (inner_line_offset * 2), radius
|
745
|
+
next
|
746
|
+
else # :solid
|
747
|
+
line_width s_width
|
748
|
+
end
|
667
749
|
stroke_rounded_rectangle bounds.top_left, bounds.width, bounds.height, radius
|
668
|
-
#undash if options.has_key? :dash_width
|
669
750
|
end
|
670
751
|
end
|
671
752
|
end
|
672
753
|
|
673
|
-
# Fills and, optionally, strokes the current bounds using the fill and
|
674
|
-
# stroke color specified, then yields to the block. The only_if option can
|
675
|
-
# be used to conditionally disable this behavior.
|
676
|
-
#
|
677
|
-
def shade_box color, line_color = nil, options = {}
|
678
|
-
if (!options.key? :only_if) || options[:only_if]
|
679
|
-
# FIXME: could use save_graphics_state here
|
680
|
-
previous_fill_color = current_fill_color
|
681
|
-
fill_color color
|
682
|
-
fill_rectangle [bounds.left, bounds.top], bounds.right, bounds.top - bounds.bottom
|
683
|
-
fill_color previous_fill_color
|
684
|
-
if line_color
|
685
|
-
line_width 0.5
|
686
|
-
previous_stroke_color = current_stroke_color
|
687
|
-
stroke_color line_color
|
688
|
-
stroke_bounds
|
689
|
-
stroke_color previous_stroke_color
|
690
|
-
end
|
691
|
-
end
|
692
|
-
yield
|
693
|
-
end
|
694
|
-
|
695
754
|
# Strokes a horizontal line using the current bounds. The width of the line
|
696
755
|
# can be specified using the line_width option. The offset from the cursor
|
697
756
|
# can be set using the at option.
|
@@ -702,21 +761,25 @@ module Asciidoctor
|
|
702
761
|
rule_width = options[:line_width] || 0.5
|
703
762
|
rule_x_start = bounds.left
|
704
763
|
rule_x_end = bounds.right
|
705
|
-
rule_inked = false
|
706
764
|
save_graphics_state do
|
707
|
-
line_width rule_width
|
708
765
|
stroke_color rule_color
|
709
766
|
case rule_style
|
710
767
|
when :dashed
|
768
|
+
line_width rule_width
|
711
769
|
dash rule_width * 4
|
712
770
|
when :dotted
|
771
|
+
line_width rule_width
|
713
772
|
dash rule_width
|
714
773
|
when :double
|
715
|
-
|
716
|
-
|
717
|
-
|
718
|
-
|
719
|
-
|
774
|
+
single_rule_width = rule_width / 3.0
|
775
|
+
line_width single_rule_width
|
776
|
+
stroke_horizontal_line rule_x_start, rule_x_end, at: (rule_y + single_rule_width)
|
777
|
+
stroke_horizontal_line rule_x_start, rule_x_end, at: (rule_y - single_rule_width)
|
778
|
+
next
|
779
|
+
else # :solid
|
780
|
+
line_width rule_width
|
781
|
+
end
|
782
|
+
stroke_horizontal_line rule_x_start, rule_x_end, at: rule_y
|
720
783
|
end
|
721
784
|
end
|
722
785
|
|
@@ -754,14 +817,14 @@ module Asciidoctor
|
|
754
817
|
def delete_page
|
755
818
|
pg = page_number
|
756
819
|
pdf_store = state.store
|
757
|
-
pdf_objs = pdf_store.instance_variable_get :@objects
|
758
|
-
pdf_ids = pdf_store.instance_variable_get :@identifiers
|
759
|
-
page_id = pdf_store.object_id_for_page pg
|
760
820
|
content_id = page.content.identifier
|
761
|
-
|
762
|
-
|
763
|
-
|
764
|
-
|
821
|
+
page_ref = page.dictionary
|
822
|
+
(prune_dests = proc do |node|
|
823
|
+
node.children.delete_if {|it| ::PDF::Core::NameTree::Node === it ? prune_dests[it] : it.value.data[0] == page_ref }
|
824
|
+
false
|
825
|
+
end)[dests.data]
|
826
|
+
# NOTE: cannot delete objects and IDs, otherwise references get corrupted; so just reset the value
|
827
|
+
(pdf_store.instance_variable_get :@objects)[content_id] = ::PDF::Core::Reference.new content_id, {}
|
765
828
|
pdf_store.pages.data[:Kids].pop
|
766
829
|
pdf_store.pages.data[:Count] -= 1
|
767
830
|
state.pages.pop
|
@@ -780,60 +843,63 @@ module Asciidoctor
|
|
780
843
|
# However, due to how page creation works in Prawn, understand that advancing
|
781
844
|
# to the next page is necessary to prevent the size & layout of the imported
|
782
845
|
# page from affecting a newly created page.
|
783
|
-
def import_page file,
|
846
|
+
def import_page file, options = {}
|
784
847
|
prev_page_layout = page.layout
|
785
848
|
prev_page_size = page.size
|
786
849
|
state.compress = false if state.compress # can't use compression if using template
|
787
850
|
prev_text_rendering_mode = (defined? @text_rendering_mode) ? @text_rendering_mode : nil
|
788
|
-
delete_page if
|
789
|
-
# NOTE use functionality provided by prawn-templates
|
790
|
-
start_new_page_discretely template: file, template_page:
|
851
|
+
delete_page if options[:replace]
|
852
|
+
# NOTE: use functionality provided by prawn-templates
|
853
|
+
start_new_page_discretely template: file, template_page: options[:page]
|
791
854
|
# prawn-templates sets text_rendering_mode to :unknown, which breaks running content; revert
|
792
855
|
@text_rendering_mode = prev_text_rendering_mode
|
793
|
-
|
794
|
-
|
795
|
-
# NOTE set page size & layout explicitly in case imported page differs
|
856
|
+
if page.imported_page?
|
857
|
+
yield if block_given?
|
858
|
+
# NOTE: set page size & layout explicitly in case imported page differs
|
796
859
|
# I'm not sure it's right to start a new page here, but unfortunately there's no other
|
797
860
|
# way atm to prevent the size & layout of the imported page from affecting subsequent pages
|
861
|
+
advance_page size: prev_page_size, layout: prev_page_layout if options.fetch :advance, true
|
862
|
+
elsif options.fetch :advance_if_missing, true
|
863
|
+
delete_page
|
864
|
+
# NOTE: see previous comment
|
798
865
|
advance_page size: prev_page_size, layout: prev_page_layout
|
866
|
+
else
|
867
|
+
delete_page
|
799
868
|
end
|
800
869
|
nil
|
801
870
|
end
|
802
871
|
|
803
|
-
# Create a new page for the specified image.
|
804
|
-
#
|
872
|
+
# Create a new page for the specified image.
|
873
|
+
#
|
874
|
+
# The image is positioned relative to the boundaries of the page.
|
805
875
|
def image_page file, options = {}
|
806
876
|
start_new_page_discretely
|
807
|
-
|
808
|
-
|
809
|
-
canvas
|
810
|
-
|
811
|
-
|
877
|
+
ex = nil
|
878
|
+
float do
|
879
|
+
canvas do
|
880
|
+
image file, ({ position: :center, vposition: :center }.merge options)
|
881
|
+
rescue
|
882
|
+
ex = $!
|
883
|
+
end
|
812
884
|
end
|
813
|
-
|
814
|
-
go_to_page image_page_number
|
885
|
+
raise ex if ex
|
815
886
|
nil
|
816
887
|
end
|
817
888
|
|
818
889
|
# Perform an operation (such as creating a new page) without triggering the on_page_create callback
|
819
890
|
#
|
820
891
|
def perform_discretely
|
821
|
-
if (saved_callback = state.on_page_create_callback)
|
822
|
-
|
823
|
-
|
824
|
-
|
825
|
-
# equivalent to calling `on_page_create &saved_callback`
|
826
|
-
state.on_page_create_callback = saved_callback
|
827
|
-
else
|
828
|
-
yield
|
829
|
-
end
|
892
|
+
state.on_page_create_callback = nil if (saved_callback = state.on_page_create_callback) != InhibitNewPageProc
|
893
|
+
yield
|
894
|
+
ensure
|
895
|
+
state.on_page_create_callback = saved_callback
|
830
896
|
end
|
831
897
|
|
832
898
|
# This method is a smarter version of start_new_page. It calls start_new_page
|
833
899
|
# if the current page is the last page of the document. Otherwise, it simply
|
834
900
|
# advances to the next existing page.
|
835
|
-
def advance_page
|
836
|
-
last_page? ? (start_new_page
|
901
|
+
def advance_page options = {}
|
902
|
+
last_page? ? (start_new_page options) : (go_to_page page_number + 1)
|
837
903
|
end
|
838
904
|
|
839
905
|
# Start a new page without triggering the on_page_create callback
|
@@ -844,115 +910,207 @@ module Asciidoctor
|
|
844
910
|
|
845
911
|
# Grouping
|
846
912
|
|
847
|
-
|
848
|
-
|
849
|
-
def group_if verdict
|
850
|
-
if verdict
|
851
|
-
state.optimize_objects = false # optimize_objects breaks group
|
852
|
-
group { yield }
|
853
|
-
else
|
854
|
-
yield
|
855
|
-
end
|
913
|
+
def allocate_prototype
|
914
|
+
@prototype = create_prototype { ::Marshal.load ::Marshal.dump self }
|
856
915
|
end
|
857
916
|
|
858
|
-
def
|
859
|
-
|
860
|
-
#Marshal.load Marshal.dump @prototype
|
861
|
-
|
862
|
-
# use cached instance, tests show it's faster
|
863
|
-
#@prototype ||= ::Prawn::Document.new
|
864
|
-
@scratch ||= if defined? @prototype # rubocop:disable Naming/MemoizedInstanceVariableName
|
865
|
-
scratch = Marshal.load Marshal.dump @prototype
|
866
|
-
scratch.instance_variable_set :@prototype, @prototype
|
867
|
-
scratch.instance_variable_set :@tmp_files, @tmp_files
|
868
|
-
# TODO: set scratch number on scratch document
|
869
|
-
scratch
|
870
|
-
else
|
871
|
-
logger.warn 'no scratch prototype available; instantiating fresh scratch document'
|
872
|
-
::Prawn::Document.new
|
873
|
-
end
|
917
|
+
def scratch
|
918
|
+
@scratch ||= ((Marshal.load Marshal.dump @prototype).send :init_scratch, self)
|
874
919
|
end
|
875
920
|
|
876
921
|
def scratch?
|
877
|
-
|
878
|
-
rescue
|
879
|
-
false # NOTE this method may get called before the state is initialized
|
880
|
-
end
|
881
|
-
alias is_scratch? scratch?
|
882
|
-
|
883
|
-
def dry_run &block
|
884
|
-
scratch = get_scratch_document
|
885
|
-
# QUESTION should we use scratch.advance_page instead?
|
886
|
-
scratch.start_new_page
|
887
|
-
start_page_number = scratch.page_number
|
888
|
-
start_y = scratch.y
|
889
|
-
scratch_bounds = scratch.bounds
|
890
|
-
original_x = scratch_bounds.absolute_left
|
891
|
-
original_width = scratch_bounds.width
|
892
|
-
scratch_bounds.instance_variable_set :@x, bounds.absolute_left
|
893
|
-
scratch_bounds.instance_variable_set :@width, bounds.width
|
894
|
-
scratch.font font_family, style: font_style, size: font_size do
|
895
|
-
scratch.instance_exec(&block)
|
896
|
-
end
|
897
|
-
# NOTE don't count excess if cursor exceeds writable area (due to padding)
|
898
|
-
full_page_height = scratch.effective_page_height
|
899
|
-
partial_page_height = [full_page_height, start_y - scratch.y].min
|
900
|
-
scratch_bounds.instance_variable_set :@x, original_x
|
901
|
-
scratch_bounds.instance_variable_set :@width, original_width
|
902
|
-
whole_pages = scratch.page_number - start_page_number
|
903
|
-
[(whole_pages * full_page_height + partial_page_height), whole_pages, partial_page_height]
|
922
|
+
@label == :scratch
|
904
923
|
end
|
905
924
|
|
906
925
|
def with_dry_run &block
|
907
|
-
|
908
|
-
instance_exec total_height, &block
|
926
|
+
yield dry_run(&block).position_onto self, cursor
|
909
927
|
end
|
910
928
|
|
911
|
-
#
|
929
|
+
# Yields to the specified block multiple times, first to determine where to render the content
|
930
|
+
# so it fits properly, then once more, this time providing access to the content's extent, to
|
931
|
+
# ink the content in the primary document.
|
912
932
|
#
|
913
|
-
#
|
914
|
-
|
915
|
-
|
916
|
-
|
917
|
-
|
918
|
-
|
919
|
-
|
920
|
-
|
921
|
-
|
922
|
-
|
923
|
-
|
933
|
+
# This method yields to the specified block in a scratch document by calling dry_run to
|
934
|
+
# determine where the content should start in the primary document. In the process, it also
|
935
|
+
# computes the extent of the content. It then returns to the primary document and yields to
|
936
|
+
# the block again, this time passing in the extent of the content. The extent can be used to
|
937
|
+
# draw a border and/or background under the content before inking it.
|
938
|
+
#
|
939
|
+
# This method is intended to enclose the conversion of a single content block, such as a
|
940
|
+
# sidebar or example block. The arrange logic attempts to keep unbreakable content on the same
|
941
|
+
# page, keeps the top caption pinned to the top of the content, computes the extent of the
|
942
|
+
# content for the purpose of drawing a border and/or background underneath it, and ensures
|
943
|
+
# that the extent does not begin near the bottom of a page if the first line of content
|
944
|
+
# doesn't fit. If unbreakable content does not fit on a single page, the content is treated as
|
945
|
+
# breakable.
|
946
|
+
#
|
947
|
+
# The block passed to this method should use advance_page to move to the next page rather than
|
948
|
+
# start_new_page. Using start_new_page can mangle the calculation of content block's extent.
|
949
|
+
#
|
950
|
+
def arrange_block node, &block
|
951
|
+
keep_together = (node.option? 'unbreakable') && !at_page_top?
|
952
|
+
doc = node.document
|
953
|
+
block_for_scratch = proc do
|
954
|
+
push_scratch doc
|
955
|
+
instance_exec(&block)
|
956
|
+
ensure
|
957
|
+
pop_scratch doc
|
924
958
|
end
|
959
|
+
extent = dry_run keep_together: keep_together, onto: [self, keep_together], &block_for_scratch
|
960
|
+
scratch? ? block_for_scratch.call : (yield extent)
|
961
|
+
end
|
925
962
|
|
926
|
-
|
927
|
-
|
928
|
-
|
963
|
+
# This method installs an on_page_create_callback that stops processing if the first page is
|
964
|
+
# exceeded while yielding to the specified block. If the content fits on a single page, the
|
965
|
+
# processing is not stopped. The purpose of this method is to determine if the content fits on
|
966
|
+
# a single page.
|
967
|
+
#
|
968
|
+
# Returns a Boolean indicating whether the content fits on a single page.
|
969
|
+
def perform_on_single_page
|
970
|
+
saved_callback, state.on_page_create_callback = state.on_page_create_callback, InhibitNewPageProc
|
971
|
+
yield
|
972
|
+
false
|
973
|
+
rescue NewPageRequiredError
|
974
|
+
true
|
975
|
+
ensure
|
976
|
+
state.on_page_create_callback = saved_callback
|
929
977
|
end
|
930
978
|
|
931
|
-
#
|
932
|
-
#
|
979
|
+
# This method installs an on_page_create_callback that stops processing if a new page is
|
980
|
+
# created without writing content to the first page while yielding to the specified block. If
|
981
|
+
# any content is written to the first page, processing is not stopped. The purpose of this
|
982
|
+
# method is to check whether any content fits on the remaining space on the current page.
|
933
983
|
#
|
934
|
-
|
935
|
-
|
936
|
-
|
937
|
-
|
984
|
+
# Returns a Boolean indicating whether any content is written on the first page.
|
985
|
+
def stop_if_first_page_empty
|
986
|
+
delegate = state.on_page_create_callback
|
987
|
+
state.on_page_create_callback = DetectEmptyFirstPageProc.curry[delegate].extend DetectEmptyFirstPage
|
988
|
+
yield
|
989
|
+
false
|
990
|
+
rescue NewPageRequiredError
|
991
|
+
true
|
992
|
+
ensure
|
993
|
+
state.on_page_create_callback = delegate
|
994
|
+
end
|
995
|
+
|
996
|
+
# NOTE: only used in dry_run since that's when DetectEmptyFirstPage is active
|
997
|
+
def tare_first_page_content_stream
|
998
|
+
return yield unless DetectEmptyFirstPage === (delegate = state.on_page_create_callback)
|
999
|
+
on_page_create_called = nil
|
1000
|
+
state.on_page_create_callback = proc do |pdf|
|
1001
|
+
on_page_create_called = true
|
1002
|
+
pdf.state.pages[pdf.page_number - 2].tare_content_stream
|
1003
|
+
delegate.call pdf
|
1004
|
+
end
|
1005
|
+
begin
|
938
1006
|
yield
|
1007
|
+
ensure
|
1008
|
+
page.tare_content_stream unless on_page_create_called
|
1009
|
+
state.on_page_create_callback = delegate
|
939
1010
|
end
|
940
1011
|
end
|
941
1012
|
|
942
|
-
|
943
|
-
|
944
|
-
|
945
|
-
|
946
|
-
|
947
|
-
|
948
|
-
|
949
|
-
|
1013
|
+
# Yields to the specified block within the context of a scratch document up to three times to
|
1014
|
+
# acertain the extent of the content block.
|
1015
|
+
#
|
1016
|
+
# The purpose of this method is two-fold. First, it works out the position where the rendered
|
1017
|
+
# content should start in the calling document. Then, it precomputes the extent of the content
|
1018
|
+
# starting from that position.
|
1019
|
+
#
|
1020
|
+
# This method returns the content's extent (the range from the start page and cursor to the
|
1021
|
+
# end page and cursor) as a ScratchExtent object or, if the onto keyword parameter is
|
1022
|
+
# specified, an Extent object. A ScratchExtent always starts the page range at 1. When the
|
1023
|
+
# ScratchExtent is positioned onto the primary document using ScratchExtent#position_onto,
|
1024
|
+
# that's when the cursor may be advanced to the next page.
|
1025
|
+
#
|
1026
|
+
# This method performs all work in a scratch document (or documents). It begins by starting a
|
1027
|
+
# new page in the scratch document, first creating the scratch document if necessary. It then
|
1028
|
+
# applies all the settings from the main document to the scratch document that impact
|
1029
|
+
# rendering. This includes the bounds, the cursor position, and the font settings.
|
1030
|
+
#
|
1031
|
+
# From this point, the number of attempts the method makes is determined by the value of the
|
1032
|
+
# keep_together keyword parameter. If the value is true (or the parent document is inhibiting
|
1033
|
+
# page creation), it starts from the top of the page, yields to the block, and tries to fit
|
1034
|
+
# the content on the current page. If the content fits, it computes and returns the
|
1035
|
+
# ScratchExtent (or Extent). If the content does not fit, it first checks if this scenario
|
1036
|
+
# should stop the operation. If the parent document is inhibiting page creation, it bubbles
|
1037
|
+
# the error. If the single_page keyword argument is :enforce, it raises a CannotFit error. If
|
1038
|
+
# the single_page keyword argument is true, it returns a ScratchExtent (or Extent) that
|
1039
|
+
# represents a full page. If none of those conditions are met, it restarts with the
|
1040
|
+
# keep_together parameter unset.
|
1041
|
+
#
|
1042
|
+
# If the keep_together parameter is not true, the method tries to render the content in the
|
1043
|
+
# scratch document from the location of the cursor in the main document. If the cursor is at
|
1044
|
+
# the top of the page, no special conditions are applied (this is the last attempt). The
|
1045
|
+
# content is rendered and the extent is computed based on where the content ended up (minus a
|
1046
|
+
# trailing empty page). If the cursor is not at the top of the page, the method renders the
|
1047
|
+
# content while listening for a page creation event before any content is written. If a new
|
1048
|
+
# page is created, and no content is written on the first page, the method restarts with the
|
1049
|
+
# cursor at the top of the page.
|
1050
|
+
#
|
1051
|
+
# Note that if the block has content that itself requires a dry run, that nested dry run will
|
1052
|
+
# be performed in a separate scratch document.
|
1053
|
+
#
|
1054
|
+
# opts - A Hash of options that configure the dry run computation:
|
1055
|
+
# :keep_together - A Boolean indicating whether an attempt should be made to keep the
|
1056
|
+
# content on the same page (optional, default: false).
|
1057
|
+
# :single_page - A Boolean indicating whether the operation should stop if the content
|
1058
|
+
# exceeds the height of a single page.
|
1059
|
+
# :onto - The document onto which to position the scratch extent. If this option is
|
1060
|
+
# set, the method returns an Extent instead of a ScratchExtent (optional, default: nil)
|
1061
|
+
# :pages_advanced - The number of pages the content has been advanced during this
|
1062
|
+
# operation (internal only) (optional, default: 0)
|
1063
|
+
#
|
1064
|
+
# Returns an Extent or ScratchExtent object that describes the bounds of the content block.
|
1065
|
+
def dry_run keep_together: nil, pages_advanced: 0, single_page: nil, onto: nil, &block
|
1066
|
+
(scratch_pdf = scratch).start_new_page
|
1067
|
+
scratch_bounds = scratch_pdf.bounds
|
1068
|
+
restore_bounds = [:@total_left_padding, :@total_right_padding, :@width, :@x].each_with_object({}) do |name, accum|
|
1069
|
+
accum[name] = scratch_bounds.instance_variable_get name
|
1070
|
+
scratch_bounds.instance_variable_set name, (bounds.instance_variable_get name)
|
1071
|
+
end
|
1072
|
+
scratch_pdf.move_cursor_to cursor unless (scratch_start_at_top = keep_together || pages_advanced > 0 || at_page_top?)
|
1073
|
+
scratch_start_cursor = scratch_pdf.cursor
|
1074
|
+
scratch_start_page = scratch_pdf.page_number
|
1075
|
+
inhibit_new_page = state.on_page_create_callback == InhibitNewPageProc
|
1076
|
+
restart = nil
|
1077
|
+
scratch_pdf.font font_family, size: font_size, style: font_style do
|
1078
|
+
prev_font_scale, scratch_pdf.font_scale = scratch_pdf.font_scale, font_scale
|
1079
|
+
if keep_together || inhibit_new_page
|
1080
|
+
if (restart = scratch_pdf.perform_on_single_page { scratch_pdf.instance_exec(&block) })
|
1081
|
+
# NOTE: propogate NewPageRequiredError from nested block, which is rendered in separate scratch document
|
1082
|
+
raise NewPageRequiredError if inhibit_new_page
|
1083
|
+
if single_page
|
1084
|
+
raise ::Prawn::Errors::CannotFit if single_page == :enforce
|
1085
|
+
# single_page and onto are mutually exclusive
|
1086
|
+
return ScratchExtent.new scratch_start_page, scratch_start_cursor, scratch_start_page, 0
|
1087
|
+
end
|
1088
|
+
end
|
1089
|
+
elsif scratch_start_at_top
|
1090
|
+
scratch_pdf.instance_exec(&block)
|
1091
|
+
elsif (restart = scratch_pdf.stop_if_first_page_empty { scratch_pdf.instance_exec(&block) })
|
1092
|
+
pages_advanced += 1
|
1093
|
+
end
|
1094
|
+
ensure
|
1095
|
+
scratch_pdf.font_scale = prev_font_scale
|
1096
|
+
end
|
1097
|
+
return dry_run pages_advanced: pages_advanced, onto: onto, &block if restart
|
1098
|
+
scratch_end_page = scratch_pdf.page_number - scratch_start_page + (scratch_start_page = 1)
|
1099
|
+
if pages_advanced > 0
|
1100
|
+
scratch_start_page += pages_advanced
|
1101
|
+
scratch_end_page += pages_advanced
|
1102
|
+
end
|
1103
|
+
scratch_end_cursor = scratch_pdf.cursor
|
1104
|
+
# NOTE: drop trailing blank page and move cursor to end of previous page
|
1105
|
+
if scratch_end_page > scratch_start_page && scratch_pdf.at_page_top?
|
1106
|
+
scratch_end_page -= 1
|
1107
|
+
scratch_end_cursor = 0
|
950
1108
|
end
|
951
|
-
|
952
|
-
|
953
|
-
|
1109
|
+
extent = ScratchExtent.new scratch_start_page, scratch_start_cursor, scratch_end_page, scratch_end_cursor
|
1110
|
+
onto ? extent.position_onto(*onto) : extent
|
1111
|
+
ensure
|
1112
|
+
restore_bounds.each {|name, val| scratch_bounds.instance_variable_set name, val }
|
954
1113
|
end
|
955
|
-
=end
|
956
1114
|
end
|
957
1115
|
end
|
958
1116
|
end
|