asciidoctor-pdf 1.5.0.beta.7 → 1.5.0.beta.8
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.adoc +32 -0
- data/README.adoc +14 -225
- data/asciidoctor-pdf.gemspec +1 -1
- data/bin/asciidoctor-pdf +1 -6
- data/data/themes/base-theme.yml +2 -0
- data/data/themes/default-theme.yml +4 -0
- data/docs/theming-guide.adoc +136 -19
- data/lib/asciidoctor/pdf/converter.rb +221 -87
- data/lib/asciidoctor/pdf/ext/asciidoctor.rb +2 -1
- data/lib/asciidoctor/pdf/ext/asciidoctor/abstract_node.rb +6 -0
- data/lib/asciidoctor/pdf/ext/core/array.rb +4 -0
- data/lib/asciidoctor/pdf/ext/core/object.rb +1 -1
- data/lib/asciidoctor/pdf/ext/core/string.rb +2 -6
- data/lib/asciidoctor/pdf/ext/prawn-table/cell/asciidoc.rb +3 -2
- data/lib/asciidoctor/pdf/ext/prawn.rb +1 -0
- data/lib/asciidoctor/pdf/ext/prawn/extensions.rb +9 -2
- data/lib/asciidoctor/pdf/ext/prawn/formatted_text/box.rb +16 -0
- data/lib/asciidoctor/pdf/ext/prawn/formatted_text/fragment.rb +6 -0
- data/lib/asciidoctor/pdf/ext/prawn/images.rb +1 -0
- data/lib/asciidoctor/pdf/formatted_text/inline_image_arranger.rb +3 -2
- data/lib/asciidoctor/pdf/formatted_text/transform.rb +11 -8
- data/lib/asciidoctor/pdf/index_catalog.rb +2 -1
- data/lib/asciidoctor/pdf/optimizer.rb +18 -8
- data/lib/asciidoctor/pdf/pdfmark.rb +10 -10
- data/lib/asciidoctor/pdf/sanitizer.rb +0 -57
- data/lib/asciidoctor/pdf/text_transformer.rb +115 -0
- data/lib/asciidoctor/pdf/version.rb +1 -1
- metadata +7 -4
@@ -14,6 +14,7 @@ require_relative 'ext/pdf-core'
|
|
14
14
|
require_relative 'temporary_path'
|
15
15
|
require_relative 'measurements'
|
16
16
|
require_relative 'sanitizer'
|
17
|
+
require_relative 'text_transformer'
|
17
18
|
require_relative 'ext/prawn'
|
18
19
|
require_relative 'formatted_text'
|
19
20
|
require_relative 'pdfmark'
|
@@ -53,12 +54,14 @@ class Converter < ::Prawn::Document
|
|
53
54
|
}
|
54
55
|
TextAlignmentNames = ['justify', 'left', 'center', 'right']
|
55
56
|
TextAlignmentRoles = ['text-justify', 'text-left', 'text-center', 'text-right']
|
57
|
+
TextDecorationStyleTable = { 'underline' => :underline, 'line-through' => :strikethrough }
|
56
58
|
BlockAlignmentNames = ['left', 'center', 'right']
|
57
59
|
AlignmentTable = { '<' => :left, '=' => :center, '>' => :right }
|
58
60
|
ColumnPositions = [:left, :center, :right]
|
59
61
|
PageLayouts = [:portrait, :landscape]
|
60
62
|
PageSides = [:recto, :verso]
|
61
63
|
(PDFVersions = { '1.3' => 1.3, '1.4' => 1.4, '1.5' => 1.5, '1.6' => 1.6, '1.7' => 1.7 }).default = 1.4
|
64
|
+
AuthorAttributeNames = ['author', 'authorinitials', 'firstname', 'middlename', 'lastname', 'email']
|
62
65
|
LF = ?\n
|
63
66
|
DoubleLF = LF * 2
|
64
67
|
TAB = ?\t
|
@@ -106,6 +109,7 @@ class Converter < ::Prawn::Document
|
|
106
109
|
CjkLineBreakRx = /(?=[\u3000\u30a0-\u30ff\u3040-\u309f\p{Han}\uff00-\uffef])/
|
107
110
|
WhitespaceChars = ' ' + TAB + LF
|
108
111
|
ValueSeparatorRx = /;|,/
|
112
|
+
HexColorRx = /^#[a-fA-F0-9]{6}$/
|
109
113
|
SourceHighlighters = ['coderay', 'pygments', 'rouge'].to_set
|
110
114
|
PygmentsBgColorRx = /^\.highlight +{ *background: *#([^;]+);/
|
111
115
|
ViewportWidth = ::Module.new
|
@@ -125,16 +129,15 @@ class Converter < ::Prawn::Document
|
|
125
129
|
doc.attributes['data-uri'] = ((doc.instance_variable_get :@attribute_overrides) || {})['data-uri'] = ''
|
126
130
|
end
|
127
131
|
@capabilities = {
|
128
|
-
honors_literal_cell_style: AsciidoctorVersion >= (::Gem::Version.create '1.5.6'),
|
129
132
|
special_sectnums: AsciidoctorVersion >= (::Gem::Version.create '1.5.7'),
|
130
133
|
syntax_highlighter: AsciidoctorVersion >= (::Gem::Version.create '2.0.0'),
|
131
134
|
}
|
135
|
+
@initial_instance_variables = [:@initial_instance_variables] + instance_variables
|
132
136
|
end
|
133
137
|
|
134
138
|
def convert node, name = nil, opts = {}
|
135
139
|
method_name = %(convert_#{name ||= node.node_name})
|
136
140
|
if respond_to? method_name
|
137
|
-
# NOTE we prepend the prefix "convert_" to avoid conflict with Prawn methods
|
138
141
|
result = send method_name, node
|
139
142
|
else
|
140
143
|
# TODO delegate to convert_method_missing
|
@@ -154,7 +157,7 @@ class Converter < ::Prawn::Document
|
|
154
157
|
node.content
|
155
158
|
elsif node.content_model != :compound && (string = node.content)
|
156
159
|
# TODO this content could be cached on repeat invocations!
|
157
|
-
layout_prose string, opts
|
160
|
+
layout_prose string, (opts.merge hyphenate: true)
|
158
161
|
end
|
159
162
|
node.document.instance_variable_set :@converter, prev_converter if prev_converter
|
160
163
|
end
|
@@ -259,7 +262,10 @@ class Converter < ::Prawn::Document
|
|
259
262
|
|
260
263
|
# NOTE delete orphaned page (a page was created but there was no additional content)
|
261
264
|
# QUESTION should we delete page if document is empty? (leaving no pages?)
|
262
|
-
|
265
|
+
if page_count > 1
|
266
|
+
go_to_page page_count unless last_page?
|
267
|
+
delete_page if page.empty?
|
268
|
+
end
|
263
269
|
|
264
270
|
toc_page_nums = @toc_extent ? (layout_toc doc, toc_num_levels, @toc_extent[:page_nums].first, @toc_extent[:start_y], num_front_matter_pages[1]) : []
|
265
271
|
end
|
@@ -273,6 +279,7 @@ class Converter < ::Prawn::Document
|
|
273
279
|
end
|
274
280
|
end
|
275
281
|
|
282
|
+
catalog.data[:PageMode] = :FullScreen if (doc.attr 'pdf-page-mode', @theme.page_mode) == 'fullscreen'
|
276
283
|
add_outline doc, (doc.attr 'outlinelevels', toc_num_levels), toc_page_nums, num_front_matter_pages[1], has_front_cover
|
277
284
|
if state.pages.size > 0 && (initial_zoom = @theme.page_initial_zoom)
|
278
285
|
case initial_zoom.to_sym
|
@@ -296,8 +303,8 @@ class Converter < ::Prawn::Document
|
|
296
303
|
# it the same as a full document.
|
297
304
|
alias convert_embedded convert_document
|
298
305
|
|
299
|
-
# TODO only allow method to be called once (or we need a reset)
|
300
306
|
def init_pdf doc
|
307
|
+
(instance_variables - @initial_instance_variables).each {|ivar| remove_instance_variable ivar } if state
|
301
308
|
pdf_opts = build_pdf_options doc, (theme = load_theme doc)
|
302
309
|
# QUESTION should page options be preserved? (otherwise, not readily available)
|
303
310
|
#@page_opts = { size: pdf_opts[:page_size], layout: pdf_opts[:page_layout] }
|
@@ -323,6 +330,7 @@ class Converter < ::Prawn::Document
|
|
323
330
|
default_kerning theme.base_font_kerning != 'none'
|
324
331
|
@fallback_fonts = [*theme.font_fallbacks]
|
325
332
|
@allow_uri_read = doc.attr? 'allow-uri-read'
|
333
|
+
@cache_uri = doc.attr? 'cache-uri'
|
326
334
|
if (bg_image = resolve_background_image doc, theme, 'page-background-image') && bg_image[0]
|
327
335
|
@page_bg_image = { verso: bg_image, recto: bg_image }
|
328
336
|
else
|
@@ -339,6 +347,14 @@ class Converter < ::Prawn::Document
|
|
339
347
|
@font_color = theme.base_font_color || '000000'
|
340
348
|
@base_align = (align = doc.attr 'text-align') && (TextAlignmentNames.include? align) ? align : theme.base_align
|
341
349
|
@cjk_line_breaks = doc.attr? 'scripts', 'cjk'
|
350
|
+
if (hyphen_lang = doc.attr 'hyphens')
|
351
|
+
hyphen_lang = doc.attr 'lang' if hyphen_lang.empty?
|
352
|
+
hyphen_lang = 'en_us' if hyphen_lang.nil_or_empty? || hyphen_lang == 'en'
|
353
|
+
hyphen_lang = (hyphen_lang.tr '-', '_').downcase
|
354
|
+
if (defined? ::Text::Hyphen) || !(Helpers.require_library 'text/hyphen', 'text-hyphen', :warn).nil?
|
355
|
+
@hyphenator = ::Text::Hyphen.new language: hyphen_lang
|
356
|
+
end
|
357
|
+
end
|
342
358
|
@text_transform = nil
|
343
359
|
@list_numerals = []
|
344
360
|
@list_bullets = []
|
@@ -470,13 +486,16 @@ class Converter < ::Prawn::Document
|
|
470
486
|
info[:Subject] = (doc.attr 'subject').as_pdf if doc.attr? 'subject'
|
471
487
|
info[:Keywords] = (doc.attr 'keywords').as_pdf if doc.attr? 'keywords'
|
472
488
|
info[:Producer] = (doc.attr 'publisher').as_pdf if doc.attr? 'publisher'
|
473
|
-
|
474
|
-
|
475
|
-
|
489
|
+
if doc.attr? 'reproducible'
|
490
|
+
info[:Creator] = 'Asciidoctor PDF, based on Prawn'.as_pdf
|
491
|
+
info[:Producer] ||= (info[:Author] || info[:Creator])
|
492
|
+
else
|
493
|
+
info[:Creator] = %(Asciidoctor PDF #{::Asciidoctor::PDF::VERSION}, based on Prawn #{::Prawn::VERSION}).as_pdf
|
494
|
+
info[:Producer] ||= (info[:Author] || info[:Creator])
|
476
495
|
# NOTE since we don't track the creation date of the input file, we map the ModDate header to the last modified
|
477
496
|
# date of the input document and the CreationDate header to the date the PDF was produced by the converter.
|
478
|
-
info[:ModDate] = ::Time.parse
|
479
|
-
info[:CreationDate] = ::Time.parse
|
497
|
+
info[:ModDate] = (::Time.parse doc.attr 'docdatetime') rescue (now ||= ::Time.now)
|
498
|
+
info[:CreationDate] = (::Time.parse doc.attr 'localdatetime') rescue (now ||= ::Time.now)
|
480
499
|
end
|
481
500
|
info
|
482
501
|
end
|
@@ -580,7 +599,7 @@ class Converter < ::Prawn::Document
|
|
580
599
|
(title = doc.attr 'footnotes-title') && (layout_caption title, category: :footnotes)
|
581
600
|
item_spacing = @theme.footnotes_item_spacing || 0
|
582
601
|
fns.each do |fn|
|
583
|
-
layout_prose %(<a name="_footnotedef_#{index = fn.index}">#{DummyText}</a>[<a anchor="_footnoteref_#{index}">#{index}</a>] #{fn.text}), margin_bottom: item_spacing
|
602
|
+
layout_prose %(<a name="_footnotedef_#{index = fn.index}">#{DummyText}</a>[<a anchor="_footnoteref_#{index}">#{index}</a>] #{fn.text}), margin_bottom: item_spacing, hyphenate: true
|
584
603
|
end
|
585
604
|
@footnotes += fns
|
586
605
|
end
|
@@ -603,8 +622,8 @@ class Converter < ::Prawn::Document
|
|
603
622
|
layout_prose node.title, align: (@theme.abstract_title_align || @base_align).to_sym, margin_top: (@theme.heading_margin_top || 0), margin_bottom: (@theme.heading_margin_bottom || 0), line_height: @theme.heading_line_height
|
604
623
|
end if node.title?
|
605
624
|
theme_font :abstract do
|
606
|
-
prose_opts = { line_height: @theme.abstract_line_height, align: (
|
607
|
-
if (text_indent = @theme.prose_text_indent)
|
625
|
+
prose_opts = { line_height: @theme.abstract_line_height, align: (@theme.abstract_align || @base_align).to_sym, hyphenate: true }
|
626
|
+
if (text_indent = @theme.prose_text_indent || 0) > 0
|
608
627
|
prose_opts[:indent_paragraphs] = text_indent
|
609
628
|
end
|
610
629
|
# FIXME control more first_line_options using theme
|
@@ -617,20 +636,16 @@ class Converter < ::Prawn::Document
|
|
617
636
|
# FIXME is playback necessary here?
|
618
637
|
child.document.playback_attributes child.attributes
|
619
638
|
if child.context == :paragraph
|
620
|
-
|
621
|
-
prose_opts[:align] = alignment
|
622
|
-
end
|
623
|
-
layout_prose child.content, prose_opts
|
639
|
+
layout_prose child.content, ((align = resolve_alignment_from_role child.roles) ? (prose_opts.merge align: align) : prose_opts.dup)
|
624
640
|
prose_opts.delete :first_line_options
|
625
|
-
prose_opts[:align] = initial_alignment
|
626
641
|
else
|
627
642
|
# FIXME this could do strange things if the wrong kind of content shows up
|
628
643
|
convert_content_for_block child
|
629
644
|
end
|
630
645
|
end
|
631
646
|
elsif node.content_model != :compound && (string = node.content)
|
632
|
-
if (
|
633
|
-
prose_opts[:align] =
|
647
|
+
if (align = resolve_alignment_from_role node.roles)
|
648
|
+
prose_opts[:align] = align
|
634
649
|
end
|
635
650
|
layout_prose string, prose_opts
|
636
651
|
end
|
@@ -651,13 +666,13 @@ class Converter < ::Prawn::Document
|
|
651
666
|
|
652
667
|
def convert_paragraph node
|
653
668
|
add_dest_for_block node if node.id
|
654
|
-
prose_opts = { margin_bottom: 0 }
|
669
|
+
prose_opts = { margin_bottom: 0, hyphenate: true }
|
655
670
|
lead = (roles = node.roles).include? 'lead'
|
656
671
|
if (align = resolve_alignment_from_role roles)
|
657
672
|
prose_opts[:align] = align
|
658
673
|
end
|
659
674
|
|
660
|
-
if (text_indent = @theme.prose_text_indent)
|
675
|
+
if (text_indent = @theme.prose_text_indent || 0) > 0
|
661
676
|
prose_opts[:indent_paragraphs] = text_indent
|
662
677
|
end
|
663
678
|
|
@@ -675,11 +690,10 @@ class Converter < ::Prawn::Document
|
|
675
690
|
|
676
691
|
if (margin_inner_val = @theme.prose_margin_inner) &&
|
677
692
|
(next_block = (siblings = node.parent.blocks)[(siblings.index node) + 1]) && next_block.context == :paragraph
|
678
|
-
|
693
|
+
margin_bottom margin_inner_val
|
679
694
|
else
|
680
|
-
|
695
|
+
margin_bottom @theme.prose_margin_bottom
|
681
696
|
end
|
682
|
-
margin_bottom margin_bottom_val
|
683
697
|
end
|
684
698
|
|
685
699
|
def convert_admonition node
|
@@ -752,7 +766,7 @@ class Converter < ::Prawn::Document
|
|
752
766
|
float do
|
753
767
|
bounding_box [0, cursor], width: label_width, height: box_height do
|
754
768
|
if icons == 'font'
|
755
|
-
# FIXME we
|
769
|
+
# FIXME we assume icon is square
|
756
770
|
icon_size = fit_icon_to_bounds icon_size
|
757
771
|
# NOTE Prawn's vertical center is not reliable, so calculate it manually
|
758
772
|
if label_valign == :center
|
@@ -776,7 +790,8 @@ class Converter < ::Prawn::Document
|
|
776
790
|
height: box_height,
|
777
791
|
fallback_font_name: fallback_svg_font_name,
|
778
792
|
enable_web_requests: allow_uri_read,
|
779
|
-
enable_file_requests_with_root: (::File.dirname icon_path)
|
793
|
+
enable_file_requests_with_root: (::File.dirname icon_path),
|
794
|
+
cache_images: cache_uri
|
780
795
|
if (icon_height = (svg_size = svg_obj.document.sizing).output_height) > box_height
|
781
796
|
icon_width = (svg_obj.resize height: (icon_height = box_height)).output_width
|
782
797
|
else
|
@@ -829,7 +844,7 @@ class Converter < ::Prawn::Document
|
|
829
844
|
end
|
830
845
|
pad_box [cpad[0], 0, cpad[2], label_width + lpad[1] + cpad[3]] do
|
831
846
|
move_down shift_top
|
832
|
-
layout_caption node.title if node.title?
|
847
|
+
layout_caption node.title, category: :admonition if node.title?
|
833
848
|
theme_font :admonition do
|
834
849
|
convert_content_for_block node
|
835
850
|
end
|
@@ -941,7 +956,7 @@ class Converter < ::Prawn::Document
|
|
941
956
|
convert_content_for_block node
|
942
957
|
else # verse
|
943
958
|
content = guard_indentation node.content
|
944
|
-
layout_prose content, normalize: false, align: :left
|
959
|
+
layout_prose content, normalize: false, align: :left, hyphenate: true
|
945
960
|
end
|
946
961
|
end
|
947
962
|
if node.attr? 'attribution', nil, false
|
@@ -1134,7 +1149,7 @@ class Converter < ::Prawn::Document
|
|
1134
1149
|
if (term_font_styles = font_styles).empty?
|
1135
1150
|
term_inline_format = true
|
1136
1151
|
else
|
1137
|
-
term_inline_format = [
|
1152
|
+
term_inline_format = [inherited: { styles: term_font_styles }]
|
1138
1153
|
end
|
1139
1154
|
term_line_metrics = calc_line_metrics @theme.description_list_term_line_height || @theme.base_line_height
|
1140
1155
|
term_padding = [term_line_metrics.padding_top, 10, (@theme.prose_margin_bottom || 0) * 0.5 + term_line_metrics.padding_bottom, 10]
|
@@ -1408,14 +1423,14 @@ class Converter < ::Prawn::Document
|
|
1408
1423
|
terms, desc = node
|
1409
1424
|
[*terms].each {|term| layout_prose %(<em>#{term.text}</em>), (opts.merge margin_top: 0, margin_bottom: @theme.description_list_term_spacing) }
|
1410
1425
|
if desc
|
1411
|
-
layout_prose desc.text, opts if desc.text?
|
1426
|
+
layout_prose desc.text, (opts.merge hyphenate: true) if desc.text?
|
1412
1427
|
convert_content_for_block desc
|
1413
1428
|
end
|
1414
1429
|
else
|
1415
1430
|
if (primary_text = node.text).nil_or_empty?
|
1416
1431
|
layout_prose DummyText, opts unless node.blocks?
|
1417
1432
|
else
|
1418
|
-
layout_prose primary_text, opts
|
1433
|
+
layout_prose primary_text, (opts.merge hyphenate: true)
|
1419
1434
|
end
|
1420
1435
|
convert_content_for_block node
|
1421
1436
|
end
|
@@ -1495,7 +1510,8 @@ class Converter < ::Prawn::Document
|
|
1495
1510
|
width: width,
|
1496
1511
|
fallback_font_name: fallback_svg_font_name,
|
1497
1512
|
enable_web_requests: allow_uri_read,
|
1498
|
-
enable_file_requests_with_root: file_request_root
|
1513
|
+
enable_file_requests_with_root: file_request_root,
|
1514
|
+
cache_images: cache_uri
|
1499
1515
|
rendered_w = (svg_size = svg_obj.document.sizing).output_width
|
1500
1516
|
if !width && (svg_obj.document.root.attributes.key? 'width')
|
1501
1517
|
# NOTE scale native width & height from px to pt and restrict width to available width
|
@@ -1565,7 +1581,7 @@ class Converter < ::Prawn::Document
|
|
1565
1581
|
layout_caption node, category: :image, side: :bottom if node.title?
|
1566
1582
|
theme_margin :block, :bottom unless pinned
|
1567
1583
|
rescue
|
1568
|
-
on_image_error :exception, node, target, (opts.merge message: %(could not embed image: #{image_path}; #{$!.message}))
|
1584
|
+
on_image_error :exception, node, target, (opts.merge message: %(could not embed image: #{image_path}; #{$!.message}#{::Prawn::Errors::UnsupportedImageType === $! ? '; install prawn-gmagick gem to add support' : ''}))
|
1569
1585
|
end
|
1570
1586
|
ensure
|
1571
1587
|
unlink_tmp_file image_path if image_path
|
@@ -1631,7 +1647,7 @@ class Converter < ::Prawn::Document
|
|
1631
1647
|
when 'vimeo'
|
1632
1648
|
video_path = %(https://vimeo.com/#{video_id = node.attr 'target'})
|
1633
1649
|
if allow_uri_read
|
1634
|
-
if
|
1650
|
+
if cache_uri
|
1635
1651
|
Helpers.require_library 'open-uri/cached', 'open-uri-cached' unless defined? ::OpenURI::Cache
|
1636
1652
|
else
|
1637
1653
|
::OpenURI
|
@@ -1950,8 +1966,10 @@ class Converter < ::Prawn::Document
|
|
1950
1966
|
head_transform = resolve_text_transform :table_head_text_transform, nil
|
1951
1967
|
row_data = []
|
1952
1968
|
row.each do |cell|
|
1969
|
+
cell_text = head_transform ? (transform_text cell.text.strip, head_transform) : cell.text.strip
|
1970
|
+
cell_text = hyphenate_text cell_text, @hyphenator if defined? @hyphenator
|
1953
1971
|
row_data << {
|
1954
|
-
content:
|
1972
|
+
content: cell_text,
|
1955
1973
|
inline_format: [normalize: true],
|
1956
1974
|
background_color: head_bg_color,
|
1957
1975
|
text_color: (theme.table_head_font_color || theme.table_font_color || @font_color),
|
@@ -2023,7 +2041,8 @@ class Converter < ::Prawn::Document
|
|
2023
2041
|
end
|
2024
2042
|
cell_line_metrics = calc_line_metrics theme.base_line_height
|
2025
2043
|
when :literal
|
2026
|
-
|
2044
|
+
# NOTE we want the raw AsciiDoc in this case
|
2045
|
+
cell_data[:content] = guard_indentation cell.instance_variable_get :@text
|
2027
2046
|
# NOTE the absence of the inline_format option implies it's disabled
|
2028
2047
|
# QUESTION should we use literal_font_*, code_font_*, or introduce another category?
|
2029
2048
|
cell_data[:font] = theme.code_font_family
|
@@ -2061,6 +2080,8 @@ class Converter < ::Prawn::Document
|
|
2061
2080
|
unless cell_data.key? :content
|
2062
2081
|
cell_text = cell.text.strip
|
2063
2082
|
cell_text = transform_text cell_text if cell_transform
|
2083
|
+
cell_text = hyphenate_text cell_text, @hyphenator if defined? @hyphenator
|
2084
|
+
cell_text = cell_text.gsub CjkLineBreakRx, ZeroWidthSpace if @cjk_line_breaks
|
2064
2085
|
if cell_text.include? LF
|
2065
2086
|
# NOTE effectively the same as calling cell.content (should we use that instead?)
|
2066
2087
|
# FIXME hard breaks not quite the same result as separate paragraphs; need custom cell impl here
|
@@ -2072,8 +2093,11 @@ class Converter < ::Prawn::Document
|
|
2072
2093
|
end
|
2073
2094
|
end
|
2074
2095
|
if node.document.attr? 'cellbgcolor'
|
2075
|
-
cell_bg_color = node.document.attr 'cellbgcolor'
|
2076
|
-
|
2096
|
+
if (cell_bg_color = node.document.attr 'cellbgcolor') == 'transparent'
|
2097
|
+
cell_data[:background_color] = body_bg_color
|
2098
|
+
elsif (cell_bg_color.start_with? '#') && (HexColorRx.match? cell_bg_color)
|
2099
|
+
cell_data[:background_color] = cell_bg_color.slice 1, cell_bg_color.length
|
2100
|
+
end
|
2077
2101
|
end
|
2078
2102
|
row_data << cell_data
|
2079
2103
|
end
|
@@ -2134,7 +2158,7 @@ class Converter < ::Prawn::Document
|
|
2134
2158
|
table_width = bounds.width * ((node.attr 'tablepcwidth') / 100.0)
|
2135
2159
|
column_widths = node.columns.map {|col| ((col.attr 'colpcwidth') * table_width) / 100.0 }
|
2136
2160
|
# NOTE until Asciidoctor 1.5.4, colpcwidth values didn't always add up to 100%; use last column to compensate
|
2137
|
-
unless column_widths.empty? || (width_delta = table_width - column_widths.
|
2161
|
+
unless column_widths.empty? || (width_delta = table_width - column_widths.sum) == 0
|
2138
2162
|
column_widths[-1] += width_delta
|
2139
2163
|
end
|
2140
2164
|
end
|
@@ -2276,6 +2300,7 @@ class Converter < ::Prawn::Document
|
|
2276
2300
|
start_new_page unless at_page_top?
|
2277
2301
|
start_new_page if @ppbook && verso_page? && !(node.option? 'nonfacing')
|
2278
2302
|
end
|
2303
|
+
add_dest_for_block node, (derive_anchor_from_id node.id, 'toc')
|
2279
2304
|
allocate_toc doc, (doc.attr 'toclevels', 2).to_i, @y, (is_book || (doc.attr? 'title-page'))
|
2280
2305
|
end
|
2281
2306
|
nil
|
@@ -2340,8 +2365,7 @@ class Converter < ::Prawn::Document
|
|
2340
2365
|
end
|
2341
2366
|
text = %(#{text}, #{pagenums.join ', '})
|
2342
2367
|
end
|
2343
|
-
layout_prose text, align: :left, margin: 0, normalize_line_height: true
|
2344
|
-
|
2368
|
+
layout_prose text, align: :left, margin: 0, normalize_line_height: true, hanging_indent: @theme.description_list_description_indent * 2
|
2345
2369
|
term.subterms.each do |subterm|
|
2346
2370
|
indent @theme.description_list_description_indent do
|
2347
2371
|
convert_index_list_item subterm
|
@@ -2442,11 +2466,14 @@ class Converter < ::Prawn::Document
|
|
2442
2466
|
if node.document.attr? 'icons', 'font'
|
2443
2467
|
if (icon_name = node.target).include? '@'
|
2444
2468
|
icon_name, icon_set = icon_name.split '@', 2
|
2469
|
+
explicit_icon_set = true
|
2470
|
+
elsif (icon_set = node.attr 'set', nil, false)
|
2471
|
+
explicit_icon_set = true
|
2445
2472
|
else
|
2446
|
-
icon_set = node.
|
2473
|
+
icon_set = node.document.attr 'icon-set', 'fa'
|
2447
2474
|
end
|
2448
|
-
icon_set
|
2449
|
-
|
2475
|
+
if icon_set == 'fa' || !(IconSets.include? icon_set)
|
2476
|
+
icon_set = 'fa'
|
2450
2477
|
# legacy name from Font Awesome < 5
|
2451
2478
|
if (remapped_icon_name = resolve_legacy_icon_name icon_name)
|
2452
2479
|
requested_icon_name = icon_name
|
@@ -2465,6 +2492,10 @@ class Converter < ::Prawn::Document
|
|
2465
2492
|
else
|
2466
2493
|
glyph = (icon_font_data icon_set).unicode icon_name rescue nil
|
2467
2494
|
end
|
2495
|
+
unless glyph || explicit_icon_set || !icon_name.start_with?(*IconSetPrefixes)
|
2496
|
+
icon_set, icon_name = icon_name.split '-', 2
|
2497
|
+
glyph = (icon_font_data icon_set).unicode icon_name rescue nil
|
2498
|
+
end
|
2468
2499
|
if glyph
|
2469
2500
|
if node.attr? 'size', nil, false
|
2470
2501
|
case (size = node.attr 'size')
|
@@ -2637,34 +2668,22 @@ class Converter < ::Prawn::Document
|
|
2637
2668
|
if (logo_align = [(logo_image_attrs.delete 'align'), @theme.title_page_logo_align, title_align.to_s].find {|val| (BlockAlignmentNames.include? val) })
|
2638
2669
|
logo_image_attrs['align'] = logo_align
|
2639
2670
|
end
|
2640
|
-
|
2641
|
-
|
2642
|
-
# FIXME delegate to method to convert page % to y value
|
2643
|
-
if logo_image_top.end_with? 'vh'
|
2644
|
-
logo_image_top = page_height - page_height * logo_image_top.to_f / 100.0
|
2645
|
-
else
|
2646
|
-
logo_image_top = bounds.absolute_top - effective_page_height * logo_image_top.to_f / 100.0
|
2671
|
+
if (logo_image_top = logo_image_attrs['top'] || @theme.title_page_logo_top)
|
2672
|
+
initial_y, @y = @y, (resolve_top logo_image_top)
|
2647
2673
|
end
|
2648
|
-
initial_y, @y = @y, logo_image_top
|
2649
2674
|
# FIXME add API to Asciidoctor for creating blocks like this (extract from extensions module?)
|
2650
2675
|
image_block = ::Asciidoctor::Block.new doc, :image, content_model: :empty, attributes: logo_image_attrs
|
2651
2676
|
# NOTE pinned option keeps image on same page
|
2652
2677
|
indent (@theme.title_page_logo_margin_left || 0), (@theme.title_page_logo_margin_right || 0) do
|
2653
2678
|
convert_image image_block, relative_to_imagesdir: relative_to_imagesdir, pinned: true
|
2654
2679
|
end
|
2655
|
-
@y = initial_y
|
2680
|
+
@y = initial_y if initial_y
|
2656
2681
|
end
|
2657
2682
|
|
2658
2683
|
# TODO prevent content from spilling to next page
|
2659
2684
|
theme_font :title_page do
|
2660
2685
|
if (title_top = @theme.title_page_title_top)
|
2661
|
-
|
2662
|
-
title_top = page_height - page_height * title_top.to_f / 100.0
|
2663
|
-
else
|
2664
|
-
title_top = bounds.absolute_top - effective_page_height * title_top.to_f / 100.0
|
2665
|
-
end
|
2666
|
-
# FIXME delegate to method to convert page % to y value
|
2667
|
-
@y = title_top
|
2686
|
+
@y = resolve_top title_top
|
2668
2687
|
end
|
2669
2688
|
unless @theme.title_page_title_display == 'none'
|
2670
2689
|
doctitle = doc.doctitle partition: true
|
@@ -2694,9 +2713,22 @@ class Converter < ::Prawn::Document
|
|
2694
2713
|
if @theme.title_page_authors_display != 'none' && (doc.attr? 'authors')
|
2695
2714
|
move_down(@theme.title_page_authors_margin_top || 0)
|
2696
2715
|
indent (@theme.title_page_authors_margin_left || 0), (@theme.title_page_authors_margin_right || 0) do
|
2716
|
+
authors_content = @theme.title_page_authors_content
|
2717
|
+
authors_content = {
|
2718
|
+
name_only: @theme.title_page_authors_content_name_only || authors_content,
|
2719
|
+
with_email: @theme.title_page_authors_content_with_email || authors_content,
|
2720
|
+
with_url: @theme.title_page_authors_content_with_url || authors_content,
|
2721
|
+
}
|
2697
2722
|
# TODO provide an API in core to get authors as an array
|
2698
2723
|
authors = (1..(doc.attr 'authorcount', 1).to_i).map {|idx|
|
2699
|
-
doc
|
2724
|
+
promote_author doc, idx do
|
2725
|
+
author_content_key = (url = doc.attr 'url') ? ((url.start_with? 'mailto:') ? :with_email : :with_url) : :name_only
|
2726
|
+
if (author_content = authors_content[author_content_key])
|
2727
|
+
apply_subs_discretely doc, author_content
|
2728
|
+
else
|
2729
|
+
doc.attr 'author'
|
2730
|
+
end
|
2731
|
+
end
|
2700
2732
|
}.join (@theme.title_page_authors_delimiter || ', ')
|
2701
2733
|
theme_font :title_page_authors do
|
2702
2734
|
layout_prose authors,
|
@@ -2806,9 +2838,15 @@ class Converter < ::Prawn::Document
|
|
2806
2838
|
end
|
2807
2839
|
outdent_section(opts.delete :outdent) do
|
2808
2840
|
margin_top top_margin
|
2841
|
+
# QUESTION should we move inherited styles to typeset_text?
|
2842
|
+
if (inherited = apply_text_decoration ::Set.new, :heading, hlevel).empty?
|
2843
|
+
inline_format_opts = true
|
2844
|
+
else
|
2845
|
+
inline_format_opts = [{ inherited: inherited }]
|
2846
|
+
end
|
2809
2847
|
typeset_text string, calc_line_metrics((opts.delete :line_height) || (hlevel ? @theme[%(heading_h#{hlevel}_line_height)] : nil) || @theme.heading_line_height || @theme.base_line_height), {
|
2810
2848
|
color: @font_color,
|
2811
|
-
inline_format:
|
2849
|
+
inline_format: inline_format_opts,
|
2812
2850
|
align: @base_align.to_sym
|
2813
2851
|
}.merge(opts)
|
2814
2852
|
margin_bottom bot_margin
|
@@ -2822,6 +2860,7 @@ class Converter < ::Prawn::Document
|
|
2822
2860
|
if (transform = resolve_text_transform opts)
|
2823
2861
|
string = transform_text string, transform
|
2824
2862
|
end
|
2863
|
+
string = hyphenate_text string, @hyphenator if (opts.delete :hyphenate) && (defined? @hyphenator)
|
2825
2864
|
# NOTE used by extensions; ensures linked text gets formatted using the link styles
|
2826
2865
|
if (anchor = opts.delete :anchor)
|
2827
2866
|
string = %(<a anchor="#{anchor}">#{string}</a>)
|
@@ -2885,7 +2924,8 @@ class Converter < ::Prawn::Document
|
|
2885
2924
|
margin_bottom: margin[:bottom],
|
2886
2925
|
align: (@theme[%(#{category_caption}_align)] || @theme.caption_align || @base_align).to_sym,
|
2887
2926
|
normalize: false,
|
2888
|
-
normalize_line_height: true
|
2927
|
+
normalize_line_height: true,
|
2928
|
+
hyphenate: true,
|
2889
2929
|
}.merge(opts)
|
2890
2930
|
if side == :top && (bb_color = @theme[%(#{category_caption}_border_bottom_color)] || @theme.caption_border_bottom_color)
|
2891
2931
|
stroke_horizontal_rule bb_color
|
@@ -2990,31 +3030,25 @@ class Converter < ::Prawn::Document
|
|
2990
3030
|
sections.each do |sect|
|
2991
3031
|
theme_font :toc, level: (sect.level + 1) do
|
2992
3032
|
sect_title = ZeroWidthSpace + (@text_transform ? (transform_text sect.numbered_title, @text_transform) : sect.numbered_title)
|
3033
|
+
hanging_indent = @theme.toc_hanging_indent || 0
|
2993
3034
|
# NOTE only write section title (excluding dots and page number) if this is a dry run
|
2994
3035
|
if scratch?
|
2995
3036
|
# FIXME use layout_prose
|
2996
3037
|
# NOTE must wrap title in empty anchor element in case links are styled with different font family / size
|
2997
|
-
typeset_text %(<a>#{sect_title}</a>), line_metrics, inline_format: true
|
3038
|
+
typeset_text %(<a>#{sect_title}</a>), line_metrics, inline_format: true, hanging_indent: hanging_indent
|
2998
3039
|
else
|
2999
3040
|
pgnum_label = ((sect.attr 'pdf-page-start') - num_front_matter_pages).to_s
|
3000
3041
|
start_page_number = page_number
|
3001
3042
|
start_cursor = cursor
|
3002
3043
|
start_dots = nil
|
3003
|
-
|
3004
|
-
|
3005
|
-
|
3006
|
-
color: @font_color,
|
3007
|
-
styles: ((@theme[%(toc_h#{sect.level + 1}_text_decoration)] || @theme.toc_text_decoration) == 'underline' ?
|
3008
|
-
(font_styles << :underline) : font_styles)
|
3009
|
-
}
|
3010
|
-
(sect_title_fragments = text_formatter.format sect_title).each do |fragment|
|
3011
|
-
fragment.update(sect_title_format_override) {|k, oval, nval| k == :styles ? (oval.merge nval) : oval }
|
3012
|
-
end
|
3044
|
+
sect_title_inherited = (apply_text_decoration ::Set.new, :toc, sect.level.next).merge anchor: (sect_anchor = sect.attr 'pdf-anchor'), color: @font_color
|
3045
|
+
# NOTE use text formatter to add anchor overlay to avoid using inline format with synthetic anchor tag
|
3046
|
+
sect_title_fragments = text_formatter.format sect_title, inherited: sect_title_inherited
|
3013
3047
|
pgnum_label_width = rendered_width_of_string pgnum_label
|
3014
3048
|
indent 0, pgnum_label_width do
|
3015
3049
|
sect_title_fragments[-1][:callback] = (last_fragment_pos = ::Asciidoctor::PDF::FormattedText::FragmentPositionRenderer.new)
|
3016
|
-
typeset_formatted_text sect_title_fragments, line_metrics
|
3017
|
-
start_dots = last_fragment_pos.right
|
3050
|
+
typeset_formatted_text sect_title_fragments, line_metrics, hanging_indent: hanging_indent
|
3051
|
+
start_dots = last_fragment_pos.right + hanging_indent
|
3018
3052
|
last_fragment_cursor = last_fragment_pos.top + line_metrics.padding_top
|
3019
3053
|
# NOTE this will be incorrect if wrapped line is all monospace
|
3020
3054
|
start_cursor = last_fragment_cursor if start_cursor - last_fragment_cursor > line_metrics.height
|
@@ -3456,21 +3490,24 @@ class Converter < ::Prawn::Document
|
|
3456
3490
|
pagenum_labels[n] = { P: (::PDF::Core::LiteralString.new %(#{i + 1})) }
|
3457
3491
|
end
|
3458
3492
|
|
3493
|
+
unless toc_page_nums.none? || (toc_title = doc.attr 'toc-title').nil_or_empty?
|
3494
|
+
toc_section = insert_toc_section doc, toc_title, toc_page_nums
|
3495
|
+
end
|
3496
|
+
|
3459
3497
|
outline.define do
|
3460
3498
|
initial_pagenum = has_front_cover ? 2 : 1
|
3461
3499
|
# FIXME use sanitize: :plain_text once available
|
3462
3500
|
if document.page_count >= initial_pagenum && (doctitle = doc.header? ? doc.doctitle : (doc.attr 'untitled-label'))
|
3463
3501
|
page title: (document.sanitize doctitle), destination: (document.dest_top has_front_cover ? 2 : 1)
|
3464
3502
|
end
|
3465
|
-
unless toc_page_nums.none? || (toc_title = doc.attr 'toc-title').nil_or_empty?
|
3466
|
-
page title: toc_title, destination: (document.dest_top toc_page_nums.first)
|
3467
|
-
end
|
3468
3503
|
# QUESTION is there any way to get add_outline_level to invoke in the context of the outline?
|
3469
3504
|
document.add_outline_level self, doc.sections, num_levels, expand_levels
|
3470
3505
|
end
|
3471
3506
|
|
3507
|
+
toc_section.parent.blocks.delete toc_section if toc_section
|
3508
|
+
|
3472
3509
|
catalog.data[:PageLabels] = state.store.ref Nums: pagenum_labels.flatten
|
3473
|
-
catalog.data[:PageMode] = :UseOutlines
|
3510
|
+
catalog.data[((doc.attr 'pdf-page-mode') || @theme.page_mode) == 'fullscreen' ? :NonFullScreenPageMode : :PageMode] = :UseOutlines
|
3474
3511
|
nil
|
3475
3512
|
end
|
3476
3513
|
|
@@ -3488,6 +3525,30 @@ class Converter < ::Prawn::Document
|
|
3488
3525
|
end
|
3489
3526
|
end
|
3490
3527
|
|
3528
|
+
def insert_toc_section doc, toc_title, toc_page_nums
|
3529
|
+
if (doc.attr? 'toc-placement', 'macro') && (toc_node = (doc.find_by context: :toc)[0])
|
3530
|
+
if (parent_section = toc_node.parent).context == :section
|
3531
|
+
grandparent_section = parent_section.parent
|
3532
|
+
toc_level = parent_section.level
|
3533
|
+
insert_idx = (grandparent_section.blocks.index parent_section) + 1
|
3534
|
+
else
|
3535
|
+
parent_section = grandparent_section = doc
|
3536
|
+
toc_level = doc.sections[0].level
|
3537
|
+
insert_idx = 0
|
3538
|
+
end
|
3539
|
+
toc_dest = toc_node.attr 'pdf-destination'
|
3540
|
+
else
|
3541
|
+
parent_section = grandparent_section = doc
|
3542
|
+
toc_level = doc.sections[0].level
|
3543
|
+
insert_idx = 0
|
3544
|
+
toc_dest = dest_top toc_page_nums.first
|
3545
|
+
end
|
3546
|
+
toc_section = Section.new grandparent_section, toc_level, false, { attributes: { 'pdf-destination' => toc_dest } }
|
3547
|
+
toc_section.title = toc_title
|
3548
|
+
grandparent_section.blocks.insert insert_idx, toc_section
|
3549
|
+
toc_section
|
3550
|
+
end
|
3551
|
+
|
3491
3552
|
def write pdf_doc, target
|
3492
3553
|
if target.respond_to? :write
|
3493
3554
|
require_relative 'ext/core/quantifiable_stdout' unless defined? ::QuantifiableStdout
|
@@ -3537,6 +3598,19 @@ class Converter < ::Prawn::Document
|
|
3537
3598
|
end
|
3538
3599
|
|
3539
3600
|
attr_reader :allow_uri_read
|
3601
|
+
attr_reader :cache_uri
|
3602
|
+
|
3603
|
+
def apply_text_decoration styles, category, level = nil
|
3604
|
+
if (text_decoration_style = TextDecorationStyleTable[(level && @theme[%(#{category}_h#{level}_text_decoration)]) || @theme[%(#{category}_text_decoration)]])
|
3605
|
+
{
|
3606
|
+
styles: (styles << text_decoration_style),
|
3607
|
+
text_decoration_color: (level && @theme[%(#{category}_h#{level}_text_decoration_color)]) || @theme[%(#{category}_text_decoration_color)],
|
3608
|
+
text_decoration_width: (level && @theme[%(#{category}_h#{level}_text_decoration_width)]) || @theme[%(#{category}_text_decoration_width)],
|
3609
|
+
}.compact
|
3610
|
+
else
|
3611
|
+
styles.empty? ? {} : { styles: styles }
|
3612
|
+
end
|
3613
|
+
end
|
3540
3614
|
|
3541
3615
|
def resolve_text_transform key, use_fallback = true
|
3542
3616
|
if (transform = ::Hash === key ? (key.delete :text_transform) : @theme[key.to_s])
|
@@ -3708,7 +3782,7 @@ class Converter < ::Prawn::Document
|
|
3708
3782
|
width_of_string str, opts
|
3709
3783
|
else
|
3710
3784
|
char_widths = chars.map {|char| rendered_width_of_char char, opts }
|
3711
|
-
char_widths.
|
3785
|
+
char_widths.sum + (char_widths.length * character_spacing)
|
3712
3786
|
end
|
3713
3787
|
end
|
3714
3788
|
|
@@ -3731,7 +3805,11 @@ class Converter < ::Prawn::Document
|
|
3731
3805
|
move_down line_metrics.padding_top
|
3732
3806
|
opts = { leading: line_metrics.leading, final_gap: line_metrics.final_gap }.merge opts
|
3733
3807
|
string = string.gsub CjkLineBreakRx, ZeroWidthSpace if @cjk_line_breaks
|
3734
|
-
if (
|
3808
|
+
if (hanging_indent = (opts.delete :hanging_indent) || 0) > 0
|
3809
|
+
indent hanging_indent do
|
3810
|
+
text string, (opts.merge indent_paragraphs: -hanging_indent)
|
3811
|
+
end
|
3812
|
+
elsif (first_line_opts = opts.delete :first_line_options)
|
3735
3813
|
# TODO good candidate for Prawn enhancement!
|
3736
3814
|
text_with_formatted_first_line string, first_line_opts, opts
|
3737
3815
|
else
|
@@ -3743,7 +3821,14 @@ class Converter < ::Prawn::Document
|
|
3743
3821
|
# QUESTION combine with typeset_text?
|
3744
3822
|
def typeset_formatted_text fragments, line_metrics, opts = {}
|
3745
3823
|
move_down line_metrics.padding_top
|
3746
|
-
|
3824
|
+
opts = { leading: line_metrics.leading, final_gap: line_metrics.final_gap }.merge opts
|
3825
|
+
if (hanging_indent = (opts.delete :hanging_indent) || 0) > 0
|
3826
|
+
indent hanging_indent do
|
3827
|
+
formatted_text fragments, (opts.merge indent_paragraphs: -hanging_indent)
|
3828
|
+
end
|
3829
|
+
else
|
3830
|
+
formatted_text fragments, opts
|
3831
|
+
end
|
3747
3832
|
move_down line_metrics.padding_bottom
|
3748
3833
|
end
|
3749
3834
|
|
@@ -3915,7 +4000,7 @@ class Converter < ::Prawn::Document
|
|
3915
4000
|
logger.warn %(allow-uri-read is not enabled; cannot embed remote image: #{image_path}) unless scratch?
|
3916
4001
|
return
|
3917
4002
|
end
|
3918
|
-
if
|
4003
|
+
if cache_uri
|
3919
4004
|
Helpers.require_library 'open-uri/cached', 'open-uri-cached' unless defined? ::OpenURI::Cache
|
3920
4005
|
else
|
3921
4006
|
::OpenURI
|
@@ -3926,6 +4011,7 @@ class Converter < ::Prawn::Document
|
|
3926
4011
|
open(image_path, (binary ? 'rb' : 'r')) {|fd| tmp_image.write fd.read }
|
3927
4012
|
tmp_image.path.extend TemporaryPath
|
3928
4013
|
rescue
|
4014
|
+
logger.warn %(could not retrieve remote image: #{image_path}; #{$!.message}) unless scratch?
|
3929
4015
|
nil
|
3930
4016
|
ensure
|
3931
4017
|
tmp_image.close
|
@@ -3976,6 +4062,7 @@ class Converter < ::Prawn::Document
|
|
3976
4062
|
image_opts = {
|
3977
4063
|
enable_file_requests_with_root: (::File.dirname image_path),
|
3978
4064
|
enable_web_requests: allow_uri_read,
|
4065
|
+
cache_images: cache_uri,
|
3979
4066
|
fallback_font_name: fallback_svg_font_name,
|
3980
4067
|
format: 'svg',
|
3981
4068
|
}
|
@@ -4122,6 +4209,16 @@ class Converter < ::Prawn::Document
|
|
4122
4209
|
end
|
4123
4210
|
end
|
4124
4211
|
|
4212
|
+
def resolve_top val
|
4213
|
+
if val.end_with? 'vh'
|
4214
|
+
page_height * (1 - (val.to_f / 100))
|
4215
|
+
elsif val.end_with? '%'
|
4216
|
+
@y - effective_page_height * (val.to_f / 100)
|
4217
|
+
else
|
4218
|
+
@y - (str_to_pt val)
|
4219
|
+
end
|
4220
|
+
end
|
4221
|
+
|
4125
4222
|
def add_link_to_image uri, image_info, image_opts
|
4126
4223
|
image_width = image_info[:width]
|
4127
4224
|
image_height = image_info[:height]
|
@@ -4173,8 +4270,7 @@ class Converter < ::Prawn::Document
|
|
4173
4270
|
if imagesdir
|
4174
4271
|
doc.set_attr 'imagesdir', imagesdir
|
4175
4272
|
else
|
4176
|
-
|
4177
|
-
doc.attributes.delete 'imagesdir'
|
4273
|
+
doc.remove_attr 'imagesdir'
|
4178
4274
|
end
|
4179
4275
|
value
|
4180
4276
|
end
|
@@ -4186,6 +4282,44 @@ class Converter < ::Prawn::Document
|
|
4186
4282
|
value
|
4187
4283
|
end
|
4188
4284
|
|
4285
|
+
def promote_author doc, idx = 1
|
4286
|
+
doc.remove_attr 'url' if (original_url = doc.attr 'url')
|
4287
|
+
email = nil
|
4288
|
+
if idx > 1
|
4289
|
+
original_attrs = AuthorAttributeNames.reduce({}) do |accum, name|
|
4290
|
+
accum[name] = doc.attr name
|
4291
|
+
if (val = doc.attr %(#{name}_#{idx}))
|
4292
|
+
doc.set_attr name, val
|
4293
|
+
# NOTE email holds url as well
|
4294
|
+
email = val if name == 'email'
|
4295
|
+
else
|
4296
|
+
doc.remove_attr name
|
4297
|
+
end
|
4298
|
+
accum
|
4299
|
+
end
|
4300
|
+
doc.set_attr 'url', ((email.include? '@') ? %(mailto:#{email}) : email) if email
|
4301
|
+
result = yield
|
4302
|
+
original_attrs.each do |name, val|
|
4303
|
+
if val
|
4304
|
+
doc.set_attr name, val
|
4305
|
+
else
|
4306
|
+
doc.remove_attr name
|
4307
|
+
end
|
4308
|
+
end
|
4309
|
+
else
|
4310
|
+
if (email = doc.attr 'email')
|
4311
|
+
doc.set_attr 'url', ((email.include? '@') ? %(mailto:#{email}) : email)
|
4312
|
+
end
|
4313
|
+
result = yield
|
4314
|
+
end
|
4315
|
+
if original_url
|
4316
|
+
doc.set_attr 'url', original_url
|
4317
|
+
elsif email
|
4318
|
+
doc.remove_attr 'url'
|
4319
|
+
end
|
4320
|
+
result
|
4321
|
+
end
|
4322
|
+
|
4189
4323
|
# NOTE assume URL is escaped (i.e., contains character references such as &)
|
4190
4324
|
def breakable_uri uri
|
4191
4325
|
scheme, address = uri.split UriSchemeBoundaryRx, 2
|