asciidoctor-pdf 2.0.0.beta.1 → 2.0.0.beta.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -52,6 +52,7 @@ module Asciidoctor
52
52
  TextAlignmentRoles = %w(text-justify text-left text-center text-right)
53
53
  TextDecorationStyleTable = { 'underline' => :underline, 'line-through' => :strikethrough }
54
54
  FontKerningTable = { 'normal' => true, 'none' => false }
55
+ BlockFloatNames = %w(left right)
55
56
  BlockAlignmentNames = %w(left center right)
56
57
  (AlignmentTable = { '<' => :left, '=' => :center, '>' => :right }).default = :left
57
58
  ColumnPositions = [:left, :center, :right]
@@ -486,8 +487,9 @@ module Asciidoctor
486
487
  theme.code_linenum_font_color ||= '999999'
487
488
  theme.callout_list_margin_top_after_code ||= 0
488
489
  theme.role_unresolved_font_color ||= 'FF0000'
489
- theme.index_columns ||= 2
490
490
  theme.footnotes_item_spacing ||= 0
491
+ theme.index_columns ||= 2
492
+ theme.index_column_gap ||= theme.base_font_size
491
493
  theme.kbd_separator ||= '+'
492
494
  theme.title_page_authors_delimiter ||= ', '
493
495
  theme.title_page_revision_delimiter ||= ', '
@@ -665,24 +667,24 @@ module Asciidoctor
665
667
  title, _, subtitle = title.rpartition sep
666
668
  title = %(#{title}\n<em class="subtitle">#{subtitle}</em>)
667
669
  end
668
- hlevel = sect.level + 1
670
+ hlevel = sect.level.next
669
671
  align = (@theme[%(heading_h#{hlevel}_text_align)] || @theme.heading_text_align || @base_text_align).to_sym
670
672
  chapterlike = !(part = sectname == 'part') && (sectname == 'chapter' || (sect.document.doctype == 'book' && sect.level == 1))
671
673
  hidden = sect.option? 'notitle'
672
674
  hopts = { align: align, level: hlevel, part: part, chapterlike: chapterlike, outdent: !(part || chapterlike) }
673
675
  if part
674
676
  unless @theme.heading_part_break_before == 'auto'
675
- start_new = true
677
+ started_new = true
676
678
  start_new_part sect
677
679
  end
678
680
  elsif chapterlike
679
681
  if @theme.heading_chapter_break_before != 'auto' ||
680
682
  (@theme.heading_part_break_after == 'always' && sect == sect.parent.sections[0])
681
- start_new = true
683
+ started_new = true
682
684
  start_new_chapter sect
683
685
  end
684
686
  end
685
- arrange_section sect, title, hopts unless hidden || start_new || at_page_top?
687
+ arrange_heading sect, title, hopts unless hidden || started_new || at_page_top? || !sect.blocks?
686
688
  # QUESTION: should we store pdf-page-start, pdf-anchor & pdf-destination in internal map?
687
689
  sect.set_attr 'pdf-page-start', (start_pgnum = page_number)
688
690
  # QUESTION: should we just assign the section this generated id?
@@ -753,14 +755,17 @@ module Asciidoctor
753
755
  end
754
756
 
755
757
  def convert_floating_title node
756
- add_dest_for_block node if node.id
758
+ title = node.title
757
759
  hlevel = node.level.next
758
- unless (align = resolve_alignment_from_role node.roles)
760
+ unless (align = resolve_text_align_from_role node.roles)
759
761
  align = (@theme[%(heading_h#{hlevel}_text_align)] || @theme.heading_text_align || @base_text_align).to_sym
760
762
  end
763
+ hopts = { align: align, level: hlevel, outdent: (parent = node.parent).context == :section }
764
+ arrange_heading node, title, hopts unless at_page_top? || node == parent.blocks[-1]
765
+ add_dest_for_block node if node.id
761
766
  # QUESTION: should we decouple styles from section titles?
762
767
  theme_font :heading, level: hlevel do
763
- ink_general_heading node, node.title, align: align, level: hlevel, outdent: (node.parent.context == :section)
768
+ ink_general_heading node, title, hopts
764
769
  end
765
770
  end
766
771
 
@@ -778,11 +783,21 @@ module Asciidoctor
778
783
  end
779
784
  # FIXME: allow theme to control more first line options
780
785
  if (line1_font_style = @theme.abstract_first_line_font_style&.to_sym) && line1_font_style != font_style
781
- first_line_options = { styles: line1_font_style == :normal ? [] : [font_style, line1_font_style] }
786
+ case line1_font_style
787
+ when :normal
788
+ first_line_options = { styles: [] }
789
+ when :normal_italic
790
+ first_line_options = { styles: [:italic] }
791
+ else
792
+ first_line_options = { styles: [font_style, line1_font_style] }
793
+ end
782
794
  end
783
795
  if (line1_font_color = @theme.abstract_first_line_font_color)
784
796
  (first_line_options ||= {})[:color] = line1_font_color
785
797
  end
798
+ if (line1_text_transform = @theme.abstract_first_line_text_transform)
799
+ (first_line_options ||= {})[:text_transform] = line1_text_transform
800
+ end
786
801
  prose_opts[:first_line_options] = first_line_options if first_line_options
787
802
  # FIXME: make this cleaner!!
788
803
  if node.blocks?
@@ -791,7 +806,7 @@ module Asciidoctor
791
806
  if child.context == :paragraph
792
807
  child.document.playback_attributes child.attributes
793
808
  prose_opts[:margin_bottom] = 0 if child == last_block
794
- ink_prose child.content, ((align = resolve_alignment_from_role child.roles) ? (prose_opts.merge align: align) : prose_opts.dup)
809
+ ink_prose child.content, ((align = resolve_text_align_from_role child.roles) ? (prose_opts.merge align: align) : prose_opts.dup)
795
810
  prose_opts.delete :first_line_options
796
811
  prose_opts.delete :margin_bottom
797
812
  else
@@ -800,7 +815,7 @@ module Asciidoctor
800
815
  end
801
816
  end
802
817
  elsif node.content_model != :compound && (string = node.content)
803
- if (align = resolve_alignment_from_role node.roles)
818
+ if (align = resolve_text_align_from_role node.roles)
804
819
  prose_opts[:align] = align
805
820
  end
806
821
  ink_prose string, (prose_opts.merge margin_bottom: 0)
@@ -824,39 +839,39 @@ module Asciidoctor
824
839
 
825
840
  def convert_paragraph node
826
841
  add_dest_for_block node if node.id
842
+
827
843
  prose_opts = { margin_bottom: 0, hyphenate: true }
828
- if (align = resolve_alignment_from_role (roles = node.roles), use_theme: true)
844
+ if (align = resolve_text_align_from_role (roles = node.roles), query_theme: true, remove_predefined: true)
829
845
  prose_opts[:align] = align
830
- roles -= TextAlignmentRoles
831
846
  end
832
-
847
+ role_keys = roles.map {|role| %(role_#{role}).to_sym } unless roles.empty?
833
848
  if (text_indent = @theme.prose_text_indent) > 0 ||
834
849
  ((text_indent = @theme.prose_text_indent_inner) > 0 &&
835
850
  (self_idx = (siblings = node.parent.blocks).index node) > 0 && siblings[self_idx - 1].context == :paragraph)
836
851
  prose_opts[:indent_paragraphs] = text_indent
837
852
  end
838
-
839
- # TODO: check if we're within one line of the bottom of the page
840
- # and advance to the next page if so (similar to logic for section titles)
841
- ink_caption node, labeled: false if node.title?
842
-
843
853
  if (bottom_gutter = @bottom_gutters[-1][node])
844
854
  prose_opts[:bottom_gutter] = bottom_gutter
845
855
  end
846
856
 
847
- if roles.empty?
848
- ink_prose node.content, prose_opts
849
- else
850
- theme_font_cascade (roles.map {|role| %(role_#{role}).to_sym }) do
851
- ink_prose node.content, prose_opts
857
+ block_next = next_enclosed_block node
858
+
859
+ insert_margin_bottom = proc do
860
+ if (margin_inner_val = @theme.prose_margin_inner) && block_next&.context == :paragraph
861
+ margin_bottom margin_inner_val
862
+ else
863
+ theme_margin :prose, :bottom, block_next
852
864
  end
853
865
  end
854
866
 
855
- block_next = next_enclosed_block node
856
- if (margin_inner_val = @theme.prose_margin_inner) && block_next&.context == :paragraph
857
- margin_bottom margin_inner_val
867
+ if (float_box = (@float_box ||= nil))
868
+ ink_paragraph_in_float_box node, float_box, prose_opts, role_keys, block_next, insert_margin_bottom
858
869
  else
859
- theme_margin :prose, :bottom, block_next
870
+ # TODO: check if we're within one line of the bottom of the page
871
+ # and advance to the next page if so (similar to logic for section titles)
872
+ ink_caption node, labeled: false if node.title?
873
+ role_keys ? theme_font_cascade(role_keys) { ink_prose node.content, prose_opts } : (ink_prose node.content, prose_opts)
874
+ insert_margin_bottom.call
860
875
  end
861
876
  end
862
877
 
@@ -1122,9 +1137,11 @@ module Asciidoctor
1122
1137
  add_dest_for_block node if node.id
1123
1138
  theme_fill_and_stroke_block :sidebar, extent if extent
1124
1139
  pad_box @theme.sidebar_padding, node do
1125
- theme_font :sidebar_title do
1126
- # QUESTION: should we allow margins of sidebar title to be customized?
1127
- ink_prose node.title, align: (@theme.sidebar_title_text_align || @theme.heading_text_align || @base_text_align).to_sym, margin_bottom: @theme.heading_margin_bottom, line_height: (@theme.heading_line_height || @theme.base_line_height)
1140
+ tare_first_page_content_stream do
1141
+ theme_font :sidebar_title do
1142
+ # QUESTION: should we allow margins of sidebar title to be customized?
1143
+ ink_prose node.title, align: (@theme.sidebar_title_text_align || @theme.heading_text_align || @base_text_align).to_sym, margin_bottom: @theme.heading_margin_bottom, line_height: (@theme.heading_line_height || @theme.base_line_height)
1144
+ end
1128
1145
  end if node.title?
1129
1146
  theme_font :sidebar do
1130
1147
  traverse node
@@ -1135,13 +1152,15 @@ module Asciidoctor
1135
1152
  end
1136
1153
 
1137
1154
  def convert_colist node
1138
- margin_top @theme.callout_list_margin_top_after_code if !at_page_top? && ([:listing, :literal].include? node.parent.blocks[(node.parent.blocks.index node) - 1].context)
1155
+ unless at_page_top? || (self_idx = (siblings = node.parent.blocks).index node) == 0 || !([:listing, :literal].include? siblings[self_idx - 1].context)
1156
+ margin_top @theme.callout_list_margin_top_after_code
1157
+ end
1139
1158
  add_dest_for_block node if node.id
1140
1159
  @list_numerals << 1
1141
1160
  last_item = node.items[-1]
1142
1161
  item_spacing = @theme.callout_list_item_spacing || @theme.list_item_spacing
1143
1162
  item_opts = { margin_bottom: item_spacing, normalize_line_height: true }
1144
- if (item_align = (resolve_alignment_from_role node.roles) || @theme.list_text_align&.to_sym)
1163
+ if (item_align = (resolve_text_align_from_role node.roles) || @theme.list_text_align&.to_sym)
1145
1164
  item_opts[:align] = item_align
1146
1165
  end
1147
1166
  theme_font :callout_list do
@@ -1360,7 +1379,7 @@ module Asciidoctor
1360
1379
  ink_caption node, category: :list, labeled: false if node.title?
1361
1380
 
1362
1381
  opts = {}
1363
- if (align = resolve_alignment_from_role node.roles)
1382
+ if (align = resolve_text_align_from_role node.roles)
1364
1383
  opts[:align] = align
1365
1384
  elsif node.style == 'bibliography'
1366
1385
  opts[:align] = :left
@@ -1553,9 +1572,13 @@ module Asciidoctor
1553
1572
 
1554
1573
  return on_image_error :missing, node, target, opts unless image_path
1555
1574
 
1556
- alignment = (alignment = node.attr 'align') ?
1557
- ((BlockAlignmentNames.include? alignment) ? alignment.to_sym : :left) :
1558
- (resolve_alignment_from_role node.roles) || @theme.image_align&.to_sym || :left
1575
+ if (float_to = node.attr 'float') && ((BlockFloatNames.include? float_to) ? float_to : (float_to = nil))
1576
+ alignment = float_to.to_sym
1577
+ elsif (alignment = node.attr 'align')
1578
+ alignment = (BlockAlignmentNames.include? alignment) ? alignment.to_sym : :left
1579
+ else
1580
+ alignment = (resolve_text_align_from_role node.roles) || @theme.image_align&.to_sym || :left
1581
+ end
1559
1582
  # TODO: support cover (aka canvas) image layout using "canvas" (or "cover") role
1560
1583
  width = resolve_explicit_width node.attributes, bounds_width: (available_w = bounds.width), support_vw: true, use_fallback: true, constrain_to_bounds: true
1561
1584
  # TODO: add `to_pt page_width` method to ViewportWidth type
@@ -1563,6 +1586,7 @@ module Asciidoctor
1563
1586
 
1564
1587
  caption_end = @theme.image_caption_end&.to_sym || :bottom
1565
1588
  caption_max_width = @theme.image_caption_max_width
1589
+ caption_max_width = 'fit-content' if float_to && !(caption_max_width&.start_with? 'fit-content')
1566
1590
  # NOTE: if width is not set explicitly and max-width is fit-content, caption height may not be accurate
1567
1591
  caption_h = node.title? ? (ink_caption node, category: :image, end: caption_end, block_align: alignment, block_width: width, max_width: caption_max_width, dry_run: true, force_top_margin: caption_end == :bottom) : 0
1568
1592
 
@@ -1570,7 +1594,7 @@ module Asciidoctor
1570
1594
  pinned = opts[:pinned]
1571
1595
 
1572
1596
  begin
1573
- rendered_w = nil
1597
+ rendered_h = rendered_w = nil
1574
1598
  span_page_width_if align_to_page do
1575
1599
  if image_format == 'svg'
1576
1600
  if ::Base64 === image_path
@@ -1649,13 +1673,37 @@ module Asciidoctor
1649
1673
  end
1650
1674
  end
1651
1675
  ink_caption node, category: :image, end: :bottom, block_align: alignment, block_width: rendered_w, max_width: caption_max_width if caption_end == :bottom && node.title?
1652
- theme_margin :block, :bottom, (next_enclosed_block node) unless pinned
1676
+ if !pinned && (block_next = next_enclosed_block node)
1677
+ if float_to && (supports_float_wrapping? block_next) && rendered_w < bounds.width
1678
+ init_float_box node, rendered_w, rendered_h + caption_h, float_to
1679
+ else
1680
+ theme_margin :block, :bottom, block_next
1681
+ end
1682
+ end
1653
1683
  rescue => e
1654
1684
  raise if ::StopIteration === e
1655
1685
  on_image_error :exception, node, target, (opts.merge message: %(could not embed image: #{image_path}; #{e.message}#{::Prawn::Errors::UnsupportedImageType === e && !(defined? ::GMagick::Image) ? '; install prawn-gmagick gem to add support' : ''}))
1656
1686
  end
1657
1687
  end
1658
1688
 
1689
+ def supports_float_wrapping? node
1690
+ node.context == :paragraph
1691
+ end
1692
+
1693
+ def init_float_box _node, block_width, block_height, float_to
1694
+ gap = ::Array === (gap = @theme.image_float_gap) ? gap.dup : [gap, gap]
1695
+ float_w = block_width + (gap[0] ||= 12)
1696
+ float_h = block_height + (gap[1] ||= 6)
1697
+ box_l = bounds.left + (float_to == 'right' ? 0 : float_w)
1698
+ box_t = cursor + block_height
1699
+ box_w = bounds.width - float_w
1700
+ box_r = box_l + box_w
1701
+ box_h = [box_t, float_h].min
1702
+ box_b = box_t - box_h
1703
+ move_cursor_to box_t
1704
+ @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 }
1705
+ end
1706
+
1659
1707
  def draw_image_border top, w, h, alignment
1660
1708
  if (Array @theme.image_border_width).any? {|it| it&.> 0 } && (@theme.image_border_color || @theme.base_border_color)
1661
1709
  if (@theme.image_border_fit || 'content') == 'auto'
@@ -1687,7 +1735,7 @@ module Asciidoctor
1687
1735
  theme_font :image_alt do
1688
1736
  alignment = (alignment = node.attr 'align') ?
1689
1737
  ((BlockAlignmentNames.include? alignment) ? alignment.to_sym : :left) :
1690
- (resolve_alignment_from_role node.roles) || (@theme.image_align&.to_sym || :left)
1738
+ (resolve_text_align_from_role node.roles) || (@theme.image_align&.to_sym || :left)
1691
1739
  ink_prose alt_text_template % alt_text_vars, align: alignment, margin: 0, normalize: false, single_line: true
1692
1740
  end
1693
1741
  ink_caption node, category: :image, end: :bottom if node.title?
@@ -1742,7 +1790,7 @@ module Asciidoctor
1742
1790
  end
1743
1791
 
1744
1792
  # QUESTION: can we avoid arranging fragments multiple times (conums & autofit) by eagerly preparing arranger?
1745
- def convert_listing_or_literal node
1793
+ def convert_code node
1746
1794
  extensions = []
1747
1795
  source_chunks = bg_color_override = font_color_override = adjusted_font_size = nil
1748
1796
  theme_font :code do
@@ -1889,17 +1937,31 @@ module Asciidoctor
1889
1937
  theme_margin :block, :bottom, (next_enclosed_block node)
1890
1938
  end
1891
1939
 
1892
- alias convert_listing convert_listing_or_literal
1893
- alias convert_literal convert_listing_or_literal
1940
+ alias convert_listing convert_code
1941
+ alias convert_literal convert_code
1942
+ alias convert_listing_or_literal convert_code
1894
1943
 
1895
1944
  def convert_pass node
1896
- node = node.dup
1897
- (subs = node.subs.dup).unshift :specialcharacters
1898
- node.instance_variable_set :@subs, subs.uniq
1899
- convert_listing_or_literal node
1945
+ theme_font :code do
1946
+ typeset_formatted_text [text: (guard_indentation node.content), color: @theme.base_font_color], (calc_line_metrics @base_line_height)
1947
+ end
1948
+ theme_margin :block, :bottom, (next_enclosed_block node)
1900
1949
  end
1901
1950
 
1902
- alias convert_stem convert_listing_or_literal
1951
+ def convert_stem node
1952
+ arrange_block node do |extent|
1953
+ add_dest_for_block node if node.id
1954
+ tare_first_page_content_stream { theme_fill_and_stroke_block :code, extent, caption_node: node }
1955
+ pad_box @theme.code_padding, node do
1956
+ theme_font :code do
1957
+ typeset_formatted_text [text: (guard_indentation node.content), color: @font_color],
1958
+ (calc_line_metrics @base_line_height),
1959
+ bottom_gutter: @bottom_gutters[-1][node]
1960
+ end
1961
+ end
1962
+ end
1963
+ theme_margin :block, :bottom, (next_enclosed_block node)
1964
+ end
1903
1965
 
1904
1966
  # Extract callout marks from string, indexed by 0-based line number
1905
1967
  # Return an Array with the processed string as the first argument
@@ -2353,11 +2415,12 @@ module Asciidoctor
2353
2415
  end
2354
2416
 
2355
2417
  def convert_thematic_break node
2356
- theme_margin :thematic_break, :top
2357
- stroke_horizontal_rule @theme.thematic_break_border_color,
2358
- line_width: @theme.thematic_break_border_width,
2359
- line_style: (@theme.thematic_break_border_style&.to_sym || :solid)
2360
- theme_margin :thematic_break, ((block_next = next_enclosed_block node) ? :bottom : :top), block_next || true
2418
+ pad_box @theme.thematic_break_padding || [@theme.thematic_break_margin_top, 0] do
2419
+ stroke_horizontal_rule @theme.thematic_break_border_color,
2420
+ line_width: @theme.thematic_break_border_width,
2421
+ line_style: (@theme.thematic_break_border_style&.to_sym || :solid)
2422
+ end
2423
+ conceal_page_top { theme_margin :block, :bottom, (next_enclosed_block node) }
2361
2424
  end
2362
2425
 
2363
2426
  def convert_toc node, opts = {}
@@ -2405,26 +2468,16 @@ module Asciidoctor
2405
2468
  def convert_index_section node
2406
2469
  space_needed_for_category = @theme.description_list_term_spacing + (2 * (height_of_typeset_text 'A'))
2407
2470
  pagenum_sequence_style = node.document.attr 'index-pagenum-sequence-style'
2408
- column_box [0, cursor], columns: @theme.index_columns, width: bounds.width, reflow_margins: true do
2409
- def @bounding_box.move_past_bottom *args # rubocop:disable Lint/NestedMethodDefinition
2410
- super(*args)
2411
- @document.bounds = @parent = @document.margin_box if @current_column == 0 && @reflow_margins
2412
- end
2471
+ column_box [0, cursor], columns: @theme.index_columns, width: bounds.width, reflow_margins: true, spacer: @theme.index_column_gap do
2413
2472
  @index.categories.each do |category|
2414
- # NOTE: cursor method always returns 0 inside column_box; breaks reference_bounds.move_past_bottom
2415
- bounds.move_past_bottom if space_needed_for_category > y - reference_bounds.absolute_bottom
2473
+ bounds.move_past_bottom if space_needed_for_category > cursor
2416
2474
  ink_prose category.name,
2417
2475
  align: :left,
2418
2476
  inline_format: false,
2419
2477
  margin_bottom: @theme.description_list_term_spacing,
2420
2478
  style: @theme.description_list_term_font_style&.to_sym
2421
2479
  category.terms.each {|term| convert_index_list_item term, pagenum_sequence_style }
2422
- # NOTE: see previous note for why we can't use margin_bottom method
2423
- if @theme.prose_margin_bottom > y - reference_bounds.absolute_bottom
2424
- bounds.move_past_bottom
2425
- else
2426
- move_down @theme.prose_margin_bottom
2427
- end
2480
+ @theme.prose_margin_bottom > cursor ? bounds.move_past_bottom : (move_down @theme.prose_margin_bottom)
2428
2481
  end
2429
2482
  end
2430
2483
  nil
@@ -2926,23 +2979,31 @@ module Asciidoctor
2926
2979
 
2927
2980
  alias start_new_part start_new_chapter
2928
2981
 
2929
- def arrange_section sect, title, opts
2930
- if sect.option? 'breakable'
2982
+ # Position the cursor for where to ink the specified section title or discrete heading node.
2983
+ #
2984
+ # This method computes whether there is enough room on the page to prevent the specified node
2985
+ # from being orphaned. If there is not enough room, the method will advance the cursor to
2986
+ # the next page. This method is not called if the cursor is already at the top of the page or
2987
+ # whether this node has no node that follows it in document order.
2988
+ def arrange_heading node, title, opts
2989
+ if node.option? 'breakable'
2931
2990
  orphaned = nil
2932
2991
  dry_run single_page: true do
2933
2992
  start_page = page
2934
2993
  theme_font :heading, level: opts[:level] do
2935
2994
  if opts[:part]
2936
- ink_part_title sect, title, opts
2995
+ ink_part_title node, title, opts
2937
2996
  elsif opts[:chapterlike]
2938
- ink_chapter_title sect, title, opts
2997
+ ink_chapter_title node, title, opts
2939
2998
  else
2940
- ink_general_heading sect, title, opts
2999
+ ink_general_heading node, title, opts
2941
3000
  end
2942
3001
  end
2943
3002
  if page == start_page
2944
3003
  page.tare_content_stream
2945
- orphaned = stop_if_first_page_empty { traverse sect }
3004
+ orphaned = stop_if_first_page_empty do
3005
+ node.context == :section ? (traverse node) : (convert (siblings = node.parent.blocks)[(siblings.index node) + 1])
3006
+ end
2946
3007
  end
2947
3008
  end
2948
3009
  start_new_page if orphaned
@@ -2954,7 +3015,10 @@ module Asciidoctor
2954
3015
  heading_h = (height_of_typeset_text title) +
2955
3016
  (@theme[%(heading_h#{hlevel}_margin_top)] || @theme.heading_margin_top) +
2956
3017
  (@theme[%(heading_h#{hlevel}_margin_bottom)] || @theme.heading_margin_bottom) + h_padding_t + h_padding_b
2957
- heading_h += @theme.heading_min_height_after if @theme.heading_min_height_after && sect.blocks?
3018
+ if (min_height_after = @theme.heading_min_height_after) &&
3019
+ (node.context == :section ? node.blocks? : node != node.parent.blocks[-1])
3020
+ heading_h += min_height_after
3021
+ end
2958
3022
  cursor >= heading_h
2959
3023
  end
2960
3024
  start_new_page unless h_fits
@@ -3021,6 +3085,64 @@ module Asciidoctor
3021
3085
  end
3022
3086
  end
3023
3087
 
3088
+ # private
3089
+ def ink_paragraph_in_float_box node, float_box, prose_opts, role_keys, block_next, insert_margin_bottom
3090
+ @float_box = para_font_descender = para_font_size = end_cursor = nil
3091
+ if role_keys
3092
+ line_metrics = theme_font_cascade role_keys do
3093
+ para_font_descender = font.descender
3094
+ para_font_size = font_size
3095
+ calc_line_metrics @base_line_height
3096
+ end
3097
+ else
3098
+ para_font_descender = font.descender
3099
+ para_font_size = font_size
3100
+ line_metrics = calc_line_metrics @base_line_height
3101
+ end
3102
+ # allocate the space of at least one empty line below block
3103
+ line_height_length = line_metrics.height + line_metrics.leading + line_metrics.padding_top
3104
+ start_page_number = float_box[:page]
3105
+ start_cursor = cursor
3106
+ block_bottom = (float_box_bottom = float_box[:bottom]) + float_box[:gap][1]
3107
+ # use :at to incorporate padding top from line metrics
3108
+ # use :final_gap to incorporate padding bottom from line metrics
3109
+ # use :draw_text_callback to track end cursor (requires applying :final_gap to result manually)
3110
+ prose_opts.update \
3111
+ at: [float_box[:left], start_cursor - line_metrics.padding_top],
3112
+ width: float_box[:width],
3113
+ height: [cursor, float_box[:height] - (float_box[:top] - start_cursor) + line_height_length].min,
3114
+ final_gap: para_font_descender + line_metrics.padding_bottom,
3115
+ draw_text_callback: (proc do |text, opts|
3116
+ draw_text! text, opts
3117
+ end_cursor = opts[:at][1] # does not include :final_gap value
3118
+ end)
3119
+ overflow_text = role_keys ?
3120
+ theme_font_cascade(role_keys) { ink_prose node.content, prose_opts } :
3121
+ (ink_prose node.content, prose_opts)
3122
+ move_cursor_to end_cursor -= prose_opts[:final_gap] if end_cursor # ink_prose with :height does not move cursor
3123
+ if overflow_text.empty?
3124
+ if block_next && (supports_float_wrapping? block_next)
3125
+ insert_margin_bottom.call
3126
+ @float_box = float_box if page_number == start_page_number && cursor > start_cursor - prose_opts[:height]
3127
+ elsif end_cursor > block_bottom
3128
+ move_cursor_to block_bottom
3129
+ theme_margin :block, :bottom, block_next
3130
+ else
3131
+ insert_margin_bottom.call
3132
+ end
3133
+ else
3134
+ overflow_prose_opts = { align: prose_opts[:align] || @base_text_align.to_sym }
3135
+ unless end_cursor
3136
+ overflow_prose_opts[:indent_paragraphs] = prose_opts[:indent_paragraphs]
3137
+ move_cursor_to float_box_bottom if start_cursor > float_box_bottom
3138
+ end
3139
+ role_keys ?
3140
+ theme_font_cascade(role_keys) { typeset_formatted_text overflow_text, line_metrics, overflow_prose_opts } :
3141
+ (typeset_formatted_text overflow_text, line_metrics, overflow_prose_opts)
3142
+ insert_margin_bottom.call
3143
+ end
3144
+ end
3145
+
3024
3146
  # NOTE: inline_format is true by default
3025
3147
  def ink_prose string, opts = {}
3026
3148
  top_margin = (margin = (opts.delete :margin)) || (opts.delete :margin_top) || 0
@@ -3043,12 +3165,13 @@ module Asciidoctor
3043
3165
  text_decoration_width: (opts.delete :text_decoration_width),
3044
3166
  }.compact
3045
3167
  end
3046
- typeset_text string, (calc_line_metrics (opts.delete :line_height) || @base_line_height), {
3168
+ result = typeset_text string, (calc_line_metrics (opts.delete :line_height) || @base_line_height), {
3047
3169
  color: @font_color,
3048
3170
  inline_format: [inline_format_opts],
3049
3171
  align: @base_text_align.to_sym,
3050
3172
  }.merge(opts)
3051
3173
  margin_bottom bot_margin
3174
+ result
3052
3175
  end
3053
3176
 
3054
3177
  def generate_manname_section node
@@ -3071,11 +3194,12 @@ module Asciidoctor
3071
3194
  # title (i.e., subject.title? returns true).
3072
3195
  def ink_caption subject, opts = {}
3073
3196
  if opts.delete :dry_run
3074
- force_top_margin = !at_page_top? if (force_top_margin = opts.delete :force_top_margin).nil?
3075
3197
  return (dry_run keep_together: true, single_page: :enforce do
3076
- # TODO: encapsulate this logic to force top margin to be applied
3077
- margin_box.instance_variable_set :@y, margin_box.absolute_top + 0.0001 if force_top_margin
3078
- ink_caption subject, opts
3198
+ if opts.delete :force_top_margin
3199
+ conceal_page_top { ink_caption subject, opts }
3200
+ else
3201
+ ink_caption subject, opts
3202
+ end
3079
3203
  end).single_page_height
3080
3204
  end
3081
3205
  if ::Asciidoctor::AbstractBlock === subject
@@ -3103,11 +3227,22 @@ module Asciidoctor
3103
3227
  if (max_width = opts.delete :max_width) && max_width != 'none'
3104
3228
  if ::String === max_width
3105
3229
  if max_width.start_with? 'fit-content'
3106
- if max_width.end_with? 't', '()'
3107
- max_width = block_width || container_width
3108
- else
3109
- max_width = (block_width || container_width) * (max_width.slice 12, max_width.length - 1).to_f / 100.0
3230
+ block_width ||= container_width
3231
+ unless max_width.end_with? 't', '()'
3232
+ max_width = block_width * (max_width.slice 12, max_width.length - 1).to_f / 100.0
3233
+ if (caption_width_delta = block_width - max_width) > 0
3234
+ case align
3235
+ when :right
3236
+ indent_by[0] += caption_width_delta
3237
+ when :center
3238
+ indent_by[0] += caption_width_delta * 0.5
3239
+ indent_by[1] += caption_width_delta * 0.5
3240
+ else # :left, nil
3241
+ indent_by[1] += caption_width_delta
3242
+ end
3243
+ end
3110
3244
  end
3245
+ max_width = block_width
3111
3246
  elsif max_width.end_with? '%'
3112
3247
  max_width = [max_width.to_f / 100 * bounds.width, bounds.width].min
3113
3248
  block_align = align
@@ -3122,11 +3257,12 @@ module Asciidoctor
3122
3257
  if (remainder = container_width - max_width) > 0
3123
3258
  case block_align
3124
3259
  when :right
3125
- indent_by = [remainder, 0]
3260
+ indent_by[0] += remainder
3126
3261
  when :center
3127
- indent_by = [(side_margin = remainder * 0.5), side_margin]
3262
+ indent_by[0] += remainder * 0.5
3263
+ indent_by[1] += remainder * 0.5
3128
3264
  else # :left, nil
3129
- indent_by = [0, remainder]
3265
+ indent_by[1] += remainder
3130
3266
  end
3131
3267
  end
3132
3268
  end
@@ -3522,8 +3658,9 @@ module Asciidoctor
3522
3658
  content = apply_subs_discretely doc, content, drop_lines_with_unresolved_attributes: true, imagesdir: @themesdir
3523
3659
  content = transform_text content, @text_transform if @text_transform
3524
3660
  end
3525
- formatted_text_box (parse_text content, color: @font_color, inline_format: [normalize: true]),
3661
+ formatted_text_box (parse_text content, inline_format: [normalize: true]),
3526
3662
  at: [left, bounds.top - trim_styles[:padding][side][0] - trim_styles[:content_offset] + ((Array trim_styles[:valign])[0] == :center ? font.descender * 0.5 : 0)],
3663
+ color: @font_color,
3527
3664
  width: colwidth,
3528
3665
  height: trim_styles[:prose_content_height][side],
3529
3666
  align: colspec[:align],
@@ -4146,9 +4283,10 @@ module Asciidoctor
4146
4283
 
4147
4284
  # TODO: document me, esp the first line formatting functionality
4148
4285
  def typeset_text string, line_metrics, opts = {}
4149
- move_down line_metrics.padding_top
4150
4286
  opts = { leading: line_metrics.leading, final_gap: line_metrics.final_gap }.merge opts
4151
4287
  string = string.gsub CjkLineBreakRx, ZeroWidthSpace if @cjk_line_breaks
4288
+ return text_box string, opts if opts[:height]
4289
+ move_down line_metrics.padding_top
4152
4290
  if (hanging_indent = (opts.delete :hanging_indent) || 0) > 0
4153
4291
  indent hanging_indent do
4154
4292
  text string, (opts.merge indent_paragraphs: -hanging_indent)
@@ -4312,10 +4450,11 @@ module Asciidoctor
4312
4450
  end
4313
4451
  end
4314
4452
 
4315
- def resolve_alignment_from_role roles, use_theme: false
4453
+ def resolve_text_align_from_role roles, query_theme: false, remove_predefined: false
4316
4454
  if (align_role = roles.reverse.find {|r| TextAlignmentRoles.include? r })
4455
+ roles.replace roles - TextAlignmentRoles if remove_predefined
4317
4456
  (align_role.slice 5, align_role.length).to_sym
4318
- elsif use_theme
4457
+ elsif query_theme
4319
4458
  roles.reverse.each do |role|
4320
4459
  if (align = @theme[%(role_#{role}_text_align)])
4321
4460
  return align.to_sym
@@ -4325,6 +4464,9 @@ module Asciidoctor
4325
4464
  end
4326
4465
  end
4327
4466
 
4467
+ # Deprecated
4468
+ alias resolve_alignment_from_role resolve_text_align_from_role
4469
+
4328
4470
  # QUESTION: is this method still necessary?
4329
4471
  def resolve_imagesdir doc
4330
4472
  if (imagesdir = doc.attr 'imagesdir').nil_or_empty? || (imagesdir = imagesdir.chomp '/') == '.'
@@ -4669,9 +4811,12 @@ module Asciidoctor
4669
4811
  alias layout_running_content ink_running_content
4670
4812
 
4671
4813
  # intercepts "class CustomPDFConverter < (Asciidoctor::Converter.for 'pdf')"
4672
- def self.method_added method
4673
- if (method_name = method.to_s).start_with? 'layout_'
4674
- alias_method %(ink_#{method_name.slice 7, method_name.length}).to_sym, method
4814
+ def self.method_added method_sym
4815
+ if (method_name = method_sym.to_s).start_with? 'layout_'
4816
+ alias_method %(ink_#{method_name.slice 7, method_name.length}).to_sym, method_sym
4817
+ elsif method_name == 'convert_listing_or_literal' || method_name == 'convert_code'
4818
+ alias_method :convert_listing, method_sym
4819
+ alias_method :convert_literal, method_sym
4675
4820
  end
4676
4821
  end
4677
4822
 
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ Prawn::Document::ColumnBox.prepend (Module.new do
4
+ def absolute_bottom
5
+ stretchy? ? @parent.absolute_bottom : super
6
+ end
7
+
8
+ def move_past_bottom *_args
9
+ initial_page = @document.page
10
+ super
11
+ if (page = @document.page) != initial_page && page.margins != initial_page.margins
12
+ @document.bounds = self.class.new @document, @parent, (margin_box = @document.margin_box).absolute_top_left,
13
+ columns: @columns, reflow_margins: true, spacer: @spacer, width: margin_box.width
14
+ end
15
+ end
16
+ end)