asciidoctor-pdf 1.5.0.alpha.7 → 1.5.0.alpha.8
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/NOTICE.adoc +2 -2
- data/README.adoc +127 -128
- data/Rakefile +5 -4
- data/bin/asciidoctor-pdf +15 -2
- data/data/fonts/notoserif-regular-latin.ttf +0 -0
- data/data/themes/default-theme.yml +15 -13
- data/docs/theme-schema.json +114 -0
- data/docs/theming-guide.adoc +386 -132
- data/lib/asciidoctor-pdf/asciidoctor_ext.rb +2 -0
- data/lib/asciidoctor-pdf/asciidoctor_ext/image.rb +18 -0
- data/lib/asciidoctor-pdf/converter.rb +377 -221
- data/lib/asciidoctor-pdf/core_ext.rb +2 -0
- data/lib/asciidoctor-pdf/core_ext/array.rb +10 -4
- data/lib/asciidoctor-pdf/core_ext/numeric.rb +11 -0
- data/lib/asciidoctor-pdf/core_ext/ostruct.rb +1 -1
- data/lib/asciidoctor-pdf/formatted_text.rb +8 -0
- data/lib/asciidoctor-pdf/{prawn_ext/formatted_text → formatted_text}/formatter.rb +6 -9
- data/lib/asciidoctor-pdf/formatted_text/inline_destination_marker.rb +16 -0
- data/lib/asciidoctor-pdf/formatted_text/inline_image_arranger.rb +125 -0
- data/lib/asciidoctor-pdf/formatted_text/inline_image_renderer.rb +45 -0
- data/lib/asciidoctor-pdf/{prawn_ext/formatted_text → formatted_text}/parser.rb +252 -218
- data/lib/asciidoctor-pdf/{prawn_ext/formatted_text → formatted_text}/parser.treetop +18 -9
- data/lib/asciidoctor-pdf/{prawn_ext/formatted_text → formatted_text}/transform.rb +80 -69
- data/lib/asciidoctor-pdf/prawn_ext.rb +2 -2
- data/lib/asciidoctor-pdf/prawn_ext/extensions.rb +164 -35
- data/lib/asciidoctor-pdf/prawn_ext/formatted_text/fragment.rb +37 -0
- data/lib/asciidoctor-pdf/prawn_ext/images.rb +11 -9
- data/lib/asciidoctor-pdf/temporary_path.rb +9 -0
- data/lib/asciidoctor-pdf/theme_loader.rb +40 -33
- data/lib/asciidoctor-pdf/version.rb +1 -1
- metadata +30 -14
@@ -0,0 +1,18 @@
|
|
1
|
+
module Asciidoctor
|
2
|
+
module Image
|
3
|
+
class << self
|
4
|
+
def image_type path
|
5
|
+
(::File.extname path).downcase[1..-1]
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
def image_type
|
10
|
+
::File.extname(inline? ? target : (attr 'target')).downcase[1..-1]
|
11
|
+
end
|
12
|
+
|
13
|
+
def target_with_image_type
|
14
|
+
image_path = inline? ? (target) : (attr 'target')
|
15
|
+
[image_path, (::File.extname image_path).downcase[1..-1]]
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -1,14 +1,16 @@
|
|
1
1
|
# encoding: UTF-8
|
2
2
|
# TODO cleanup imports...decide what belongs in asciidoctor-pdf.rb
|
3
|
-
require_relative 'core_ext/array'
|
4
3
|
require 'prawn'
|
5
4
|
require 'prawn-svg'
|
6
5
|
require 'prawn/table'
|
7
6
|
require 'prawn/templates'
|
8
7
|
require 'prawn/icon'
|
8
|
+
require_relative 'core_ext'
|
9
9
|
require_relative 'pdf_core_ext'
|
10
|
+
require_relative 'temporary_path'
|
10
11
|
require_relative 'sanitizer'
|
11
12
|
require_relative 'prawn_ext'
|
13
|
+
require_relative 'formatted_text'
|
12
14
|
require_relative 'pdfmarks'
|
13
15
|
require_relative 'asciidoctor_ext'
|
14
16
|
require_relative 'theme_loader'
|
@@ -55,9 +57,19 @@ class Converter < ::Prawn::Document
|
|
55
57
|
circle: (unicode_char 0x25e6),
|
56
58
|
square: (unicode_char 0x25aa)
|
57
59
|
}
|
60
|
+
# NOTE Default theme font uses ballot boxes from FontAwesome
|
61
|
+
BallotBox = {
|
62
|
+
checked: (unicode_char 0x2611),
|
63
|
+
unchecked: (unicode_char 0x2610)
|
64
|
+
}
|
65
|
+
IconSets = ['fa', 'fi', 'octicon', 'pf']
|
66
|
+
MeasurementRxt = '\\d+(?:\\.\\d+)?(?:in|cm|mm|pt|)'
|
67
|
+
MeasurementPartsRx = /^(\d+(?:\.\d+)?)(in|mm|cm|pt|)$/
|
68
|
+
PageSizeRx = /^(?:\[(#{MeasurementRxt}), ?(#{MeasurementRxt})\]|(#{MeasurementRxt})(?: x |x)(#{MeasurementRxt})|\S+)$/
|
58
69
|
# CalloutExtractRx synced from /lib/asciidoctor.rb of Asciidoctor core
|
59
70
|
CalloutExtractRx = /(?:(?:\/\/|#|--|;;) ?)?(\\)?<!?(--|)(\d+)\2>(?=(?: ?\\?<!?\2\d+\2>)*$)/
|
60
71
|
ImageAttributeValueRx = /^image:{1,2}(.*?)\[(.*?)\]$/
|
72
|
+
LineScanRx = /\n|.+/
|
61
73
|
|
62
74
|
def initialize backend, opts
|
63
75
|
super
|
@@ -91,7 +103,7 @@ class Converter < ::Prawn::Document
|
|
91
103
|
if node.blocks?
|
92
104
|
node.content
|
93
105
|
elsif node.content_model != :compound && (string = node.content)
|
94
|
-
# TODO this content could be
|
106
|
+
# TODO this content could be cached on repeat invocations!
|
95
107
|
layout_prose string, opts
|
96
108
|
end
|
97
109
|
node.document.instance_variable_set :@converter, prev_converter if prev_converter
|
@@ -109,10 +121,10 @@ class Converter < ::Prawn::Document
|
|
109
121
|
# FIXME implement fitting and centering for SVG
|
110
122
|
# TODO implement image scaling (numeric value or "fit")
|
111
123
|
float { canvas { image @page_bg_image, position: :center, fit: [bounds.width, bounds.height] } }
|
112
|
-
elsif @page_bg_color
|
124
|
+
elsif @page_bg_color && @page_bg_color != 'FFFFFF'
|
113
125
|
fill_absolute_bounds @page_bg_color
|
114
126
|
end
|
115
|
-
end
|
127
|
+
end if respond_to? :on_page_create
|
116
128
|
|
117
129
|
layout_cover_page :front, doc
|
118
130
|
layout_title_page doc
|
@@ -121,12 +133,12 @@ class Converter < ::Prawn::Document
|
|
121
133
|
|
122
134
|
toc_start_page_num = page_number
|
123
135
|
num_toc_levels = (doc.attr 'toclevels', 2).to_i
|
124
|
-
if doc.attr? 'toc'
|
136
|
+
if (include_toc = doc.attr? 'toc')
|
125
137
|
toc_page_nums = ()
|
126
138
|
dry_run do
|
127
139
|
toc_page_nums = layout_toc doc, num_toc_levels, 1
|
128
140
|
end
|
129
|
-
# reserve pages for the toc
|
141
|
+
# NOTE reserve pages for the toc
|
130
142
|
toc_page_nums.each do
|
131
143
|
start_new_page
|
132
144
|
end
|
@@ -136,7 +148,7 @@ class Converter < ::Prawn::Document
|
|
136
148
|
font @theme.base_font_family, size: @theme.base_font_size
|
137
149
|
convert_content_for_block doc
|
138
150
|
|
139
|
-
toc_page_nums = if
|
151
|
+
toc_page_nums = if include_toc
|
140
152
|
layout_toc doc, num_toc_levels, toc_start_page_num, num_front_matter_pages
|
141
153
|
else
|
142
154
|
(0..-1)
|
@@ -177,9 +189,7 @@ class Converter < ::Prawn::Document
|
|
177
189
|
end
|
178
190
|
end
|
179
191
|
@fallback_fonts = [*theme.font_fallbacks]
|
180
|
-
|
181
|
-
@page_bg_color = nil
|
182
|
-
end
|
192
|
+
@page_bg_color = resolve_theme_color :page_background_color, 'FFFFFF'
|
183
193
|
@font_color = theme.base_font_color || '000000'
|
184
194
|
@text_transform = nil
|
185
195
|
@stamps = {}
|
@@ -197,37 +207,39 @@ class Converter < ::Prawn::Document
|
|
197
207
|
skip_page_creation: true,
|
198
208
|
}
|
199
209
|
|
200
|
-
if doc.attr? 'pdf-page-size'
|
201
|
-
|
210
|
+
page_size = if (doc.attr? 'pdf-page-size') && (m = PageSizeRx.match(doc.attr 'pdf-page-size'))
|
211
|
+
# e.g, [8.5in, 11in]
|
212
|
+
if m[1]
|
213
|
+
[m[1], m[2]]
|
214
|
+
# e.g, 8.5in x 11in
|
215
|
+
elsif m[3]
|
216
|
+
[m[3], m[4]]
|
217
|
+
# e.g, A4
|
218
|
+
else
|
219
|
+
m[0]
|
220
|
+
end
|
202
221
|
else
|
203
|
-
|
222
|
+
theme.page_size
|
204
223
|
end
|
205
224
|
|
206
|
-
|
225
|
+
page_size = case page_size
|
207
226
|
when ::String
|
227
|
+
# TODO extract helper method to check for named page size
|
208
228
|
if ::PDF::Core::PageGeometry::SIZES.key?(page_size = page_size.upcase)
|
209
229
|
page_size
|
210
|
-
else
|
211
|
-
'LETTER'
|
212
230
|
end
|
213
231
|
when ::Array
|
214
|
-
page_size.
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
when 'mm'
|
224
|
-
val * (72 / 25.4)
|
225
|
-
when 'cm'
|
226
|
-
val * (720 / 25.4)
|
227
|
-
when 'pt'
|
228
|
-
val
|
229
|
-
end
|
232
|
+
unless page_size.size == 2
|
233
|
+
page_size = page_size[0..1].fill(0..1) {|i| page_size[i] || 0}
|
234
|
+
end
|
235
|
+
page_size.map do |dim|
|
236
|
+
if ::Numeric === dim
|
237
|
+
# dimension cannot be less than 0
|
238
|
+
dim > 0 ? dim : break
|
239
|
+
elsif ::String === dim && (m = (MeasurementPartsRx.match dim))
|
240
|
+
val = to_pt m[1].to_f, m[2]
|
230
241
|
# NOTE 4 is the max practical precision in PDFs
|
242
|
+
# QUESTION should we make rounding a feature of the to_pt method?
|
231
243
|
if (val = val.round 4) == (i_val = val.to_i)
|
232
244
|
val = i_val
|
233
245
|
end
|
@@ -235,13 +247,12 @@ class Converter < ::Prawn::Document
|
|
235
247
|
else
|
236
248
|
break
|
237
249
|
end
|
238
|
-
|
250
|
+
end
|
239
251
|
end
|
240
252
|
|
241
|
-
pdf_opts[:page_size]
|
253
|
+
pdf_opts[:page_size] = (page_size || 'LETTER')
|
242
254
|
|
243
|
-
|
244
|
-
pdf_opts[:text_formatter] ||= ::Asciidoctor::Prawn::FormattedTextFormatter.new theme: theme
|
255
|
+
pdf_opts[:text_formatter] ||= FormattedText::Formatter.new theme: theme
|
245
256
|
pdf_opts
|
246
257
|
end
|
247
258
|
|
@@ -328,7 +339,6 @@ class Converter < ::Prawn::Document
|
|
328
339
|
end
|
329
340
|
end
|
330
341
|
# QUESTION should we be adding margin below the abstract??
|
331
|
-
#move_down @theme.block_margin_bottom
|
332
342
|
#theme_margin :block, :bottom
|
333
343
|
end
|
334
344
|
|
@@ -353,6 +363,8 @@ class Converter < ::Prawn::Document
|
|
353
363
|
prose_opts[:align] = :right
|
354
364
|
when 'text-justify'
|
355
365
|
prose_opts[:align] = :justify
|
366
|
+
when 'text-center'
|
367
|
+
prose_opts[:align] = :center
|
356
368
|
when 'lead'
|
357
369
|
is_lead = true
|
358
370
|
#when 'signature'
|
@@ -376,7 +388,6 @@ class Converter < ::Prawn::Document
|
|
376
388
|
# FIXME alignment of content is off
|
377
389
|
def convert_admonition node
|
378
390
|
add_dest_for_block node if node.id
|
379
|
-
#move_down @theme.block_margin_top unless at_page_top?
|
380
391
|
theme_margin :block, :top
|
381
392
|
icons = node.document.attr? 'icons', 'font'
|
382
393
|
label = icons ? (node.attr 'name').to_sym : node.caption.upcase
|
@@ -417,13 +428,11 @@ class Converter < ::Prawn::Document
|
|
417
428
|
end
|
418
429
|
#end
|
419
430
|
end
|
420
|
-
#move_down @theme.block_margin_bottom
|
421
431
|
theme_margin :block, :bottom
|
422
432
|
end
|
423
433
|
|
424
434
|
def convert_example node
|
425
435
|
add_dest_for_block node if node.id
|
426
|
-
#move_down @theme.block_margin_top unless at_page_top?
|
427
436
|
theme_margin :block, :top
|
428
437
|
keep_together do |box_height = nil|
|
429
438
|
caption_height = node.title? ? (layout_caption node) : 0
|
@@ -440,7 +449,6 @@ class Converter < ::Prawn::Document
|
|
440
449
|
end
|
441
450
|
end
|
442
451
|
end
|
443
|
-
#move_down @theme.block_margin_bottom
|
444
452
|
theme_margin :block, :bottom
|
445
453
|
end
|
446
454
|
|
@@ -465,7 +473,6 @@ class Converter < ::Prawn::Document
|
|
465
473
|
def convert_quote_or_verse node
|
466
474
|
add_dest_for_block node if node.id
|
467
475
|
border_width = @theme.blockquote_border_width || 0
|
468
|
-
#move_down @theme.block_margin_top unless at_page_top?
|
469
476
|
theme_margin :block, :top
|
470
477
|
keep_together do |box_height = nil|
|
471
478
|
start_cursor = cursor
|
@@ -493,7 +500,6 @@ class Converter < ::Prawn::Document
|
|
493
500
|
end
|
494
501
|
end
|
495
502
|
end
|
496
|
-
#move_down @theme.block_margin_bottom
|
497
503
|
theme_margin :block, :bottom
|
498
504
|
end
|
499
505
|
|
@@ -502,7 +508,6 @@ class Converter < ::Prawn::Document
|
|
502
508
|
|
503
509
|
def convert_sidebar node
|
504
510
|
add_dest_for_block node if node.id
|
505
|
-
#move_down @theme.block_margin_top unless at_page_top?
|
506
511
|
theme_margin :block, :top
|
507
512
|
keep_together do |box_height = nil|
|
508
513
|
if box_height
|
@@ -526,19 +531,21 @@ class Converter < ::Prawn::Document
|
|
526
531
|
move_up(@theme.prose_margin_bottom || @theme.vertical_rhythm)
|
527
532
|
end
|
528
533
|
end
|
529
|
-
#move_down @theme.block_margin_bottom
|
530
534
|
theme_margin :block, :bottom
|
531
535
|
end
|
532
536
|
|
533
537
|
def convert_colist node
|
534
538
|
# HACK undo the margin below previous listing or literal block
|
535
539
|
# TODO allow this to be set using colist_margin_top
|
536
|
-
unless at_page_top?
|
537
|
-
|
538
|
-
|
539
|
-
|
540
|
-
|
541
|
-
|
540
|
+
unless at_page_top?
|
541
|
+
# NOTE this logic won't work for a colist nested inside a list item until Asciidoctor 1.5.3
|
542
|
+
if (self_idx = node.parent.blocks.index node) && self_idx > 0 &&
|
543
|
+
[:listing, :literal].include?(node.parent.blocks[self_idx - 1].context)
|
544
|
+
move_up ((@theme.block_margin_bottom || @theme.vertical_rhythm) / 2.0)
|
545
|
+
# or we could do...
|
546
|
+
#move_up (@theme.block_margin_bottom || @theme.vertical_rhythm)
|
547
|
+
#move_down (@theme.caption_margin_inside * 2)
|
548
|
+
end
|
542
549
|
end
|
543
550
|
add_dest_for_block node if node.id
|
544
551
|
@list_numbers ||= []
|
@@ -584,7 +591,7 @@ class Converter < ::Prawn::Document
|
|
584
591
|
# FIXME extract ensure_space (or similar) method
|
585
592
|
start_new_page if cursor < @theme.base_line_height_length * (terms.size + 1)
|
586
593
|
terms.each do |term|
|
587
|
-
layout_prose term.text, style: @theme.description_list_term_font_style.to_sym, margin_top: 0, margin_bottom: (@theme.vertical_rhythm / 3.0), align: :left
|
594
|
+
layout_prose term.text, style: (@theme.description_list_term_font_style || :normal).to_sym, margin_top: 0, margin_bottom: (@theme.vertical_rhythm / 3.0), align: :left
|
588
595
|
end
|
589
596
|
if desc
|
590
597
|
indent @theme.description_list_description_indent do
|
@@ -623,27 +630,30 @@ class Converter < ::Prawn::Document
|
|
623
630
|
@list_numbers.pop
|
624
631
|
end
|
625
632
|
|
626
|
-
# TODO implement checklist
|
627
633
|
def convert_ulist node
|
628
634
|
add_dest_for_block node if node.id
|
629
|
-
|
630
|
-
|
631
|
-
when 'bibliography'
|
632
|
-
:square
|
633
|
-
else
|
634
|
-
style.to_sym
|
635
|
-
end
|
635
|
+
if node.option? 'checklist'
|
636
|
+
@list_bullets << :checkbox
|
636
637
|
else
|
637
|
-
|
638
|
-
|
639
|
-
|
640
|
-
|
641
|
-
|
642
|
-
|
643
|
-
|
638
|
+
bullet_type = if (style = node.style)
|
639
|
+
case style
|
640
|
+
when 'bibliography'
|
641
|
+
:square
|
642
|
+
else
|
643
|
+
style.to_sym
|
644
|
+
end
|
645
|
+
else
|
646
|
+
case (node.level % 3)
|
647
|
+
when 1
|
648
|
+
:disc
|
649
|
+
when 2
|
650
|
+
:circle
|
651
|
+
when 0
|
652
|
+
:square
|
653
|
+
end
|
644
654
|
end
|
655
|
+
@list_bullets << Bullets[bullet_type]
|
645
656
|
end
|
646
|
-
@list_bullets << Bullets[bullet_type]
|
647
657
|
convert_outline_list node
|
648
658
|
@list_bullets.pop
|
649
659
|
end
|
@@ -671,28 +681,39 @@ class Converter < ::Prawn::Document
|
|
671
681
|
|
672
682
|
def convert_outline_list_item node, complex = false
|
673
683
|
# TODO move this to a draw_bullet (or draw_marker) method
|
674
|
-
|
684
|
+
case (list_type = node.parent.context)
|
675
685
|
when :ulist
|
676
|
-
@list_bullets.last
|
686
|
+
marker = @list_bullets.last
|
687
|
+
if marker == :checkbox
|
688
|
+
if node.attr? 'checkbox'
|
689
|
+
marker = BallotBox[(node.attr? 'checked') ? :checked : :unchecked]
|
690
|
+
else
|
691
|
+
# QUESTION should we remove marker indent in this case?
|
692
|
+
marker = nil
|
693
|
+
end
|
694
|
+
end
|
677
695
|
when :olist
|
678
696
|
@list_numbers << (index = @list_numbers.pop).next
|
679
|
-
%(#{index}.)
|
697
|
+
marker = %(#{index}.)
|
680
698
|
else
|
681
699
|
warn %(asciidoctor: WARNING: unknown list type #{list_type.inspect})
|
682
|
-
Bullets[:disc]
|
700
|
+
marker = Bullets[:disc]
|
683
701
|
end
|
684
702
|
|
685
|
-
|
686
|
-
|
687
|
-
|
688
|
-
|
689
|
-
|
690
|
-
|
691
|
-
|
692
|
-
|
693
|
-
|
694
|
-
|
695
|
-
|
703
|
+
if marker
|
704
|
+
marker_width = width_of marker
|
705
|
+
start_position = -marker_width + -(width_of 'x')
|
706
|
+
float do
|
707
|
+
bounding_box [start_position, cursor], width: marker_width do
|
708
|
+
layout_prose marker,
|
709
|
+
align: :right,
|
710
|
+
color: (@theme.outline_list_marker_font_color || @font_color),
|
711
|
+
normalize: false,
|
712
|
+
inline_format: false,
|
713
|
+
margin: 0,
|
714
|
+
character_spacing: -0.5,
|
715
|
+
single_line: true
|
716
|
+
end
|
696
717
|
end
|
697
718
|
end
|
698
719
|
|
@@ -713,14 +734,13 @@ class Converter < ::Prawn::Document
|
|
713
734
|
end
|
714
735
|
|
715
736
|
def convert_image node
|
737
|
+
node.extend ::Asciidoctor::Image unless ::Asciidoctor::Image === node
|
716
738
|
valid_image = true
|
717
|
-
target = node.
|
718
|
-
# TODO file extension should be an attribute on an image node
|
719
|
-
image_type = (::File.extname target)[1..-1].downcase
|
739
|
+
target, image_type = node.target_with_image_type
|
720
740
|
|
721
741
|
if image_type == 'gif'
|
722
742
|
valid_image = false
|
723
|
-
warn %(asciidoctor: WARNING: GIF image format not supported. Please convert
|
743
|
+
warn %(asciidoctor: WARNING: GIF image format not supported. Please convert #{target} to PNG.)
|
724
744
|
#elsif image_type == 'pdf'
|
725
745
|
# import_page image_path
|
726
746
|
# return
|
@@ -731,11 +751,22 @@ class Converter < ::Prawn::Document
|
|
731
751
|
warn %(asciidoctor: WARNING: image to embed not found or not readable: #{image_path || target})
|
732
752
|
end
|
733
753
|
|
754
|
+
# QUESTION if we advance to new page, shouldn't dest point there too?
|
734
755
|
add_dest_for_block node if node.id
|
756
|
+
position = ((node.attr 'align') || @theme.image_align || :left).to_sym
|
735
757
|
|
736
758
|
unless valid_image
|
737
759
|
theme_margin :block, :top
|
738
|
-
|
760
|
+
if (link = node.attr 'link')
|
761
|
+
alt_text = %(<a href="#{link}">[#{NoBreakSpace}#{node.attr 'alt'}#{NoBreakSpace}]</a> | <em>#{target}</em>)
|
762
|
+
else
|
763
|
+
alt_text = %([#{NoBreakSpace}#{node.attr 'alt'}#{NoBreakSpace}] | <em>#{target}</em>)
|
764
|
+
end
|
765
|
+
layout_prose alt_text,
|
766
|
+
normalize: false,
|
767
|
+
margin: 0,
|
768
|
+
single_line: true,
|
769
|
+
align: position
|
739
770
|
layout_caption node, position: :bottom if node.title?
|
740
771
|
theme_margin :block, :bottom
|
741
772
|
return
|
@@ -744,37 +775,35 @@ class Converter < ::Prawn::Document
|
|
744
775
|
theme_margin :block, :top
|
745
776
|
|
746
777
|
# TODO support cover (aka canvas) image layout using "canvas" (or "cover") role
|
747
|
-
|
748
|
-
|
749
|
-
|
750
|
-
|
778
|
+
# NOTE height values are basically ignored (image is scaled proportionally based on width)
|
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
|
751
787
|
elsif node.attr? 'width'
|
752
|
-
|
753
|
-
|
754
|
-
bounds.width * (@theme.image_scaled_width_default || 0.75)
|
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
|
755
790
|
end
|
756
|
-
|
791
|
+
|
757
792
|
case image_type
|
758
793
|
when 'svg'
|
759
|
-
# NOTE prawn-svg can't position, so we have to do it manually (file issue?)
|
760
|
-
left = case position
|
761
|
-
when :left
|
762
|
-
0
|
763
|
-
when :right
|
764
|
-
bounds.width - width
|
765
|
-
when :center
|
766
|
-
((bounds.width - width) / 2.0).floor
|
767
|
-
end
|
768
794
|
begin
|
769
|
-
|
795
|
+
svg_obj = ::Prawn::Svg::Interface.new (::IO.read image_path), self, position: position, width: width
|
796
|
+
actual_w = svg_obj.document.sizing.output_width
|
797
|
+
actual_h = svg_obj.document.sizing.output_height
|
798
|
+
rel_left = { left: 0, right: (bounds.width - actual_w), center: ((bounds.width - actual_w) / 2.0) }[position]
|
799
|
+
# TODO layout SVG without using keep_together (since we know the dimensions already); always render caption
|
770
800
|
keep_together do |box_height = nil|
|
801
|
+
svg_obj.instance_variable_set :@prawn, self
|
802
|
+
svg_obj.draw
|
771
803
|
if box_height && (link = node.attr 'link')
|
772
|
-
|
773
|
-
|
774
|
-
|
775
|
-
A: { Type: :Action, S: :URI, URI: (str2pdfval link) }
|
776
|
-
else
|
777
|
-
svg img_data, at: [left, cursor], width: width
|
804
|
+
link_annotation [(abs_left = rel_left + bounds.absolute_left), y, (abs_left + actual_w), (y + actual_h)],
|
805
|
+
Border: [0, 0, 0],
|
806
|
+
A: { Type: :Action, S: :URI, URI: (str2pdfval link) }
|
778
807
|
end
|
779
808
|
layout_caption node, position: :bottom if node.title?
|
780
809
|
end
|
@@ -784,32 +813,37 @@ class Converter < ::Prawn::Document
|
|
784
813
|
else
|
785
814
|
begin
|
786
815
|
# FIXME temporary workaround to group caption & image
|
787
|
-
# Prawn doesn't provide access to rendered width
|
788
|
-
# image on the page
|
816
|
+
# Prawn doesn't provide access to rendered width & height before placing image on page
|
789
817
|
# FIXME this code really needs to be better organized!
|
790
818
|
image_obj, image_info = build_image_object image_path
|
791
|
-
|
819
|
+
if width
|
820
|
+
rendered_w, rendered_h = image_info.calc_image_dimensions width: width
|
821
|
+
else
|
822
|
+
# NOTE native size is in pixels, so scale by 75%; restrict to max width of bounds.width
|
823
|
+
rendered_w = [bounds.width, image_info.width * 0.75].min
|
824
|
+
rendered_h = (rendered_w * image_info.height) / image_info.width
|
825
|
+
end
|
792
826
|
# TODO move this calculation into a method
|
793
827
|
caption_height = node.title? ?
|
794
828
|
(@theme.caption_margin_inside + @theme.caption_margin_outside + @theme.base_line_height_length) : 0
|
795
|
-
|
796
|
-
|
797
|
-
|
798
|
-
if
|
799
|
-
|
800
|
-
|
829
|
+
if rendered_h > (available_height = cursor - caption_height)
|
830
|
+
start_new_page unless at_page_top?
|
831
|
+
# NOTE shrink image so it fits on a single page
|
832
|
+
if rendered_h > (available_height = cursor - caption_height)
|
833
|
+
rendered_w = (rendered_w * available_height) / rendered_h
|
834
|
+
rendered_h = available_height
|
801
835
|
# FIXME workaround to fix Prawn not adding fill and stroke commands
|
802
836
|
# on page that only has an image; breakage occurs when line numbers are added
|
803
837
|
fill_color self.fill_color
|
804
838
|
stroke_color self.stroke_color
|
805
839
|
end
|
806
840
|
end
|
841
|
+
# NOTE must calculate link position before embedding to get proper boundaries
|
807
842
|
if (link = node.attr 'link')
|
808
|
-
|
809
|
-
img_x, img_y
|
810
|
-
link_box = [img_x, (img_y - actual_h), img_x + actual_w, img_y]
|
843
|
+
img_x, img_y = image_position rendered_w, rendered_h, position: position
|
844
|
+
link_box = [img_x, (img_y - rendered_h), (img_x + rendered_w), img_y]
|
811
845
|
end
|
812
|
-
embed_image image_obj, image_info, width:
|
846
|
+
embed_image image_obj, image_info, width: rendered_w, position: position
|
813
847
|
if link
|
814
848
|
link_annotation link_box,
|
815
849
|
Border: [0, 0, 0],
|
@@ -825,7 +859,7 @@ class Converter < ::Prawn::Document
|
|
825
859
|
unlink_tmp_file image_path
|
826
860
|
end
|
827
861
|
|
828
|
-
#
|
862
|
+
# QUESTION can we avoid arranging fragments multiple times (conums & autofit) by eagerly preparing arranger?
|
829
863
|
def convert_listing_or_literal node
|
830
864
|
add_dest_for_block node if node.id
|
831
865
|
# HACK disable built-in syntax highlighter; must be done before calling node.content!
|
@@ -834,13 +868,11 @@ class Converter < ::Prawn::Document
|
|
834
868
|
highlighter = node.document.attr 'source-highlighter'
|
835
869
|
# NOTE the source highlighter logic below handles the callouts and highlight subs
|
836
870
|
prev_subs = subs.dup
|
837
|
-
subs.
|
838
|
-
subs.delete :highlight
|
871
|
+
subs.delete_all :highlight, :callouts
|
839
872
|
else
|
840
873
|
highlighter = nil
|
841
874
|
prev_subs = nil
|
842
875
|
end
|
843
|
-
# FIXME source highlighter freaks out about the non-breaking space characters; does it?
|
844
876
|
source_string = preserve_indentation node.content
|
845
877
|
source_chunks = case highlighter
|
846
878
|
when 'coderay'
|
@@ -858,28 +890,55 @@ class Converter < ::Prawn::Document
|
|
858
890
|
conum_mapping ? (restore_conums fragments, conum_mapping) : fragments
|
859
891
|
else
|
860
892
|
# NOTE only format if we detect a need
|
861
|
-
|
893
|
+
if source_string =~ BuiltInEntityCharOrTagRx
|
894
|
+
text_formatter.format source_string
|
895
|
+
else
|
896
|
+
[{ text: source_string }]
|
897
|
+
end
|
862
898
|
end
|
863
899
|
|
864
900
|
node.subs.replace prev_subs if prev_subs
|
865
901
|
|
866
|
-
#move_down @theme.block_margin_top unless at_page_top?
|
867
902
|
theme_margin :block, :top
|
868
903
|
|
904
|
+
if (node.option? 'autofit') || (node.document.attr? 'autofit-option')
|
905
|
+
adjusted_font_size = theme_font_size_autofit source_chunks, :code
|
906
|
+
else
|
907
|
+
adjusted_font_size = nil
|
908
|
+
end
|
909
|
+
|
869
910
|
keep_together do |box_height = nil|
|
870
911
|
caption_height = node.title? ? (layout_caption node) : 0
|
871
912
|
theme_font :code do
|
872
913
|
if box_height
|
873
914
|
float do
|
874
|
-
#
|
875
|
-
|
915
|
+
# TODO move the multi-page logic to theme_fill_and_stroke_bounds
|
916
|
+
unless (b_width = @theme.code_border_width || 0) == 0
|
917
|
+
b_radius = (@theme.code_border_radius || 0) + b_width
|
918
|
+
bg_color = @theme.code_background_color || @page_bg_color
|
919
|
+
end
|
876
920
|
remaining_height = box_height - caption_height
|
877
921
|
i = 0
|
878
922
|
while remaining_height > 0
|
879
|
-
start_new_page if i > 0
|
923
|
+
start_new_page if (new_page_started = i > 0)
|
880
924
|
fill_height = [remaining_height, cursor].min
|
881
925
|
bounding_box [0, cursor], width: bounds.width, height: fill_height do
|
882
926
|
theme_fill_and_stroke_bounds :code
|
927
|
+
unless b_width == 0
|
928
|
+
if new_page_started
|
929
|
+
indent b_radius, b_radius do
|
930
|
+
# dashed line to indicate continuation from previous page
|
931
|
+
stroke_horizontal_rule bg_color, line_width: b_width, line_style: :dashed
|
932
|
+
end
|
933
|
+
end
|
934
|
+
if remaining_height > fill_height
|
935
|
+
move_down fill_height
|
936
|
+
indent b_radius, b_radius do
|
937
|
+
# dashed line to indicate continuation on next page
|
938
|
+
stroke_horizontal_rule bg_color, line_width: b_width, line_style: :dashed
|
939
|
+
end
|
940
|
+
end
|
941
|
+
end
|
883
942
|
end
|
884
943
|
remaining_height -= fill_height
|
885
944
|
i += 1
|
@@ -888,13 +947,14 @@ class Converter < ::Prawn::Document
|
|
888
947
|
end
|
889
948
|
|
890
949
|
pad_box @theme.code_padding do
|
891
|
-
typeset_formatted_text source_chunks, (calc_line_metrics @theme.code_line_height),
|
950
|
+
typeset_formatted_text source_chunks, (calc_line_metrics @theme.code_line_height),
|
951
|
+
color: (@theme.code_font_color || @font_color),
|
952
|
+
size: adjusted_font_size
|
892
953
|
end
|
893
954
|
end
|
894
955
|
end
|
895
956
|
stroke_horizontal_rule @theme.caption_border_bottom_color if node.title? && @theme.caption_border_bottom_color
|
896
957
|
|
897
|
-
#move_down @theme.block_margin_bottom
|
898
958
|
theme_margin :block, :bottom
|
899
959
|
end
|
900
960
|
|
@@ -923,6 +983,9 @@ class Converter < ::Prawn::Document
|
|
923
983
|
end
|
924
984
|
|
925
985
|
# Restore the conums into the Array of formatted text fragments
|
986
|
+
#--
|
987
|
+
# QUESTION can this be done more efficiently?
|
988
|
+
# QUESTION can we reuse arrange_fragments_by_line?
|
926
989
|
def restore_conums fragments, conum_mapping
|
927
990
|
lines = []
|
928
991
|
line_num = 0
|
@@ -943,16 +1006,16 @@ class Converter < ::Prawn::Document
|
|
943
1006
|
conum_color = @theme.conum_font_color
|
944
1007
|
last_line_num = lines.size - 1
|
945
1008
|
# append conums to appropriate lines, then flatten to an array of fragments
|
946
|
-
lines.flat_map.with_index do |line,
|
947
|
-
if (conums = conum_mapping.delete
|
1009
|
+
lines.flat_map.with_index do |line, cur_line_num|
|
1010
|
+
if (conums = conum_mapping.delete cur_line_num)
|
948
1011
|
conums = conums.map {|num| conum_glyph num }
|
949
1012
|
# ensure there's at least one space between content and conum(s)
|
950
1013
|
if line.size > 0 && (end_text = line.last[:text]) && !(end_text.end_with? ' ')
|
951
1014
|
line.last[:text] = %(#{end_text} )
|
952
1015
|
end
|
953
|
-
line << { text: (conums * ' '), color: conum_color }
|
1016
|
+
line << (conum_color ? { text: (conums * ' '), color: conum_color } : { text: (conums * ' ') })
|
954
1017
|
end
|
955
|
-
line << { text: EOL } unless
|
1018
|
+
line << { text: EOL } unless cur_line_num == last_line_num
|
956
1019
|
line
|
957
1020
|
end
|
958
1021
|
end
|
@@ -973,30 +1036,16 @@ class Converter < ::Prawn::Document
|
|
973
1036
|
table_header = false
|
974
1037
|
theme = @theme
|
975
1038
|
|
976
|
-
#
|
977
|
-
|
978
|
-
|
979
|
-
|
980
|
-
|
981
|
-
unless (bg_color = theme.table_background_color) && bg_color != 'transparent'
|
982
|
-
bg_color = page_bg_color
|
983
|
-
end
|
984
|
-
|
985
|
-
unless (head_bg_color = theme.table_head_background_color) && head_bg_color != 'transparent'
|
986
|
-
head_bg_color = bg_color
|
987
|
-
end
|
988
|
-
|
989
|
-
unless (foot_bg_color = theme.table_foot_background_color) && foot_bg_color != 'transparent'
|
990
|
-
foot_bg_color = bg_color
|
991
|
-
end
|
992
|
-
|
993
|
-
unless (odd_row_bg_color = theme.table_odd_row_background_color) && odd_row_bg_color != 'transparent'
|
994
|
-
odd_row_bg_color = bg_color
|
995
|
-
end
|
996
|
-
|
997
|
-
unless (even_row_bg_color = theme.table_even_row_background_color) && even_row_bg_color != 'transparent'
|
998
|
-
even_row_bg_color = bg_color
|
1039
|
+
# NOTE use an explicit white background if no background color is set and table is nested inside a block
|
1040
|
+
if (tbl_bg_color = resolve_theme_color :table_background_color, @page_bg_color)
|
1041
|
+
tbl_bg_color = nil if tbl_bg_color == 'FFFFFF' && node.parent.context == :section
|
1042
|
+
else
|
1043
|
+
tbl_bg_color = 'FFFFFF' unless node.parent.context == :section
|
999
1044
|
end
|
1045
|
+
head_bg_color = resolve_theme_color :table_head_background_color, tbl_bg_color
|
1046
|
+
foot_bg_color = resolve_theme_color :table_foot_background_color, tbl_bg_color
|
1047
|
+
odd_row_bg_color = resolve_theme_color :table_odd_row_background_color, tbl_bg_color
|
1048
|
+
even_row_bg_color = resolve_theme_color :table_even_row_background_color, tbl_bg_color
|
1000
1049
|
|
1001
1050
|
table_data = []
|
1002
1051
|
node.rows[:head].each do |rows|
|
@@ -1141,16 +1190,19 @@ class Converter < ::Prawn::Document
|
|
1141
1190
|
end
|
1142
1191
|
|
1143
1192
|
def convert_thematic_break node
|
1144
|
-
#move_down @theme.thematic_break_margin_top
|
1145
1193
|
theme_margin :thematic_break, :top
|
1146
|
-
stroke_horizontal_rule @theme.thematic_break_border_color, line_width: @theme.thematic_break_border_width
|
1147
|
-
#move_down @theme.thematic_break_margin_bottom
|
1194
|
+
stroke_horizontal_rule @theme.thematic_break_border_color, line_width: @theme.thematic_break_border_width, line_style: (@theme.thematic_break_border_style || :solid).to_sym
|
1148
1195
|
theme_margin :thematic_break, :bottom
|
1149
1196
|
end
|
1150
1197
|
|
1151
1198
|
# deprecated
|
1152
1199
|
alias :convert_horizontal_rule :convert_thematic_break
|
1153
1200
|
|
1201
|
+
# NOTE manual placement not yet possible, so return nil
|
1202
|
+
def convert_toc node
|
1203
|
+
nil
|
1204
|
+
end
|
1205
|
+
|
1154
1206
|
def convert_page_break node
|
1155
1207
|
start_new_page unless at_page_top?
|
1156
1208
|
end
|
@@ -1208,7 +1260,7 @@ class Converter < ::Prawn::Document
|
|
1208
1260
|
end
|
1209
1261
|
|
1210
1262
|
def convert_inline_button node
|
1211
|
-
%(<
|
1263
|
+
%(<strong>[#{NarrowNoBreakSpace}#{node.text}#{NarrowNoBreakSpace}]</strong>)
|
1212
1264
|
end
|
1213
1265
|
|
1214
1266
|
def convert_inline_callout node
|
@@ -1216,20 +1268,65 @@ class Converter < ::Prawn::Document
|
|
1216
1268
|
# NOTE CMYK value gets flattened here, but is restored by formatted text parser
|
1217
1269
|
%(<color rgb="#{conum_color}">#{conum_glyph node.text.to_i}</color>)
|
1218
1270
|
else
|
1219
|
-
node.text
|
1271
|
+
conum_glyph node.text.to_i
|
1220
1272
|
end
|
1221
1273
|
end
|
1222
1274
|
|
1223
1275
|
def convert_inline_footnote node
|
1224
1276
|
if (index = node.attr 'index')
|
1225
1277
|
#text = node.document.footnotes.find {|fn| fn.index == index }.text
|
1226
|
-
%(
|
1278
|
+
%(<sup>[#{index}: #{node.text}]</sup>)
|
1227
1279
|
elsif node.type == :xref
|
1228
1280
|
# NOTE footnote reference not found
|
1229
|
-
%(
|
1281
|
+
%(<sup><color rgb="FF0000">[#{node.text}]</color></sup>)
|
1230
1282
|
end
|
1231
1283
|
end
|
1232
1284
|
|
1285
|
+
def convert_inline_image node
|
1286
|
+
img = nil
|
1287
|
+
if node.type == 'icon'
|
1288
|
+
if node.document.attr? 'icons', 'font'
|
1289
|
+
if (icon_name = node.target).include? '@'
|
1290
|
+
icon_name, icon_set = icon_name.split '@', 2
|
1291
|
+
else
|
1292
|
+
icon_set = node.attr 'set', (node.document.attr 'icon-set', 'fa')
|
1293
|
+
end
|
1294
|
+
icon_set = 'fa' unless IconSets.include? icon_set
|
1295
|
+
if node.attr? 'size'
|
1296
|
+
size = (size = (node.attr 'size')) == 'lg' ? '1.3333em' : (size.sub 'x', 'em')
|
1297
|
+
size_attr = %( size="#{size}")
|
1298
|
+
else
|
1299
|
+
size_attr = nil
|
1300
|
+
end
|
1301
|
+
@icon_font_data ||= ::Prawn::Icon::FontData.load self, icon_set
|
1302
|
+
begin
|
1303
|
+
# TODO support rotate and flip attributes; support fw (full-width) size
|
1304
|
+
img = %(<font name="#{icon_set}"#{size_attr}>#{@icon_font_data.unicode icon_name}</font>)
|
1305
|
+
rescue
|
1306
|
+
warn %(asciidoctor: WARNING: #{icon_name} is not a valid icon name in the #{icon_set} icon set)
|
1307
|
+
end
|
1308
|
+
end
|
1309
|
+
else
|
1310
|
+
node.extend ::Asciidoctor::Image unless ::Asciidoctor::Image === node
|
1311
|
+
target, image_type = node.target_with_image_type
|
1312
|
+
valid = true
|
1313
|
+
if image_type == 'gif'
|
1314
|
+
warn %(asciidoctor: WARNING: GIF image format not supported. Please convert #{target} to PNG.) unless scratch?
|
1315
|
+
valid = false
|
1316
|
+
end
|
1317
|
+
unless (image_path = resolve_image_path node, target) && (::File.readable? image_path)
|
1318
|
+
warn %(asciidoctor: WARNING: image to embed not found or not readable: #{image_path || target}) unless scratch?
|
1319
|
+
valid = false
|
1320
|
+
end
|
1321
|
+
if valid
|
1322
|
+
width_attr = (node.attr? 'width') ? %( width="#{node.attr 'width'}") : nil
|
1323
|
+
img = %(<img src="#{image_path}" type="#{image_type}" alt="#{node.attr 'alt'}"#{width_attr} tmp="#{TemporaryPath === image_path}">)
|
1324
|
+
end
|
1325
|
+
end
|
1326
|
+
img ||= %([#{node.attr 'alt'}])
|
1327
|
+
(node.attr? 'link') ? %(<a href="#{node.attr 'link'}">#{img}</a>) : img
|
1328
|
+
end
|
1329
|
+
|
1233
1330
|
def convert_inline_indexterm node
|
1234
1331
|
node.type == :visible ? node.text : nil
|
1235
1332
|
end
|
@@ -1317,7 +1414,7 @@ class Converter < ::Prawn::Document
|
|
1317
1414
|
end
|
1318
1415
|
end
|
1319
1416
|
end
|
1320
|
-
if !bg_image && (bg_color =
|
1417
|
+
if !bg_image && (bg_color = resolve_theme_color :title_page_background_color)
|
1321
1418
|
@page_bg_color = bg_color
|
1322
1419
|
else
|
1323
1420
|
bg_color = nil
|
@@ -1419,7 +1516,7 @@ class Converter < ::Prawn::Document
|
|
1419
1516
|
end
|
1420
1517
|
# QUESTION should we go to page 1 when position == :front?
|
1421
1518
|
go_to_page page_count if position == :back
|
1422
|
-
if
|
1519
|
+
if cover_image.downcase.end_with? '.pdf'
|
1423
1520
|
import_page cover_image
|
1424
1521
|
else
|
1425
1522
|
image_page cover_image, canvas: true
|
@@ -1441,26 +1538,24 @@ class Converter < ::Prawn::Document
|
|
1441
1538
|
|
1442
1539
|
# QUESTION why doesn't layout_heading set the font??
|
1443
1540
|
def layout_heading string, opts = {}
|
1444
|
-
|
1445
|
-
|
1541
|
+
top_margin = (margin = (opts.delete :margin)) || (opts.delete :margin_top) || @theme.heading_margin_top
|
1542
|
+
bot_margin = margin || (opts.delete :margin_bottom) || @theme.heading_margin_bottom
|
1446
1543
|
if (transform = (opts.delete :text_transform) || @text_transform)
|
1447
1544
|
string = transform_text string, transform
|
1448
1545
|
end
|
1449
|
-
|
1450
|
-
self.margin_top margin_top
|
1546
|
+
margin_top top_margin
|
1451
1547
|
typeset_text string, calc_line_metrics((opts.delete :line_height) || @theme.heading_line_height), {
|
1452
1548
|
color: @font_color,
|
1453
1549
|
inline_format: true,
|
1454
1550
|
align: :left
|
1455
1551
|
}.merge(opts)
|
1456
|
-
|
1457
|
-
self.margin_bottom margin_bottom
|
1552
|
+
margin_bottom bot_margin
|
1458
1553
|
end
|
1459
1554
|
|
1460
1555
|
# NOTE inline_format is true by default
|
1461
1556
|
def layout_prose string, opts = {}
|
1462
1557
|
top_margin = (margin = (opts.delete :margin)) || (opts.delete :margin_top) || @theme.prose_margin_top || 0
|
1463
|
-
|
1558
|
+
bot_margin = margin || (opts.delete :margin_bottom) || @theme.prose_margin_bottom || @theme.vertical_rhythm
|
1464
1559
|
if (transform = (opts.delete :text_transform) || @text_transform)
|
1465
1560
|
string = transform_text string, transform
|
1466
1561
|
end
|
@@ -1482,7 +1577,7 @@ class Converter < ::Prawn::Document
|
|
1482
1577
|
inline_format: [{ normalize: (opts.delete :normalize) != false }],
|
1483
1578
|
align: (@theme.base_align || :left).to_sym
|
1484
1579
|
}.merge(opts)
|
1485
|
-
margin_bottom
|
1580
|
+
margin_bottom bot_margin
|
1486
1581
|
end
|
1487
1582
|
|
1488
1583
|
# Render the caption and return the height of the rendered content
|
@@ -1545,7 +1640,7 @@ class Converter < ::Prawn::Document
|
|
1545
1640
|
end
|
1546
1641
|
|
1547
1642
|
def layout_toc_level sections, num_levels, line_metrics, dot_width, num_front_matter_pages = 0
|
1548
|
-
toc_dot_color = @theme.
|
1643
|
+
toc_dot_color = @theme.toc_dot_leader_font_color || @theme.toc_font_color || @font_color
|
1549
1644
|
sections.each do |sect|
|
1550
1645
|
theme_font :toc, level: (sect.level + 1) do
|
1551
1646
|
sect_title = @text_transform ? (transform_text sect.numbered_title, @text_transform) : sect.numbered_title
|
@@ -1566,6 +1661,7 @@ class Converter < ::Prawn::Document
|
|
1566
1661
|
spacer_width = (width_of NoBreakSpace) * 0.75
|
1567
1662
|
# FIXME this calculation will be wrong if a style is set per level
|
1568
1663
|
num_dots = ((bounds.width - (width_of %(#{sect_title}#{sect_page_num}), inline_format: true) - spacer_width) / dot_width).floor
|
1664
|
+
num_dots = 0 if num_dots < 0
|
1569
1665
|
# FIXME dots don't line up if width of page numbers differ
|
1570
1666
|
typeset_formatted_text [
|
1571
1667
|
{ text: %(#{(@theme.toc_dot_leader_content || DotLeaderDefault) * num_dots}), color: toc_dot_color },
|
@@ -1675,10 +1771,11 @@ class Converter < ::Prawn::Document
|
|
1675
1771
|
trim_content_height = trim_height - trim_padding[0] - trim_padding[2] - trim_line_metrics.padding_top
|
1676
1772
|
trim_left = page_margin_left
|
1677
1773
|
trim_width = page_width - trim_left - page_margin_right
|
1678
|
-
trim_font_color = @theme.header_font_color
|
1679
|
-
trim_bg_color =
|
1774
|
+
trim_font_color = @theme.header_font_color || @font_color
|
1775
|
+
trim_bg_color = resolve_theme_color :header_background_color
|
1680
1776
|
trim_border_width = @theme.header_border_width || @theme.base_border_width
|
1681
|
-
|
1777
|
+
trim_border_style = (@theme.header_border_style || :solid).to_sym
|
1778
|
+
trim_border_color = resolve_theme_color :header_border_color
|
1682
1779
|
trim_valign = (@theme.header_valign || :center).to_sym
|
1683
1780
|
trim_img_valign = @theme.header_image_valign || trim_valign
|
1684
1781
|
else
|
@@ -1688,10 +1785,11 @@ class Converter < ::Prawn::Document
|
|
1688
1785
|
trim_content_height = trim_height - trim_padding[0] - trim_padding[2] - trim_line_metrics.padding_top
|
1689
1786
|
trim_left = page_margin_left
|
1690
1787
|
trim_width = page_width - trim_left - page_margin_right
|
1691
|
-
trim_font_color = @theme.footer_font_color
|
1692
|
-
trim_bg_color =
|
1788
|
+
trim_font_color = @theme.footer_font_color || @font_color
|
1789
|
+
trim_bg_color = resolve_theme_color :footer_background_color
|
1693
1790
|
trim_border_width = @theme.footer_border_width || @theme.base_border_width
|
1694
|
-
|
1791
|
+
trim_border_style = (@theme.footer_border_style || :solid).to_sym
|
1792
|
+
trim_border_color = resolve_theme_color :footer_border_color
|
1695
1793
|
trim_valign = (@theme.footer_valign || :center).to_sym
|
1696
1794
|
trim_img_valign = @theme.footer_image_valign || trim_valign
|
1697
1795
|
end
|
@@ -1699,9 +1797,7 @@ class Converter < ::Prawn::Document
|
|
1699
1797
|
trim_stamp = %(#{position})
|
1700
1798
|
trim_content_left = trim_left + trim_padding[3]
|
1701
1799
|
trim_content_width = trim_width - trim_padding[3] - trim_padding[1]
|
1702
|
-
|
1703
|
-
trim_bg_color = nil if trim_bg_color == 'transparent'
|
1704
|
-
trim_border_color = nil if trim_border_color == 'transparent' || trim_border_width == 0
|
1800
|
+
trim_border_color = nil if trim_border_width == 0
|
1705
1801
|
if ['top', 'center', 'bottom'].include? trim_img_valign
|
1706
1802
|
trim_img_valign = trim_img_valign.to_sym
|
1707
1803
|
end
|
@@ -1715,15 +1811,14 @@ class Converter < ::Prawn::Document
|
|
1715
1811
|
if trim_border_color
|
1716
1812
|
# TODO stroke_horizontal_rule should support :at
|
1717
1813
|
move_down bounds.height if position == :header
|
1718
|
-
stroke_horizontal_rule trim_border_color, line_width: trim_border_width
|
1814
|
+
stroke_horizontal_rule trim_border_color, line_width: trim_border_width, line_style: trim_border_style
|
1719
1815
|
end
|
1720
1816
|
end
|
1721
1817
|
else
|
1722
1818
|
bounding_box [trim_left, trim_top], width: trim_width, height: trim_height do
|
1723
1819
|
# TODO stroke_horizontal_rule should support :at
|
1724
1820
|
move_down bounds.height if position == :header
|
1725
|
-
stroke_horizontal_rule trim_border_color, line_width: trim_border_width
|
1726
|
-
move_up bounds.height if position == :header
|
1821
|
+
stroke_horizontal_rule trim_border_color, line_width: trim_border_width, line_style: trim_border_style
|
1727
1822
|
end
|
1728
1823
|
end
|
1729
1824
|
end
|
@@ -1736,7 +1831,7 @@ class Converter < ::Prawn::Document
|
|
1736
1831
|
next if page.imported_page?
|
1737
1832
|
visual_pgnum = page_number - skip
|
1738
1833
|
# FIXME we need to have a content setting for chapter pages
|
1739
|
-
content_by_alignment = content_dict[
|
1834
|
+
content_by_alignment = content_dict[visual_pgnum.odd? ? :recto : :verso]
|
1740
1835
|
doc.set_attr 'page-number', visual_pgnum
|
1741
1836
|
# TODO populate chapter-number
|
1742
1837
|
# TODO populate numbered and unnumbered chapter and section titles
|
@@ -1841,7 +1936,8 @@ class Converter < ::Prawn::Document
|
|
1841
1936
|
|
1842
1937
|
def write pdf_doc, target
|
1843
1938
|
pdf_doc.render_file target
|
1844
|
-
|
1939
|
+
# write scratch document if debug is enabled (or perhaps DEBUG_STEPS env)
|
1940
|
+
#get_scratch_document.render_file 'scratch.pdf'
|
1845
1941
|
# QUESTION restore attributes first?
|
1846
1942
|
@pdfmarks.generate_file target if @pdfmarks
|
1847
1943
|
end
|
@@ -1860,6 +1956,16 @@ class Converter < ::Prawn::Document
|
|
1860
1956
|
::File.absolute_path font_file, fonts_dir
|
1861
1957
|
end
|
1862
1958
|
|
1959
|
+
# QUESTION should we pass a category as an argument?
|
1960
|
+
# QUESTION should we make this a method on the theme ostruct? (e.g., @theme.resolve_color key, fallback)
|
1961
|
+
def resolve_theme_color key, fallback_color = nil
|
1962
|
+
if (color = @theme[key.to_s]) && color != 'transparent'
|
1963
|
+
color
|
1964
|
+
else
|
1965
|
+
fallback_color
|
1966
|
+
end
|
1967
|
+
end
|
1968
|
+
|
1863
1969
|
def theme_fill_and_stroke_bounds category
|
1864
1970
|
fill_and_stroke_bounds @theme[%(#{category}_background_color)], @theme[%(#{category}_border_color)],
|
1865
1971
|
line_width: @theme[%(#{category}_border_width)],
|
@@ -1927,6 +2033,73 @@ class Converter < ::Prawn::Document
|
|
1927
2033
|
@text_transform = prev_transform if transform
|
1928
2034
|
end
|
1929
2035
|
|
2036
|
+
# Calculate the font size (down to the minimum font size) that would allow
|
2037
|
+
# all the specified fragments to fit in the available width without wrapping lines.
|
2038
|
+
#
|
2039
|
+
# Return the calculated font size if an adjustment is necessary or nil if no
|
2040
|
+
# font size adjustment is necessary.
|
2041
|
+
def theme_font_size_autofit fragments, category
|
2042
|
+
arranger = arrange_fragments_by_line fragments
|
2043
|
+
adjusted_font_size = nil
|
2044
|
+
theme_font category do
|
2045
|
+
# NOTE finalizing the line here generates fragments using current font settings
|
2046
|
+
arranger.finalize_line
|
2047
|
+
actual_width = width_of_fragments arranger.fragments
|
2048
|
+
unless ::Array === (padding = @theme[%(#{category}_padding)])
|
2049
|
+
padding = [padding] * 4
|
2050
|
+
end
|
2051
|
+
bounds.add_left_padding(p_left = padding[3] || 0)
|
2052
|
+
bounds.add_right_padding(p_right = padding[1] || 0)
|
2053
|
+
if actual_width > bounds.width
|
2054
|
+
adjusted_font_size = ((bounds.width * font_size).to_f / actual_width).with_precision 4
|
2055
|
+
if (min = @theme[%(#{category}_font_size_min)] || @theme.base_font_size_min) && adjusted_font_size < min
|
2056
|
+
adjusted_font_size = min
|
2057
|
+
end
|
2058
|
+
end
|
2059
|
+
bounds.subtract_left_padding p_left
|
2060
|
+
bounds.subtract_right_padding p_right
|
2061
|
+
end
|
2062
|
+
adjusted_font_size
|
2063
|
+
end
|
2064
|
+
|
2065
|
+
# Arrange fragments by line in an arranger and return an unfinalized arranger.
|
2066
|
+
#
|
2067
|
+
# Finalizing the arranger is deferred since it must be done in the context of
|
2068
|
+
# the global font settings you want applied to each fragment.
|
2069
|
+
def arrange_fragments_by_line fragments, opts = {}
|
2070
|
+
arranger = ::Prawn::Text::Formatted::Arranger.new self
|
2071
|
+
by_line = arranger.consumed = []
|
2072
|
+
fragments.each do |fragment|
|
2073
|
+
if (txt = fragment[:text]) == EOL
|
2074
|
+
by_line << fragment
|
2075
|
+
elsif txt.include? EOL
|
2076
|
+
txt.scan(LineScanRx) do |line|
|
2077
|
+
by_line << fragment.merge(text: line)
|
2078
|
+
end
|
2079
|
+
else
|
2080
|
+
by_line << fragment
|
2081
|
+
end
|
2082
|
+
end
|
2083
|
+
arranger
|
2084
|
+
end
|
2085
|
+
|
2086
|
+
# Calculate the width that is needed to print all the
|
2087
|
+
# fragments without wrapping any lines.
|
2088
|
+
#
|
2089
|
+
# This method assumes endlines are represented as discrete entries in the
|
2090
|
+
# fragments array.
|
2091
|
+
def width_of_fragments fragments
|
2092
|
+
line_widths = [0]
|
2093
|
+
fragments.each do |fragment|
|
2094
|
+
if fragment.text == EOL
|
2095
|
+
line_widths << 0
|
2096
|
+
else
|
2097
|
+
line_widths[-1] += fragment.width
|
2098
|
+
end
|
2099
|
+
end
|
2100
|
+
line_widths.max
|
2101
|
+
end
|
2102
|
+
|
1930
2103
|
# TODO document me, esp the first line formatting functionality
|
1931
2104
|
def typeset_text string, line_metrics, opts = {}
|
1932
2105
|
move_down line_metrics.padding_top
|
@@ -2001,17 +2174,18 @@ class Converter < ::Prawn::Document
|
|
2001
2174
|
# the temporary file. If the target is a URI and the allow-uri-read attribute
|
2002
2175
|
# is not set, or the URI cannot be read, this method returns a nil value.
|
2003
2176
|
#
|
2004
|
-
# When a temporary file is used, the
|
2005
|
-
# @tmp_file instance variable of the return string.
|
2177
|
+
# When a temporary file is used, the TemporaryPath type is mixed into the path string.
|
2006
2178
|
def resolve_image_path node, image_path = nil, image_type = nil
|
2007
2179
|
imagesdir = resolve_imagesdir(doc = node.document)
|
2008
2180
|
image_path ||= (node.attr 'target', nil, false)
|
2009
|
-
image_type ||=
|
2181
|
+
image_type ||= ::Asciidoctor::Image.image_type image_path
|
2010
2182
|
# handle case when image is a URI
|
2011
2183
|
if (node.is_uri? image_path) || (imagesdir && (node.is_uri? imagesdir) &&
|
2012
2184
|
(image_path = (node.normalize_web_path image_path, image_base_uri, false)))
|
2013
2185
|
unless doc.attr? 'allow-uri-read'
|
2014
|
-
|
2186
|
+
unless scratch?
|
2187
|
+
warn %(asciidoctor: WARNING: allow-uri-read is not enabled; cannot embed remote image: #{image_path})
|
2188
|
+
end
|
2015
2189
|
return
|
2016
2190
|
end
|
2017
2191
|
if doc.attr? 'cache-uri'
|
@@ -2022,7 +2196,7 @@ class Converter < ::Prawn::Document
|
|
2022
2196
|
begin
|
2023
2197
|
open(image_path, (binary ? 'rb' : 'r')) {|fd| tmp_image.write(fd.read) }
|
2024
2198
|
tmp_image_path = tmp_image.path
|
2025
|
-
tmp_image_path.
|
2199
|
+
tmp_image_path.extend TemporaryPath
|
2026
2200
|
rescue
|
2027
2201
|
tmp_image_path = nil
|
2028
2202
|
ensure
|
@@ -2037,11 +2211,9 @@ class Converter < ::Prawn::Document
|
|
2037
2211
|
|
2038
2212
|
# QUESTION is there a better way to do this?
|
2039
2213
|
# I suppose we could have @tmp_files as an instance variable on converter instead
|
2040
|
-
|
2041
|
-
|
2042
|
-
|
2043
|
-
holder.remove_instance_variable :@tmp_file
|
2044
|
-
end
|
2214
|
+
# It might be sufficient to delete temporary files once per conversion
|
2215
|
+
def unlink_tmp_file path
|
2216
|
+
path.unlink if TemporaryPath === path
|
2045
2217
|
end
|
2046
2218
|
|
2047
2219
|
# QUESTION move to prawn/extensions.rb?
|
@@ -2064,22 +2236,6 @@ class Converter < ::Prawn::Document
|
|
2064
2236
|
end
|
2065
2237
|
end
|
2066
2238
|
end
|
2067
|
-
|
2068
|
-
def create_stamps
|
2069
|
-
create_stamp 'masthead' do
|
2070
|
-
canvas do
|
2071
|
-
save_graphics_state do
|
2072
|
-
stroke_color '000000'
|
2073
|
-
x_margin = mm2pt 20
|
2074
|
-
y_margin = mm2pt 15
|
2075
|
-
stroke_horizontal_line x_margin, bounds.right - x_margin, at: bounds.top - y_margin
|
2076
|
-
stroke_horizontal_line x_margin, bounds.right - x_margin, at: y_margin
|
2077
|
-
end
|
2078
|
-
end
|
2079
|
-
end
|
2080
|
-
|
2081
|
-
@stamps_initialized = true
|
2082
|
-
end
|
2083
2239
|
=end
|
2084
2240
|
end
|
2085
2241
|
end
|