asciidoctor-pdf 2.1.5 → 2.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -46,8 +46,9 @@ module Asciidoctor
46
46
  tip: { name: 'far-lightbulb', stroke_color: '111111', size: 24 },
47
47
  warning: { name: 'fas-exclamation-triangle', stroke_color: 'BF6900', size: 24 },
48
48
  }
49
- TextAlignmentNames = %w(justify left center right)
50
- TextAlignmentRoles = %w(text-justify text-left text-center text-right)
49
+ TextAlignmentNames = { 'justify' => true, 'left' => true, 'center' => true, 'right' => true }
50
+ IndentableTextAlignments = { justify: true, left: true }
51
+ TextAlignmentRoles = { 'text-justify' => true, 'text-left' => true, 'text-center' => true, 'text-right' => true }
51
52
  TextDecorationStyleTable = { 'underline' => :underline, 'line-through' => :strikethrough }
52
53
  FontKerningTable = { 'normal' => true, 'none' => false }
53
54
  BlockFloatNames = %w(left right)
@@ -102,6 +103,7 @@ module Asciidoctor
102
103
  'filled' => (?\u2776..?\u277f).to_a + (?\u24eb..?\u24f4).to_a,
103
104
  }
104
105
  TypographicQuotes = %w(“ ” ‘ ’)
106
+ InlineFormatSniffRx = /[<&]/
105
107
  SimpleAttributeRefRx = /(?<!\\)\{\w+(?:-\w+)*\}/
106
108
  MeasurementRxt = '\\d+(?:\\.\\d+)?(?:in|cm|mm|p[txc])?'
107
109
  MeasurementPartsRx = /^(\d+(?:\.\d+)?)(in|mm|cm|p[txc])?$/
@@ -160,11 +162,11 @@ module Asciidoctor
160
162
  doc.promote_preface_block
161
163
  init_pdf doc
162
164
  # set default value for outline, outline-title, and pagenums attributes if not otherwise set
163
- doc.attributes['outline'] = '' unless (doc.attribute_locked? 'outline') || ((doc.instance_variable_get :@attributes_modified).include? 'outline')
164
- doc.attributes['outline-title'] = '' unless (doc.attribute_locked? 'outline-title') || ((doc.instance_variable_get :@attributes_modified).include? 'outline-title')
165
- doc.attributes['pagenums'] = '' unless (doc.attribute_locked? 'pagenums') || ((doc.instance_variable_get :@attributes_modified).include? 'pagenums')
165
+ doc.attributes['outline'] = '' if doc.attr_unspecified? 'outline'
166
+ doc.attributes['outline-title'] = '' if doc.attr_unspecified? 'outline-title'
167
+ doc.attributes['pagenums'] = '' if doc.attr_unspecified? 'pagenums'
166
168
 
167
- on_page_create(&(method :init_page))
169
+ on_page_create(&(method :init_page).curry[doc])
168
170
 
169
171
  marked_page_number = page_number
170
172
  # NOTE: a new page will already be started (page_number = 2) if the front cover image is a PDF
@@ -179,7 +181,7 @@ module Asciidoctor
179
181
  end
180
182
  start_new_page
181
183
  else
182
- @page_margin_by_side[:cover] = @page_margin_by_side[:recto] if @media == 'prepress' && page_number == 0
184
+ @page_margin[:cover] = @page_margin[page.layout][:recto] if @media == 'prepress' && page_number == 0
183
185
  start_new_page unless page&.empty? # rubocop:disable Lint/SafeNavigationWithEmpty
184
186
  # NOTE: the base font must be set before any content is written to the main or scratch document
185
187
  # this method is called inside ink_title_page if the title page is active
@@ -200,19 +202,24 @@ module Asciidoctor
200
202
  if (insert_toc = (doc.attr? 'toc') && !((toc_placement = doc.attr 'toc-placement') == 'macro' || toc_placement == 'preamble') && !(get_entries_for_toc doc).empty?)
201
203
  start_new_page if @ppbook && verso_page?
202
204
  add_dest_for_block doc, id: 'toc', y: (at_page_top? ? page_height : nil)
203
- @toc_extent = allocate_toc doc, toc_num_levels, cursor, title_page_on
205
+ @toc_extent = allocate_toc doc, toc_num_levels, cursor, (title_page_on && theme.toc_break_after != 'auto')
204
206
  else
205
207
  @toc_extent = nil
206
208
  end
207
209
 
208
- start_new_page if @ppbook && verso_page? && !(((next_block = doc.first_child)&.context == :preamble ? next_block.first_child : next_block)&.option? 'nonfacing')
210
+ if @ppbook && verso_page? && !(((next_block = doc.first_child)&.context == :preamble ? next_block.first_child : next_block)&.option? 'nonfacing')
211
+ min_start_at = 0
212
+ start_new_page
213
+ else
214
+ min_start_at = 1
215
+ end
209
216
 
210
217
  if title_page_on
211
218
  zero_page_offset = has_front_cover ? 1 : 0
212
219
  first_page_offset = has_title_page ? zero_page_offset.next : zero_page_offset
213
220
  body_offset = (body_start_page_number = page_number) - 1
214
221
  if ::Integer === (running_content_start_at = @theme.running_content_start_at)
215
- running_content_body_offset = body_offset + [running_content_start_at.pred, 0].max
222
+ running_content_body_offset = body_offset + [running_content_start_at.pred, min_start_at.pred].max
216
223
  running_content_start_at = 'body'
217
224
  else
218
225
  running_content_body_offset = body_offset
@@ -226,7 +233,7 @@ module Asciidoctor
226
233
  end
227
234
  end
228
235
  if ::Integer === (page_numbering_start_at = @theme.page_numbering_start_at)
229
- page_numbering_body_offset = body_offset + [page_numbering_start_at.pred, 0].max
236
+ page_numbering_body_offset = body_offset + [page_numbering_start_at.pred, min_start_at.pred].max
230
237
  page_numbering_start_at = 'body'
231
238
  else
232
239
  page_numbering_body_offset = body_offset
@@ -351,7 +358,6 @@ module Asciidoctor
351
358
  @cache_uri = doc.attr? 'cache-uri'
352
359
  @jail_dir = doc.safe < ::Asciidoctor::SafeMode::SAFE ? nil : doc.base_dir
353
360
  @media ||= doc.attr 'media', 'screen'
354
- @page_margin_by_side = { recto: (page_margin_recto = page_margin), verso: (page_margin_verso = page_margin), cover: page_margin }
355
361
  case doc.attr 'pdf-folio-placement', (@media == 'prepress' ? 'physical' : 'virtual')
356
362
  when 'physical'
357
363
  @folio_placement = { basis: :physical }
@@ -362,6 +368,13 @@ module Asciidoctor
362
368
  else
363
369
  @folio_placement = { basis: :virtual }
364
370
  end
371
+ @page_margin = { cover: page_margin }
372
+ @page_margin[:portrait] = @page_margin[:landscape] = { recto: (page_margin_recto = page_margin), verso: (page_margin_verso = page_margin) }
373
+ if (rotated_page_margin = resolve_page_margin (doc.attr 'pdf-page-margin-rotated') || theme.page_margin_rotated)
374
+ rotated_page_margin = expand_margin_value rotated_page_margin
375
+ @edge_shorthand_cache = nil
376
+ @page_margin[PageLayouts[(PageLayouts.index page.layout) - 1]] = { recto: rotated_page_margin, verso: rotated_page_margin.dup }
377
+ end
365
378
  if @media == 'prepress'
366
379
  @ppbook = doc.doctype == 'book'
367
380
  if (page_margin_outer = theme.page_margin_outer)
@@ -373,17 +386,7 @@ module Asciidoctor
373
386
  else
374
387
  @ppbook = nil
375
388
  end
376
- if (bg_image = resolve_background_image doc, theme, 'page-background-image')&.first
377
- @page_bg_image = { verso: bg_image, recto: bg_image }
378
- else
379
- @page_bg_image = { verso: nil, recto: nil }
380
- end
381
- if (bg_image = resolve_background_image doc, theme, 'page-background-image-verso')
382
- @page_bg_image[:verso] = bg_image[0] && bg_image
383
- end
384
- if (bg_image = resolve_background_image doc, theme, 'page-background-image-recto')
385
- @page_bg_image[:recto] = bg_image[0] && bg_image
386
- end
389
+ @page_bg_image = {}
387
390
  @page_bg_color = resolve_theme_color :page_background_color, 'FFFFFF'
388
391
  # QUESTION: should ThemeLoader handle registering fonts instead?
389
392
  register_fonts theme.font_catalog, ((doc.attr 'pdf-fontsdir')&.sub '{docdir}', (doc.attr 'docdir')) || 'GEM_FONTS_DIR'
@@ -393,11 +396,10 @@ module Asciidoctor
393
396
  @font_scale = 1
394
397
  @font_color = theme.base_font_color
395
398
  @text_decoration_width = theme.base_text_decoration_width
396
- @base_text_align = (text_align = doc.attr 'text-align') && (TextAlignmentNames.include? text_align) ? text_align : theme.base_text_align
399
+ @base_text_align = (text_align = doc.attr 'text-align') && TextAlignmentNames[text_align] ? text_align : theme.base_text_align
397
400
  @base_line_height = theme.base_line_height
398
401
  @cjk_line_breaks = doc.attr? 'scripts', 'cjk'
399
- if (hyphen_lang = (doc.attr 'hyphens') ||
400
- (((doc.attribute_locked? 'hyphens') || ((doc.instance_variable_get :@attributes_modified).include? 'hyphens')) ? nil : @theme.base_hyphens)) &&
402
+ if (hyphen_lang = (doc.attr 'hyphens') || ((doc.attr_unspecified? 'hyphens') ? @theme.base_hyphens : nil)) &&
401
403
  ((defined? ::Text::Hyphen::VERSION) || !(Helpers.require_library 'text/hyphen', 'text-hyphen', :warn).nil?)
402
404
  hyphen_lang = doc.attr 'lang' if !(::String === hyphen_lang) || hyphen_lang.empty?
403
405
  hyphen_lang = 'en_us' if hyphen_lang.nil_or_empty? || hyphen_lang == 'en'
@@ -435,34 +437,7 @@ module Asciidoctor
435
437
  end
436
438
 
437
439
  def build_pdf_options doc, theme
438
- case (page_margin = (doc.attr 'pdf-page-margin') || theme.page_margin)
439
- when ::Array
440
- if page_margin.empty?
441
- page_margin = nil
442
- else
443
- page_margin = page_margin.slice 0, 4 if page_margin.length > 4
444
- page_margin = page_margin.map {|v| ::Numeric === v ? v : (str_to_pt v.to_s) }
445
- end
446
- when ::Numeric
447
- page_margin = [page_margin]
448
- when ::String
449
- if page_margin.empty?
450
- page_margin = nil
451
- elsif (page_margin.start_with? '[') && (page_margin.end_with? ']')
452
- if (page_margin = (page_margin.slice 1, page_margin.length - 2).rstrip).empty?
453
- page_margin = nil
454
- else
455
- if (page_margin = page_margin.split ',', -1).length > 4
456
- page_margin = page_margin.slice 0, 4
457
- end
458
- page_margin = page_margin.map {|v| str_to_pt v.rstrip }
459
- end
460
- else
461
- page_margin = [(str_to_pt page_margin)]
462
- end
463
- else
464
- page_margin = nil
465
- end
440
+ page_margin = resolve_page_margin (doc.attr 'pdf-page-margin') || theme.page_margin
466
441
 
467
442
  if (doc.attr? 'pdf-page-size') && PageSizeRx =~ (doc.attr 'pdf-page-size')
468
443
  # e.g, [8.5in, 11in]
@@ -560,8 +535,10 @@ module Asciidoctor
560
535
  elsif (theme_name = doc.attr 'pdf-theme')
561
536
  theme = ThemeLoader.load_theme theme_name, (user_themesdir = (doc.attr 'pdf-themesdir')&.sub '{docdir}', (doc.attr 'docdir'))
562
537
  @themesdir = theme.__dir__
563
- else
538
+ elsif (doc.attr 'media', 'screen') == 'screen'
564
539
  @themesdir = (theme = ThemeLoader.load_theme).__dir__
540
+ else
541
+ @themesdir = (theme = ThemeLoader.load_theme 'default-for-print').__dir__
565
542
  end
566
543
  prepare_theme theme
567
544
  rescue
@@ -604,6 +581,7 @@ module Asciidoctor
604
581
  theme.code_linenum_font_color ||= '999999'
605
582
  theme.callout_list_margin_top_after_code ||= 0
606
583
  theme.role_unresolved_font_color ||= 'FF0000'
584
+ theme.footnotes_margin_top ||= 'auto'
607
585
  theme.footnotes_item_spacing ||= 0
608
586
  theme.index_columns ||= 2
609
587
  theme.index_column_gap ||= theme.base_font_size
@@ -768,22 +746,16 @@ module Asciidoctor
768
746
  pagenums = consolidate_ranges term.dests.map {|dest| dest[:page] }.uniq
769
747
  end
770
748
  pagenums.each do |pagenum|
771
- # NOTE: addresses a very minor kerning issue for text adjacent to the comma
772
- if (prev_fragment = term_fragments[-1]).size == 1
773
- if ::String === pagenum
774
- term_fragments[-1] = prev_fragment.merge text: %(#{prev_fragment[:text]}, #{pagenum})
775
- next
776
- else
777
- term_fragments[-1] = prev_fragment.merge text: %(#{prev_fragment[:text]}, )
778
- end
749
+ if ::String === pagenum
750
+ term_fragments << ({ text: %(, #{pagenum}) })
779
751
  else
780
- term_fragments << ({ text: ', ' })
752
+ term_fragments << { text: ', ' }
753
+ term_fragments << pagenum
781
754
  end
782
- term_fragments << (::String === pagenum ? { text: pagenum } : pagenum)
783
755
  end
784
756
  end
785
757
  subterm_indent = @theme.description_list_description_indent
786
- typeset_formatted_text term_fragments, (calc_line_metrics @base_line_height), align: :left, color: @font_color, hanging_indent: subterm_indent * 2
758
+ typeset_formatted_text term_fragments, (calc_line_metrics @base_line_height), align: :left, color: @font_color, hanging_indent: subterm_indent * 2, consolidate: true
787
759
  indent subterm_indent do
788
760
  term.subterms.each do |subterm|
789
761
  convert_index_term subterm, pagenum_sequence_style
@@ -809,10 +781,7 @@ module Asciidoctor
809
781
  ink_prose node.title, align: (@theme.abstract_title_text_align || @base_text_align).to_sym, margin_top: @theme.heading_margin_top, margin_bottom: @theme.heading_margin_bottom, line_height: (@theme.heading_line_height || @theme.base_line_height)
810
782
  end if node.title?
811
783
  theme_font :abstract do
812
- prose_opts = { align: (@theme.abstract_text_align || @base_text_align).to_sym, hyphenate: true }
813
- if (text_indent = @theme.prose_text_indent) > 0
814
- prose_opts[:indent_paragraphs] = text_indent
815
- end
784
+ prose_opts = { align: (@theme.abstract_text_align || @base_text_align).to_sym, hyphenate: true, margin_bottom: 0 }
816
785
  # FIXME: allow theme to control more first line options
817
786
  if (line1_font_style = @theme.abstract_first_line_font_style&.to_sym) && line1_font_style != font_style
818
787
  case line1_font_style
@@ -833,14 +802,11 @@ module Asciidoctor
833
802
  prose_opts[:first_line_options] = first_line_options if first_line_options
834
803
  # FIXME: make this cleaner!!
835
804
  if node.blocks?
836
- last_block = node.last_child
837
805
  node.blocks.each do |child|
838
806
  if child.context == :paragraph
839
807
  child.document.playback_attributes child.attributes
840
- prose_opts[:margin_bottom] = 0 if child == last_block
841
- ink_prose child.content, ((text_align = resolve_text_align_from_role child.roles) ? (prose_opts.merge align: text_align) : prose_opts.dup)
808
+ convert_paragraph child, prose_opts.dup
842
809
  prose_opts.delete :first_line_options
843
- prose_opts.delete :margin_bottom
844
810
  else
845
811
  # FIXME: this could do strange things if the wrong kind of content shows up
846
812
  child.convert
@@ -850,7 +816,10 @@ module Asciidoctor
850
816
  if (text_align = resolve_text_align_from_role node.roles)
851
817
  prose_opts[:align] = text_align
852
818
  end
853
- ink_prose string, (prose_opts.merge margin_bottom: 0)
819
+ if IndentableTextAlignments[prose_opts[:align]] && (text_indent = @theme.prose_text_indent) > 0
820
+ prose_opts[:indent_paragraphs] = text_indent
821
+ end
822
+ ink_prose string, prose_opts
854
823
  end
855
824
  end
856
825
  end
@@ -859,16 +828,19 @@ module Asciidoctor
859
828
  theme_margin :block, :bottom, (next_enclosed_block node)
860
829
  end
861
830
 
862
- def convert_paragraph node
831
+ def convert_paragraph node, opts = nil
863
832
  add_dest_for_block node if node.id
864
833
 
865
- prose_opts = { margin_bottom: 0, hyphenate: true }
834
+ prose_opts = opts || { margin_bottom: 0, hyphenate: true }
866
835
  if (text_align = resolve_text_align_from_role (roles = node.roles), query_theme: true, remove_predefined: true)
867
836
  prose_opts[:align] = text_align
837
+ else
838
+ text_align = @base_text_align.to_sym
868
839
  end
869
840
  role_keys = roles.map {|role| %(role_#{role}) } unless roles.empty?
870
- if (text_indent = @theme.prose_text_indent) > 0 ||
871
- ((text_indent = @theme.prose_text_indent_inner) > 0 && node.previous_sibling&.context == :paragraph)
841
+ if IndentableTextAlignments[text_align] &&
842
+ ((text_indent = @theme.prose_text_indent) > 0 ||
843
+ ((text_indent = @theme.prose_text_indent_inner) > 0 && node.previous_sibling&.context == :paragraph))
872
844
  prose_opts[:indent_paragraphs] = text_indent
873
845
  end
874
846
  if (bottom_gutter = @bottom_gutters[-1][node])
@@ -915,7 +887,7 @@ module Asciidoctor
915
887
  label_width = label_min_width || (icon_size * 1.5)
916
888
  elsif (icon_path = has_icon || !(icon_path = (@theme[%(admonition_icon_#{type})] || {})[:image]) ?
917
889
  (get_icon_image_path node, type) :
918
- (ThemeLoader.resolve_theme_asset (apply_subs_discretely doc, icon_path, subs: [:attributes]), @themesdir)) &&
890
+ (ThemeLoader.resolve_theme_asset (apply_subs_discretely doc, icon_path, subs: [:attributes], imagesdir: @themesdir), @themesdir)) &&
919
891
  (::File.readable? icon_path)
920
892
  icons = true
921
893
  # TODO: introduce @theme.admonition_image_width? or use size key from admonition_icon_<name>?
@@ -1073,7 +1045,7 @@ module Asciidoctor
1073
1045
  else
1074
1046
  highlighter = nil
1075
1047
  end
1076
- prev_subs = (subs = node.subs).dup
1048
+ saved_subs = (subs = node.subs).dup
1077
1049
  callouts_enabled = subs.include? :callouts
1078
1050
  highlight_idx = subs.index :highlight
1079
1051
  # NOTE: scratch? here only applies if listing block is nested inside another block
@@ -1083,16 +1055,33 @@ module Asciidoctor
1083
1055
  # switch the :highlight sub back to :specialcharacters
1084
1056
  subs[highlight_idx] = :specialcharacters
1085
1057
  else
1086
- prev_subs = nil
1058
+ saved_subs = nil
1087
1059
  end
1088
1060
  source_string = guard_indentation node.content
1089
1061
  elsif highlight_idx
1090
1062
  # NOTE: the source highlighter logic below handles the highlight and callouts subs
1091
- subs.replace subs - [:highlight, :callouts]
1092
- # NOTE: indentation guards will be added by the source highlighter logic
1093
- source_string = expand_tabs node.content
1063
+ if (subs - [:highlight, :callouts]).empty?
1064
+ subs.clear
1065
+ # NOTE: indentation guards will be added by the source highlighter logic
1066
+ source_string = expand_tabs node.content
1067
+ else
1068
+ if callouts_enabled
1069
+ saved_lines = node.lines.dup
1070
+ subs.delete :callouts
1071
+ prev_subs = subs.dup
1072
+ subs.clear
1073
+ source_string, conum_mapping = extract_conums node.content
1074
+ node.lines.replace (source_string.split LF)
1075
+ subs.replace prev_subs
1076
+ callouts_enabled = false
1077
+ end
1078
+ subs[highlight_idx] = :specialcharacters
1079
+ # NOTE: indentation guards will be added by the source highlighter logic
1080
+ source_string = expand_tabs unescape_xml (sanitize node.content, compact: false)
1081
+ node.lines.replace saved_lines if saved_lines
1082
+ end
1094
1083
  else
1095
- highlighter = prev_subs = nil
1084
+ highlighter = saved_subs = nil
1096
1085
  source_string = guard_indentation node.content
1097
1086
  end
1098
1087
  else
@@ -1184,7 +1173,7 @@ module Asciidoctor
1184
1173
  # NOTE: only format if we detect a need (callouts or inline formatting)
1185
1174
  source_chunks = (XMLMarkupRx.match? source_string) ? (text_formatter.format source_string) : [text: source_string]
1186
1175
  end
1187
- node.subs.replace prev_subs if prev_subs
1176
+ node.subs.replace saved_subs if saved_subs
1188
1177
  adjusted_font_size = ((node.option? 'autofit') || (node.document.attr? 'autofit-option')) ? (compute_autofit_font_size source_chunks, :code) : nil
1189
1178
  end
1190
1179
 
@@ -1308,7 +1297,7 @@ module Asciidoctor
1308
1297
  content = guard_indentation node.content
1309
1298
  ink_prose content,
1310
1299
  normalize: false,
1311
- align: :left,
1300
+ align: (resolve_text_align_from_role node.roles) || :left,
1312
1301
  hyphenate: true,
1313
1302
  margin_bottom: 0,
1314
1303
  bottom_gutter: (attribution ? nil : @bottom_gutters[-1][node])
@@ -1462,7 +1451,8 @@ module Asciidoctor
1462
1451
  end
1463
1452
  max_term_width += (term_padding[1] + term_padding[3])
1464
1453
  term_column_width = [max_term_width, bounds.width * 0.5].min
1465
- table table_data, position: :left, cell_style: { border_width: 0 }, column_widths: [term_column_width] do
1454
+ table table_data, position: :left, column_widths: [term_column_width] do
1455
+ cells.style border_width: 0
1466
1456
  @pdf.ink_table_caption node if node.title?
1467
1457
  end
1468
1458
  theme_margin :prose, :bottom, (next_enclosed_block actual_node) #unless actual_node.nested?
@@ -1631,6 +1621,9 @@ module Asciidoctor
1631
1621
  marker = node.parent.style == 'decimal' && index.abs < 10 ? %(#{index < 0 ? '-' : ''}0#{index.abs}.) : %(#{index}.)
1632
1622
  dir = (node.parent.option? 'reversed') ? :pred : :next
1633
1623
  @list_numerals << (index.public_send dir)
1624
+ [:font_color, :font_family, :font_size, :font_style, :line_height].each do |prop|
1625
+ marker_style[prop] = @theme[%(olist_marker_#{prop})] || marker_style[prop]
1626
+ end
1634
1627
  end
1635
1628
  end
1636
1629
  else # :ulist
@@ -1645,7 +1638,7 @@ module Asciidoctor
1645
1638
  else
1646
1639
  marker = @theme[%(ulist_marker_#{marker_type}_content)] || Bullets[marker_type]
1647
1640
  end
1648
- [:font_color, :font_family, :font_size, :line_height].each do |prop|
1641
+ [:font_color, :font_family, :font_size, :font_style, :line_height].each do |prop|
1649
1642
  marker_style[prop] = @theme[%(ulist_marker_#{marker_type}_#{prop})] || @theme[%(ulist_marker_#{prop})] || marker_style[prop]
1650
1643
  end if marker
1651
1644
  end
@@ -1656,8 +1649,9 @@ module Asciidoctor
1656
1649
  log :info, 'deprecated fa icon set found in theme; use fas, far, or fab instead'
1657
1650
  marker_style[:font_family] = FontAwesomeIconSets.find {|candidate| (icon_font_data candidate).yaml[candidate].value? marker } || 'fas'
1658
1651
  end
1652
+ marker_style[:font_style] &&= marker_style[:font_style].to_sym
1659
1653
  marker_gap = rendered_width_of_char 'x'
1660
- font marker_style[:font_family], marker_style[:font_size] do
1654
+ font marker_style[:font_family], size: marker_style[:font_size], style: marker_style[:font_style] do
1661
1655
  marker_width = rendered_width_of_string marker
1662
1656
  # NOTE: compensate if character_spacing is not applied to first character
1663
1657
  # see https://github.com/prawnpdf/prawn/commit/c61c5d48841910aa11b9e3d6f0e01b68ce435329
@@ -1676,6 +1670,7 @@ module Asciidoctor
1676
1670
  color: marker_style[:font_color],
1677
1671
  inline_format: false,
1678
1672
  line_height: marker_style[:line_height],
1673
+ style: marker_style[:font_style],
1679
1674
  margin: 0,
1680
1675
  normalize: false,
1681
1676
  single_line: true
@@ -1703,7 +1698,7 @@ module Asciidoctor
1703
1698
  alignment = float_to.to_sym
1704
1699
  elsif (alignment = node.attr 'align')
1705
1700
  alignment = (BlockAlignmentNames.include? alignment) ? alignment.to_sym : :left
1706
- elsif !(alignment = node.roles.reverse.find {|r| BlockAlignmentNames.include? r }&.to_sym)
1701
+ elsif !(alignment = node.roles.reverse.find {|role| BlockAlignmentNames.include? role }&.to_sym)
1707
1702
  alignment = @theme.image_align&.to_sym || :left
1708
1703
  end
1709
1704
  end
@@ -1937,14 +1932,16 @@ module Asciidoctor
1937
1932
  page_layout = nil
1938
1933
  end
1939
1934
 
1940
- if at_page_top?
1935
+ if at_page_top? && !(node.option? 'always')
1941
1936
  if page_layout && page_layout != page.layout && page.empty?
1942
1937
  delete_current_page
1943
- advance_page layout: page_layout
1938
+ advance_page layout: page_layout, margin: @page_margin[page_layout][page_side nil, @folio_placement[:inverted]]
1944
1939
  end
1945
1940
  elsif page_layout
1946
- advance_page layout: page_layout
1941
+ bounds.current_column = bounds.last_column if ColumnBox === bounds && !(node.has_role? 'column')
1942
+ advance_page layout: page_layout, margin: @page_margin[page_layout][page_side nil, @folio_placement[:inverted]]
1947
1943
  else
1944
+ bounds.current_column = bounds.last_column if ColumnBox === bounds && !(node.has_role? 'column')
1948
1945
  advance_page
1949
1946
  end
1950
1947
  end
@@ -2217,7 +2214,7 @@ module Asciidoctor
2217
2214
 
2218
2215
  if node.option? 'autowidth'
2219
2216
  table_width = (node.attr? 'width') ? bounds.width * ((node.attr 'tablepcwidth') / 100.0) :
2220
- (((node.has_role? 'stretch') || (node.has_role? 'spread')) ? bounds.width : nil)
2217
+ (((node.has_role? 'stretch')) ? bounds.width : nil)
2221
2218
  column_widths = []
2222
2219
  else
2223
2220
  table_width = bounds.width * ((node.attr 'tablepcwidth') / 100.0)
@@ -2239,10 +2236,10 @@ module Asciidoctor
2239
2236
  # NOTE: position is handled by this method
2240
2237
  position: :left,
2241
2238
  # NOTE: the border color, style, and width of the outer frame is set in the table callback block
2242
- cell_style: { border_color: grid_color.values, border_lines: grid_style.values, border_width: grid_width.values },
2243
2239
  width: table_width,
2244
2240
  column_widths: column_widths,
2245
2241
  }
2242
+ cell_style = { border_color: grid_color.values, border_lines: grid_style.values, border_width: grid_width.values }
2246
2243
 
2247
2244
  # QUESTION: should we support nth; should we support sequence of roles?
2248
2245
  case node.attr 'stripes', nil, 'table-stripes'
@@ -2258,6 +2255,8 @@ module Asciidoctor
2258
2255
 
2259
2256
  left_padding = right_padding = nil
2260
2257
  table table_data, table_settings do
2258
+ # NOTE: cell_style must be applied manually to be compatible with both prawn-table 0.2.2 and prawn-table 0.2.3
2259
+ cells.style cell_style
2261
2260
  @column_widths = column_widths unless column_widths.empty?
2262
2261
  # NOTE: call width to capture resolved table width
2263
2262
  table_width = width
@@ -2381,6 +2380,9 @@ module Asciidoctor
2381
2380
  node.content
2382
2381
  elsif node.content_model != :compound && (string = node.content)
2383
2382
  prose_opts = opts.merge hyphenate: true, margin_bottom: 0
2383
+ if (text_align = resolve_text_align_from_role node.roles)
2384
+ prose_opts[:align] = text_align
2385
+ end
2384
2386
  if (bottom_gutter = @bottom_gutters[-1][node])
2385
2387
  prose_opts[:bottom_gutter] = bottom_gutter
2386
2388
  end
@@ -2429,7 +2431,7 @@ module Asciidoctor
2429
2431
  # QUESTION: should we insert breakable chars into URI when building fragment instead?
2430
2432
  %(#{anchor}<a href="#{target}"#{attrs.join}>#{breakable_uri text}</a>)
2431
2433
  # NOTE: @media may not be initialized if method is called before convert phase
2432
- elsif (doc.attr? 'show-link-uri') || !(@media == 'screen' || (doc.attribute_locked? 'show-link-uri') || ((doc.instance_variable_get :@attributes_modified).include? 'show-link-uri'))
2434
+ elsif (doc.attr? 'show-link-uri') || (@media != 'screen' && (doc.attr_unspecified? 'show-link-uri'))
2433
2435
  # QUESTION: should we insert breakable chars into URI when building fragment instead?
2434
2436
  # TODO: allow style of printed link to be controlled by theme
2435
2437
  %(#{anchor}<a href="#{target}"#{attrs.join}>#{text}</a> [<font size="0.85em">#{breakable_uri bare_target}</font>&#93;)
@@ -2496,9 +2498,9 @@ module Asciidoctor
2496
2498
  else
2497
2499
  label = index
2498
2500
  end
2499
- %(#{anchor}<sup>[<a anchor="_footnotedef_#{index}">#{label}</a>]</sup>)
2501
+ %(<sup class="wj">#{anchor}[<a anchor="_footnotedef_#{index}">#{label}</a>]</sup>)
2500
2502
  elsif node.type == :xref
2501
- %(<sup><font color="#{theme.role_unresolved_font_color}">[#{node.text}]</font></sup>)
2503
+ %(<sup class="wj"><font color="#{theme.role_unresolved_font_color}">[#{node.text}]</font></sup>)
2502
2504
  else
2503
2505
  log :warn, %(unknown footnote type: #{node.type.inspect})
2504
2506
  nil
@@ -2793,6 +2795,11 @@ module Asciidoctor
2793
2795
  if (imagesdir = opts[:imagesdir])
2794
2796
  imagesdir_to_restore = doc.attr 'imagesdir'
2795
2797
  doc.set_attr 'imagesdir', imagesdir
2798
+ remove_docimagesdir = doc.set_attr 'docimagesdir', (::File.absolute_path imagesdir_to_restore.to_s, (doc.attr 'docdir', '')), false
2799
+ end
2800
+ if (page_layout = opts[:page_layout])
2801
+ page_layout_to_restore = doc.attr 'page-layout'
2802
+ doc.set_attr 'page-layout', page.layout.to_s
2796
2803
  end
2797
2804
  # FIXME: get sub_attributes to handle drop-line w/o a warning
2798
2805
  doc.set_attr 'attribute-missing', 'skip' unless (attribute_missing = doc.attr 'attribute-missing') == 'skip'
@@ -2801,20 +2808,26 @@ module Asciidoctor
2801
2808
  value = (value.split LF).delete_if {|line| SimpleAttributeRefRx.match? line }.join LF if opts[:drop_lines_with_unresolved_attributes] && (value.include? '{')
2802
2809
  value = value.gsub '\{', '{' if escaped_attr_ref
2803
2810
  doc.set_attr 'attribute-missing', attribute_missing unless attribute_missing == 'skip'
2804
- imagesdir_to_restore ? (doc.set_attr 'imagesdir', imagesdir_to_restore) : (doc.remove_attr 'imagesdir') if imagesdir
2811
+ page_layout_to_restore ? (doc.set_attr 'page-layout', page_layout_to_restore) : (doc.remove_attr 'page-layout') if page_layout
2812
+ if imagesdir
2813
+ imagesdir_to_restore ? (doc.set_attr 'imagesdir', imagesdir_to_restore) : (doc.remove_attr 'imagesdir')
2814
+ doc.remove_attr 'docimagesdir' if remove_docimagesdir
2815
+ end
2805
2816
  value
2806
2817
  end
2807
2818
 
2808
2819
  # Position the cursor for where to ink the specified section title or discrete heading node.
2809
2820
  #
2810
- # This method computes whether there is enough room on the page to prevent the specified node
2811
- # from being orphaned. If there is not enough room, the method will advance the cursor to
2821
+ # This method computes whether there's enough room on the page to prevent the specified node
2822
+ # from being orphaned. If there's not enough room, the method will advance the cursor to
2812
2823
  # the next page. This method is not called if the cursor is already at the top of the page or
2813
2824
  # whether this node has no node that follows it in document order.
2814
2825
  def arrange_heading node, title, opts
2815
- if node.option? 'breakable'
2826
+ if (min_height_after = @theme.heading_min_height_after) == 'auto' || (node.option? 'breakable')
2816
2827
  orphaned = nil
2828
+ doc = node.document
2817
2829
  dry_run single_page: true do
2830
+ push_scratch doc
2818
2831
  start_page = page
2819
2832
  theme_font :heading, level: opts[:level] do
2820
2833
  if opts[:part]
@@ -2829,19 +2842,18 @@ module Asciidoctor
2829
2842
  page.tare_content_stream
2830
2843
  orphaned = stop_if_first_page_empty { node.context == :section ? (traverse node) : (convert node.next_sibling) }
2831
2844
  end
2845
+ ensure
2846
+ pop_scratch doc
2832
2847
  end
2833
2848
  advance_page if orphaned
2834
2849
  else
2835
2850
  theme_font :heading, level: (hlevel = opts[:level]) do
2836
2851
  h_padding_t, h_padding_r, h_padding_b, h_padding_l = expand_padding_value @theme[%(heading_h#{hlevel}_padding)]
2837
2852
  h_fits = indent h_padding_l, h_padding_r do
2838
- # FIXME: this height doesn't account for impact of text transform or inline formatting
2839
- heading_h = (height_of_typeset_text title) +
2853
+ heading_h = (height_of_typeset_text title, inline_format: true, text_transform: @text_transform) +
2840
2854
  (@theme[%(heading_h#{hlevel}_margin_top)] || @theme.heading_margin_top) +
2841
2855
  (@theme[%(heading_h#{hlevel}_margin_bottom)] || @theme.heading_margin_bottom) + h_padding_t + h_padding_b
2842
- if (min_height_after = @theme.heading_min_height_after) && (node.context == :section ? node.blocks? : !node.last_child?)
2843
- heading_h += min_height_after
2844
- end
2856
+ heading_h += min_height_after if min_height_after && (node.context == :section ? node.blocks? : !node.last_child?)
2845
2857
  cursor >= heading_h
2846
2858
  end
2847
2859
  advance_page unless h_fits
@@ -3010,8 +3022,16 @@ module Asciidoctor
3010
3022
  end
3011
3023
 
3012
3024
  def height_of_typeset_text string, opts = {}
3025
+ if (transform = opts[:text_transform])
3026
+ string = transform_text string, transform
3027
+ end
3028
+ if (inline_format = opts[:inline_format]) && (InlineFormatSniffRx.match? string)
3029
+ fragments = parse_text string, inline_format: inline_format
3030
+ else
3031
+ fragments = [{ text: string }]
3032
+ end
3013
3033
  line_metrics = (calc_line_metrics opts[:line_height] || @base_line_height)
3014
- (height_of string, leading: line_metrics.leading, final_gap: line_metrics.final_gap) + line_metrics.padding_top + (opts[:single_line] ? 0 : line_metrics.padding_bottom)
3034
+ (height_of_formatted fragments, leading: line_metrics.leading, final_gap: line_metrics.final_gap) + line_metrics.padding_top + (opts[:single_line] ? 0 : line_metrics.padding_bottom)
3015
3035
  end
3016
3036
 
3017
3037
  # Render the caption in the current document. If the dry_run option is true, return the height.
@@ -3103,7 +3123,7 @@ module Asciidoctor
3103
3123
  opts = opts.merge inherited
3104
3124
  end
3105
3125
  unless scratch? || !(bg_color = @theme[%(#{category_caption}_background_color)] || @theme.caption_background_color)
3106
- caption_height = height_of_typeset_text string
3126
+ caption_height = height_of_typeset_text string, inline_format: true, text_transform: @text_transform
3107
3127
  fill_at = [bounds.left, cursor]
3108
3128
  fill_at[1] -= (margin[:top] || 0) unless at_page_top?
3109
3129
  float { bounding_box(fill_at, width: container_width, height: caption_height) { fill_bounds bg_color } }
@@ -3143,7 +3163,7 @@ module Asciidoctor
3143
3163
  open_graphics_state if face == :front
3144
3164
  return
3145
3165
  elsif image_path == '~'
3146
- @page_margin_by_side[:cover] = @page_margin_by_side[:recto] if @media == 'prepress'
3166
+ @page_margin[:cover] = @page_margin[page.layout][:recto] if @media == 'prepress'
3147
3167
  return
3148
3168
  end
3149
3169
 
@@ -3164,9 +3184,9 @@ module Asciidoctor
3164
3184
  def ink_footnotes node
3165
3185
  return if (fns = (doc = node.document).footnotes - @rendered_footnotes).empty?
3166
3186
  theme_margin :block, :bottom if node.context == :document || node == node.document.last_child
3167
- theme_margin :footnotes, :top
3187
+ theme_margin :footnotes, :top unless (valign_bottom = @theme.footnotes_margin_top == 'auto')
3168
3188
  with_dry_run do |extent|
3169
- if (single_page_height = extent&.single_page_height) && (delta = cursor - single_page_height - 0.0001) > 0
3189
+ if valign_bottom && (single_page_height = extent&.single_page_height) && (delta = cursor - single_page_height - 0.0001) > 0
3170
3190
  move_down delta
3171
3191
  end
3172
3192
  theme_font :footnotes do
@@ -3274,7 +3294,7 @@ module Asciidoctor
3274
3294
 
3275
3295
  def allocate_running_content_layout doc, page, periphery, cache
3276
3296
  cache[layout = page.layout] ||= begin
3277
- page_margin_recto = @page_margin_by_side[:recto]
3297
+ page_margin_recto = @page_margin[layout][:recto]
3278
3298
  trim_margin_recto = @theme[%(#{periphery}_recto_margin)] || @theme[%(#{periphery}_margin)] || [0, 'inherit', 0, 'inherit']
3279
3299
  trim_margin_recto = (expand_margin_value trim_margin_recto).map.with_index {|v, i| i.odd? && v == 'inherit' ? page_margin_recto[i] : v.to_f }
3280
3300
  trim_content_margin_recto = @theme[%(#{periphery}_recto_content_margin)] || @theme[%(#{periphery}_content_margin)] || [0, 'inherit', 0, 'inherit']
@@ -3284,7 +3304,7 @@ module Asciidoctor
3284
3304
  else
3285
3305
  trim_padding_recto = trim_content_margin_recto
3286
3306
  end
3287
- page_margin_verso = @page_margin_by_side[:verso]
3307
+ page_margin_verso = @page_margin[layout][:verso]
3288
3308
  trim_margin_verso = @theme[%(#{periphery}_verso_margin)] || @theme[%(#{periphery}_margin)] || [0, 'inherit', 0, 'inherit']
3289
3309
  trim_margin_verso = (expand_margin_value trim_margin_verso).map.with_index {|v, i| i.odd? && v == 'inherit' ? page_margin_verso[i] : v.to_f }
3290
3310
  trim_content_margin_verso = @theme[%(#{periphery}_verso_content_margin)] || @theme[%(#{periphery}_content_margin)] || [0, 'inherit', 0, 'inherit']
@@ -3679,7 +3699,7 @@ module Asciidoctor
3679
3699
  logo_image_attrs = (AttributeList.new $2).parse %w(alt width height)
3680
3700
  if logo_image_from_theme
3681
3701
  relative_to_imagesdir = false
3682
- logo_image_path = apply_subs_discretely doc, $1, subs: [:attributes]
3702
+ logo_image_path = apply_subs_discretely doc, $1, subs: [:attributes], imagesdir: @themesdir
3683
3703
  logo_image_path = ThemeLoader.resolve_theme_asset logo_image_path, @themesdir unless doc.is_uri? logo_image_path
3684
3704
  else
3685
3705
  relative_to_imagesdir = true
@@ -3689,7 +3709,7 @@ module Asciidoctor
3689
3709
  logo_image_attrs = {}
3690
3710
  relative_to_imagesdir = false
3691
3711
  if logo_image_from_theme
3692
- logo_image_path = apply_subs_discretely doc, logo_image_path, subs: [:attributes]
3712
+ logo_image_path = apply_subs_discretely doc, logo_image_path, subs: [:attributes], imagesdir: @themesdir
3693
3713
  logo_image_path = ThemeLoader.resolve_theme_asset logo_image_path, @themesdir unless doc.is_uri? logo_image_path
3694
3714
  end
3695
3715
  end
@@ -3775,12 +3795,12 @@ module Asciidoctor
3775
3795
  end
3776
3796
  end
3777
3797
 
3778
- def allocate_toc doc, toc_num_levels, toc_start_cursor, title_page_on
3798
+ def allocate_toc doc, toc_num_levels, toc_start_cursor, break_after_toc
3779
3799
  toc_start_page_number = page_number
3780
3800
  to_page = nil
3781
3801
  extent = dry_run onto: self do
3782
3802
  to_page = (ink_toc doc, toc_num_levels, toc_start_page_number, toc_start_cursor).end
3783
- theme_margin :block, :bottom unless title_page_on
3803
+ theme_margin :block, :bottom unless break_after_toc
3784
3804
  end
3785
3805
  # NOTE: patch for custom converters that allocate extra TOC pages without actually creating them
3786
3806
  if to_page > extent.to.page
@@ -3788,7 +3808,7 @@ module Asciidoctor
3788
3808
  extent.to.cursor = bounds.height
3789
3809
  end
3790
3810
  # NOTE: reserve pages for the toc; leaves cursor on page after last page in toc
3791
- if title_page_on
3811
+ if break_after_toc
3792
3812
  extent.each_page { start_new_page }
3793
3813
  else
3794
3814
  extent.each_page {|first_page| start_new_page unless first_page }
@@ -4059,18 +4079,14 @@ module Asciidoctor
4059
4079
  return []
4060
4080
  elsif (image_path.include? ':') && image_path =~ ImageAttributeValueRx
4061
4081
  image_attrs = (AttributeList.new $2).parse %w(alt width)
4062
- if from_theme
4063
- image_path = apply_subs_discretely doc, $1, subs: [:attributes]
4064
- image_relative_to = @themesdir
4065
- else
4066
- image_path = $1
4067
- image_relative_to = true
4068
- end
4069
- elsif from_theme
4070
- image_path = apply_subs_discretely doc, image_path, subs: [:attributes]
4071
- image_relative_to = @themesdir
4082
+ image_path = $1
4083
+ image_relative_to = true
4084
+ end
4085
+ if from_theme
4086
+ image_path = apply_subs_discretely doc, image_path, subs: [:attributes], imagesdir: (image_relative_to = @themesdir), page_layout: page.layout.to_s
4087
+ elsif image_path.include? '{page-layout}'
4088
+ image_path = image_path.sub '{page-layout}', page.layout.to_s
4072
4089
  end
4073
-
4074
4090
  image_path, image_format = ::Asciidoctor::Image.target_and_format image_path, image_attrs
4075
4091
  image_path = resolve_image_path doc, image_path, image_format, image_relative_to
4076
4092
 
@@ -4284,9 +4300,30 @@ module Asciidoctor
4284
4300
  end
4285
4301
  end
4286
4302
 
4303
+ def resolve_page_margin value
4304
+ return if value.nil_or_empty?
4305
+ case value
4306
+ when ::Array
4307
+ value = value.slice 0, 4 if value.length > 4
4308
+ value.map {|v| ::Numeric === v ? v : (str_to_pt v.to_s) }
4309
+ when ::Numeric
4310
+ [value]
4311
+ when ::String
4312
+ if (value.start_with? '[') && (value.end_with? ']')
4313
+ return if (value = (value.slice 1, value.length - 2).rstrip).empty?
4314
+ if (value = value.split ',', -1).length > 4
4315
+ value = value.slice 0, 4
4316
+ end
4317
+ value.map {|v| str_to_pt v.rstrip }
4318
+ else
4319
+ [(str_to_pt value)]
4320
+ end
4321
+ end
4322
+ end
4323
+
4287
4324
  def resolve_text_align_from_role roles, query_theme: false, remove_predefined: false
4288
- if (align_role = roles.reverse.find {|r| TextAlignmentRoles.include? r })
4289
- roles.replace roles - TextAlignmentRoles if remove_predefined
4325
+ if (align_role = roles.reverse.find {|role| TextAlignmentRoles[role] })
4326
+ roles.replace roles - TextAlignmentRoles.keys if remove_predefined
4290
4327
  (align_role.slice 5, align_role.length).to_sym
4291
4328
  elsif query_theme
4292
4329
  roles.reverse.each do |role|
@@ -4336,17 +4373,17 @@ module Asciidoctor
4336
4373
  elsif @ppbook && page_number > 0 && recto_page?
4337
4374
  start_new_page
4338
4375
  end
4339
- side = page_side (recycle ? nil : page_number + 1), @folio_placement[:inverted]
4340
- prev_bg_image = @page_bg_image[side]
4341
- prev_bg_color = @page_bg_color
4342
4376
  if (bg_image = resolve_background_image doc, @theme, 'title-page-background-image')
4343
- @page_bg_image[side] = bg_image[0] && bg_image
4377
+ side = page_side (recycle ? nil : page_number + 1), @folio_placement[:inverted]
4378
+ prev_bg_image = get_page_bg_image doc, @theme, (layout = page.layout), side
4379
+ @page_bg_image[layout][side] = bg_image[0] && bg_image
4344
4380
  end
4345
4381
  if (bg_color = resolve_theme_color :title_page_background_color)
4382
+ prev_bg_color = @page_bg_color
4346
4383
  @page_bg_color = bg_color
4347
4384
  end
4348
- recycle ? float { init_page self } : start_new_page
4349
- @page_bg_image[side] = prev_bg_image if bg_image
4385
+ recycle ? float { init_page doc, self } : start_new_page
4386
+ @page_bg_image[layout][side] = prev_bg_image if bg_image
4350
4387
  @page_bg_color = prev_bg_color if bg_color
4351
4388
  true
4352
4389
  end
@@ -4433,7 +4470,12 @@ module Asciidoctor
4433
4470
  if opts.key? :level
4434
4471
  hlevel_category = %(#{category}_h#{opts[:level]})
4435
4472
  family = @theme[%(#{hlevel_category}_font_family)] || @theme[%(#{category}_font_family)] || @theme.base_font_family || font_family
4436
- size = (@theme[%(#{hlevel_category}_font_size)] || @theme[%(#{category}_font_size)] || @root_font_size) * @font_scale
4473
+ if (size = @theme[%(#{hlevel_category}_font_size)] || @theme[%(#{category}_font_size)])
4474
+ scale = @font_scale unless ::String === size
4475
+ else
4476
+ scale = @font_scale
4477
+ size = @root_font_size
4478
+ end
4437
4479
  style = @theme[%(#{hlevel_category}_font_style)] || @theme[%(#{category}_font_style)]
4438
4480
  color = @theme[%(#{hlevel_category}_font_color)] || @theme[%(#{category}_font_color)]
4439
4481
  kerning = resolve_font_kerning @theme[%(#{hlevel_category}_font_kerning)] || @theme[%(#{category}_font_kerning)]
@@ -4443,7 +4485,11 @@ module Asciidoctor
4443
4485
  else
4444
4486
  inherited_font = font_info
4445
4487
  family = @theme[%(#{category}_font_family)] || inherited_font[:family]
4446
- size = (size = @theme[%(#{category}_font_size)]) ? size * @font_scale : inherited_font[:size]
4488
+ if (size = @theme[%(#{category}_font_size)])
4489
+ scale = @font_scale unless ::String === size
4490
+ else
4491
+ size = inherited_font[:size]
4492
+ end
4447
4493
  style = @theme[%(#{category}_font_style)] || inherited_font[:style]
4448
4494
  color = @theme[%(#{category}_font_color)]
4449
4495
  kerning = resolve_font_kerning @theme[%(#{category}_font_kerning)]
@@ -4459,6 +4505,7 @@ module Asciidoctor
4459
4505
 
4460
4506
  result = nil
4461
4507
  font family, size: size, style: style&.to_sym do
4508
+ @font_size *= scale if scale
4462
4509
  result = yield
4463
4510
  ensure
4464
4511
  @font_color = prev_color if color
@@ -4518,6 +4565,7 @@ module Asciidoctor
4518
4565
  # QUESTION: combine with typeset_text?
4519
4566
  def typeset_formatted_text fragments, line_metrics, opts = {}
4520
4567
  opts = { leading: line_metrics.leading, initial_gap: line_metrics.padding_top, final_gap: line_metrics.final_gap }.merge opts
4568
+ fragments = consolidate_fragments fragments if opts.delete :consolidate
4521
4569
  if (hanging_indent = (opts.delete :hanging_indent) || 0) > 0
4522
4570
  indent hanging_indent do
4523
4571
  formatted_text fragments, (opts.merge indent_paragraphs: -hanging_indent)
@@ -4727,6 +4775,20 @@ module Asciidoctor
4727
4775
  end
4728
4776
  end
4729
4777
 
4778
+ def consolidate_fragments fragments
4779
+ return fragments unless fragments.size > 1
4780
+ accum = []
4781
+ prev_fragment = nil
4782
+ fragments.each do |fragment|
4783
+ if prev_fragment && fragment == (prev_fragment.merge text: (fragment_text = fragment[:text]))
4784
+ prev_fragment[:text] += fragment_text
4785
+ else
4786
+ accum << (prev_fragment = fragment)
4787
+ end
4788
+ end
4789
+ accum
4790
+ end
4791
+
4730
4792
  def conum_glyph number
4731
4793
  @conum_glyphs[number - 1]
4732
4794
  end
@@ -4795,6 +4857,23 @@ module Asciidoctor
4795
4857
  (code.start_with? '\u') ? ([((code.slice 2, code.length).to_i 16)].pack 'U1') : code
4796
4858
  end
4797
4859
 
4860
+ def get_page_bg_image doc, theme_, layout, side
4861
+ (@page_bg_image[layout] ||= begin
4862
+ if (bg_image = resolve_background_image doc, theme_, 'page-background-image')&.first
4863
+ val = { verso: bg_image, recto: bg_image }
4864
+ else
4865
+ val = { verso: nil, recto: nil }
4866
+ end
4867
+ if (bg_image = resolve_background_image doc, theme_, 'page-background-image-verso')
4868
+ val[:verso] = bg_image[0] && bg_image
4869
+ end
4870
+ if (bg_image = resolve_background_image doc, theme_, 'page-background-image-recto')
4871
+ val[:recto] = bg_image[0] && bg_image
4872
+ end
4873
+ val
4874
+ end)[side]
4875
+ end
4876
+
4798
4877
  def get_icon_image_path node, type, resolve = true
4799
4878
  doc = node.document
4800
4879
  doc.remove_attr 'data-uri' if (data_uri_enabled = doc.attr? 'data-uri')
@@ -4818,18 +4897,17 @@ module Asciidoctor
4818
4897
  @float_box = { page: page_number, top: box_t, right: box_r, bottom: box_b, left: box_l, width: box_w, height: box_h, gap: gap }
4819
4898
  end
4820
4899
 
4821
- # NOTE: init_page is called within a float context; this will suppress prawn-svg messing with the cursor
4822
4900
  # NOTE: init_page is not called for imported pages, cover pages, image pages, and pages in the scratch document
4823
- def init_page *_args
4901
+ def init_page doc, _self
4824
4902
  next_page_side = page_side nil, @folio_placement[:inverted]
4825
- if @media == 'prepress' && (next_page_margin = @page_margin_by_side[page_number == 1 ? :cover : next_page_side]) != page_margin
4903
+ if @media == 'prepress' && (next_page_margin = page_number == 1 ? @page_margin[:cover] : @page_margin[page.layout][next_page_side]) != page_margin
4826
4904
  set_page_margin next_page_margin
4827
4905
  end
4828
4906
  unless @page_bg_color == 'FFFFFF'
4829
4907
  fill_absolute_bounds @page_bg_color
4830
4908
  tare = true
4831
4909
  end
4832
- if (bg_image_path, bg_image_opts = @page_bg_image[next_page_side])
4910
+ if (bg_image_path, bg_image_opts = get_page_bg_image doc, @theme, (layout = page.layout), next_page_side)
4833
4911
  begin
4834
4912
  if bg_image_opts[:format] == 'pdf'
4835
4913
  # NOTE: pages that use PDF for the background do not support a background color or running content
@@ -4840,9 +4918,10 @@ module Asciidoctor
4840
4918
  end
4841
4919
  tare = true
4842
4920
  rescue
4843
- facing_page_side = (PageSides - [next_page_side])[0]
4844
- @page_bg_image[facing_page_side] = nil if @page_bg_image[facing_page_side] == @page_bg_image[next_page_side]
4845
- @page_bg_image[next_page_side] = nil
4921
+ facing_page_side = PageSides[(PageSides.index next_page_side) - 1]
4922
+ bg_image_by_side = @page_bg_image[layout]
4923
+ bg_image_by_side[facing_page_side] = nil if bg_image_by_side[facing_page_side] == bg_image_by_side[next_page_side]
4924
+ bg_image_by_side[next_page_side] = nil
4846
4925
  log :warn, %(could not embed page background image: #{bg_image_path}; #{$!.message})
4847
4926
  end
4848
4927
  end
@@ -5013,7 +5092,9 @@ module Asciidoctor
5013
5092
  end
5014
5093
 
5015
5094
  def resolve_top val
5016
- if val.end_with? 'vh'
5095
+ if ::Numeric === val
5096
+ @y - val
5097
+ elsif val.end_with? 'vh'
5017
5098
  page_height * (1 - (val.to_f / 100))
5018
5099
  elsif val.end_with? '%'
5019
5100
  @y - effective_page_height * (val.to_f / 100)
@@ -5105,20 +5186,17 @@ module Asciidoctor
5105
5186
  @label = :scratch
5106
5187
  @save_state = nil
5107
5188
  @scratch_depth = 0
5108
- # NOTE: don't need background image in scratch document; can cause marshal error anyway
5109
- saved_page_bg_image, @page_bg_image = @page_bg_image, { verso: nil, recto: nil }
5110
5189
  # NOTE: pdfmark has a reference to the Asciidoctor::Document, which we don't want to serialize
5111
5190
  saved_pdfmark, @pdfmark = @pdfmark, nil
5112
5191
  # IMPORTANT: don't set font before using marshal as it causes serialization to fail
5113
5192
  result = yield
5114
5193
  @pdfmark = saved_pdfmark
5115
- @page_bg_image = saved_page_bg_image
5116
5194
  @label = :primary
5117
5195
  result
5118
5196
  end
5119
5197
 
5120
5198
  def init_scratch originator
5121
- if @media == 'prepress' && page_margin != (page_margin_recto = @page_margin_by_side[:recto])
5199
+ if @media == 'prepress' && page_margin != (page_margin_recto = @page_margin[page.layout][:recto])
5122
5200
  # NOTE: prepare scratch document to use page margin from recto side (which has same width as verso side)
5123
5201
  set_page_margin page_margin_recto
5124
5202
  end