asciidoctor-pdf 2.3.19 → 2.3.21

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 465fb67274afdfc25ce6bddf7a7f0f8a7079bcc828d11cd86472c30f62b6cf5c
4
- data.tar.gz: e034a8cbedf9329cdd48d2cc03063ca146ae9cf1ed28e2417b878ce14b4ca680
3
+ metadata.gz: 4250f6c0a0f203fe5e164656467da521bf4741771dd2627030c02a5401c24970
4
+ data.tar.gz: ef2cdb91e970f26ec2cbbb680d94db21d613aaf3c339492f63bbdb5c30a19337
5
5
  SHA512:
6
- metadata.gz: a8da14edb4931a36dcfe223d1ccbe9dbaf6aa3c0a443818412062850158a5c7f15eebb6b3cbe39824440b9c9613d232ee2537a5d77ec156d491e8e619139694a
7
- data.tar.gz: 991f86d8acdddb6e4ec1773d5db27fce3673ec4f8a0c4b7889a6095ef69c52d1e08b0dc63fcdde797beaf4f4d12bea49f11595f67e576d5da7594386f9c76f6e
6
+ metadata.gz: a53a41fffe731219da61c25eea7f443dd7cb3b64594e116637f28f2f43173a8d4f43c1b7040cacee56566a3e0bb6be474a6d488275d02094646ea1d48b1fb9a2
7
+ data.tar.gz: 717af76d4f0f4d958301f099e68ccd3395bc54b5740b2fa47d741fee2bd13aec2d2f48e5443c5d43f64535f23f6b12dc1a00d1c0dd74216a0f95f7ac15845e89
data/CHANGELOG.adoc CHANGED
@@ -5,6 +5,45 @@
5
5
  This document provides a high-level view of the changes to the {project-name} by release.
6
6
  For a detailed view of what has changed, refer to the {url-repo}/commits/main[commit history] on GitHub.
7
7
 
8
+ == 2.3.21 (2025-10-08) - @mojavelinux
9
+
10
+ Improvements::
11
+
12
+ * remove shy hyphens from text in log message when formatted text cannot be parsed (#2599)
13
+ * clear float box if next block falls outside of flaot group (#2596)
14
+ * resolve fonts relative to theme dir before GEM_FONTS_DIR if `pdf-fontsdir` not specified (#2349)
15
+ * add see and see-also associations on index terms to index (#2527)
16
+
17
+ === Details
18
+
19
+ {url-repo}/releases/tag/v2.3.21[git tag] | {url-repo}/compare/v2.3.20\...v2.3.21[full diff]
20
+
21
+ == 2.3.20 (2025-09-17) - @mojavelinux
22
+
23
+ Improvements::
24
+
25
+ * add symbols for shift, command, option, and return keys to the fallback font
26
+ * render horizontal dlist without using prawn-table, allowing the description to exceed the height of one page (#2591)
27
+ * allow relative font size for sub and sup to be set independently (support combined setting for backwards compatibility)
28
+ * allow value of `base-font-size-min` and `<category>-font-size-min` theme keys to be relative (e.g., 0.75em) (#2547)
29
+ * respect `hide-uri-scheme` attribute when appending URL to link text when media is print or prepress (#2583)
30
+
31
+ Bug Fixes::
32
+
33
+ * always force NULL character to have 0 width, even if missing from font (#2585)
34
+ * don't drop line in running content if attribute reference resolves to text that contains attribute reference (not originally present)
35
+ * correctly calculate extent for block in columns that starts below doctitle (#2579)
36
+ * support internal link on image (value of link attribute starts with #) (#2572)
37
+
38
+ Compliance::
39
+
40
+ * suppress warning about the use of Ruby's logger gem when loading Asciidoctor
41
+ * ignore error if bigdecimal gem cannot be eagerly required
42
+
43
+ === Details
44
+
45
+ {url-repo}/releases/tag/v2.3.20[git tag] | {url-repo}/compare/v2.3.19\...v2.3.20[full diff]
46
+
8
47
  == 2.3.19 (2024-10-11) - @mojavelinux
9
48
 
10
49
  Improvements::
data/README.adoc CHANGED
@@ -1,6 +1,6 @@
1
1
  = Asciidoctor PDF: A native PDF converter for AsciiDoc
2
2
  Dan Allen <https://github.com/mojavelinux[@mojavelinux]>; Sarah White <https://github.com/graphitefriction[@graphitefriction]>
3
- v2.3.19, 2024-10-11
3
+ v2.3.21, 2025-10-08
4
4
  // Settings:
5
5
  :experimental:
6
6
  :idprefix:
@@ -41,7 +41,7 @@ Gem::Specification.new do |s|
41
41
  s.add_runtime_dependency 'prawn-templates', '~> 0.1.0'
42
42
  s.add_runtime_dependency 'prawn-svg', '~> 0.34.0'
43
43
  s.add_runtime_dependency 'prawn-icon', '~> 3.0.0'
44
- s.add_runtime_dependency 'concurrent-ruby', '~> 1.1'
44
+ s.add_runtime_dependency 'concurrent-ruby', '~> 1.3'
45
45
  s.add_runtime_dependency 'treetop', '~> 1.6.0'
46
46
 
47
47
  s.add_development_dependency 'rake', '~> 13.0.0'
@@ -1,6 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'formatted_string'
4
3
  require_relative 'formatted_text'
5
4
  require_relative 'index_catalog'
6
5
  require_relative 'pdfmark'
@@ -104,7 +103,7 @@ module Asciidoctor
104
103
  }
105
104
  TypographicQuotes = %w(&#8220; &#8221; &#8216; &#8217;)
106
105
  InlineFormatSniffRx = /[<&]/
107
- SimpleAttributeRefRx = /(?<!\\)\{\w+(?:-\w+)*\}/
106
+ SimpleAttributeRefRx = /(?<!\\)\{(\w+(?:-\w+)*)\}/
108
107
  MeasurementRxt = '\\d+(?:\\.\\d+)?(?:in|cm|mm|p[txc])?'
109
108
  MeasurementPartsRx = /^(\d+(?:\.\d+)?)(in|mm|cm|p[txc])?$/
110
109
  PageSizeRx = /^(?:\[(#{MeasurementRxt}), ?(#{MeasurementRxt})\]|(#{MeasurementRxt})(?: x |x)(#{MeasurementRxt})|\S+)$/
@@ -403,7 +402,7 @@ module Asciidoctor
403
402
  @page_bg_image = {}
404
403
  @page_bg_color = resolve_theme_color :page_background_color, 'FFFFFF'
405
404
  # QUESTION: should ThemeLoader handle registering fonts instead?
406
- register_fonts theme.font_catalog, ((doc.attr 'pdf-fontsdir')&.sub '{docdir}', (doc.attr 'docdir')) || 'GEM_FONTS_DIR'
405
+ register_fonts theme.font_catalog, ((doc.attr 'pdf-fontsdir')&.sub '{docdir}', (doc.attr 'docdir')) || (theme.__dir__ == ThemeLoader::ThemesDir ? 'GEM_FONTS_DIR' : %(#{theme.__dir__};GEM_FONTS_DIR))
407
406
  default_kerning theme.base_font_kerning != 'none'
408
407
  @fallback_fonts = Array theme.font_fallbacks
409
408
  @root_font_size = theme.base_font_size
@@ -710,6 +709,7 @@ module Asciidoctor
710
709
  end
711
710
 
712
711
  def convert_index_section node
712
+ @index.link_associations
713
713
  if ColumnBox === bounds || (columns = @theme.index_columns || 1) < 2
714
714
  convert_index_categories @index.categories, (node.document.attr 'index-pagenum-sequence-style')
715
715
  else
@@ -739,41 +739,73 @@ module Asciidoctor
739
739
  end
740
740
 
741
741
  def convert_index_term term, pagenum_sequence_style = nil
742
- term_fragments = term.name.fragments
742
+ # NOTE: dup fragment list and all fragments to avoid any modification
743
+ term_fragments = term.name.fragments.map(&:dup)
743
744
  unless term.container?
744
- pagenum_fragment = (parse_text %(<a>#{DummyText}</a>), inline_format: true)[0]
745
745
  if @media == 'screen'
746
- case pagenum_sequence_style
747
- when 'page'
748
- pagenums = term.dests.uniq {|dest| dest[:page] }.map {|dest| pagenum_fragment.merge anchor: dest[:anchor], text: dest[:page] }
749
- when 'range'
750
- first_anchor_per_page = {}.tap {|accum| term.dests.each {|dest| accum[dest[:page]] ||= dest[:anchor] } }
751
- pagenums = (consolidate_ranges first_anchor_per_page.keys).map do |range|
752
- anchor = first_anchor_per_page[(range.include? '-') ? (range.partition '-')[0] : range]
753
- pagenum_fragment.merge text: range, anchor: anchor
754
- end
755
- else # term
756
- pagenums = term.dests.map {|dest| pagenum_fragment.merge text: dest[:page], anchor: dest[:anchor] }
746
+ link_fragment = (parse_text %(<a>#{DummyText}</a>), inline_format: true)[0]
747
+ term_fragments.unshift name: term.anchor, callback: [FormattedText::InlineDestinationMarker], text: DummyText
748
+ end
749
+ if (see = term.see)
750
+ term_fragments << { text: ' (see ' }
751
+ if link_fragment && IndexTerm === see
752
+ link_fragment_ = link_fragment.merge anchor: see.anchor
753
+ see.name.fragments.each {|it| term_fragments << (link_fragment_.merge it) }
754
+ else
755
+ term_fragments.concat see.name.fragments
757
756
  end
757
+ term_fragments << { text: ')' }
758
758
  else
759
- pagenums = consolidate_ranges term.dests.map {|dest| dest[:page] }.uniq
760
- end
761
- pagenums.each do |pagenum|
762
- if ::String === pagenum
763
- term_fragments << ({ text: %(, #{pagenum}) })
759
+ if @media == 'screen'
760
+ case pagenum_sequence_style
761
+ when 'page'
762
+ pagenums = term.dests.uniq {|dest| dest[:page] }.map {|dest| link_fragment.merge anchor: dest[:anchor], text: dest[:page] }
763
+ when 'range'
764
+ first_anchor_per_page = {}.tap {|accum| term.dests.each {|dest| accum[dest[:page]] ||= dest[:anchor] } }
765
+ pagenums = (consolidate_ranges first_anchor_per_page.keys).map do |range|
766
+ anchor = first_anchor_per_page[(range.include? '-') ? (range.partition '-')[0] : range]
767
+ link_fragment.merge text: range, anchor: anchor
768
+ end
769
+ else # term
770
+ pagenums = term.dests.map {|dest| link_fragment.merge text: dest[:page], anchor: dest[:anchor] }
771
+ end
764
772
  else
765
- term_fragments << { text: ', ' }
766
- term_fragments << pagenum
773
+ pagenums = consolidate_ranges term.dests.map {|dest| dest[:page] }.uniq
774
+ end
775
+ pagenums.each do |pagenum|
776
+ if ::String === pagenum
777
+ term_fragments << ({ text: %(, #{pagenum}) })
778
+ else
779
+ term_fragments << { text: ', ' }
780
+ term_fragments << pagenum
781
+ end
782
+ end
783
+ if (see_also = term.see_also)
784
+ see_also_items = []
785
+ see_also.each do |also_term|
786
+ see_also_fragments = [{ text: '(see also ' }]
787
+ if link_fragment && IndexTerm === also_term
788
+ link_fragment_ = link_fragment.merge anchor: also_term.anchor
789
+ also_term.name.fragments.map {|it| see_also_fragments << (link_fragment_.merge it) }
790
+ else
791
+ see_also_fragments.concat also_term.name.fragments
792
+ end
793
+ see_also_fragments << { text: ')' }
794
+ see_also_items << see_also_fragments
795
+ end
767
796
  end
768
797
  end
769
798
  end
770
799
  subterm_indent = @theme.description_list_description_indent
771
800
  typeset_formatted_text term_fragments, (calc_line_metrics @base_line_height), align: :left, color: @font_color, hanging_indent: subterm_indent * 2, consolidate: true
772
801
  indent subterm_indent do
802
+ see_also_items&.each do |see_also_fragments|
803
+ typeset_formatted_text see_also_fragments, (calc_line_metrics @base_line_height), align: :left, color: @font_color, hanging_indent: subterm_indent * 2, consolidate: true
804
+ end
773
805
  term.subterms.each do |subterm|
774
806
  convert_index_term subterm, pagenum_sequence_style
775
- end
776
- end unless term.leaf?
807
+ end unless term.leaf?
808
+ end if see_also_items || !term.leaf?
777
809
  end
778
810
 
779
811
  def convert_preamble node
@@ -871,14 +903,19 @@ module Asciidoctor
871
903
  end
872
904
 
873
905
  if (float_box = (@float_box ||= nil))
874
- ink_paragraph_in_float_box node, float_box, prose_opts, role_keys, block_next, insert_margin_bottom
875
- else
876
- # TODO: check if we're within one line of the bottom of the page
877
- # and advance to the next page if so (similar to logic for section titles)
878
- ink_caption node, labeled: false if node.title?
879
- role_keys ? theme_font_cascade(role_keys) { ink_prose node.content, prose_opts } : (ink_prose node.content, prose_opts)
880
- insert_margin_bottom.call
906
+ if (prev_sibling = node.previous_sibling)&.context == :open && (prev_sibling.role? 'float-group')
907
+ move_cursor_to float_box[:bottom]
908
+ @float_box = nil
909
+ else
910
+ ink_paragraph_in_float_box node, float_box, prose_opts, role_keys, block_next, insert_margin_bottom
911
+ return
912
+ end
881
913
  end
914
+ # TODO: check if we're within one line of the bottom of the page
915
+ # and advance to the next page if so (similar to logic for section titles)
916
+ ink_caption node, labeled: false if node.title?
917
+ role_keys ? theme_font_cascade(role_keys) { ink_prose node.content, prose_opts } : (ink_prose node.content, prose_opts)
918
+ insert_margin_bottom.call
882
919
  end
883
920
 
884
921
  def convert_admonition node
@@ -1421,55 +1458,62 @@ module Asciidoctor
1421
1458
  convert_list list
1422
1459
  markers.pop
1423
1460
  when 'horizontal'
1424
- table_data = []
1425
- term_padding = term_padding_no_blocks = term_font_color = term_transform = desc_padding = term_line_metrics = term_inline_format = term_kerning = nil
1461
+ ink_caption node, category: :description_list, labeled: false if node.title?
1426
1462
  max_term_width = 0
1463
+ term_height = term_padding = term_font_styles = term_transform = desc_padding = term_line_metrics = term_inline_format = term_kerning = nil
1427
1464
  theme_font :description_list_term do
1428
- term_font_color = @font_color
1465
+ term_height = height_of_typeset_text 'A'
1429
1466
  term_transform = @text_transform
1430
1467
  term_inline_format = (term_font_styles = font_styles).empty? ? true : [inherited: { styles: term_font_styles }]
1431
1468
  term_line_metrics = calc_line_metrics @base_line_height
1432
- term_padding_no_blocks = [term_line_metrics.padding_top, 10, term_line_metrics.padding_bottom, 10]
1433
- (term_padding = (term_padding_no_blocks.drop 0))[2] += @theme.prose_margin_bottom * 0.5
1469
+ term_padding = [term_line_metrics.padding_top, 10, term_line_metrics.padding_bottom, 10]
1434
1470
  desc_padding = [0, 10, 0, 10]
1435
1471
  term_kerning = default_kerning?
1436
1472
  end
1437
- actual_node, node = node, node.dup
1438
- (node.instance_variable_set :@blocks, node.items.map(&:dup)).each do |item|
1473
+ prose_height = height_of_typeset_text 'A'
1474
+ items = node.items.map do |item|
1439
1475
  terms, desc = item
1440
- term_text = terms.map(&:text).join ?\n
1441
- term_text = transform_text term_text, term_transform if term_transform
1442
- if (term_width = width_of term_text, inline_format: term_inline_format, kerning: term_kerning) > max_term_width
1443
- max_term_width = term_width
1444
- end
1445
- row_data = [{
1446
- text_color: term_font_color,
1447
- kerning: term_kerning,
1448
- content: term_text,
1449
- inline_format: term_inline_format,
1450
- padding: desc&.blocks? ? term_padding : term_padding_no_blocks,
1451
- leading: term_line_metrics.leading,
1452
- # FIXME: prawn-table doesn't have support for final_gap option
1453
- #final_gap: term_line_metrics.final_gap,
1454
- valign: :top,
1455
- }]
1456
- if desc
1457
- desc_container = Block.new node, :open
1458
- desc_container << (Block.new desc_container, :paragraph, source: (desc.instance_variable_get :@text), subs: :default) if desc.text?
1459
- desc.blocks.each {|b| desc_container << b.dup } if desc.blocks?
1460
- row_data << { content: (::Prawn::Table::Cell::AsciiDoc.new self, content: (item[1] = desc_container), text_color: @font_color, padding: desc_padding, valign: :top, source_location: desc.source_location) }
1461
- else
1462
- row_data << {}
1476
+ terms_as_text = terms.map do |term|
1477
+ term_text = term_transform ? (transform_text term.text, term_transform) : term.text
1478
+ if (term_width = width_of term_text, inline_format: term_inline_format, kerning: term_kerning) > max_term_width
1479
+ max_term_width = term_width
1480
+ end
1481
+ term_text
1463
1482
  end
1464
- table_data << row_data
1483
+ [terms_as_text, desc]
1465
1484
  end
1466
- max_term_width += (term_padding[1] + term_padding[3])
1467
- term_column_width = [max_term_width, bounds.width * 0.5].min
1468
- table table_data, position: :left, column_widths: [term_column_width] do
1469
- cells.style border_width: 0
1470
- @pdf.ink_table_caption node if node.title?
1485
+ term_side_padding = term_padding[1] + (term_left = term_padding[3])
1486
+ max_term_width += term_side_padding
1487
+ term_column_width = [max_term_width, bounds.width * 0.5 - term_side_padding].min
1488
+ term_spacing = @theme.description_list_term_spacing
1489
+ items.each do |terms_as_text, desc|
1490
+ advance_page if !at_page_top? && cursor < [((term_spacing + term_height) * terms_as_text.size) - term_spacing, desc ? prose_height : 0].max
1491
+ initial_y = y
1492
+ initial_page_number = page_number
1493
+ indent term_left, (bounds.width - term_column_width) do
1494
+ theme_font :description_list_term do
1495
+ terms_as_text.each_with_index do |term_text, idx|
1496
+ ink_prose term_text, margin_top: (idx > 0 ? term_spacing : 0), margin_bottom: 0, align: :left, normalize_line_height: true, styles: term_font_styles
1497
+ end
1498
+ end
1499
+ end
1500
+ if desc # rubocop:disable Style/Next
1501
+ after_term_y = y
1502
+ after_term_page_number = page_number
1503
+ go_to_page initial_page_number if page_number > initial_page_number
1504
+ @y = initial_y
1505
+ indent term_column_width + desc_padding[3], desc_padding[1] do
1506
+ traverse_list_item desc, :dlist_desc, normalize_line_height: true, margin_bottom: ((next_enclosed_block desc, descend: true) ? nil : 0)
1507
+ end
1508
+ if page_number < after_term_page_number
1509
+ go_to_page after_term_page_number
1510
+ @y = after_term_y
1511
+ elsif y > after_term_y
1512
+ @y = after_term_y
1513
+ end
1514
+ end
1471
1515
  end
1472
- theme_margin :prose, :bottom, (next_enclosed_block actual_node) #unless actual_node.nested?
1516
+ theme_margin :prose, :bottom, (next_enclosed_block node) unless node.nested?
1473
1517
  when 'qanda'
1474
1518
  @list_numerals << 1
1475
1519
  convert_list node
@@ -2459,6 +2503,7 @@ module Asciidoctor
2459
2503
  %(#{anchor}<a href="#{target}"#{attrs.join}>#{breakable_uri text}</a>)
2460
2504
  # NOTE: @media may not be initialized if method is called before convert phase
2461
2505
  elsif (doc.attr? 'show-link-uri') || (@media != 'screen' && (doc.attr_unspecified? 'show-link-uri'))
2506
+ bare_target = (bare_target.split UriSchemeBoundaryRx, 2)[1] || bare_target if doc.attr? 'hide-uri-scheme'
2462
2507
  # QUESTION: should we insert breakable chars into URI when building fragment instead?
2463
2508
  # TODO: allow style of printed link to be controlled by theme
2464
2509
  %(#{anchor}<a href="#{target}"#{attrs.join}>#{text}</a> [<font size="0.85em">#{breakable_uri bare_target}</font>&#93;)
@@ -2639,7 +2684,11 @@ module Asciidoctor
2639
2684
  img = %([#{node.attr 'alt'}&#93;)
2640
2685
  end
2641
2686
  end
2642
- (node.attr? 'link') ? %(<a href="#{node.attr 'link'}">#{img}</a>) : img
2687
+ if (node.attr? 'link') && (link = node.attr 'link')
2688
+ link.chr == '#' ? %(<a anchor="#{link.slice 1}">#{img}</a>) : %(<a href="#{link}">#{img}</a>)
2689
+ else
2690
+ img
2691
+ end
2643
2692
  end
2644
2693
 
2645
2694
  def convert_inline_indexterm node
@@ -2655,12 +2704,19 @@ module Asciidoctor
2655
2704
  # NOTE: page number (:page key) is added by InlineDestinationMarker
2656
2705
  dest = { anchor: (anchor_name = @index.next_anchor_name) }
2657
2706
  anchor = %(<a id="#{anchor_name}" type="indexterm"#{visible ? ' visible="true"' : ''}>#{DummyText}</a>)
2707
+ assoc = {}
2708
+ if (see = node.attr 'see')
2709
+ assoc[:see] = parse_text see, inline_format: [normalize: true]
2710
+ end
2711
+ if (see_also = node.attr 'see-also')
2712
+ assoc[:see_also] = see_also.map {|term| parse_text term, inline_format: [normalize: true] }
2713
+ end
2658
2714
  if visible
2659
2715
  visible_term = node.text
2660
- @index.store_primary_term (FormattedString.new parse_text visible_term, inline_format: [normalize: true]), dest
2716
+ @index.store_term [(parse_text visible_term, inline_format: [normalize: true])], dest, assoc
2661
2717
  %(#{anchor}#{visible_term})
2662
2718
  else
2663
- @index.store_term (node.attr 'terms').map {|term| FormattedString.new parse_text term, inline_format: [normalize: true] }, dest
2719
+ @index.store_term (node.attr 'terms').map {|term| parse_text term, inline_format: [normalize: true] }, dest, assoc
2664
2720
  anchor
2665
2721
  end
2666
2722
  end
@@ -2831,8 +2887,11 @@ module Asciidoctor
2831
2887
  # FIXME: get sub_attributes to handle drop-line w/o a warning
2832
2888
  doc.set_attr 'attribute-missing', 'skip' unless (attribute_missing = doc.attr 'attribute-missing') == 'skip'
2833
2889
  value = value.gsub '\{', '\\\\\\{' if (escaped_attr_ref = value.include? '\{')
2890
+ value_before_subs = value
2834
2891
  value = (subs = opts[:subs]) ? (doc.apply_subs value, subs) : (doc.apply_subs value)
2835
- value = (value.split LF).delete_if {|line| SimpleAttributeRefRx.match? line }.join LF if opts[:drop_lines_with_unresolved_attributes] && (value.include? '{')
2892
+ if opts[:drop_lines_with_unresolved_attributes] && (value.include? '{')
2893
+ value = (value.split LF).delete_if {|line| SimpleAttributeRefRx =~ line && (value_before_subs.include? '{' + $1 + '}') }.join LF
2894
+ end
2836
2895
  value = value.gsub '\{', '{' if escaped_attr_ref
2837
2896
  doc.set_attr 'attribute-missing', attribute_missing unless attribute_missing == 'skip'
2838
2897
  page_layout_to_restore ? (doc.set_attr 'page-layout', page_layout_to_restore) : (doc.remove_attr 'page-layout') if page_layout
@@ -4724,7 +4783,12 @@ module Asciidoctor
4724
4783
  image_y = y - image_opts[:vposition]
4725
4784
  end unless (image_y = image_opts[:y])
4726
4785
 
4727
- link_annotation [image_x, (image_y - image_height), (image_x + image_width), image_y], Border: [0, 0, 0], A: { Type: :Action, S: :URI, URI: uri.as_pdf }
4786
+ loc = [image_x, (image_y - image_height), (image_x + image_width), image_y]
4787
+ if uri.chr == '#'
4788
+ link_annotation loc, Border: [0, 0, 0], Dest: (uri.slice 1)
4789
+ else
4790
+ link_annotation loc, Border: [0, 0, 0], A: { Type: :Action, S: :URI, URI: uri.as_pdf }
4791
+ end
4728
4792
  end
4729
4793
 
4730
4794
  def admonition_icon_data key
@@ -4805,7 +4869,7 @@ module Asciidoctor
4805
4869
  padding = expand_padding_value @theme[%(#{category}_padding)]
4806
4870
  if actual_width > (available_width = bounds.width - padding[3].to_f - padding[1].to_f)
4807
4871
  adjusted_font_size = ((available_width * font_size).to_f / actual_width).truncate 4
4808
- if (min = @theme[%(#{category}_font_size_min)] || @theme.base_font_size_min) && adjusted_font_size < min
4872
+ if (min = @theme[%(#{category}_font_size_min)] || @theme.base_font_size_min) && adjusted_font_size < (min = resolve_font_size min)
4809
4873
  min
4810
4874
  else
4811
4875
  adjusted_font_size
@@ -4813,6 +4877,19 @@ module Asciidoctor
4813
4877
  end
4814
4878
  end
4815
4879
 
4880
+ def resolve_font_size value
4881
+ return value unless ::String === value
4882
+ if value.end_with? 'rem'
4883
+ @root_font_size * value.to_f
4884
+ elsif value.end_with? 'em'
4885
+ font_size * value.to_f
4886
+ elsif value.end_with? '%'
4887
+ font_size * (value.to_f / 100)
4888
+ else
4889
+ value.to_f
4890
+ end
4891
+ end
4892
+
4816
4893
  def consolidate_ranges nums
4817
4894
  if nums.size > 1
4818
4895
  prev = nil
@@ -1146,7 +1146,11 @@ module Asciidoctor
1146
1146
  bounds_copy.instance_variable_set :@parent, saved_bounds
1147
1147
  bounds_copy.single_file if ColumnBox === bounds_copy
1148
1148
  end
1149
- scratch_pdf.move_cursor_to cursor unless (scratch_start_at_top = keep_together || pages_advanced > 0 || at_page_top?)
1149
+ if !(scratch_start_at_top = keep_together || pages_advanced > 0 || at_page_top?)
1150
+ scratch_pdf.move_cursor_to cursor
1151
+ elsif ColumnBox === bounds
1152
+ scratch_pdf.move_cursor_to scratch_pdf.bounds.top
1153
+ end
1150
1154
  scratch_start_cursor = scratch_pdf.cursor
1151
1155
  scratch_start_page = scratch_pdf.page_number
1152
1156
  inhibit_new_page = state.on_page_create_callback == InhibitNewPageProc
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ Prawn::Font::TTF.prepend (Module.new do
4
+ def character_width_by_code code
5
+ return 0 if code == 0
6
+ super
7
+ end
8
+ end)
@@ -9,7 +9,7 @@ Prawn::Text::Formatted::Arranger.prepend (Module.new do
9
9
  super
10
10
  @dummy_text = ?\u0000
11
11
  @normalize_line_height = false
12
- @sub_and_sup_relative_size = 0.583
12
+ @sub_relative_size = @sup_relative_size = 0.583
13
13
  end
14
14
 
15
15
  def format_array= array
@@ -38,13 +38,18 @@ Prawn::Text::Formatted::Arranger.prepend (Module.new do
38
38
  end
39
39
 
40
40
  def apply_font_size size, styles
41
- if (subscript? styles) || (superscript? styles)
41
+ if (sub = subscript? styles) || (superscript? styles)
42
42
  size ||= @document.font_size
43
+ if instance_variable_defined? :@sub_and_sup_relative_size
44
+ relative_size = @sub_and_sup_relative_size
45
+ else
46
+ relative_size = sub ? @sub_relative_size : @sup_relative_size
47
+ end
43
48
  if String === size
44
49
  units = (size.end_with? 'em', '%') ? ((size.end_with? '%') ? '%' : 'em') : ''
45
- size = %(#{size.to_f * @sub_and_sup_relative_size}#{units})
50
+ size = %(#{size.to_f * relative_size}#{units})
46
51
  else
47
- size *= @sub_and_sup_relative_size
52
+ size *= relative_size
48
53
  end
49
54
  @document.font_size(size) { yield }
50
55
  elsif size
@@ -8,6 +8,7 @@ Prawn::FLOAT_PRECISION = 1e-3
8
8
  require_relative 'prawn/document/column_box'
9
9
  require_relative 'prawn/font_metric_cache'
10
10
  require_relative 'prawn/font/afm'
11
+ require_relative 'prawn/font/ttf'
11
12
  require_relative 'prawn/images'
12
13
  require_relative 'prawn/formatted_text/arranger'
13
14
  require_relative 'prawn/formatted_text/box'
@@ -10,6 +10,7 @@ module Asciidoctor
10
10
 
11
11
  FormattingSnifferPattern = /[<&]/
12
12
  WHITESPACE = %( \t\n)
13
+ SHY = ::Prawn::Text::SHY
13
14
 
14
15
  def initialize options = {}
15
16
  @parser = MarkupParser.new
@@ -26,7 +27,7 @@ module Asciidoctor
26
27
  return @transform.apply parsed.content, [], inherited
27
28
  end
28
29
  reason = @parser.failure_reason.sub %r/ at line \d+, column \d+ \(byte (\d+)\)(.*)/, '\2 at byte \1'
29
- logger.error %(failed to parse formatted text: #{string} (reason: #{reason})) unless @scratch
30
+ logger.error %(failed to parse formatted text: #{string.tr SHY, ''} (reason: #{reason.tr SHY, ''})) unless @scratch
30
31
  end
31
32
  [inherited ? (inherited.merge text: string) : { text: string }]
32
33
  end
@@ -212,8 +212,12 @@ module Asciidoctor
212
212
  fragment[:callback] = [TextBackgroundAndBorderRenderer] | fragment[:callback]
213
213
  end
214
214
  end if attributes.key? :class
215
- if inherited && (link = inherited[:link])
216
- fragment[:link] = link
215
+ if inherited
216
+ if (link = inherited[:link])
217
+ fragment[:link] = link
218
+ elsif (anchor = inherited[:anchor])
219
+ fragment[:anchor] = anchor
220
+ end
217
221
  end
218
222
  if (img_w = attributes[:width])
219
223
  fragment[:image_width] = img_w
@@ -1,5 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'digest'
4
+ require_relative 'formatted_string'
5
+
3
6
  module Asciidoctor
4
7
  module PDF
5
8
  class IndexCatalog
@@ -20,29 +23,29 @@ module Asciidoctor
20
23
  %(__indexterm-#{@sequence += 1})
21
24
  end
22
25
 
23
- def store_term names, dest
24
- if (num_terms = names.size) > 2
25
- store_tertiary_term names[0], names[1], names[2], dest
26
+ def store_term names, dest, assoc = {}
27
+ if (num_terms = (names = names.map {|name| FormattedString.new name }).size) == 1
28
+ store_primary_term names[0], dest, assoc
26
29
  elsif num_terms == 2
27
- store_secondary_term names[0], names[1], dest
28
- elsif num_terms == 1
29
- store_primary_term names[0], dest
30
+ store_secondary_term names[0], names[1], dest, assoc
31
+ elsif num_terms > 2
32
+ store_tertiary_term names[0], names[1], names[2], dest, assoc
30
33
  end
31
34
  end
32
35
 
33
- def store_primary_term name, dest = nil
36
+ def store_primary_term name, dest = nil, assoc = {}
34
37
  store_dest dest if dest
35
- (init_category name.chr.upcase).store_term name, dest
38
+ (init_category name.chr.upcase).store_term name, dest, assoc
36
39
  end
37
40
 
38
- def store_secondary_term primary_name, secondary_name, dest = nil
41
+ def store_secondary_term primary_name, secondary_name, dest = nil, assoc = {}
39
42
  store_dest dest if dest
40
- (store_primary_term primary_name).store_term secondary_name, dest
43
+ (store_primary_term primary_name).store_term secondary_name, dest, assoc
41
44
  end
42
45
 
43
- def store_tertiary_term primary_name, secondary_name, tertiary_name, dest
46
+ def store_tertiary_term primary_name, secondary_name, tertiary_name, dest, assoc = {}
44
47
  store_dest dest
45
- (store_secondary_term primary_name, secondary_name).store_term tertiary_name, dest
48
+ (store_secondary_term primary_name, secondary_name).store_term tertiary_name, dest, assoc
46
49
  end
47
50
 
48
51
  def init_category name
@@ -54,6 +57,34 @@ module Asciidoctor
54
57
  @categories[name]
55
58
  end
56
59
 
60
+ def find_primary_term name
61
+ @categories.each_value do |category|
62
+ term = category.terms.find {|candidate| candidate.name == name }
63
+ return term if term
64
+ end
65
+ nil
66
+ end
67
+
68
+ def link_associations group = nil
69
+ if group
70
+ group.terms.each do |term|
71
+ associations = term.associations
72
+ if (see_name = associations[:see])
73
+ see_name = FormattedString.new see_name
74
+ term.see = (find_primary_term see_name) || (UnresolvedIndexTerm.new see_name)
75
+ elsif (see_also_names = associations[:see_also])
76
+ term.see_also = see_also_names.map do |see_also_name|
77
+ see_also_name = FormattedString.new see_also_name
78
+ (find_primary_term see_also_name) || (UnresolvedIndexTerm.new see_also_name)
79
+ end
80
+ end
81
+ link_associations term unless term.leaf?
82
+ end
83
+ else
84
+ @categories.each_value {|category| link_associations category }
85
+ end
86
+ end
87
+
57
88
  def store_dest dest
58
89
  @dests[dest[:anchor]] = dest
59
90
  end
@@ -83,9 +114,18 @@ module Asciidoctor
83
114
  @terms = {}
84
115
  end
85
116
 
86
- def store_term name, dest
117
+ def store_term name, dest, assoc = {}
87
118
  term = (@terms[name] ||= (IndexTerm.new name))
88
119
  term.add_dest dest if dest
120
+ term.associations ||= {}
121
+ unless assoc.empty?
122
+ if !term.associations[:see] && (see = assoc[:see])
123
+ term.associations[:see] = see
124
+ end
125
+ if (see_also = assoc[:see_also])
126
+ (term.associations[:see_also] ||= []).concat see_also
127
+ end
128
+ end
89
129
  term
90
130
  end
91
131
 
@@ -100,10 +140,22 @@ module Asciidoctor
100
140
 
101
141
  class IndexTermCategory < IndexTermGroup; end
102
142
 
143
+ class UnresolvedIndexTerm < IndexTermGroup; end
144
+
103
145
  class IndexTerm < IndexTermGroup
146
+ attr_reader :anchor
147
+
148
+ attr_accessor :associations
149
+
150
+ attr_accessor :see
151
+
152
+ attr_writer :see_also
153
+
104
154
  def initialize name
105
155
  super
106
156
  @dests = ::Set.new
157
+ @anchor = %(__indextermdef-#{(::Digest::MD5.new << name.to_s).hexdigest})
158
+ @associations = @see = @see_also = nil
107
159
  end
108
160
 
109
161
  alias subterms terms
@@ -124,6 +176,10 @@ module Asciidoctor
124
176
  def leaf?
125
177
  @terms.empty?
126
178
  end
179
+
180
+ def see_also
181
+ @see_also&.sort
182
+ end
127
183
  end
128
184
  end
129
185
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Asciidoctor
4
4
  module PDF
5
- VERSION = '2.3.19'
5
+ VERSION = '2.3.21'
6
6
  end
7
7
  end
@@ -2,7 +2,10 @@
2
2
 
3
3
  proc do
4
4
  old_verbose, $VERBOSE = $VERBOSE, nil
5
- require 'bigdecimal' # eagerly require bigdecimal without warnings to avoid warning caused by ttfunk 1.7.0
5
+ begin
6
+ require 'bigdecimal' # try to eagerly require bigdecimal with warnings off to avoid warning caused by ttfunk 1.7.0
7
+ rescue Exception # rubocop:disable Lint/SuppressedException,Lint/RescueException
8
+ end
6
9
  $VERBOSE = old_verbose
7
10
  end.call
8
11
 
@@ -11,7 +14,11 @@ autoload :StringIO, 'stringio'
11
14
  autoload :Tempfile, 'tempfile'
12
15
  require 'time' unless defined? Time.parse
13
16
  require_relative 'pdf/version'
14
- require 'asciidoctor'
17
+ proc do
18
+ old_verbose, $VERBOSE = $VERBOSE, nil
19
+ require 'asciidoctor' # avoid warning in Ruby 3.4 caused by use of logger
20
+ $VERBOSE = old_verbose
21
+ end.call
15
22
  require 'prawn'
16
23
  require 'prawn/templates'
17
24
  require_relative 'pdf/measurements'
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: asciidoctor-pdf
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.3.19
4
+ version: 2.3.21
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dan Allen
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2024-10-11 00:00:00.000000000 Z
12
+ date: 2025-10-08 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: asciidoctor
@@ -129,14 +129,14 @@ dependencies:
129
129
  requirements:
130
130
  - - "~>"
131
131
  - !ruby/object:Gem::Version
132
- version: '1.1'
132
+ version: '1.3'
133
133
  type: :runtime
134
134
  prerelease: false
135
135
  version_requirements: !ruby/object:Gem::Requirement
136
136
  requirements:
137
137
  - - "~>"
138
138
  - !ruby/object:Gem::Version
139
- version: '1.1'
139
+ version: '1.3'
140
140
  - !ruby/object:Gem::Dependency
141
141
  name: treetop
142
142
  requirement: !ruby/object:Gem::Requirement
@@ -290,6 +290,7 @@ files:
290
290
  - lib/asciidoctor/pdf/ext/prawn/document/column_box.rb
291
291
  - lib/asciidoctor/pdf/ext/prawn/extensions.rb
292
292
  - lib/asciidoctor/pdf/ext/prawn/font/afm.rb
293
+ - lib/asciidoctor/pdf/ext/prawn/font/ttf.rb
293
294
  - lib/asciidoctor/pdf/ext/prawn/font_metric_cache.rb
294
295
  - lib/asciidoctor/pdf/ext/prawn/formatted_text/arranger.rb
295
296
  - lib/asciidoctor/pdf/ext/prawn/formatted_text/box.rb
@@ -351,7 +352,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
351
352
  - !ruby/object:Gem::Version
352
353
  version: '0'
353
354
  requirements: []
354
- rubygems_version: 3.5.16
355
+ rubygems_version: 3.5.22
355
356
  signing_key:
356
357
  specification_version: 4
357
358
  summary: Converts AsciiDoc documents to PDF using Asciidoctor and Prawn