asciidoctor-pdf 2.2.0 → 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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5dbb3a6c2c5b664510e19714de969553f3943994b4be273238c646e41274885f
4
- data.tar.gz: f6495e965c651113bbc61383d82a18db4fb78a7df64644801bd10fba1c6ed078
3
+ metadata.gz: fa858819d28baa5f07261f05d2ac3694e571683f3c99b9cbf80b11bd7d79c4b3
4
+ data.tar.gz: 243f4f2a35fcd2d3a2d6c9449499c3097804865abe137058dd55975970fbded9
5
5
  SHA512:
6
- metadata.gz: 0b93162e6b69d1c32cf3365c08f78457f80cb1588a98c847b0ddfa6946074ba1c7c163009434243d9c78d87e6982c84b2c74eb06fbb72003f003854fbadea926
7
- data.tar.gz: 2cc9d0d4d64ac4ae889e54cf272339019ec91a812ba38f22f28b81abb8daa6af086941e8b50bb95a70f1f1028dd06879952fd51da4219fc46db523f4757ca774
6
+ metadata.gz: 64571af8d39650e26245fd746675c8af1b3b98ee0b8b6d2de705acaf93849bbc5ed51ae36543ac06105e102b79ed698a24676ca76d63c91213399bd029b58e85
7
+ data.tar.gz: f446904f582d0d423c9ea918a23bff541ec65be94e0be7b94a07e1e190a6e66ee4aeb2c7ad403c61773708feb30adb2949e2822a56f7f0c3bd1b291191225c48
data/CHANGELOG.adoc CHANGED
@@ -5,6 +5,36 @@
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.0 (2022-08-16) - @mojavelinux
9
+
10
+ Enhancements::
11
+
12
+ * place footnotes directly below last block of content if `footnotes-margin-top` theme key is 0 (#2291)
13
+ * allow page / column break to be forced using `always` option (e.g., `[%always]`) (#2300)
14
+ * insert column break instead of page break in multi-column layout if `column` role is specified on page break macro (#2293)
15
+ * use relative font size for big and small roles (#2307)
16
+ * use default-for-print theme by default if media is `print` or `prepress` (#2306)
17
+ * support text alignment roles on all styled paragraphs
18
+ * support text alignment roles on verse block
19
+
20
+ Bug Fixes::
21
+
22
+ * only indent text that starts at left margin (i.e., when text align is left or justify) (#2298)
23
+ * apply text transform and formatting when checking height of heading for orphan prevention
24
+ * apply text transform and formatting when computing height of background for caption
25
+ * honor theme settings (`prose-margin-inner` and `prose-text-indent-inner`) for inner paragraphs in abstract
26
+ * prevent footnote label from being split across lines (#2297)
27
+ * keep footnote label with preceding text if adjacent (#2297)
28
+ * strip formatting added to source block by custom subs when syntax highlighter is enabled (#2086)
29
+
30
+ Compliance::
31
+
32
+ * remove support for deprecated `spread` role on table
33
+
34
+ === Details
35
+
36
+ {url-repo}/releases/tag/v2.3.0[git tag] | {url-repo}/compare/v2.2.0\...v2.3.0[full diff]
37
+
8
38
  == 2.2.0 (2022-07-22) - @mojavelinux
9
39
 
10
40
  Enhancements::
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.2.0, 2022-07-22
3
+ v2.3.0, 2022-08-16
4
4
  // Settings:
5
5
  :experimental:
6
6
  :idprefix:
@@ -17,8 +17,8 @@ base_border_color: '000000'
17
17
  role_lead_font_size: 13.5
18
18
  role_line-through_text_decoration: line-through
19
19
  role_underline_text_decoration: underline
20
- role_big_font_size: 14
21
- role_small_font_size: 10
20
+ role_big_font_size: 1.2em
21
+ role_small_font_size: 0.8em
22
22
  role_subtitle_font_size: 0.8em
23
23
  button_content: '[%s]'
24
24
  button_font_family: Courier
@@ -23,28 +23,14 @@ page:
23
23
  size: A4
24
24
  base:
25
25
  text_align: justify
26
- # color as hex string (leading # is optional)
27
26
  font_color: 333333
28
- # color as RGB array
29
- #font_color: [51, 51, 51]
30
- # color as CMYK array (approximated)
31
- #font_color: [0, 0, 0, 0.92]
32
- #font_color: [0, 0, 0, 92%]
33
27
  font_family: Noto Serif
34
- # choose one of these font_size/line_height_length combinations
35
- #font_size: 14
36
- #line_height_length: 20
37
- #font_size: 11.25
38
- #line_height_length: 18
39
- #font_size: 11.2
40
- #line_height_length: 16
41
28
  font_size: 10.5
42
- #line_height_length: 15
43
- # correct line height for Noto Serif metrics
29
+ # line_height_length is really just a vertical spacing variable; it's not actually the height of a line
44
30
  line_height_length: 12
45
- #font_size: 11.25
46
- #line_height_length: 18
47
- line_height: $base_line_height_length / $base_font_size
31
+ # The Noto font family has a built-in line height of 1.36
32
+ # With this line_height, a line of text will occupy a height of 15.78pt
33
+ line_height: $base_line_height_length / 10.5
48
34
  font_size_large: round($base_font_size * 1.25)
49
35
  font_size_small: round($base_font_size * 0.85)
50
36
  font_size_min: $base_font_size * 0.75
@@ -60,16 +46,13 @@ role:
60
46
  underline:
61
47
  text_decoration: underline
62
48
  big:
63
- font_size: $base_font_size_large
49
+ font_size: 1.2em
64
50
  small:
65
- font_size: $base_font_size_small
51
+ font_size: 0.8em
66
52
  subtitle:
67
53
  font_color: 999999
68
54
  font_size: 0.8em
69
55
  font_style: normal_italic
70
- # FIXME vertical_rhythm is weird; we should think in terms of ems
71
- #vertical_rhythm: $base_line_height_length * 2 / 3
72
- # correct line height for Noto Serif metrics (comes with built-in line height)
73
56
  vertical_rhythm: $base_line_height_length
74
57
  horizontal_rhythm: $base_line_height_length
75
58
  link:
@@ -107,8 +90,7 @@ heading:
107
90
  h4_font_size: $base_font_size_large
108
91
  h5_font_size: $base_font_size
109
92
  h6_font_size: $base_font_size_small
110
- #line_height: 1.4
111
- # correct line height for Noto Serif metrics (comes with built-in line height)
93
+ # rely on built-in line height in Noto
112
94
  line_height: 1
113
95
  margin_top: $vertical_rhythm * 0.4
114
96
  margin_bottom: $vertical_rhythm * 0.9
@@ -156,11 +138,6 @@ admonition:
156
138
  column_rule_color: $base_border_color
157
139
  column_rule_width: $base_border_width
158
140
  padding: [$vertical_rhythm / 3.0, $horizontal_rhythm, $vertical_rhythm / 3.0, $horizontal_rhythm]
159
- #icon:
160
- # tip:
161
- # name: far-lightbulb
162
- # stroke_color: 111111
163
- # size: 24
164
141
  label:
165
142
  text_transform: uppercase
166
143
  font_style: bold
@@ -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(&#8220; &#8221; &#8216; &#8217;)
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])?$/
@@ -394,7 +396,7 @@ module Asciidoctor
394
396
  @font_scale = 1
395
397
  @font_color = theme.base_font_color
396
398
  @text_decoration_width = theme.base_text_decoration_width
397
- @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
398
400
  @base_line_height = theme.base_line_height
399
401
  @cjk_line_breaks = doc.attr? 'scripts', 'cjk'
400
402
  if (hyphen_lang = (doc.attr 'hyphens') || ((doc.attr_unspecified? 'hyphens') ? @theme.base_hyphens : nil)) &&
@@ -533,8 +535,10 @@ module Asciidoctor
533
535
  elsif (theme_name = doc.attr 'pdf-theme')
534
536
  theme = ThemeLoader.load_theme theme_name, (user_themesdir = (doc.attr 'pdf-themesdir')&.sub '{docdir}', (doc.attr 'docdir'))
535
537
  @themesdir = theme.__dir__
536
- else
538
+ elsif (doc.attr 'media', 'screen') == 'screen'
537
539
  @themesdir = (theme = ThemeLoader.load_theme).__dir__
540
+ else
541
+ @themesdir = (theme = ThemeLoader.load_theme 'default-for-print').__dir__
538
542
  end
539
543
  prepare_theme theme
540
544
  rescue
@@ -577,6 +581,7 @@ module Asciidoctor
577
581
  theme.code_linenum_font_color ||= '999999'
578
582
  theme.callout_list_margin_top_after_code ||= 0
579
583
  theme.role_unresolved_font_color ||= 'FF0000'
584
+ theme.footnotes_margin_top ||= 'auto'
580
585
  theme.footnotes_item_spacing ||= 0
581
586
  theme.index_columns ||= 2
582
587
  theme.index_column_gap ||= theme.base_font_size
@@ -741,22 +746,16 @@ module Asciidoctor
741
746
  pagenums = consolidate_ranges term.dests.map {|dest| dest[:page] }.uniq
742
747
  end
743
748
  pagenums.each do |pagenum|
744
- # NOTE: addresses a very minor kerning issue for text adjacent to the comma
745
- if (prev_fragment = term_fragments[-1]).size == 1
746
- if ::String === pagenum
747
- term_fragments[-1] = prev_fragment.merge text: %(#{prev_fragment[:text]}, #{pagenum})
748
- next
749
- else
750
- term_fragments[-1] = prev_fragment.merge text: %(#{prev_fragment[:text]}, )
751
- end
749
+ if ::String === pagenum
750
+ term_fragments << ({ text: %(, #{pagenum}) })
752
751
  else
753
- term_fragments << ({ text: ', ' })
752
+ term_fragments << { text: ', ' }
753
+ term_fragments << pagenum
754
754
  end
755
- term_fragments << (::String === pagenum ? { text: pagenum } : pagenum)
756
755
  end
757
756
  end
758
757
  subterm_indent = @theme.description_list_description_indent
759
- 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
760
759
  indent subterm_indent do
761
760
  term.subterms.each do |subterm|
762
761
  convert_index_term subterm, pagenum_sequence_style
@@ -782,10 +781,7 @@ module Asciidoctor
782
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)
783
782
  end if node.title?
784
783
  theme_font :abstract do
785
- prose_opts = { align: (@theme.abstract_text_align || @base_text_align).to_sym, hyphenate: true }
786
- if (text_indent = @theme.prose_text_indent) > 0
787
- prose_opts[:indent_paragraphs] = text_indent
788
- end
784
+ prose_opts = { align: (@theme.abstract_text_align || @base_text_align).to_sym, hyphenate: true, margin_bottom: 0 }
789
785
  # FIXME: allow theme to control more first line options
790
786
  if (line1_font_style = @theme.abstract_first_line_font_style&.to_sym) && line1_font_style != font_style
791
787
  case line1_font_style
@@ -806,14 +802,11 @@ module Asciidoctor
806
802
  prose_opts[:first_line_options] = first_line_options if first_line_options
807
803
  # FIXME: make this cleaner!!
808
804
  if node.blocks?
809
- last_block = node.last_child
810
805
  node.blocks.each do |child|
811
806
  if child.context == :paragraph
812
807
  child.document.playback_attributes child.attributes
813
- prose_opts[:margin_bottom] = 0 if child == last_block
814
- 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
815
809
  prose_opts.delete :first_line_options
816
- prose_opts.delete :margin_bottom
817
810
  else
818
811
  # FIXME: this could do strange things if the wrong kind of content shows up
819
812
  child.convert
@@ -823,7 +816,10 @@ module Asciidoctor
823
816
  if (text_align = resolve_text_align_from_role node.roles)
824
817
  prose_opts[:align] = text_align
825
818
  end
826
- 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
827
823
  end
828
824
  end
829
825
  end
@@ -832,16 +828,19 @@ module Asciidoctor
832
828
  theme_margin :block, :bottom, (next_enclosed_block node)
833
829
  end
834
830
 
835
- def convert_paragraph node
831
+ def convert_paragraph node, opts = nil
836
832
  add_dest_for_block node if node.id
837
833
 
838
- prose_opts = { margin_bottom: 0, hyphenate: true }
834
+ prose_opts = opts || { margin_bottom: 0, hyphenate: true }
839
835
  if (text_align = resolve_text_align_from_role (roles = node.roles), query_theme: true, remove_predefined: true)
840
836
  prose_opts[:align] = text_align
837
+ else
838
+ text_align = @base_text_align.to_sym
841
839
  end
842
840
  role_keys = roles.map {|role| %(role_#{role}) } unless roles.empty?
843
- if (text_indent = @theme.prose_text_indent) > 0 ||
844
- ((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))
845
844
  prose_opts[:indent_paragraphs] = text_indent
846
845
  end
847
846
  if (bottom_gutter = @bottom_gutters[-1][node])
@@ -1046,7 +1045,7 @@ module Asciidoctor
1046
1045
  else
1047
1046
  highlighter = nil
1048
1047
  end
1049
- prev_subs = (subs = node.subs).dup
1048
+ saved_subs = (subs = node.subs).dup
1050
1049
  callouts_enabled = subs.include? :callouts
1051
1050
  highlight_idx = subs.index :highlight
1052
1051
  # NOTE: scratch? here only applies if listing block is nested inside another block
@@ -1056,16 +1055,33 @@ module Asciidoctor
1056
1055
  # switch the :highlight sub back to :specialcharacters
1057
1056
  subs[highlight_idx] = :specialcharacters
1058
1057
  else
1059
- prev_subs = nil
1058
+ saved_subs = nil
1060
1059
  end
1061
1060
  source_string = guard_indentation node.content
1062
1061
  elsif highlight_idx
1063
1062
  # NOTE: the source highlighter logic below handles the highlight and callouts subs
1064
- subs.replace subs - [:highlight, :callouts]
1065
- # NOTE: indentation guards will be added by the source highlighter logic
1066
- 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
1067
1083
  else
1068
- highlighter = prev_subs = nil
1084
+ highlighter = saved_subs = nil
1069
1085
  source_string = guard_indentation node.content
1070
1086
  end
1071
1087
  else
@@ -1157,7 +1173,7 @@ module Asciidoctor
1157
1173
  # NOTE: only format if we detect a need (callouts or inline formatting)
1158
1174
  source_chunks = (XMLMarkupRx.match? source_string) ? (text_formatter.format source_string) : [text: source_string]
1159
1175
  end
1160
- node.subs.replace prev_subs if prev_subs
1176
+ node.subs.replace saved_subs if saved_subs
1161
1177
  adjusted_font_size = ((node.option? 'autofit') || (node.document.attr? 'autofit-option')) ? (compute_autofit_font_size source_chunks, :code) : nil
1162
1178
  end
1163
1179
 
@@ -1281,7 +1297,7 @@ module Asciidoctor
1281
1297
  content = guard_indentation node.content
1282
1298
  ink_prose content,
1283
1299
  normalize: false,
1284
- align: :left,
1300
+ align: (resolve_text_align_from_role node.roles) || :left,
1285
1301
  hyphenate: true,
1286
1302
  margin_bottom: 0,
1287
1303
  bottom_gutter: (attribution ? nil : @bottom_gutters[-1][node])
@@ -1682,7 +1698,7 @@ module Asciidoctor
1682
1698
  alignment = float_to.to_sym
1683
1699
  elsif (alignment = node.attr 'align')
1684
1700
  alignment = (BlockAlignmentNames.include? alignment) ? alignment.to_sym : :left
1685
- 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)
1686
1702
  alignment = @theme.image_align&.to_sym || :left
1687
1703
  end
1688
1704
  end
@@ -1916,14 +1932,16 @@ module Asciidoctor
1916
1932
  page_layout = nil
1917
1933
  end
1918
1934
 
1919
- if at_page_top?
1935
+ if at_page_top? && !(node.option? 'always')
1920
1936
  if page_layout && page_layout != page.layout && page.empty?
1921
1937
  delete_current_page
1922
1938
  advance_page layout: page_layout, margin: @page_margin[page_layout][page_side nil, @folio_placement[:inverted]]
1923
1939
  end
1924
1940
  elsif page_layout
1941
+ bounds.current_column = bounds.last_column if ColumnBox === bounds && !(node.has_role? 'column')
1925
1942
  advance_page layout: page_layout, margin: @page_margin[page_layout][page_side nil, @folio_placement[:inverted]]
1926
1943
  else
1944
+ bounds.current_column = bounds.last_column if ColumnBox === bounds && !(node.has_role? 'column')
1927
1945
  advance_page
1928
1946
  end
1929
1947
  end
@@ -2196,7 +2214,7 @@ module Asciidoctor
2196
2214
 
2197
2215
  if node.option? 'autowidth'
2198
2216
  table_width = (node.attr? 'width') ? bounds.width * ((node.attr 'tablepcwidth') / 100.0) :
2199
- (((node.has_role? 'stretch') || (node.has_role? 'spread')) ? bounds.width : nil)
2217
+ (((node.has_role? 'stretch')) ? bounds.width : nil)
2200
2218
  column_widths = []
2201
2219
  else
2202
2220
  table_width = bounds.width * ((node.attr 'tablepcwidth') / 100.0)
@@ -2362,6 +2380,9 @@ module Asciidoctor
2362
2380
  node.content
2363
2381
  elsif node.content_model != :compound && (string = node.content)
2364
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
2365
2386
  if (bottom_gutter = @bottom_gutters[-1][node])
2366
2387
  prose_opts[:bottom_gutter] = bottom_gutter
2367
2388
  end
@@ -2477,9 +2498,9 @@ module Asciidoctor
2477
2498
  else
2478
2499
  label = index
2479
2500
  end
2480
- %(#{anchor}<sup>[<a anchor="_footnotedef_#{index}">#{label}</a>]</sup>)
2501
+ %(<sup class="wj">#{anchor}[<a anchor="_footnotedef_#{index}">#{label}</a>]</sup>)
2481
2502
  elsif node.type == :xref
2482
- %(<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>)
2483
2504
  else
2484
2505
  log :warn, %(unknown footnote type: #{node.type.inspect})
2485
2506
  nil
@@ -2829,8 +2850,7 @@ module Asciidoctor
2829
2850
  theme_font :heading, level: (hlevel = opts[:level]) do
2830
2851
  h_padding_t, h_padding_r, h_padding_b, h_padding_l = expand_padding_value @theme[%(heading_h#{hlevel}_padding)]
2831
2852
  h_fits = indent h_padding_l, h_padding_r do
2832
- # FIXME: this height doesn't account for impact of text transform or inline formatting
2833
- heading_h = (height_of_typeset_text title) +
2853
+ heading_h = (height_of_typeset_text title, inline_format: true, text_transform: @text_transform) +
2834
2854
  (@theme[%(heading_h#{hlevel}_margin_top)] || @theme.heading_margin_top) +
2835
2855
  (@theme[%(heading_h#{hlevel}_margin_bottom)] || @theme.heading_margin_bottom) + h_padding_t + h_padding_b
2836
2856
  heading_h += min_height_after if min_height_after && (node.context == :section ? node.blocks? : !node.last_child?)
@@ -3002,8 +3022,16 @@ module Asciidoctor
3002
3022
  end
3003
3023
 
3004
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
3005
3033
  line_metrics = (calc_line_metrics opts[:line_height] || @base_line_height)
3006
- (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)
3007
3035
  end
3008
3036
 
3009
3037
  # Render the caption in the current document. If the dry_run option is true, return the height.
@@ -3095,7 +3123,7 @@ module Asciidoctor
3095
3123
  opts = opts.merge inherited
3096
3124
  end
3097
3125
  unless scratch? || !(bg_color = @theme[%(#{category_caption}_background_color)] || @theme.caption_background_color)
3098
- caption_height = height_of_typeset_text string
3126
+ caption_height = height_of_typeset_text string, inline_format: true, text_transform: @text_transform
3099
3127
  fill_at = [bounds.left, cursor]
3100
3128
  fill_at[1] -= (margin[:top] || 0) unless at_page_top?
3101
3129
  float { bounding_box(fill_at, width: container_width, height: caption_height) { fill_bounds bg_color } }
@@ -3156,9 +3184,9 @@ module Asciidoctor
3156
3184
  def ink_footnotes node
3157
3185
  return if (fns = (doc = node.document).footnotes - @rendered_footnotes).empty?
3158
3186
  theme_margin :block, :bottom if node.context == :document || node == node.document.last_child
3159
- theme_margin :footnotes, :top
3187
+ theme_margin :footnotes, :top unless (valign_bottom = @theme.footnotes_margin_top == 'auto')
3160
3188
  with_dry_run do |extent|
3161
- 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
3162
3190
  move_down delta
3163
3191
  end
3164
3192
  theme_font :footnotes do
@@ -4294,8 +4322,8 @@ module Asciidoctor
4294
4322
  end
4295
4323
 
4296
4324
  def resolve_text_align_from_role roles, query_theme: false, remove_predefined: false
4297
- if (align_role = roles.reverse.find {|r| TextAlignmentRoles.include? r })
4298
- 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
4299
4327
  (align_role.slice 5, align_role.length).to_sym
4300
4328
  elsif query_theme
4301
4329
  roles.reverse.each do |role|
@@ -4537,6 +4565,7 @@ module Asciidoctor
4537
4565
  # QUESTION: combine with typeset_text?
4538
4566
  def typeset_formatted_text fragments, line_metrics, opts = {}
4539
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
4540
4569
  if (hanging_indent = (opts.delete :hanging_indent) || 0) > 0
4541
4570
  indent hanging_indent do
4542
4571
  formatted_text fragments, (opts.merge indent_paragraphs: -hanging_indent)
@@ -4746,6 +4775,20 @@ module Asciidoctor
4746
4775
  end
4747
4776
  end
4748
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
+
4749
4792
  def conum_glyph number
4750
4793
  @conum_glyphs[number - 1]
4751
4794
  end
@@ -3,6 +3,10 @@
3
3
  Prawn::Document::ColumnBox.prepend (Module.new do
4
4
  attr_accessor :current_column
5
5
 
6
+ def last_column
7
+ @columns - 1
8
+ end
9
+
6
10
  def move_past_bottom
7
11
  (doc = @document).y = @y
8
12
  return if (@current_column = (@current_column + 1) % @columns) > 0
@@ -26,6 +26,17 @@ Prawn::Text::Formatted::Arranger.prepend (Module.new do
26
26
  (string = super) == @dummy_text ? (string.extend Prawn::Text::NoopLstripBang) : string
27
27
  end
28
28
 
29
+ def preview_joined_string
30
+ if (next_unconsumed = @unconsumed[0] || {})[:wj] && !(@consumed[-1] || [])[:wj]
31
+ idx = 0
32
+ str = '' if (str = next_unconsumed[:text]) == @dummy_text
33
+ while (next_unconsumed = @unconsumed[idx += 1] || {})[:wj] && (next_string = next_unconsumed[:text])
34
+ str += next_string unless next_string == @dummy_text
35
+ end
36
+ str unless str == ''
37
+ end
38
+ end
39
+
29
40
  def apply_font_size size, styles
30
41
  if (subscript? styles) || (superscript? styles)
31
42
  size ||= @document.font_size
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ Prawn::Text::Formatted::LineWrap.prepend (Module.new do
4
+ def add_fragment_to_line fragment
5
+ case fragment
6
+ when ''
7
+ true
8
+ when ?\n
9
+ @newline_encountered = true
10
+ false
11
+ else
12
+ if (joined_string = @arranger.preview_joined_string)
13
+ joined_string_width = @document.width_of (tokenize joined_string)[0], kerning: @kerning
14
+ else
15
+ joined_string_width = 0
16
+ end
17
+ last_idx = (segments = tokenize fragment).length - 1
18
+ segments.each_with_index do |segment, idx|
19
+ if segment == (zero_width_space segment.encoding)
20
+ segment_width = effective_segment_width = 0
21
+ else
22
+ segment_width = effective_segment_width = @document.width_of segment, kerning: @kerning
23
+ effective_segment_width += joined_string_width if idx === last_idx
24
+ end
25
+ if @accumulated_width + effective_segment_width <= @width
26
+ @accumulated_width += segment_width
27
+ if segment[-1] == (shy = soft_hyphen segment.encoding)
28
+ @accumulated_width -= (@document.width_of shy, kerning: @kerning)
29
+ end
30
+ @fragment_output += segment
31
+ else
32
+ @line_contains_more_than_one_word = false if @accumulated_width == 0 && @line_contains_more_than_one_word
33
+ end_of_the_line_reached segment
34
+ fragment_finished fragment
35
+ return false
36
+ end
37
+ end
38
+ fragment_finished fragment
39
+ true
40
+ end
41
+ end
42
+ end)
@@ -13,5 +13,6 @@ require_relative 'prawn/formatted_text/arranger'
13
13
  require_relative 'prawn/formatted_text/box'
14
14
  require_relative 'prawn/formatted_text/fragment'
15
15
  require_relative 'prawn/formatted_text/indented_paragraph_wrap'
16
+ require_relative 'prawn/formatted_text/line_wrap'
16
17
  require_relative 'prawn/formatted_text/protect_bottom_gutter'
17
18
  require_relative 'prawn/extensions'
@@ -315,10 +315,12 @@ module Asciidoctor
315
315
  end) : value
316
316
  elsif (value = attrs[:id])
317
317
  # NOTE: text is null character, which is used as placeholder text so Prawn doesn't drop fragment
318
- fragment = { name: value, callback: [InlineDestinationMarker] }
318
+ new_fragment = { name: value, callback: [InlineDestinationMarker] }
319
+ new_fragment[:wj] = true if fragment[:wj]
319
320
  if (type = attrs[:type])
320
- fragment[:type] = type.to_sym
321
+ new_fragment[:type] = type.to_sym
321
322
  end
323
+ fragment = new_fragment
322
324
  visible = nil
323
325
  end
324
326
  end
@@ -357,6 +359,7 @@ module Asciidoctor
357
359
  end
358
360
  # TODO: we could limit to select tags, but doesn't seem to really affect performance
359
361
  attrs[:class].split.each do |class_name|
362
+ fragment[:wj] = true if class_name == 'wj'
360
363
  next unless @theme_settings.key? class_name
361
364
  update_fragment fragment, @theme_settings[class_name]
362
365
  # NOTE: defer assignment of callback since we must look at combined styles of element and role
@@ -8,7 +8,7 @@ module Asciidoctor
8
8
  '&gt;' => '>',
9
9
  '&amp;' => '&',
10
10
  }
11
- XMLSpecialCharsRx = /(?:#{XMLSpecialChars.keys.join '|'})/
11
+ XMLSpecialCharsRx = /&(?:[lg]t|amp);/
12
12
  InverseXMLSpecialChars = XMLSpecialChars.invert
13
13
  InverseXMLSpecialCharsRx = /[#{InverseXMLSpecialChars.keys.join}]/
14
14
  (BuiltInNamedEntities = {
@@ -28,16 +28,20 @@ module Asciidoctor
28
28
  #
29
29
  # FIXME: move to a module so we can mix it in elsewhere
30
30
  # FIXME: add option to control escaping entities, or a filter mechanism in general
31
- def sanitize string
31
+ def sanitize string, compact: true
32
32
  string = string.gsub SanitizeXMLRx, '' if string.include? '<'
33
33
  string = string.gsub(CharRefRx) { $1 ? BuiltInNamedEntities[$1] : ([$2 ? $2.to_i : ($3.to_i 16)].pack 'U1') } if string.include? '&'
34
- string.strip.tr_s ' ', ' '
34
+ compact ? (string.strip.tr_s ' ', ' ') : string
35
35
  end
36
36
 
37
37
  def escape_xml string
38
38
  string.gsub InverseXMLSpecialCharsRx, InverseXMLSpecialChars
39
39
  end
40
40
 
41
+ def unescape_xml string
42
+ string.gsub XMLSpecialCharsRx, XMLSpecialChars
43
+ end
44
+
41
45
  def escape_amp string
42
46
  string.gsub UnescapedAmpersandRx, '&amp;'
43
47
  end
@@ -160,7 +160,7 @@ module Asciidoctor
160
160
  data[key] = {}.tap do |accum|
161
161
  val.each do |key2, val2|
162
162
  key2 = key2.tr '-', '_' if key2.include? '-'
163
- accum[key2.to_sym] = (key2.end_with? '_color') ? (to_color evaluate val2, data) : (evaluate val2, data)
163
+ accum[key2.to_sym] = (key2.end_with? '_color') ? (to_color evaluate val2, data, math: false) : (evaluate val2, data)
164
164
  end
165
165
  end if val
166
166
  elsif ::Hash === val
@@ -173,20 +173,18 @@ module Asciidoctor
173
173
  end
174
174
  elsif (rekey = DeprecatedKeys[key]) ||
175
175
  ((key.start_with? 'role_') && (key.end_with? '_align') && (rekey = key.sub RoleAlignKeyRx, '_text_align'))
176
- data[rekey] = evaluate val, data
176
+ data[rekey] = evaluate val, data, math: false
177
177
  elsif PaddingBottomHackKeys.include? key
178
178
  val = evaluate val, data
179
179
  # normalize padding hacks for themes designed before the converter had smart margins
180
180
  val[2] = val[0] if ::Array === val && val[0].to_f >= 0 && val[2].to_f <= 0
181
181
  data[key] = val
182
- # QUESTION: do we really need to evaluate_math in this case?
183
182
  elsif key.end_with? '_color'
184
- if key == 'table_border_color'
185
- data[key] = ::Array === val ? val.map {|it| to_color evaluate it, data } : (to_color evaluate val, data)
186
- elsif key == 'table_grid_color' && ::Array === val && val.size == 2
187
- data[key] = val.map {|it| to_color evaluate it, data }
183
+ # assume table_grid_color is a single color unless the value is a 2-element array for backwards compatibility
184
+ if key == 'table_border_color' ? ::Array === val : (key == 'table_grid_color' && ::Array === val && val.size == 2)
185
+ data[key] = val.map {|it| to_color evaluate it, data, math: false }
188
186
  else
189
- data[key] = to_color evaluate val, data
187
+ data[key] = to_color evaluate val, data, math: false
190
188
  end
191
189
  elsif key.end_with? '_content'
192
190
  data[key] = (expand_vars val.to_s, data).to_s
@@ -196,12 +194,12 @@ module Asciidoctor
196
194
  data
197
195
  end
198
196
 
199
- def evaluate expr, vars
197
+ def evaluate expr, vars, math: true
200
198
  case expr
201
199
  when ::String
202
- evaluate_math expand_vars expr, vars
200
+ math ? (evaluate_math expand_vars expr, vars) : (expand_vars expr, vars)
203
201
  when ::Array
204
- expr.map {|e| evaluate e, vars }
202
+ expr.map {|e| evaluate e, vars, math: math }
205
203
  else
206
204
  expr
207
205
  end
@@ -233,7 +231,6 @@ module Asciidoctor
233
231
  def evaluate_math expr
234
232
  return expr if !(::String === expr) || ColorValue === expr
235
233
  # resolve measurement values (e.g., 0.5in => 36)
236
- # QUESTION: should we round the value? perhaps leave that to the precision functions
237
234
  # NOTE: leave % as a string; handled by converter for now
238
235
  original, expr = expr, (resolve_measurement_values expr)
239
236
  while true
@@ -257,8 +254,7 @@ module Asciidoctor
257
254
  end
258
255
  end
259
256
  if (expr.end_with? ')') && expr =~ PrecisionFuncRx
260
- op = $1
261
- offset = op.length + 1
257
+ offset = (op = $1).length + 1
262
258
  expr = expr[offset...-1].to_f.send op.to_sym
263
259
  end
264
260
  if expr == original
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Asciidoctor
4
4
  module PDF
5
- VERSION = '2.2.0'
5
+ VERSION = '2.3.0'
6
6
  end
7
7
  end
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.2.0
4
+ version: 2.3.0
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: 2022-07-22 00:00:00.000000000 Z
12
+ date: 2022-08-16 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: asciidoctor
@@ -280,6 +280,7 @@ files:
280
280
  - lib/asciidoctor/pdf/ext/prawn/formatted_text/box.rb
281
281
  - lib/asciidoctor/pdf/ext/prawn/formatted_text/fragment.rb
282
282
  - lib/asciidoctor/pdf/ext/prawn/formatted_text/indented_paragraph_wrap.rb
283
+ - lib/asciidoctor/pdf/ext/prawn/formatted_text/line_wrap.rb
283
284
  - lib/asciidoctor/pdf/ext/prawn/formatted_text/protect_bottom_gutter.rb
284
285
  - lib/asciidoctor/pdf/ext/prawn/images.rb
285
286
  - lib/asciidoctor/pdf/ext/pygments.rb