asciidoctor-pdf 2.2.0 → 2.3.0

Sign up to get free protection for your applications and to get access to all the features.
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