asciidoctor-pdf 1.5.0.alpha.8 → 1.5.0.alpha.9
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.adoc +9 -1
- data/Rakefile +1 -0
- data/data/fonts/{LICENSE-noto-fonts-2014-11-17 → LICENSE-noto-2015-06-05} +0 -0
- data/data/fonts/{mplus1mn-bolditalic-ascii.ttf → mplus1mn-bold_italic-ascii.ttf} +0 -0
- data/data/fonts/{mplus1p-regular-multilingual.ttf → mplus1p-regular-fallback.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 +90 -0
- data/data/themes/default-theme.yml +97 -92
- data/docs/theming-guide.adoc +93 -21
- data/lib/asciidoctor-pdf/converter.rb +419 -216
- data/lib/asciidoctor-pdf/core_ext/quantifiable_stdout.rb +20 -0
- data/lib/asciidoctor-pdf/formatted_text/formatter.rb +6 -5
- data/lib/asciidoctor-pdf/formatted_text/inline_image_arranger.rb +3 -3
- data/lib/asciidoctor-pdf/formatted_text/transform.rb +36 -26
- data/lib/asciidoctor-pdf/pdf_core_ext.rb +1 -1
- data/lib/asciidoctor-pdf/pdf_core_ext/page.rb +22 -0
- data/lib/asciidoctor-pdf/prawn_ext.rb +1 -0
- data/lib/asciidoctor-pdf/prawn_ext/coderay_encoder.rb +24 -4
- data/lib/asciidoctor-pdf/prawn_ext/extensions.rb +73 -6
- data/lib/asciidoctor-pdf/prawn_ext/font/afm.rb +19 -0
- data/lib/asciidoctor-pdf/prawn_ext/formatted_text/fragment.rb +1 -1
- data/lib/asciidoctor-pdf/prawn_ext/images.rb +16 -0
- data/lib/asciidoctor-pdf/rouge_ext.rb +4 -0
- data/lib/asciidoctor-pdf/rouge_ext/css_theme.rb +14 -0
- data/lib/asciidoctor-pdf/rouge_ext/formatters/prawn.rb +121 -0
- data/lib/asciidoctor-pdf/rouge_ext/themes/pastie.rb +61 -0
- data/lib/asciidoctor-pdf/theme_loader.rb +32 -14
- data/lib/asciidoctor-pdf/version.rb +1 -1
- metadata +23 -15
- data/data/fonts/notoserif-bold-latin.ttf +0 -0
- data/data/fonts/notoserif-bolditalic-latin.ttf +0 -0
- data/data/fonts/notoserif-italic-latin.ttf +0 -0
- data/data/fonts/notoserif-regular-latin.ttf +0 -0
@@ -27,13 +27,11 @@ class Converter < ::Prawn::Document
|
|
27
27
|
|
28
28
|
register_for 'pdf'
|
29
29
|
|
30
|
-
def self.unicode_char number
|
31
|
-
[number].pack 'U*'
|
32
|
-
end
|
33
|
-
|
34
30
|
# NOTE require_library doesn't support require_relative and we don't modify the load path for this gem
|
35
|
-
CodeRayRequirePath = ::File.join(
|
31
|
+
CodeRayRequirePath = ::File.join (::File.dirname __FILE__), 'prawn_ext/coderay_encoder'
|
32
|
+
RougeRequirePath = ::File.join (::File.dirname __FILE__), 'rouge_ext'
|
36
33
|
|
34
|
+
AsciidoctorVersion = ::Gem::Version.create ::Asciidoctor::VERSION
|
37
35
|
AdmonitionIcons = {
|
38
36
|
caution: { key: 'fa-fire', color: 'BF3400' },
|
39
37
|
important: { key: 'fa-exclamation-circle', color: 'BF0000' },
|
@@ -42,27 +40,34 @@ class Converter < ::Prawn::Document
|
|
42
40
|
warning: { key: 'fa-exclamation-triangle', color: 'BF6900' }
|
43
41
|
}
|
44
42
|
Alignments = [:left, :center, :right]
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
43
|
+
EOL = %(\n)
|
44
|
+
TAB = %(\t)
|
45
|
+
InnerIndent = %(\n )
|
46
|
+
# a no-break space is used to replace a leading space to prevent Prawn from trimming indentation
|
47
|
+
# a leading zero-width space can't be used as it gets dropped when calculating the line width
|
48
|
+
GuardedIndent = %(\u00a0)
|
49
|
+
GuardedInnerIndent = %(\n\u00a0)
|
50
|
+
TabRx = /\t/
|
51
|
+
TabIndentRx = /^\t+/
|
52
|
+
NoBreakSpace = %(\u00a0)
|
53
|
+
NarrowSpace = %(\u2009)
|
54
|
+
NarrowNoBreakSpace = %(\u202f)
|
55
|
+
ZeroWidthSpace = %(\u200b)
|
56
|
+
HairSpace = %(\u200a)
|
57
|
+
DotLeaderDefault = '. '
|
58
|
+
EmDash = %(\u2014)
|
59
|
+
LowercaseGreekA = %(\u03b1)
|
55
60
|
Bullets = {
|
56
|
-
disc: (
|
57
|
-
circle: (
|
58
|
-
square: (
|
61
|
+
disc: %(\u2022),
|
62
|
+
circle: %(\u25e6),
|
63
|
+
square: %(\u25aa)
|
59
64
|
}
|
60
65
|
# NOTE Default theme font uses ballot boxes from FontAwesome
|
61
66
|
BallotBox = {
|
62
|
-
checked: (
|
63
|
-
unchecked: (
|
67
|
+
checked: %(\u2611),
|
68
|
+
unchecked: %(\u2610)
|
64
69
|
}
|
65
|
-
IconSets = ['fa', 'fi', 'octicon', 'pf']
|
70
|
+
IconSets = ['fa', 'fi', 'octicon', 'pf'].to_set
|
66
71
|
MeasurementRxt = '\\d+(?:\\.\\d+)?(?:in|cm|mm|pt|)'
|
67
72
|
MeasurementPartsRx = /^(\d+(?:\.\d+)?)(in|mm|cm|pt|)$/
|
68
73
|
PageSizeRx = /^(?:\[(#{MeasurementRxt}), ?(#{MeasurementRxt})\]|(#{MeasurementRxt})(?: x |x)(#{MeasurementRxt})|\S+)$/
|
@@ -70,6 +75,7 @@ class Converter < ::Prawn::Document
|
|
70
75
|
CalloutExtractRx = /(?:(?:\/\/|#|--|;;) ?)?(\\)?<!?(--|)(\d+)\2>(?=(?: ?\\?<!?\2\d+\2>)*$)/
|
71
76
|
ImageAttributeValueRx = /^image:{1,2}(.*?)\[(.*?)\]$/
|
72
77
|
LineScanRx = /\n|.+/
|
78
|
+
SourceHighlighters = ['coderay', 'pygments', 'rouge'].to_set
|
73
79
|
|
74
80
|
def initialize backend, opts
|
75
81
|
super
|
@@ -78,6 +84,9 @@ class Converter < ::Prawn::Document
|
|
78
84
|
#htmlsyntax 'xml'
|
79
85
|
@list_numbers = []
|
80
86
|
@list_bullets = []
|
87
|
+
@capabilities = {
|
88
|
+
expands_tabs: (::Asciidoctor::VERSION.start_with? '1.5.3.') || AsciidoctorVersion >= (::Gem::Version.create '1.5.3')
|
89
|
+
}
|
81
90
|
end
|
82
91
|
|
83
92
|
def convert node, name = nil, opts = {}
|
@@ -113,14 +122,19 @@ class Converter < ::Prawn::Document
|
|
113
122
|
init_pdf doc
|
114
123
|
# data-uri doesn't apply to PDF, so explicitly disable (is there a better place?)
|
115
124
|
doc.attributes.delete 'data-uri'
|
125
|
+
# set default value for pagenums if not otherwise set
|
126
|
+
unless (doc.attribute_locked? 'pagenums') || ((doc.instance_variable_get :@attributes_modified).include? 'pagenums')
|
127
|
+
doc.attributes['pagenums'] = ''
|
128
|
+
end
|
116
129
|
#assign_missing_section_ids doc
|
117
130
|
|
131
|
+
# NOTE the on_page_create callback is called within a float context
|
118
132
|
on_page_create do
|
119
133
|
# TODO implement as a watermark (on top)
|
120
134
|
if @page_bg_image
|
121
135
|
# FIXME implement fitting and centering for SVG
|
122
136
|
# TODO implement image scaling (numeric value or "fit")
|
123
|
-
|
137
|
+
canvas { image @page_bg_image, position: :center, fit: [bounds.width, bounds.height] }
|
124
138
|
elsif @page_bg_color && @page_bg_color != 'FFFFFF'
|
125
139
|
fill_absolute_bounds @page_bg_color
|
126
140
|
end
|
@@ -129,7 +143,8 @@ class Converter < ::Prawn::Document
|
|
129
143
|
layout_cover_page :front, doc
|
130
144
|
layout_title_page doc
|
131
145
|
|
132
|
-
|
146
|
+
# NOTE a new page will already be started if the cover image is a PDF
|
147
|
+
start_new_page unless page_is_empty?
|
133
148
|
|
134
149
|
toc_start_page_num = page_number
|
135
150
|
num_toc_levels = (doc.attr 'toclevels', 2).to_i
|
@@ -148,17 +163,18 @@ class Converter < ::Prawn::Document
|
|
148
163
|
font @theme.base_font_family, size: @theme.base_font_size
|
149
164
|
convert_content_for_block doc
|
150
165
|
|
166
|
+
# NOTE delete orphaned page (a page was created but there was no additional content)
|
167
|
+
delete_page if page_is_empty?
|
168
|
+
|
151
169
|
toc_page_nums = if include_toc
|
152
170
|
layout_toc doc, num_toc_levels, toc_start_page_num, num_front_matter_pages
|
153
171
|
else
|
154
172
|
(0..-1)
|
155
173
|
end
|
156
174
|
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
layout_running_content :footer, doc, skip: num_front_matter_pages
|
161
|
-
end
|
175
|
+
layout_running_content :header, doc, skip: num_front_matter_pages unless doc.noheader
|
176
|
+
layout_running_content :footer, doc, skip: num_front_matter_pages unless doc.nofooter
|
177
|
+
|
162
178
|
add_outline doc, num_toc_levels, toc_page_nums, num_front_matter_pages
|
163
179
|
catalog.data[:ViewerPreferences] = [:FitWindow]
|
164
180
|
|
@@ -178,19 +194,18 @@ class Converter < ::Prawn::Document
|
|
178
194
|
theme = ThemeLoader.load_theme doc.attr('pdf-style'), (stylesdir = (doc.attr 'pdf-stylesdir'))
|
179
195
|
@theme = theme
|
180
196
|
pdf_opts = (build_pdf_options doc, theme)
|
197
|
+
# QUESTION should we preserve page options (otherwise, not readily available)
|
198
|
+
#@page_opts = { size: pdf_opts[:page_size], layout: pdf_opts[:page_layout] }
|
181
199
|
::Prawn::Document.instance_method(:initialize).bind(self).call pdf_opts
|
182
200
|
# QUESTION should ThemeLoader register fonts?
|
183
201
|
register_fonts theme.font_catalog, (doc.attr 'scripts', 'latin'), (doc.attr 'pdf-fontsdir', ThemeLoader::FontsDir)
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
else
|
188
|
-
warn %(asciidoctor: WARNING: page background image #{bg_image} not found or readable)
|
189
|
-
end
|
202
|
+
@page_bg_image = nil
|
203
|
+
if (bg_image = resolve_background_image doc, theme, 'page-background-image')
|
204
|
+
@page_bg_image = (bg_image == 'none' ? nil : bg_image)
|
190
205
|
end
|
191
|
-
@fallback_fonts = [*theme.font_fallbacks]
|
192
206
|
@page_bg_color = resolve_theme_color :page_background_color, 'FFFFFF'
|
193
|
-
@
|
207
|
+
@fallback_fonts = [*theme.font_fallbacks]
|
208
|
+
@font_color = theme.base_font_color
|
194
209
|
@text_transform = nil
|
195
210
|
@stamps = {}
|
196
211
|
init_scratch_prototype
|
@@ -202,8 +217,8 @@ class Converter < ::Prawn::Document
|
|
202
217
|
#compress: true,
|
203
218
|
#optimize_objects: true,
|
204
219
|
info: (build_pdf_info doc),
|
205
|
-
margin:
|
206
|
-
page_layout:
|
220
|
+
margin: theme.page_margin,
|
221
|
+
page_layout: theme.page_layout.to_sym,
|
207
222
|
skip_page_creation: true,
|
208
223
|
}
|
209
224
|
|
@@ -391,19 +406,24 @@ class Converter < ::Prawn::Document
|
|
391
406
|
theme_margin :block, :top
|
392
407
|
icons = node.document.attr? 'icons', 'font'
|
393
408
|
label = icons ? (node.attr 'name').to_sym : node.caption.upcase
|
394
|
-
|
409
|
+
# FIXME this shift stuff is a real hack until we have proper margin collapsing
|
410
|
+
shift_base = @theme.prose_margin_bottom
|
395
411
|
#shift_top = icons ? (shift_base / 3.0) : 0
|
396
412
|
#shift_bottom = icons ? ((shift_base * 2) / 3.0) : shift_base
|
397
413
|
shift_top = shift_base / 3.0
|
398
414
|
shift_bottom = (shift_base * 2) / 3.0
|
399
415
|
keep_together do |box_height = nil|
|
400
416
|
#theme_font :admonition do
|
417
|
+
# FIXME this is a fudge calculation for the icon width
|
401
418
|
label_width = icons ? (bounds.width / 12.0) : (width_of label)
|
402
|
-
|
403
|
-
|
419
|
+
abs_left = bounds.absolute_left
|
420
|
+
abs_right = bounds.absolute_right
|
421
|
+
pad_box @theme.admonition_padding do
|
422
|
+
left_padding = bounds.absolute_left - abs_left
|
423
|
+
right_padding = abs_right - bounds.absolute_right
|
404
424
|
if box_height
|
405
425
|
float do
|
406
|
-
bounding_box [0, cursor], width: label_width +
|
426
|
+
bounding_box [0, cursor], width: label_width + right_padding, height: box_height do
|
407
427
|
# IMPORTANT the label must fit in the alotted space or it shows up on another page!
|
408
428
|
# QUESTION anyway to prevent text overflow in the case it doesn't fit?
|
409
429
|
stroke_vertical_rule @theme.admonition_border_color, at: bounds.width
|
@@ -418,7 +438,7 @@ class Converter < ::Prawn::Document
|
|
418
438
|
end
|
419
439
|
end
|
420
440
|
end
|
421
|
-
indent label_width +
|
441
|
+
indent label_width + left_padding + right_padding do
|
422
442
|
move_down shift_top
|
423
443
|
layout_caption node.title if node.title?
|
424
444
|
convert_content_for_block node
|
@@ -443,7 +463,7 @@ class Converter < ::Prawn::Document
|
|
443
463
|
end
|
444
464
|
end
|
445
465
|
end
|
446
|
-
pad_box
|
466
|
+
pad_box @theme.example_padding do
|
447
467
|
theme_font :example do
|
448
468
|
convert_content_for_block node
|
449
469
|
end
|
@@ -472,17 +492,17 @@ class Converter < ::Prawn::Document
|
|
472
492
|
|
473
493
|
def convert_quote_or_verse node
|
474
494
|
add_dest_for_block node if node.id
|
475
|
-
border_width = @theme.blockquote_border_width
|
495
|
+
border_width = @theme.blockquote_border_width
|
476
496
|
theme_margin :block, :top
|
477
497
|
keep_together do |box_height = nil|
|
478
498
|
start_cursor = cursor
|
479
|
-
|
480
|
-
pad_box [@theme.vertical_rhythm / 2.0, @theme.horizontal_rhythm, -(@theme.vertical_rhythm / 2.0), @theme.horizontal_rhythm + border_width / 2.0] do
|
499
|
+
pad_box @theme.blockquote_padding do
|
481
500
|
theme_font :blockquote do
|
482
501
|
if node.context == :quote
|
483
502
|
convert_content_for_block node
|
484
503
|
else # verse
|
485
|
-
|
504
|
+
content = preserve_indentation node.content, (node.attr 'tabsize')
|
505
|
+
layout_prose content, normalize: false, align: :left
|
486
506
|
end
|
487
507
|
end
|
488
508
|
theme_font :blockquote_cite do
|
@@ -517,7 +537,7 @@ class Converter < ::Prawn::Document
|
|
517
537
|
end
|
518
538
|
end
|
519
539
|
end
|
520
|
-
pad_box @theme.
|
540
|
+
pad_box @theme.sidebar_padding do
|
521
541
|
if node.title?
|
522
542
|
theme_font :sidebar_title do
|
523
543
|
# QUESTION should we allow margins of sidebar title to be customized?
|
@@ -527,8 +547,6 @@ class Converter < ::Prawn::Document
|
|
527
547
|
theme_font :sidebar do
|
528
548
|
convert_content_for_block node
|
529
549
|
end
|
530
|
-
# FIXME HACK compensate for margin bottom of sidebar content
|
531
|
-
move_up(@theme.prose_margin_bottom || @theme.vertical_rhythm)
|
532
550
|
end
|
533
551
|
end
|
534
552
|
theme_margin :block, :bottom
|
@@ -541,10 +559,10 @@ class Converter < ::Prawn::Document
|
|
541
559
|
# NOTE this logic won't work for a colist nested inside a list item until Asciidoctor 1.5.3
|
542
560
|
if (self_idx = node.parent.blocks.index node) && self_idx > 0 &&
|
543
561
|
[:listing, :literal].include?(node.parent.blocks[self_idx - 1].context)
|
544
|
-
move_up
|
562
|
+
move_up @theme.block_margin_bottom / 2.0
|
545
563
|
# or we could do...
|
546
|
-
#move_up
|
547
|
-
#move_down
|
564
|
+
#move_up @theme.block_margin_bottom
|
565
|
+
#move_down @theme.caption_margin_inside * 2
|
548
566
|
end
|
549
567
|
end
|
550
568
|
add_dest_for_block node if node.id
|
@@ -561,8 +579,8 @@ class Converter < ::Prawn::Document
|
|
561
579
|
end
|
562
580
|
@list_numbers.pop
|
563
581
|
# correct bottom margin of last item
|
564
|
-
list_margin_bottom = @theme.prose_margin_bottom
|
565
|
-
margin_bottom
|
582
|
+
list_margin_bottom = @theme.prose_margin_bottom
|
583
|
+
margin_bottom list_margin_bottom - @theme.outline_list_item_spacing
|
566
584
|
end
|
567
585
|
|
568
586
|
def convert_colist_item node
|
@@ -579,7 +597,7 @@ class Converter < ::Prawn::Document
|
|
579
597
|
|
580
598
|
indent marker_width do
|
581
599
|
convert_content_for_list_item node,
|
582
|
-
margin_bottom:
|
600
|
+
margin_bottom: @theme.outline_list_item_spacing
|
583
601
|
end
|
584
602
|
end
|
585
603
|
|
@@ -591,7 +609,7 @@ class Converter < ::Prawn::Document
|
|
591
609
|
# FIXME extract ensure_space (or similar) method
|
592
610
|
start_new_page if cursor < @theme.base_line_height_length * (terms.size + 1)
|
593
611
|
terms.each do |term|
|
594
|
-
layout_prose term.text, style:
|
612
|
+
layout_prose term.text, style: @theme.description_list_term_font_style.to_sym, margin_top: 0, margin_bottom: @theme.description_list_term_spacing, align: :left
|
595
613
|
end
|
596
614
|
if desc
|
597
615
|
indent @theme.description_list_description_indent do
|
@@ -674,8 +692,8 @@ class Converter < ::Prawn::Document
|
|
674
692
|
# However, don't leave gap at the bottom of a nested list
|
675
693
|
unless complex || (::Asciidoctor::List === node.parent && node.parent.outline?)
|
676
694
|
# correct bottom margin of last item
|
677
|
-
list_margin_bottom = @theme.prose_margin_bottom
|
678
|
-
margin_bottom
|
695
|
+
list_margin_bottom = @theme.prose_margin_bottom
|
696
|
+
margin_bottom list_margin_bottom - @theme.outline_list_item_spacing
|
679
697
|
end
|
680
698
|
end
|
681
699
|
|
@@ -721,7 +739,7 @@ class Converter < ::Prawn::Document
|
|
721
739
|
convert_content_for_list_item node
|
722
740
|
else
|
723
741
|
convert_content_for_list_item node,
|
724
|
-
margin_bottom:
|
742
|
+
margin_bottom: @theme.outline_list_item_spacing
|
725
743
|
end
|
726
744
|
end
|
727
745
|
|
@@ -741,9 +759,6 @@ class Converter < ::Prawn::Document
|
|
741
759
|
if image_type == 'gif'
|
742
760
|
valid_image = false
|
743
761
|
warn %(asciidoctor: WARNING: GIF image format not supported. Please convert #{target} to PNG.)
|
744
|
-
#elsif image_type == 'pdf'
|
745
|
-
# import_page image_path
|
746
|
-
# return
|
747
762
|
end
|
748
763
|
|
749
764
|
unless (image_path = resolve_image_path node, target) && (::File.readable? image_path)
|
@@ -751,9 +766,12 @@ class Converter < ::Prawn::Document
|
|
751
766
|
warn %(asciidoctor: WARNING: image to embed not found or not readable: #{image_path || target})
|
752
767
|
end
|
753
768
|
|
769
|
+
# NOTE import_page automatically advances to next page afterwards
|
770
|
+
return import_page image_path if image_type == 'pdf'
|
771
|
+
|
754
772
|
# QUESTION if we advance to new page, shouldn't dest point there too?
|
755
773
|
add_dest_for_block node if node.id
|
756
|
-
position = ((node.attr 'align') || @theme.image_align
|
774
|
+
position = ((node.attr 'align') || @theme.image_align).to_sym
|
757
775
|
|
758
776
|
unless valid_image
|
759
777
|
theme_margin :block, :top
|
@@ -762,11 +780,7 @@ class Converter < ::Prawn::Document
|
|
762
780
|
else
|
763
781
|
alt_text = %([#{NoBreakSpace}#{node.attr 'alt'}#{NoBreakSpace}] | <em>#{target}</em>)
|
764
782
|
end
|
765
|
-
layout_prose alt_text,
|
766
|
-
normalize: false,
|
767
|
-
margin: 0,
|
768
|
-
single_line: true,
|
769
|
-
align: position
|
783
|
+
layout_prose alt_text, normalize: false, margin: 0, single_line: true, align: position
|
770
784
|
layout_caption node, position: :bottom if node.title?
|
771
785
|
theme_margin :block, :bottom
|
772
786
|
return
|
@@ -774,34 +788,33 @@ class Converter < ::Prawn::Document
|
|
774
788
|
|
775
789
|
theme_margin :block, :top
|
776
790
|
|
791
|
+
# NOTE image is scaled proportionally based on width (height is ignored)
|
777
792
|
# TODO support cover (aka canvas) image layout using "canvas" (or "cover") role
|
778
|
-
|
779
|
-
width = if node.attr? 'pdfwidth'
|
780
|
-
if (pdfwidth = node.attr 'pdfwidth').end_with? '%'
|
781
|
-
(pdfwidth.to_f / 100) * bounds.width
|
782
|
-
else
|
783
|
-
str_to_pt pdfwidth
|
784
|
-
end
|
785
|
-
elsif node.attr? 'scaledwidth'
|
786
|
-
((node.attr 'scaledwidth').to_f / 100) * bounds.width
|
787
|
-
elsif node.attr? 'width'
|
788
|
-
# NOTE width is in pixels, so scale by 75%; restrict to max width of bounds.width
|
789
|
-
[bounds.width, (node.attr 'width').to_f * 0.75].min
|
790
|
-
end
|
793
|
+
width = resolve_explicit_width node.attributes, bounds.width
|
791
794
|
|
792
795
|
case image_type
|
793
796
|
when 'svg'
|
794
797
|
begin
|
795
|
-
|
796
|
-
|
797
|
-
|
798
|
-
|
798
|
+
svg_data = ::IO.read image_path
|
799
|
+
svg_obj = ::Prawn::Svg::Interface.new svg_data, self, position: position, width: width
|
800
|
+
svg_size = svg_obj.document.sizing
|
801
|
+
rendered_w = svg_size.output_width
|
802
|
+
if !width && (svg_obj.document.root.attributes.key? 'width')
|
803
|
+
# NOTE scale native width & height by 75% to convert px to pt; restrict width to bounds.width
|
804
|
+
if (adjusted_w = [bounds.width, rendered_w * 0.75].min) != rendered_w
|
805
|
+
# FIXME would be nice to have a resize/recalculate method; instead, just reconstruct
|
806
|
+
svg_obj = ::Prawn::Svg::Interface.new svg_data, self, position: position, width: (rendered_w = adjusted_w)
|
807
|
+
svg_size = svg_obj.document.sizing
|
808
|
+
end
|
809
|
+
end
|
810
|
+
# TODO shrink image to fit on a single page if height exceeds page height
|
811
|
+
rendered_h = svg_size.output_height
|
799
812
|
# TODO layout SVG without using keep_together (since we know the dimensions already); always render caption
|
800
813
|
keep_together do |box_height = nil|
|
801
814
|
svg_obj.instance_variable_set :@prawn, self
|
802
815
|
svg_obj.draw
|
803
816
|
if box_height && (link = node.attr 'link')
|
804
|
-
link_annotation [(abs_left =
|
817
|
+
link_annotation [(abs_left = svg_obj.position[0] + bounds.absolute_left), y, (abs_left + rendered_w), (y + rendered_h)],
|
805
818
|
Border: [0, 0, 0],
|
806
819
|
A: { Type: :Action, S: :URI, URI: (str2pdfval link) }
|
807
820
|
end
|
@@ -812,14 +825,14 @@ class Converter < ::Prawn::Document
|
|
812
825
|
end
|
813
826
|
else
|
814
827
|
begin
|
815
|
-
# FIXME temporary workaround to group caption & image
|
816
|
-
# Prawn doesn't provide access to rendered width & height before placing image on page
|
817
828
|
# FIXME this code really needs to be better organized!
|
829
|
+
# FIXME temporary workaround to group caption & image
|
830
|
+
# NOTE use low-level API to access intrinsic dimensions; build_image_object caches image data previously loaded
|
818
831
|
image_obj, image_info = build_image_object image_path
|
819
832
|
if width
|
820
833
|
rendered_w, rendered_h = image_info.calc_image_dimensions width: width
|
821
834
|
else
|
822
|
-
# NOTE native
|
835
|
+
# NOTE scale native width & height by 75% to convert px to pt; restrict width to bounds.width
|
823
836
|
rendered_w = [bounds.width, image_info.width * 0.75].min
|
824
837
|
rendered_h = (rendered_w * image_info.height) / image_info.width
|
825
838
|
end
|
@@ -828,7 +841,7 @@ class Converter < ::Prawn::Document
|
|
828
841
|
(@theme.caption_margin_inside + @theme.caption_margin_outside + @theme.base_line_height_length) : 0
|
829
842
|
if rendered_h > (available_height = cursor - caption_height)
|
830
843
|
start_new_page unless at_page_top?
|
831
|
-
# NOTE shrink image so it fits on a single page
|
844
|
+
# NOTE shrink image so it fits on a single page if height exceeds page height
|
832
845
|
if rendered_h > (available_height = cursor - caption_height)
|
833
846
|
rendered_w = (rendered_w * available_height) / rendered_h
|
834
847
|
rendered_h = available_height
|
@@ -862,18 +875,40 @@ class Converter < ::Prawn::Document
|
|
862
875
|
# QUESTION can we avoid arranging fragments multiple times (conums & autofit) by eagerly preparing arranger?
|
863
876
|
def convert_listing_or_literal node
|
864
877
|
add_dest_for_block node if node.id
|
878
|
+
|
865
879
|
# HACK disable built-in syntax highlighter; must be done before calling node.content!
|
866
|
-
|
867
|
-
|
868
|
-
|
869
|
-
|
870
|
-
|
871
|
-
subs.
|
880
|
+
if node.style == 'source' && node.attributes['language'] &&
|
881
|
+
(highlighter = node.document.attributes['source-highlighter']) &&
|
882
|
+
(SourceHighlighters.include? highlighter)
|
883
|
+
prev_subs = (subs = node.subs).dup
|
884
|
+
# NOTE the highlight sub is only set for coderay and pygments atm
|
885
|
+
highlight_idx = subs.index :highlight
|
886
|
+
# NOTE scratch? here only applies if listing block is nested inside another block
|
887
|
+
if scratch?
|
888
|
+
highlighter = nil
|
889
|
+
if highlight_idx
|
890
|
+
# switch the :highlight sub back to :specialcharacters
|
891
|
+
subs[highlight_idx] = :specialcharacters
|
892
|
+
else
|
893
|
+
prev_subs = nil
|
894
|
+
end
|
895
|
+
source_string = preserve_indentation node.content, (node.attr 'tabsize')
|
896
|
+
else
|
897
|
+
# NOTE the source highlighter logic below handles the callouts and highlight subs
|
898
|
+
if highlight_idx
|
899
|
+
subs.delete_all :highlight, :callouts
|
900
|
+
else
|
901
|
+
subs.delete_all :specialcharacters, :callouts
|
902
|
+
end
|
903
|
+
# the indent guard will be added by the source highlighter logic
|
904
|
+
source_string = preserve_indentation node.content, (node.attr 'tabsize'), false
|
905
|
+
end
|
872
906
|
else
|
873
907
|
highlighter = nil
|
874
908
|
prev_subs = nil
|
909
|
+
source_string = preserve_indentation node.content, (node.attr 'tabsize')
|
875
910
|
end
|
876
|
-
|
911
|
+
|
877
912
|
source_chunks = case highlighter
|
878
913
|
when 'coderay'
|
879
914
|
Helpers.require_library CodeRayRequirePath, 'coderay' unless defined? ::Asciidoctor::Prawn::CodeRayEncoder
|
@@ -882,14 +917,22 @@ class Converter < ::Prawn::Document
|
|
882
917
|
conum_mapping ? (restore_conums fragments, conum_mapping) : fragments
|
883
918
|
when 'pygments'
|
884
919
|
Helpers.require_library 'pygments', 'pygments.rb' unless defined? ::Pygments
|
885
|
-
source_string, conum_mapping = extract_conums source_string
|
886
920
|
lexer = ::Pygments::Lexer[node.attr 'language', 'text', false] || ::Pygments::Lexer['text']
|
887
921
|
pygments_config = { nowrap: true, noclasses: true, style: (node.document.attr 'pygments-style') || 'pastie' }
|
922
|
+
source_string, conum_mapping = extract_conums source_string
|
888
923
|
result = lexer.highlight source_string, options: pygments_config
|
889
|
-
fragments = text_formatter.format result
|
924
|
+
fragments = guard_indentation text_formatter.format result
|
925
|
+
conum_mapping ? (restore_conums fragments, conum_mapping) : fragments
|
926
|
+
when 'rouge'
|
927
|
+
Helpers.require_library RougeRequirePath, 'rouge' unless defined? ::Rouge::Formatters::Prawn
|
928
|
+
lexer = ::Rouge::Lexer.find(node.attr 'language', 'text', false) || ::Rouge::Lexers::PlainText
|
929
|
+
formatter = (@rouge_formatter ||= ::Rouge::Formatters::Prawn.new theme: (node.document.attr 'rouge-style'))
|
930
|
+
source_string, conum_mapping = extract_conums source_string
|
931
|
+
# NOTE trailing endline is added to address https://github.com/jneen/rouge/issues/279
|
932
|
+
fragments = formatter.format (lexer.lex %(#{source_string}#{EOL})), line_numbers: (node.attr? 'linenums')
|
890
933
|
conum_mapping ? (restore_conums fragments, conum_mapping) : fragments
|
891
934
|
else
|
892
|
-
# NOTE only format if we detect a need
|
935
|
+
# NOTE only format if we detect a need (callouts or inline formatting)
|
893
936
|
if source_string =~ BuiltInEntityCharOrTagRx
|
894
937
|
text_formatter.format source_string
|
895
938
|
else
|
@@ -948,6 +991,7 @@ class Converter < ::Prawn::Document
|
|
948
991
|
|
949
992
|
pad_box @theme.code_padding do
|
950
993
|
typeset_formatted_text source_chunks, (calc_line_metrics @theme.code_line_height),
|
994
|
+
# QUESTION should we require the code_font_color to be set?
|
951
995
|
color: (@theme.code_font_color || @font_color),
|
952
996
|
size: adjusted_font_size
|
953
997
|
end
|
@@ -997,7 +1041,7 @@ class Converter < ::Prawn::Document
|
|
997
1041
|
elsif text.include? EOL
|
998
1042
|
text.split(EOL, -1).each_with_index do |line_in_fragment, idx|
|
999
1043
|
line = (lines[line_num += 1] ||= []) unless idx == 0
|
1000
|
-
line << fragment.merge
|
1044
|
+
line << (fragment.merge text: line_in_fragment) unless line_in_fragment.empty?
|
1001
1045
|
end
|
1002
1046
|
else
|
1003
1047
|
line << fragment
|
@@ -1029,6 +1073,18 @@ class Converter < ::Prawn::Document
|
|
1029
1073
|
glyph
|
1030
1074
|
end
|
1031
1075
|
|
1076
|
+
# Adds guards to preserve indentation
|
1077
|
+
def guard_indentation fragments
|
1078
|
+
start_of_line = true
|
1079
|
+
fragments.each do |fragment|
|
1080
|
+
next if (text = fragment[:text]).empty?
|
1081
|
+
text[0] = GuardedIndent if start_of_line && (text.start_with? ' ')
|
1082
|
+
text.gsub! InnerIndent, GuardedInnerIndent if text.include? InnerIndent
|
1083
|
+
start_of_line = text.end_with? EOL
|
1084
|
+
end
|
1085
|
+
fragments
|
1086
|
+
end
|
1087
|
+
|
1032
1088
|
def convert_table node
|
1033
1089
|
add_dest_for_block node if node.id
|
1034
1090
|
num_rows = 0
|
@@ -1036,12 +1092,15 @@ class Converter < ::Prawn::Document
|
|
1036
1092
|
table_header = false
|
1037
1093
|
theme = @theme
|
1038
1094
|
|
1039
|
-
|
1040
|
-
|
1041
|
-
|
1042
|
-
|
1043
|
-
|
1044
|
-
|
1095
|
+
tbl_bg_color = resolve_theme_color :table_background_color
|
1096
|
+
# QUESTION should we fallback to page background color? (which is never transparent)
|
1097
|
+
#tbl_bg_color = resolve_theme_color :table_background_color, @page_bg_color
|
1098
|
+
# ...and if so, should we try to be helpful and use @page_bg_color for tables nested in blocks?
|
1099
|
+
#unless tbl_bg_color
|
1100
|
+
# tbl_bg_color = @page_bg_color unless [:section, :document].include? node.parent.context
|
1101
|
+
#end
|
1102
|
+
|
1103
|
+
# NOTE emulate table bg color by using it as a fallback value for each element
|
1045
1104
|
head_bg_color = resolve_theme_color :table_head_background_color, tbl_bg_color
|
1046
1105
|
foot_bg_color = resolve_theme_color :table_foot_background_color, tbl_bg_color
|
1047
1106
|
odd_row_bg_color = resolve_theme_color :table_odd_row_background_color, tbl_bg_color
|
@@ -1056,12 +1115,12 @@ class Converter < ::Prawn::Document
|
|
1056
1115
|
rows.each do |cell|
|
1057
1116
|
row_data << {
|
1058
1117
|
content: (head_transform ? (transform_text cell.text, head_transform) : cell.text),
|
1059
|
-
inline_format: [
|
1118
|
+
inline_format: [normalize: true],
|
1060
1119
|
background_color: head_bg_color,
|
1061
1120
|
text_color: (theme.table_head_font_color || theme.table_font_color || @font_color),
|
1062
1121
|
size: (theme.table_head_font_size || theme.table_font_size),
|
1063
1122
|
font: (theme.table_head_font_family || theme.table_font_family),
|
1064
|
-
font_style:
|
1123
|
+
font_style: theme.table_head_font_style.to_sym,
|
1065
1124
|
colspan: cell.colspan || 1,
|
1066
1125
|
rowspan: cell.rowspan || 1,
|
1067
1126
|
align: (cell.attr 'halign').to_sym,
|
@@ -1077,7 +1136,7 @@ class Converter < ::Prawn::Document
|
|
1077
1136
|
rows.each do |cell|
|
1078
1137
|
cell_data = {
|
1079
1138
|
content: cell.text,
|
1080
|
-
inline_format: [
|
1139
|
+
inline_format: [normalize: true],
|
1081
1140
|
text_color: (theme.table_body_font_color || @font_color),
|
1082
1141
|
size: theme.table_font_size,
|
1083
1142
|
font: theme.table_font_family,
|
@@ -1090,8 +1149,29 @@ class Converter < ::Prawn::Document
|
|
1090
1149
|
case cell.style
|
1091
1150
|
when :emphasis
|
1092
1151
|
cell_data[:font_style] = :italic
|
1093
|
-
when :strong
|
1152
|
+
when :strong
|
1094
1153
|
cell_data[:font_style] = :bold
|
1154
|
+
when :header
|
1155
|
+
unless defined? header_cell_data
|
1156
|
+
header_cell_data = {}
|
1157
|
+
{
|
1158
|
+
'align' => :align,
|
1159
|
+
'font_color' => :text_color,
|
1160
|
+
'font_family' => :font,
|
1161
|
+
'font_size' => :size,
|
1162
|
+
'font_style' => :font_style
|
1163
|
+
}.each do |theme_key, key|
|
1164
|
+
if (val = theme[%(table_header_cell_#{theme_key})])
|
1165
|
+
header_cell_data[key] = val
|
1166
|
+
end
|
1167
|
+
end
|
1168
|
+
header_cell_data[:font_style] ||= :bold
|
1169
|
+
if (val = resolve_theme_color :table_header_cell_background_color)
|
1170
|
+
header_cell_data[:background_color] = val
|
1171
|
+
end
|
1172
|
+
end
|
1173
|
+
|
1174
|
+
cell_data.update header_cell_data unless header_cell_data.empty?
|
1095
1175
|
when :monospaced
|
1096
1176
|
cell_data[:font] = theme.literal_font_family
|
1097
1177
|
if (size = theme.literal_font_size)
|
@@ -1107,8 +1187,6 @@ class Converter < ::Prawn::Document
|
|
1107
1187
|
table_data << row_data
|
1108
1188
|
end
|
1109
1189
|
|
1110
|
-
column_widths = node.columns.map {|col| ((col.attr 'colpcwidth') * bounds.width) / 100.0 }
|
1111
|
-
|
1112
1190
|
border = {}
|
1113
1191
|
table_border_width = theme.table_border_width
|
1114
1192
|
[:top, :bottom, :left, :right, :cols, :rows].each {|edge| border[edge] = table_border_width }
|
@@ -1134,6 +1212,13 @@ class Converter < ::Prawn::Document
|
|
1134
1212
|
border[:top] = border[:right] = border[:bottom] = border[:left] = 0
|
1135
1213
|
end
|
1136
1214
|
|
1215
|
+
if node.option? 'autowidth'
|
1216
|
+
column_widths = []
|
1217
|
+
else
|
1218
|
+
table_width = bounds.width * ((node.attr 'tablepcwidth') / 100.0)
|
1219
|
+
column_widths = node.columns.map {|col| ((col.attr 'colpcwidth') * table_width) / 100.0 }
|
1220
|
+
end
|
1221
|
+
|
1137
1222
|
table_settings = {
|
1138
1223
|
header: table_header,
|
1139
1224
|
cell_style: {
|
@@ -1191,7 +1276,7 @@ class Converter < ::Prawn::Document
|
|
1191
1276
|
|
1192
1277
|
def convert_thematic_break node
|
1193
1278
|
theme_margin :thematic_break, :top
|
1194
|
-
stroke_horizontal_rule @theme.thematic_break_border_color, line_width: @theme.thematic_break_border_width, line_style:
|
1279
|
+
stroke_horizontal_rule @theme.thematic_break_border_color, line_width: @theme.thematic_break_border_width, line_style: @theme.thematic_break_border_style.to_sym
|
1195
1280
|
theme_margin :thematic_break, :bottom
|
1196
1281
|
end
|
1197
1282
|
|
@@ -1203,6 +1288,7 @@ class Converter < ::Prawn::Document
|
|
1203
1288
|
nil
|
1204
1289
|
end
|
1205
1290
|
|
1291
|
+
# NOTE to insert sequential page breaks, you must put {nbsp} between page breaks
|
1206
1292
|
def convert_page_break node
|
1207
1293
|
start_new_page unless at_page_top?
|
1208
1294
|
end
|
@@ -1275,10 +1361,10 @@ class Converter < ::Prawn::Document
|
|
1275
1361
|
def convert_inline_footnote node
|
1276
1362
|
if (index = node.attr 'index')
|
1277
1363
|
#text = node.document.footnotes.find {|fn| fn.index == index }.text
|
1278
|
-
%(<
|
1364
|
+
%( <color rgb="#999999">[#{index}: #{node.text}]</color>)
|
1279
1365
|
elsif node.type == :xref
|
1280
1366
|
# NOTE footnote reference not found
|
1281
|
-
%(<
|
1367
|
+
%( <color rgb="FF0000">[#{node.text}]</color>)
|
1282
1368
|
end
|
1283
1369
|
end
|
1284
1370
|
|
@@ -1386,40 +1472,19 @@ class Converter < ::Prawn::Document
|
|
1386
1472
|
|
1387
1473
|
# FIXME only create title page if doctype=book!
|
1388
1474
|
def layout_title_page doc
|
1389
|
-
return unless doc.header? && !doc.
|
1475
|
+
return unless doc.header? && !doc.notitle
|
1390
1476
|
|
1391
1477
|
prev_bg_image = @page_bg_image
|
1392
1478
|
prev_bg_color = @page_bg_color
|
1393
|
-
if (bg_image = (doc.attr 'title-background-image', @theme.title_page_background_image))
|
1394
|
-
if bg_image == 'none'
|
1395
|
-
@page_bg_image = nil
|
1396
|
-
else
|
1397
|
-
if bg_image =~ ImageAttributeValueRx
|
1398
|
-
bg_image = $1
|
1399
|
-
# QUESTION should we support width and height?
|
1400
|
-
end
|
1401
1479
|
|
1402
|
-
|
1403
|
-
|
1404
|
-
resolve_image_path doc, bg_image
|
1405
|
-
else
|
1406
|
-
ThemeLoader.resolve_theme_asset bg_image, (doc.attr 'pdf-stylesdir')
|
1407
|
-
end
|
1408
|
-
|
1409
|
-
if resolved_bg_image && (::File.readable? resolved_bg_image)
|
1410
|
-
@page_bg_image = resolved_bg_image
|
1411
|
-
else
|
1412
|
-
warn %(asciidoctor: WARNING: title page background image #{resolved_bg_image || bg_image} not found or readable)
|
1413
|
-
bg_image = nil
|
1414
|
-
end
|
1415
|
-
end
|
1480
|
+
if (bg_image = resolve_background_image doc, @theme, 'title-page-background-image')
|
1481
|
+
@page_bg_image = (bg_image == 'none' ? nil : bg_image)
|
1416
1482
|
end
|
1417
|
-
if
|
1483
|
+
if (bg_color = resolve_theme_color :title_page_background_color)
|
1418
1484
|
@page_bg_color = bg_color
|
1419
|
-
else
|
1420
|
-
bg_color = nil
|
1421
1485
|
end
|
1422
|
-
|
1486
|
+
# NOTE a new page will already be started if the cover image is a PDF
|
1487
|
+
start_new_page unless page_is_empty?
|
1423
1488
|
@page_bg_image = prev_bg_image if bg_image
|
1424
1489
|
@page_bg_color = prev_bg_color if bg_color
|
1425
1490
|
|
@@ -1427,24 +1492,23 @@ class Converter < ::Prawn::Document
|
|
1427
1492
|
font @theme.base_font_family, size: @theme.base_font_size
|
1428
1493
|
|
1429
1494
|
# QUESTION allow aligment per element on title page?
|
1430
|
-
title_align =
|
1495
|
+
title_align = @theme.title_page_align.to_sym
|
1431
1496
|
|
1432
|
-
#
|
1497
|
+
# TODO disallow .pdf as image type
|
1433
1498
|
if (logo_image_path = (doc.attr 'title-logo-image', @theme.title_page_logo_image))
|
1434
|
-
if logo_image_path =~ ImageAttributeValueRx
|
1499
|
+
if (logo_image_path.include? ':') && logo_image_path =~ ImageAttributeValueRx
|
1435
1500
|
logo_image_path = $1
|
1436
|
-
logo_image_attrs = AttributeList.new
|
1501
|
+
logo_image_attrs = (AttributeList.new $2).parse ['alt', 'width', 'height']
|
1437
1502
|
else
|
1438
1503
|
logo_image_attrs = {}
|
1439
1504
|
end
|
1440
1505
|
# HACK quick fix to resolve image path relative to theme
|
1441
1506
|
unless doc.attr? 'title-logo-image'
|
1442
|
-
|
1443
|
-
logo_image_path = ::File.expand_path logo_image_path, (doc.attr 'pdf-stylesdir', ThemeLoader::ThemesDir)
|
1507
|
+
logo_image_path = ThemeLoader.resolve_theme_asset logo_image_path, (doc.attr 'pdf-stylesdir')
|
1444
1508
|
end
|
1445
1509
|
logo_image_attrs['target'] = logo_image_path
|
1446
1510
|
logo_image_attrs['align'] ||= (@theme.title_page_logo_align || title_align.to_s)
|
1447
|
-
logo_image_top = (logo_image_attrs['top'] || @theme.title_page_logo_top
|
1511
|
+
logo_image_top = (logo_image_attrs['top'] || @theme.title_page_logo_top)
|
1448
1512
|
# FIXME delegate to method to convert page % to y value
|
1449
1513
|
logo_image_top = [(page_height - page_height * (logo_image_top.to_i / 100.0)), bounds.absolute_top].min
|
1450
1514
|
float do
|
@@ -1452,6 +1516,7 @@ class Converter < ::Prawn::Document
|
|
1452
1516
|
# FIXME add API to Asciidoctor for creating blocks like this (extract from extensions module?)
|
1453
1517
|
image_block = ::Asciidoctor::Block.new doc, :image, content_model: :empty, attributes: logo_image_attrs
|
1454
1518
|
# FIXME prevent image from spilling to next page
|
1519
|
+
# QUESTION should we shave off margin top/bottom?
|
1455
1520
|
convert_image image_block
|
1456
1521
|
end
|
1457
1522
|
end
|
@@ -1511,12 +1576,13 @@ class Converter < ::Prawn::Document
|
|
1511
1576
|
# TODO turn processing of attribute with inline image a utility function in Asciidoctor
|
1512
1577
|
# FIXME verify cover_image exists!
|
1513
1578
|
if (cover_image = (doc.attr %(#{position}-cover-image)))
|
1514
|
-
if cover_image =~ ImageAttributeValueRx
|
1579
|
+
if (cover_image.include? ':') && cover_image =~ ImageAttributeValueRx
|
1515
1580
|
cover_image = resolve_image_path doc, $1
|
1516
1581
|
end
|
1517
1582
|
# QUESTION should we go to page 1 when position == :front?
|
1518
1583
|
go_to_page page_count if position == :back
|
1519
1584
|
if cover_image.downcase.end_with? '.pdf'
|
1585
|
+
# NOTE import_page automatically advances to next page afterwards
|
1520
1586
|
import_page cover_image
|
1521
1587
|
else
|
1522
1588
|
image_page cover_image, canvas: true
|
@@ -1554,8 +1620,8 @@ class Converter < ::Prawn::Document
|
|
1554
1620
|
|
1555
1621
|
# NOTE inline_format is true by default
|
1556
1622
|
def layout_prose string, opts = {}
|
1557
|
-
top_margin = (margin = (opts.delete :margin)) || (opts.delete :margin_top) || @theme.prose_margin_top
|
1558
|
-
bot_margin = margin || (opts.delete :margin_bottom) || @theme.prose_margin_bottom
|
1623
|
+
top_margin = (margin = (opts.delete :margin)) || (opts.delete :margin_top) || @theme.prose_margin_top
|
1624
|
+
bot_margin = margin || (opts.delete :margin_bottom) || @theme.prose_margin_bottom
|
1559
1625
|
if (transform = (opts.delete :text_transform) || @text_transform)
|
1560
1626
|
string = transform_text string, transform
|
1561
1627
|
end
|
@@ -1568,14 +1634,12 @@ class Converter < ::Prawn::Document
|
|
1568
1634
|
string = %(<a anchor="#{anchor}">#{string}</a>)
|
1569
1635
|
end
|
1570
1636
|
end
|
1571
|
-
# preserve leading space using non-breaking space chars
|
1572
|
-
string = preserve_indentation string if opts.delete :preserve
|
1573
1637
|
margin_top top_margin
|
1574
1638
|
typeset_text string, calc_line_metrics((opts.delete :line_height) || @theme.base_line_height), {
|
1575
1639
|
color: @font_color,
|
1576
1640
|
# NOTE normalize makes endlines soft (replaces "\n" with ' ')
|
1577
|
-
inline_format: [
|
1578
|
-
align:
|
1641
|
+
inline_format: [normalize: (opts.delete :normalize) != false],
|
1642
|
+
align: @theme.base_align.to_sym
|
1579
1643
|
}.merge(opts)
|
1580
1644
|
margin_bottom bot_margin
|
1581
1645
|
end
|
@@ -1602,7 +1666,7 @@ class Converter < ::Prawn::Document
|
|
1602
1666
|
layout_prose string, {
|
1603
1667
|
margin_top: margin[:top],
|
1604
1668
|
margin_bottom: margin[:bottom],
|
1605
|
-
align:
|
1669
|
+
align: @theme.caption_align.to_sym,
|
1606
1670
|
normalize: false
|
1607
1671
|
}.merge(opts)
|
1608
1672
|
if position == :top && @theme.caption_border_bottom_color
|
@@ -1627,7 +1691,7 @@ class Converter < ::Prawn::Document
|
|
1627
1691
|
# QUESTION shouldn't we skip this whole method if num_levels == 0?
|
1628
1692
|
if num_levels > 0
|
1629
1693
|
theme_margin :toc, :top
|
1630
|
-
line_metrics = calc_line_metrics @theme.toc_line_height
|
1694
|
+
line_metrics = calc_line_metrics @theme.toc_line_height
|
1631
1695
|
dot_width = nil
|
1632
1696
|
theme_font :toc do
|
1633
1697
|
dot_width = width_of(@theme.toc_dot_leader_content || DotLeaderDefault)
|
@@ -1665,6 +1729,7 @@ class Converter < ::Prawn::Document
|
|
1665
1729
|
# FIXME dots don't line up if width of page numbers differ
|
1666
1730
|
typeset_formatted_text [
|
1667
1731
|
{ text: %(#{(@theme.toc_dot_leader_content || DotLeaderDefault) * num_dots}), color: toc_dot_color },
|
1732
|
+
# FIXME this spacing doesn't always work out
|
1668
1733
|
{ text: NoBreakSpace, size: (@font_size * 0.5) },
|
1669
1734
|
{ text: sect_page_num.to_s, anchor: sect_anchor, color: @font_color }], line_metrics, align: :right
|
1670
1735
|
go_to_page end_page_number if start_page_number != end_page_number
|
@@ -1672,7 +1737,7 @@ class Converter < ::Prawn::Document
|
|
1672
1737
|
end
|
1673
1738
|
end
|
1674
1739
|
if sect.level < num_levels
|
1675
|
-
indent
|
1740
|
+
indent @theme.toc_indent do
|
1676
1741
|
layout_toc_level sect.sections, num_levels, line_metrics, dot_width, num_front_matter_pages
|
1677
1742
|
end
|
1678
1743
|
end
|
@@ -1695,7 +1760,7 @@ class Converter < ::Prawn::Document
|
|
1695
1760
|
num_pages = page_count - skip
|
1696
1761
|
|
1697
1762
|
# FIXME probably need to treat doctypes differently
|
1698
|
-
sections = doc.find_by(context: :section) {|sect| sect.level < 3 }
|
1763
|
+
sections = doc.find_by(context: :section) {|sect| sect.level < 3 } || []
|
1699
1764
|
|
1700
1765
|
# index chapters and sections by the visual page number on which they start
|
1701
1766
|
chapter_start_pages = {}
|
@@ -1726,49 +1791,52 @@ class Converter < ::Prawn::Document
|
|
1726
1791
|
sections_by_page[num] = last_sect
|
1727
1792
|
end
|
1728
1793
|
|
1729
|
-
doctitle = doc.doctitle partition: true
|
1794
|
+
doctitle = doc.doctitle partition: true, use_fallback: true
|
1730
1795
|
# NOTE set doctitle again so it's properly escaped
|
1731
1796
|
doc.set_attr 'doctitle', doctitle.combined
|
1732
1797
|
doc.set_attr 'document-title', doctitle.main
|
1733
1798
|
doc.set_attr 'document-subtitle', doctitle.subtitle
|
1734
1799
|
doc.set_attr 'page-count', num_pages
|
1735
1800
|
|
1801
|
+
fallback_footer_content = {
|
1802
|
+
recto: { right: '{page-number}' },
|
1803
|
+
verso: { left: '{page-number}' }
|
1804
|
+
}
|
1736
1805
|
# TODO move this to a method so it can be reused; cache results
|
1737
1806
|
content_dict = [:recto, :verso].inject({}) do |acc, side|
|
1738
1807
|
side_content = {}
|
1739
1808
|
Alignments.each do |align|
|
1740
1809
|
if (val = @theme[%(#{position}_#{side}_content_#{align})])
|
1741
|
-
|
1810
|
+
# TODO support image URL (using resolve_image_path)
|
1811
|
+
if (val.include? ':') && val =~ ImageAttributeValueRx &&
|
1742
1812
|
::File.readable?(path = (ThemeLoader.resolve_theme_asset $1, (doc.attr 'pdf-stylesdir')))
|
1743
|
-
attrs = AttributeList.new
|
1744
|
-
|
1745
|
-
|
1813
|
+
attrs = (AttributeList.new $2).parse
|
1814
|
+
width = resolve_explicit_width attrs, bounds.width
|
1815
|
+
# QUESTION should we lookup and scale intrinsic width if explicit width is not given?
|
1816
|
+
unless width
|
1817
|
+
width = [bounds.width, (intrinsic_image_dimensions path)[:width] * 0.75].min
|
1818
|
+
end
|
1819
|
+
side_content[align] = { path: path, width: width }
|
1746
1820
|
else
|
1747
|
-
side_content[align]
|
1821
|
+
side_content[align] = val
|
1748
1822
|
end
|
1749
1823
|
end
|
1750
1824
|
end
|
1751
|
-
|
1752
|
-
|
1753
|
-
|
1754
|
-
when :recto
|
1755
|
-
acc[side] = { right: '{page-number}' }
|
1756
|
-
when :verso
|
1757
|
-
acc[side] = { left: '{page-number}' }
|
1758
|
-
end
|
1825
|
+
# NOTE set fallbacks if not explicitly disabled
|
1826
|
+
if side_content.empty? && position == :footer && @theme[%(footer_#{side}_content)] != 'none'
|
1827
|
+
side_content = fallback_footer_content[side]
|
1759
1828
|
end
|
1829
|
+
|
1830
|
+
acc[side] = side_content
|
1760
1831
|
acc
|
1761
1832
|
end
|
1762
1833
|
|
1763
|
-
# QUESTION should we support footer_line_height?
|
1764
|
-
#trim_line_metrics = calc_line_metrics @theme.base_line_height
|
1765
|
-
trim_line_metrics = calc_line_metrics
|
1766
1834
|
if position == :header
|
1835
|
+
trim_line_metrics = calc_line_metrics(@theme.header_line_height || @theme.base_line_height)
|
1767
1836
|
trim_top = page_height
|
1768
1837
|
# NOTE height is required atm
|
1769
1838
|
trim_height = @theme.header_height || page_margin_top
|
1770
1839
|
trim_padding = @theme.header_padding || [0, 0, 0, 0]
|
1771
|
-
trim_content_height = trim_height - trim_padding[0] - trim_padding[2] - trim_line_metrics.padding_top
|
1772
1840
|
trim_left = page_margin_left
|
1773
1841
|
trim_width = page_width - trim_left - page_margin_right
|
1774
1842
|
trim_font_color = @theme.header_font_color || @font_color
|
@@ -1776,13 +1844,13 @@ class Converter < ::Prawn::Document
|
|
1776
1844
|
trim_border_width = @theme.header_border_width || @theme.base_border_width
|
1777
1845
|
trim_border_style = (@theme.header_border_style || :solid).to_sym
|
1778
1846
|
trim_border_color = resolve_theme_color :header_border_color
|
1779
|
-
trim_valign = (@theme.
|
1780
|
-
trim_img_valign = @theme.
|
1847
|
+
trim_valign = (@theme.header_vertical_align || :middle).to_sym
|
1848
|
+
trim_img_valign = @theme.header_image_vertical_align
|
1781
1849
|
else
|
1850
|
+
trim_line_metrics = calc_line_metrics(@theme.footer_line_height || @theme.base_line_height)
|
1782
1851
|
# NOTE height is required atm
|
1783
1852
|
trim_top = trim_height = @theme.footer_height || page_margin_bottom
|
1784
1853
|
trim_padding = @theme.footer_padding || [0, 0, 0, 0]
|
1785
|
-
trim_content_height = trim_height - trim_padding[0] - trim_padding[2] - trim_line_metrics.padding_top
|
1786
1854
|
trim_left = page_margin_left
|
1787
1855
|
trim_width = page_width - trim_left - page_margin_right
|
1788
1856
|
trim_font_color = @theme.footer_font_color || @font_color
|
@@ -1790,19 +1858,29 @@ class Converter < ::Prawn::Document
|
|
1790
1858
|
trim_border_width = @theme.footer_border_width || @theme.base_border_width
|
1791
1859
|
trim_border_style = (@theme.footer_border_style || :solid).to_sym
|
1792
1860
|
trim_border_color = resolve_theme_color :footer_border_color
|
1793
|
-
trim_valign = (@theme.
|
1794
|
-
trim_img_valign = @theme.
|
1861
|
+
trim_valign = (@theme.footer_vertical_align || :middle).to_sym
|
1862
|
+
trim_img_valign = @theme.footer_image_vertical_align
|
1795
1863
|
end
|
1796
1864
|
|
1797
|
-
trim_stamp =
|
1865
|
+
trim_stamp = position.to_s
|
1798
1866
|
trim_content_left = trim_left + trim_padding[3]
|
1867
|
+
trim_content_height = trim_height - trim_padding[0] - trim_padding[2] - trim_line_metrics.padding_top - trim_line_metrics.padding_bottom
|
1799
1868
|
trim_content_width = trim_width - trim_padding[3] - trim_padding[1]
|
1800
1869
|
trim_border_color = nil if trim_border_width == 0
|
1801
|
-
|
1870
|
+
trim_valign = :center if trim_valign == :middle
|
1871
|
+
case trim_img_valign
|
1872
|
+
when nil
|
1873
|
+
trim_img_valign = trim_valign
|
1874
|
+
when 'middle'
|
1875
|
+
trim_img_valign = :center
|
1876
|
+
when 'top', 'center', 'bottom'
|
1802
1877
|
trim_img_valign = trim_img_valign.to_sym
|
1803
1878
|
end
|
1804
1879
|
|
1805
1880
|
if trim_bg_color || trim_border_color
|
1881
|
+
# NOTE switch to first content page so stamp will get created properly (can't create on imported page)
|
1882
|
+
prev_page_number = page_number
|
1883
|
+
go_to_page start
|
1806
1884
|
create_stamp trim_stamp do
|
1807
1885
|
canvas do
|
1808
1886
|
if trim_bg_color
|
@@ -1824,17 +1902,20 @@ class Converter < ::Prawn::Document
|
|
1824
1902
|
end
|
1825
1903
|
end
|
1826
1904
|
@stamps[position] = true
|
1905
|
+
go_to_page prev_page_number
|
1827
1906
|
end
|
1828
1907
|
|
1908
|
+
pagenums_enabled = doc.attr? 'pagenums'
|
1829
1909
|
repeat (start..page_count), dynamic: true do
|
1830
1910
|
# NOTE don't write on pages which are imported / inserts (otherwise we can get a corrupt PDF)
|
1831
1911
|
next if page.imported_page?
|
1832
1912
|
visual_pgnum = page_number - skip
|
1833
1913
|
# FIXME we need to have a content setting for chapter pages
|
1834
1914
|
content_by_alignment = content_dict[visual_pgnum.odd? ? :recto : :verso]
|
1835
|
-
doc.set_attr 'page-number', visual_pgnum
|
1836
1915
|
# TODO populate chapter-number
|
1837
1916
|
# TODO populate numbered and unnumbered chapter and section titles
|
1917
|
+
# FIXME leave page-number attribute unset once we filter lines with unresolved attributes (see below)
|
1918
|
+
doc.set_attr 'page-number', (pagenums_enabled ? visual_pgnum : '')
|
1838
1919
|
doc.set_attr 'chapter-title', (chapters_by_page[visual_pgnum] || '')
|
1839
1920
|
doc.set_attr 'section-title', (sections_by_page[visual_pgnum] || '')
|
1840
1921
|
doc.set_attr 'section-or-chapter-title', (sections_by_page[visual_pgnum] || chapters_by_page[visual_pgnum] || '')
|
@@ -1848,16 +1929,26 @@ class Converter < ::Prawn::Document
|
|
1848
1929
|
# FIXME we need to have a content setting for chapter pages
|
1849
1930
|
case (content = content_by_alignment[align])
|
1850
1931
|
when ::Hash
|
1851
|
-
#
|
1852
|
-
|
1853
|
-
|
1854
|
-
|
1855
|
-
|
1932
|
+
# NOTE image placement respects padding; use negative image_vertical_align value to revert
|
1933
|
+
trim_v_padding = trim_padding[0] + trim_padding[2]
|
1934
|
+
# NOTE bounding_box is redundant if trim_v_padding is 0
|
1935
|
+
bounding_box [0, cursor - trim_padding[0]], width: bounds.width, height: (bounds.height - trim_v_padding) do
|
1936
|
+
# NOTE float ensures cursor position is restored and returns us to current page if we overrun
|
1937
|
+
float do
|
1938
|
+
#image content[:path], vposition: trim_img_valign, position: align, width: content[:width]
|
1939
|
+
# NOTE use :fit to prevent image from overflowing page (at the cost of scaling it)
|
1940
|
+
image content[:path], vposition: trim_img_valign, position: align, fit: [content[:width], bounds.height]
|
1941
|
+
end
|
1856
1942
|
end
|
1857
1943
|
when ::String
|
1858
|
-
|
1859
|
-
|
1860
|
-
|
1944
|
+
if content == '{page-number}'
|
1945
|
+
content = pagenums_enabled ? visual_pgnum.to_s : nil
|
1946
|
+
else
|
1947
|
+
# FIXME drop lines with unresolved attributes
|
1948
|
+
content = doc.apply_subs content
|
1949
|
+
end
|
1950
|
+
formatted_text_box parse_text(content, color: trim_font_color, inline_format: [normalize: true]),
|
1951
|
+
at: [0, trim_content_height + trim_padding[2] + trim_line_metrics.padding_bottom],
|
1861
1952
|
height: trim_content_height,
|
1862
1953
|
align: align,
|
1863
1954
|
valign: trim_valign,
|
@@ -1884,7 +1975,7 @@ class Converter < ::Prawn::Document
|
|
1884
1975
|
|
1885
1976
|
# title page (i)
|
1886
1977
|
# TODO same conditional logic as in layout_title_page; consolidate
|
1887
|
-
if doc.header? && !doc.
|
1978
|
+
if doc.header? && !doc.notitle
|
1888
1979
|
page_num_labels[0] = { P: ::PDF::Core::LiteralString.new(front_matter_counter.next!.to_s) }
|
1889
1980
|
end
|
1890
1981
|
|
@@ -1935,11 +2026,18 @@ class Converter < ::Prawn::Document
|
|
1935
2026
|
end
|
1936
2027
|
|
1937
2028
|
def write pdf_doc, target
|
1938
|
-
|
2029
|
+
if target.respond_to? :write
|
2030
|
+
require_relative 'core_ext/quantifiable_stdout' unless defined? ::QuantifiableStdout
|
2031
|
+
target = ::QuantifiableStdout.new STDOUT if target == STDOUT
|
2032
|
+
pdf_doc.render target
|
2033
|
+
else
|
2034
|
+
pdf_doc.render_file target
|
2035
|
+
# QUESTION restore attributes first?
|
2036
|
+
@pdfmarks.generate_file target if @pdfmarks
|
2037
|
+
end
|
1939
2038
|
# write scratch document if debug is enabled (or perhaps DEBUG_STEPS env)
|
1940
2039
|
#get_scratch_document.render_file 'scratch.pdf'
|
1941
|
-
|
1942
|
-
@pdfmarks.generate_file target if @pdfmarks
|
2040
|
+
nil
|
1943
2041
|
end
|
1944
2042
|
|
1945
2043
|
def register_fonts font_catalog, scripts = 'latin', fonts_dir
|
@@ -1988,18 +2086,22 @@ class Converter < ::Prawn::Document
|
|
1988
2086
|
# Start a new page if y value is greater than remaining space on page.
|
1989
2087
|
def margin y, position
|
1990
2088
|
unless y == 0 || at_page_top?
|
1991
|
-
if cursor
|
1992
|
-
@margin_box.move_past_bottom
|
1993
|
-
else
|
2089
|
+
if cursor > y
|
1994
2090
|
move_down y
|
2091
|
+
else
|
2092
|
+
# go to the next page
|
2093
|
+
# NOTE we don't use `move_down cursor` because we often have to check at_page_top?
|
2094
|
+
@margin_box.move_past_bottom
|
1995
2095
|
end
|
1996
2096
|
end
|
1997
2097
|
end
|
1998
2098
|
|
1999
2099
|
# Lookup margin for theme element and position, then delegate to margin method.
|
2000
|
-
# If
|
2100
|
+
# If margin value is not found, assume:
|
2101
|
+
# - 0 when position = :top
|
2102
|
+
# - @theme.vertical_spacing when position = :bottom
|
2001
2103
|
def theme_margin category, position
|
2002
|
-
margin(@theme[%(#{category}_margin_#{position})] || (position == :bottom ? @theme.
|
2104
|
+
margin (@theme[%(#{category}_margin_#{position})] || (position == :bottom ? @theme.vertical_spacing : 0)), position
|
2003
2105
|
end
|
2004
2106
|
|
2005
2107
|
def theme_font category, opts = {}
|
@@ -2042,22 +2144,20 @@ class Converter < ::Prawn::Document
|
|
2042
2144
|
arranger = arrange_fragments_by_line fragments
|
2043
2145
|
adjusted_font_size = nil
|
2044
2146
|
theme_font category do
|
2045
|
-
# NOTE finalizing the line here generates fragments using current font settings
|
2147
|
+
# NOTE finalizing the line here generates fragments & calculates their widths using the current font settings
|
2148
|
+
# CAUTION it also removes zero-width spaces
|
2046
2149
|
arranger.finalize_line
|
2047
2150
|
actual_width = width_of_fragments arranger.fragments
|
2048
2151
|
unless ::Array === (padding = @theme[%(#{category}_padding)])
|
2049
2152
|
padding = [padding] * 4
|
2050
2153
|
end
|
2051
|
-
bounds.
|
2052
|
-
|
2053
|
-
|
2054
|
-
adjusted_font_size = ((bounds.width * font_size).to_f / actual_width).with_precision 4
|
2154
|
+
available_width = bounds.width - (padding[3] || 0) - (padding[1] || 0)
|
2155
|
+
if actual_width > available_width
|
2156
|
+
adjusted_font_size = ((available_width * font_size).to_f / actual_width).with_precision 4
|
2055
2157
|
if (min = @theme[%(#{category}_font_size_min)] || @theme.base_font_size_min) && adjusted_font_size < min
|
2056
2158
|
adjusted_font_size = min
|
2057
2159
|
end
|
2058
2160
|
end
|
2059
|
-
bounds.subtract_left_padding p_left
|
2060
|
-
bounds.subtract_right_padding p_right
|
2061
2161
|
end
|
2062
2162
|
adjusted_font_size
|
2063
2163
|
end
|
@@ -2074,7 +2174,7 @@ class Converter < ::Prawn::Document
|
|
2074
2174
|
by_line << fragment
|
2075
2175
|
elsif txt.include? EOL
|
2076
2176
|
txt.scan(LineScanRx) do |line|
|
2077
|
-
by_line << fragment.merge
|
2177
|
+
by_line << (line == EOL ? { text: EOL } : (fragment.merge text: line))
|
2078
2178
|
end
|
2079
2179
|
else
|
2080
2180
|
by_line << fragment
|
@@ -2125,8 +2225,60 @@ class Converter < ::Prawn::Document
|
|
2125
2225
|
(height_of string, leading: line_metrics.leading, final_gap: line_metrics.final_gap) + line_metrics.padding_top + line_metrics.padding_bottom
|
2126
2226
|
end
|
2127
2227
|
|
2128
|
-
def preserve_indentation string
|
2129
|
-
|
2228
|
+
def preserve_indentation string, tab_size = nil, guard_indent = true
|
2229
|
+
return '' unless string
|
2230
|
+
# expand tabs if they aren't already expanded, even if explicitly disabled
|
2231
|
+
# NOTE Asciidoctor >= 1.5.3 already replaces tabs if tabsize attribute is positive
|
2232
|
+
if ((tab_size = tab_size.to_i) < 1 || !@capabilities[:expands_tabs]) && (string.include? TAB)
|
2233
|
+
# Asciidoctor <= 1.5.2 already does tab replacement in some cases, so be consistent about tab size
|
2234
|
+
full_tab_space = ' ' * (tab_size = 4)
|
2235
|
+
result = []
|
2236
|
+
string.each_line do |line|
|
2237
|
+
if line.start_with? TAB
|
2238
|
+
# NOTE '+' operator is faster than interpolation in this case
|
2239
|
+
if guard_indent
|
2240
|
+
line.sub!(TabIndentRx) {|tabs| GuardedIndent + (full_tab_space * tabs.length).chop! }
|
2241
|
+
else
|
2242
|
+
line.sub!(TabIndentRx) {|tabs| full_tab_space * tabs.length }
|
2243
|
+
end
|
2244
|
+
leading_space = false
|
2245
|
+
# QUESTION should we check for EOL first?
|
2246
|
+
elsif line == EOL
|
2247
|
+
result << line
|
2248
|
+
next
|
2249
|
+
else
|
2250
|
+
leading_space = guard_indent && (line.start_with? ' ')
|
2251
|
+
end
|
2252
|
+
|
2253
|
+
if line.include? TAB
|
2254
|
+
# keep track of how many spaces were added to adjust offset in match data
|
2255
|
+
spaces_added = 0
|
2256
|
+
line.gsub!(TabRx) {
|
2257
|
+
# calculate how many spaces this tab represents, then replace tab with spaces
|
2258
|
+
if (offset = ($~.begin 0) + spaces_added) % tab_size == 0
|
2259
|
+
spaces_added += (tab_size - 1)
|
2260
|
+
full_tab_space
|
2261
|
+
else
|
2262
|
+
unless (spaces = tab_size - offset % tab_size) == 1
|
2263
|
+
spaces_added += (spaces - 1)
|
2264
|
+
end
|
2265
|
+
' ' * spaces
|
2266
|
+
end
|
2267
|
+
}
|
2268
|
+
end
|
2269
|
+
|
2270
|
+
# NOTE we save time by adding indent guard per line while performing tab expansion
|
2271
|
+
line[0] = GuardedIndent if leading_space
|
2272
|
+
result << line
|
2273
|
+
end
|
2274
|
+
result.join
|
2275
|
+
else
|
2276
|
+
if guard_indent
|
2277
|
+
string[0] = GuardedIndent if string.start_with? ' '
|
2278
|
+
string.gsub! InnerIndent, GuardedInnerIndent if string.include? InnerIndent
|
2279
|
+
end
|
2280
|
+
string
|
2281
|
+
end
|
2130
2282
|
end
|
2131
2283
|
|
2132
2284
|
# If an id is provided or the node passed as the first argument has an id,
|
@@ -2209,6 +2361,57 @@ class Converter < ::Prawn::Document
|
|
2209
2361
|
end
|
2210
2362
|
end
|
2211
2363
|
|
2364
|
+
# Resolve the path to the background image either from a document attribute or theme key.
|
2365
|
+
#
|
2366
|
+
# Returns The string "none" if the background image value is none, otherwise the resolved
|
2367
|
+
# path to the image. If neither the document attribute or theme key are specified, or
|
2368
|
+
# the image path cannot be resolved, return nil.
|
2369
|
+
def resolve_background_image doc, theme, key
|
2370
|
+
if (bg_image = (doc_attr_val = (doc.attr key)) || theme[(key.tr '-', '_').to_sym])
|
2371
|
+
return bg_image if bg_image == 'none'
|
2372
|
+
|
2373
|
+
if (bg_image.include? ':') && bg_image =~ ImageAttributeValueRx
|
2374
|
+
# QUESTION should we support width and height in this case?
|
2375
|
+
bg_image = $1
|
2376
|
+
end
|
2377
|
+
|
2378
|
+
if (bg_image = doc_attr_val ? (resolve_image_path doc, bg_image) :
|
2379
|
+
(ThemeLoader.resolve_theme_asset bg_image, (doc.attr 'pdf-stylesdir')))
|
2380
|
+
if ::File.readable? bg_image
|
2381
|
+
bg_image
|
2382
|
+
else
|
2383
|
+
warn %(asciidoctor: WARNING: #{key.tr '-', ' '} #{bg_image} not found or readable)
|
2384
|
+
nil
|
2385
|
+
end
|
2386
|
+
end
|
2387
|
+
end
|
2388
|
+
end
|
2389
|
+
|
2390
|
+
# Resolves the explicit width as a PDF pt value, if specified.
|
2391
|
+
#
|
2392
|
+
# Resolves the explicit width, first considering the pdfwidth attribute, then
|
2393
|
+
# the scaledwidth attribute and finally the width attribute. If the specified
|
2394
|
+
# value is in pixels, the value is scaled by 75% to perform approximate
|
2395
|
+
# CSS px to PDF pt conversion. If the resolved width is larger than the
|
2396
|
+
# max_width, the max_width value is returned.
|
2397
|
+
#--
|
2398
|
+
# QUESTION should we enforce positive result?
|
2399
|
+
def resolve_explicit_width attrs, max_width = bounds.width
|
2400
|
+
if attrs.key? 'pdfwidth'
|
2401
|
+
if (pdfwidth = attrs['pdfwidth']).end_with? '%'
|
2402
|
+
(pdfwidth.to_f / 100) * max_width
|
2403
|
+
else
|
2404
|
+
str_to_pt pdfwidth
|
2405
|
+
end
|
2406
|
+
elsif attrs.key? 'scaledwidth'
|
2407
|
+
(attrs['scaledwidth'].to_f / 100) * max_width
|
2408
|
+
elsif attrs.key? 'width'
|
2409
|
+
# QUESTION should we honor percentage width value?
|
2410
|
+
# NOTE scale width down 75% to convert px to pt; restrict width to bounds.width
|
2411
|
+
[max_width, attrs['width'].to_f * 0.75].min
|
2412
|
+
end
|
2413
|
+
end
|
2414
|
+
|
2212
2415
|
# QUESTION is there a better way to do this?
|
2213
2416
|
# I suppose we could have @tmp_files as an instance variable on converter instead
|
2214
2417
|
# It might be sufficient to delete temporary files once per conversion
|