asciidoctor-pdf 1.6.1 → 2.0.0.alpha.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.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
|