asciidoctor-pdf 2.2.0 → 2.3.1

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: 9d88e98bb4e7e4afe22f1911c28813edbfe65af757b2cfb5899c754aa811ac12
4
+ data.tar.gz: 931e8ad9378ec457be0566cae2a43d5dddce06d5f821251bf842fc429b689a08
5
5
  SHA512:
6
- metadata.gz: 0b93162e6b69d1c32cf3365c08f78457f80cb1588a98c847b0ddfa6946074ba1c7c163009434243d9c78d87e6982c84b2c74eb06fbb72003f003854fbadea926
7
- data.tar.gz: 2cc9d0d4d64ac4ae889e54cf272339019ec91a812ba38f22f28b81abb8daa6af086941e8b50bb95a70f1f1028dd06879952fd51da4219fc46db523f4757ca774
6
+ metadata.gz: 2b530895e85531f879ad2bf1b8672281e0bc4af53cba666817d5d01349022fc0beaffe758b9def65e82db79f7e86c6bed9fcb70688e65ada7ecb993b5597f748
7
+ data.tar.gz: d97b859cccfb0ff54e65f4ccae7fc4a604c3ccb33a2727d08e2a523896ec045777999397ee506959173b404d13d2b253456943ef93b390f192ed831f4418a170
data/CHANGELOG.adoc CHANGED
@@ -5,6 +5,57 @@
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.1 (2022-09-17) - @mojavelinux
9
+
10
+ Improvements::
11
+
12
+ * provide a fallback value for `base-font-size` when loading theme (#2343)
13
+
14
+ Bug Fixes::
15
+
16
+ * fix crash when smallcaps text transform is applied to a phrase (#2339)
17
+ * don't add chapter signifier if `chapter-signifier` is unset (#2328)
18
+ * don't add part signifier if `part-signifier` is unset (#2328)
19
+ * don't include bottom margin when computing heading height if `heading-min-height-after` theme key is empty (#2326)
20
+ * draw border on heading (section title or discrete heading) if it is advanced to next page (#2322)
21
+ * arrange heading even if section is empty
22
+ * ensure `heading-min-height-after` theme key is ignored if section is empty or discrete heading is last child
23
+ * don't force justify first line of abstract if it ends with a hard break
24
+
25
+ === Details
26
+
27
+ {url-repo}/releases/tag/v2.3.1[git tag] | {url-repo}/compare/v2.3.0\...v2.3.1[full diff]
28
+
29
+ == 2.3.0 (2022-08-16) - @mojavelinux
30
+
31
+ Enhancements::
32
+
33
+ * place footnotes directly below last block of content if `footnotes-margin-top` theme key is 0 (#2291)
34
+ * allow page / column break to be forced using `always` option (e.g., `[%always]`) (#2300)
35
+ * insert column break instead of page break in multi-column layout if `column` role is specified on page break macro (#2293)
36
+ * use relative font size for big and small roles (#2307)
37
+ * use default-for-print theme by default if media is `print` or `prepress` (#2306)
38
+ * support text alignment roles on all styled paragraphs
39
+ * support text alignment roles on verse block
40
+
41
+ Bug Fixes::
42
+
43
+ * only indent text that starts at left margin (i.e., when text align is left or justify) (#2298)
44
+ * apply text transform and formatting when checking height of heading for orphan prevention
45
+ * apply text transform and formatting when computing height of background for caption
46
+ * honor theme settings (`prose-margin-inner` and `prose-text-indent-inner`) for inner paragraphs in abstract
47
+ * prevent footnote label from being split across lines (#2297)
48
+ * keep footnote label with preceding text if adjacent (#2297)
49
+ * strip formatting added to source block by custom subs when syntax highlighter is enabled (#2086)
50
+
51
+ Compliance::
52
+
53
+ * remove support for deprecated `spread` role on table
54
+
55
+ === Details
56
+
57
+ {url-repo}/releases/tag/v2.3.0[git tag] | {url-repo}/compare/v2.2.0\...v2.3.0[full diff]
58
+
8
59
  == 2.2.0 (2022-07-22) - @mojavelinux
9
60
 
10
61
  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.1, 2022-09-17
4
4
  // Settings:
5
5
  :experimental:
6
6
  :idprefix:
@@ -37,9 +37,9 @@ image:{url-project-repo}/workflows/CI/badge.svg[Build Status (GitHub Actions),li
37
37
  image:https://img.shields.io/gem/v/asciidoctor-pdf.svg[Latest Release, link={url-gem}]
38
38
  endif::[]
39
39
 
40
- Asciidoctor PDF is a native PDF converter for AsciiDoc that plugs into the `pdf` backend.
41
- It bypasses the requirement to generate an intermediary format such as DocBook, Apache FO, or LaTeX.
42
- Instead, you can use Asciidoctor PDF to convert your documents directly from AsciiDoc to PDF.
40
+ Asciidoctor PDF is a native PDF converter for AsciiDoc that serves the `pdf` backend.
41
+ It bypasses the step of generating an intermediary format such as DocBook, Apache FO, or LaTeX in order to produce PDF.
42
+ Instead, you use Asciidoctor PDF to convert your documents directly from AsciiDoc to PDF with Asciidoctor.
43
43
  The aim of this library is to take the pain out of creating PDF documents from AsciiDoc.
44
44
 
45
45
  [NOTE]
@@ -8,7 +8,7 @@ Gem::Specification.new do |s|
8
8
  s.name = 'asciidoctor-pdf'
9
9
  s.version = Asciidoctor::PDF::VERSION
10
10
  s.summary = 'Converts AsciiDoc documents to PDF using Asciidoctor and Prawn'
11
- s.description = 'An extension for Asciidoctor that converts AsciiDoc documents to PDF using the Prawn PDF library.'
11
+ s.description = 'An add-on converter for Asciidoctor that converts AsciiDoc documents to PDF using the Prawn PDF generation library.'
12
12
  s.authors = ['Dan Allen', 'Sarah White']
13
13
  s.email = 'dan@opendevise.com'
14
14
  s.homepage = 'https://asciidoctor.org/docs/asciidoctor-pdf'
@@ -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
@@ -625,7 +630,6 @@ module Asciidoctor
625
630
  sect.remove
626
631
  return
627
632
  end
628
-
629
633
  title = sect.numbered_title formal: true
630
634
  sep = (sect.attr 'separator') || (sect.document.attr 'title-separator') || ''
631
635
  if !sep.empty? && (title.include? (sep = %(#{sep} )))
@@ -650,7 +654,7 @@ module Asciidoctor
650
654
  start_new_chapter sect
651
655
  end
652
656
  end
653
- arrange_heading sect, title, hopts unless hidden || started_new || at_page_top? || sect.empty?
657
+ arrange_heading sect, title, hopts unless hidden || started_new || at_page_top?
654
658
  # QUESTION: should we store pdf-page-start, pdf-anchor & pdf-destination in internal map?
655
659
  sect.set_attr 'pdf-page-start', (start_pgnum = page_number)
656
660
  # QUESTION: should we just assign the section this generated id?
@@ -741,22 +745,16 @@ module Asciidoctor
741
745
  pagenums = consolidate_ranges term.dests.map {|dest| dest[:page] }.uniq
742
746
  end
743
747
  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
748
+ if ::String === pagenum
749
+ term_fragments << ({ text: %(, #{pagenum}) })
752
750
  else
753
- term_fragments << ({ text: ', ' })
751
+ term_fragments << { text: ', ' }
752
+ term_fragments << pagenum
754
753
  end
755
- term_fragments << (::String === pagenum ? { text: pagenum } : pagenum)
756
754
  end
757
755
  end
758
756
  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
757
+ typeset_formatted_text term_fragments, (calc_line_metrics @base_line_height), align: :left, color: @font_color, hanging_indent: subterm_indent * 2, consolidate: true
760
758
  indent subterm_indent do
761
759
  term.subterms.each do |subterm|
762
760
  convert_index_term subterm, pagenum_sequence_style
@@ -782,10 +780,7 @@ module Asciidoctor
782
780
  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
781
  end if node.title?
784
782
  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
783
+ prose_opts = { align: (@theme.abstract_text_align || @base_text_align).to_sym, hyphenate: true, margin_bottom: 0 }
789
784
  # FIXME: allow theme to control more first line options
790
785
  if (line1_font_style = @theme.abstract_first_line_font_style&.to_sym) && line1_font_style != font_style
791
786
  case line1_font_style
@@ -806,14 +801,11 @@ module Asciidoctor
806
801
  prose_opts[:first_line_options] = first_line_options if first_line_options
807
802
  # FIXME: make this cleaner!!
808
803
  if node.blocks?
809
- last_block = node.last_child
810
804
  node.blocks.each do |child|
811
805
  if child.context == :paragraph
812
806
  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)
807
+ convert_paragraph child, prose_opts.dup
815
808
  prose_opts.delete :first_line_options
816
- prose_opts.delete :margin_bottom
817
809
  else
818
810
  # FIXME: this could do strange things if the wrong kind of content shows up
819
811
  child.convert
@@ -823,7 +815,10 @@ module Asciidoctor
823
815
  if (text_align = resolve_text_align_from_role node.roles)
824
816
  prose_opts[:align] = text_align
825
817
  end
826
- ink_prose string, (prose_opts.merge margin_bottom: 0)
818
+ if IndentableTextAlignments[prose_opts[:align]] && (text_indent = @theme.prose_text_indent) > 0
819
+ prose_opts[:indent_paragraphs] = text_indent
820
+ end
821
+ ink_prose string, prose_opts
827
822
  end
828
823
  end
829
824
  end
@@ -832,16 +827,19 @@ module Asciidoctor
832
827
  theme_margin :block, :bottom, (next_enclosed_block node)
833
828
  end
834
829
 
835
- def convert_paragraph node
830
+ def convert_paragraph node, opts = nil
836
831
  add_dest_for_block node if node.id
837
832
 
838
- prose_opts = { margin_bottom: 0, hyphenate: true }
833
+ prose_opts = opts || { margin_bottom: 0, hyphenate: true }
839
834
  if (text_align = resolve_text_align_from_role (roles = node.roles), query_theme: true, remove_predefined: true)
840
835
  prose_opts[:align] = text_align
836
+ else
837
+ text_align = @base_text_align.to_sym
841
838
  end
842
839
  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)
840
+ if IndentableTextAlignments[text_align] &&
841
+ ((text_indent = @theme.prose_text_indent) > 0 ||
842
+ ((text_indent = @theme.prose_text_indent_inner) > 0 && node.previous_sibling&.context == :paragraph))
845
843
  prose_opts[:indent_paragraphs] = text_indent
846
844
  end
847
845
  if (bottom_gutter = @bottom_gutters[-1][node])
@@ -882,8 +880,7 @@ module Asciidoctor
882
880
  if (doc = node.document).attr? 'icons'
883
881
  if !(has_icon = node.attr? 'icon') && (doc.attr 'icons') == 'font'
884
882
  icons = 'font'
885
- label_text = type.to_sym
886
- icon_data = admonition_icon_data label_text
883
+ icon_data = admonition_icon_data type.to_sym
887
884
  icon_size = icon_data[:size] || 24
888
885
  label_width = label_min_width || (icon_size * 1.5)
889
886
  elsif (icon_path = has_icon || !(icon_path = (@theme[%(admonition_icon_#{type})] || {})[:image]) ?
@@ -1046,7 +1043,7 @@ module Asciidoctor
1046
1043
  else
1047
1044
  highlighter = nil
1048
1045
  end
1049
- prev_subs = (subs = node.subs).dup
1046
+ saved_subs = (subs = node.subs).dup
1050
1047
  callouts_enabled = subs.include? :callouts
1051
1048
  highlight_idx = subs.index :highlight
1052
1049
  # NOTE: scratch? here only applies if listing block is nested inside another block
@@ -1056,16 +1053,33 @@ module Asciidoctor
1056
1053
  # switch the :highlight sub back to :specialcharacters
1057
1054
  subs[highlight_idx] = :specialcharacters
1058
1055
  else
1059
- prev_subs = nil
1056
+ saved_subs = nil
1060
1057
  end
1061
1058
  source_string = guard_indentation node.content
1062
1059
  elsif highlight_idx
1063
1060
  # 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
1061
+ if (subs - [:highlight, :callouts]).empty?
1062
+ subs.clear
1063
+ # NOTE: indentation guards will be added by the source highlighter logic
1064
+ source_string = expand_tabs node.content
1065
+ else
1066
+ if callouts_enabled
1067
+ saved_lines = node.lines.dup
1068
+ subs.delete :callouts
1069
+ prev_subs = subs.dup
1070
+ subs.clear
1071
+ source_string, conum_mapping = extract_conums node.content
1072
+ node.lines.replace (source_string.split LF)
1073
+ subs.replace prev_subs
1074
+ callouts_enabled = false
1075
+ end
1076
+ subs[highlight_idx] = :specialcharacters
1077
+ # NOTE: indentation guards will be added by the source highlighter logic
1078
+ source_string = expand_tabs unescape_xml (sanitize node.content, compact: false)
1079
+ node.lines.replace saved_lines if saved_lines
1080
+ end
1067
1081
  else
1068
- highlighter = prev_subs = nil
1082
+ highlighter = saved_subs = nil
1069
1083
  source_string = guard_indentation node.content
1070
1084
  end
1071
1085
  else
@@ -1157,7 +1171,7 @@ module Asciidoctor
1157
1171
  # NOTE: only format if we detect a need (callouts or inline formatting)
1158
1172
  source_chunks = (XMLMarkupRx.match? source_string) ? (text_formatter.format source_string) : [text: source_string]
1159
1173
  end
1160
- node.subs.replace prev_subs if prev_subs
1174
+ node.subs.replace saved_subs if saved_subs
1161
1175
  adjusted_font_size = ((node.option? 'autofit') || (node.document.attr? 'autofit-option')) ? (compute_autofit_font_size source_chunks, :code) : nil
1162
1176
  end
1163
1177
 
@@ -1281,7 +1295,7 @@ module Asciidoctor
1281
1295
  content = guard_indentation node.content
1282
1296
  ink_prose content,
1283
1297
  normalize: false,
1284
- align: :left,
1298
+ align: (resolve_text_align_from_role node.roles) || :left,
1285
1299
  hyphenate: true,
1286
1300
  margin_bottom: 0,
1287
1301
  bottom_gutter: (attribution ? nil : @bottom_gutters[-1][node])
@@ -1682,7 +1696,7 @@ module Asciidoctor
1682
1696
  alignment = float_to.to_sym
1683
1697
  elsif (alignment = node.attr 'align')
1684
1698
  alignment = (BlockAlignmentNames.include? alignment) ? alignment.to_sym : :left
1685
- elsif !(alignment = node.roles.reverse.find {|r| BlockAlignmentNames.include? r }&.to_sym)
1699
+ elsif !(alignment = node.roles.reverse.find {|role| BlockAlignmentNames.include? role }&.to_sym)
1686
1700
  alignment = @theme.image_align&.to_sym || :left
1687
1701
  end
1688
1702
  end
@@ -1916,14 +1930,16 @@ module Asciidoctor
1916
1930
  page_layout = nil
1917
1931
  end
1918
1932
 
1919
- if at_page_top?
1933
+ if at_page_top? && !(node.option? 'always')
1920
1934
  if page_layout && page_layout != page.layout && page.empty?
1921
1935
  delete_current_page
1922
1936
  advance_page layout: page_layout, margin: @page_margin[page_layout][page_side nil, @folio_placement[:inverted]]
1923
1937
  end
1924
1938
  elsif page_layout
1939
+ bounds.current_column = bounds.last_column if ColumnBox === bounds && !(node.has_role? 'column')
1925
1940
  advance_page layout: page_layout, margin: @page_margin[page_layout][page_side nil, @folio_placement[:inverted]]
1926
1941
  else
1942
+ bounds.current_column = bounds.last_column if ColumnBox === bounds && !(node.has_role? 'column')
1927
1943
  advance_page
1928
1944
  end
1929
1945
  end
@@ -2196,7 +2212,7 @@ module Asciidoctor
2196
2212
 
2197
2213
  if node.option? 'autowidth'
2198
2214
  table_width = (node.attr? 'width') ? bounds.width * ((node.attr 'tablepcwidth') / 100.0) :
2199
- (((node.has_role? 'stretch') || (node.has_role? 'spread')) ? bounds.width : nil)
2215
+ (((node.has_role? 'stretch')) ? bounds.width : nil)
2200
2216
  column_widths = []
2201
2217
  else
2202
2218
  table_width = bounds.width * ((node.attr 'tablepcwidth') / 100.0)
@@ -2362,6 +2378,9 @@ module Asciidoctor
2362
2378
  node.content
2363
2379
  elsif node.content_model != :compound && (string = node.content)
2364
2380
  prose_opts = opts.merge hyphenate: true, margin_bottom: 0
2381
+ if (text_align = resolve_text_align_from_role node.roles)
2382
+ prose_opts[:align] = text_align
2383
+ end
2365
2384
  if (bottom_gutter = @bottom_gutters[-1][node])
2366
2385
  prose_opts[:bottom_gutter] = bottom_gutter
2367
2386
  end
@@ -2477,9 +2496,9 @@ module Asciidoctor
2477
2496
  else
2478
2497
  label = index
2479
2498
  end
2480
- %(#{anchor}<sup>[<a anchor="_footnotedef_#{index}">#{label}</a>]</sup>)
2499
+ %(<sup class="wj">#{anchor}[<a anchor="_footnotedef_#{index}">#{label}</a>]</sup>)
2481
2500
  elsif node.type == :xref
2482
- %(<sup><font color="#{theme.role_unresolved_font_color}">[#{node.text}]</font></sup>)
2501
+ %(<sup class="wj"><font color="#{theme.role_unresolved_font_color}">[#{node.text}]</font></sup>)
2483
2502
  else
2484
2503
  log :warn, %(unknown footnote type: #{node.type.inspect})
2485
2504
  nil
@@ -2827,14 +2846,15 @@ module Asciidoctor
2827
2846
  advance_page if orphaned
2828
2847
  else
2829
2848
  theme_font :heading, level: (hlevel = opts[:level]) do
2849
+ if (space_below = ::Numeric === min_height_after ? min_height_after : 0) > 0 && (node.context == :section ? node.blocks? : !node.last_child?)
2850
+ space_below += @theme[%(heading_h#{hlevel}_margin_bottom)] || @theme.heading_margin_bottom
2851
+ else
2852
+ space_below = 0
2853
+ end
2830
2854
  h_padding_t, h_padding_r, h_padding_b, h_padding_l = expand_padding_value @theme[%(heading_h#{hlevel}_padding)]
2831
2855
  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) +
2834
- (@theme[%(heading_h#{hlevel}_margin_top)] || @theme.heading_margin_top) +
2835
- (@theme[%(heading_h#{hlevel}_margin_bottom)] || @theme.heading_margin_bottom) + h_padding_t + h_padding_b
2836
- heading_h += min_height_after if min_height_after && (node.context == :section ? node.blocks? : !node.last_child?)
2837
- cursor >= heading_h
2856
+ cursor >= (height_of_typeset_text title, inline_format: true, text_transform: @text_transform) +
2857
+ h_padding_t + h_padding_b + (@theme[%(heading_h#{hlevel}_margin_top)] || @theme.heading_margin_top) + space_below
2838
2858
  end
2839
2859
  advance_page unless h_fits
2840
2860
  end
@@ -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
@@ -3220,8 +3248,8 @@ module Asciidoctor
3220
3248
  align: @base_text_align.to_sym,
3221
3249
  }.merge(opts)
3222
3250
  end
3223
- if h_category && @theme[%(#{h_category}_border_width)] &&
3224
- (@theme[%(#{h_category}_border_color)] || @theme.base_border_color) && page_number == start_page_number
3251
+ if h_category && @theme[%(#{h_category}_border_width)] && (@theme[%(#{h_category}_border_color)] || @theme.base_border_color)
3252
+ start_cursor = bounds.top unless page_number == start_page_number
3225
3253
  float do
3226
3254
  bounding_box [bounds.left, start_cursor], width: bounds.width, height: start_cursor - cursor do
3227
3255
  theme_fill_and_stroke_bounds h_category
@@ -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
@@ -4,16 +4,19 @@ class Asciidoctor::Section
4
4
  def numbered_title opts = {}
5
5
  @cached_numbered_title ||= nil
6
6
  unless @cached_numbered_title
7
- if @numbered && !@caption && (slevel = @level) <= (@document.attr 'sectnumlevels', 3).to_i
7
+ doc = @document
8
+ if @numbered && !@caption && (slevel = @level) <= (doc.attr 'sectnumlevels', 3).to_i
8
9
  @is_numbered = true
9
- if @document.doctype == 'book'
10
+ if doc.doctype == 'book'
10
11
  case slevel
11
12
  when 0
12
13
  @cached_numbered_title = %(#{sectnum nil, ':'} #{title})
13
- @cached_formal_numbered_title = %(#{@document.attr 'part-signifier', 'Part'} #{@cached_numbered_title}).lstrip
14
+ signifier = doc.attributes['part-signifier'] || ((doc.attr_unspecified? 'part-signifier') ? 'Part' : '')
15
+ @cached_formal_numbered_title = %(#{signifier}#{signifier.empty? ? '' : ' '}#{@cached_numbered_title})
14
16
  when 1
15
17
  @cached_numbered_title = %(#{sectnum} #{title})
16
- @cached_formal_numbered_title = %(#{@document.attr 'chapter-signifier', 'Chapter'} #{@cached_numbered_title}).lstrip
18
+ signifier = doc.attributes['chapter-signifier'] || ((doc.attr_unspecified? 'chapter-signifier') ? 'Chapter' : '')
19
+ @cached_formal_numbered_title = %(#{signifier}#{signifier.empty? ? '' : ' '}#{@cached_numbered_title})
17
20
  else
18
21
  @cached_formal_numbered_title = @cached_numbered_title = %(#{sectnum} #{title})
19
22
  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
@@ -500,22 +500,13 @@ module Asciidoctor
500
500
  end
501
501
  if first_line_text_transform
502
502
  # NOTE: applying text transform here could alter the wrapping, so isolate first line and shrink it to fit
503
- first_line_text = (box.instance_variable_get :@printed_lines)[0]
504
- unless first_line_text == fragments[0][:text]
505
- original_fragments, fragments = fragments, []
506
- original_fragments.reduce '' do |traced, fragment|
507
- fragments << fragment
508
- # NOTE: we could just do a length comparison here
509
- if (traced += fragment[:text]).start_with? first_line_text
510
- fragment[:text] = fragment[:text][0...-(traced.length - first_line_text.length)]
511
- break
512
- end
513
- traced
514
- end
515
- end
516
- fragments.each {|fragment| fragment[:text] = transform_text fragment[:text], first_line_text_transform }
503
+ first_line_fragments = (box.instance_variable_get :@arranger).consumed
504
+ fragments = first_line_fragments.map {|fragment| fragment.merge text: (transform_text fragment[:text], first_line_text_transform) }
517
505
  first_line_options[:overflow] = :shrink_to_fit
518
- @final_gap = first_line_options[:force_justify] = true if remaining_fragments
506
+ if remaining_fragments
507
+ @final_gap = true
508
+ first_line_options[:force_justify] = true if first_line_options[:align] == :justify && first_line_fragments[-1][:text] != ?\n
509
+ end
519
510
  end
520
511
  if text_indent
521
512
  indent(text_indent) { fill_formatted_text_box fragments, first_line_options }
@@ -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'
@@ -176,8 +176,10 @@ module Asciidoctor
176
176
  end
177
177
  if (text_transform = fragment.delete :text_transform)
178
178
  text = (text_chunks = extract_text pcdata).join
179
- text_io = StringIO.new transform_text text, text_transform
180
- restore_text pcdata, text_chunks.each_with_object([]) {|chunk, accum| accum << (text_io.read chunk.length) }
179
+ chars = (StringIO.new transform_text text, text_transform).each_char
180
+ restore_text pcdata, (text_chunks.each_with_object [] do |chunk, accum|
181
+ accum << chunk.length.times.map { chars.next }.join
182
+ end)
181
183
  end
182
184
  # NOTE: decorate child fragments with inherited properties from this element
183
185
  apply pcdata, fragments, fragment
@@ -315,10 +317,12 @@ module Asciidoctor
315
317
  end) : value
316
318
  elsif (value = attrs[:id])
317
319
  # NOTE: text is null character, which is used as placeholder text so Prawn doesn't drop fragment
318
- fragment = { name: value, callback: [InlineDestinationMarker] }
320
+ new_fragment = { name: value, callback: [InlineDestinationMarker] }
321
+ new_fragment[:wj] = true if fragment[:wj]
319
322
  if (type = attrs[:type])
320
- fragment[:type] = type.to_sym
323
+ new_fragment[:type] = type.to_sym
321
324
  end
325
+ fragment = new_fragment
322
326
  visible = nil
323
327
  end
324
328
  end
@@ -357,6 +361,7 @@ module Asciidoctor
357
361
  end
358
362
  # TODO: we could limit to select tags, but doesn't seem to really affect performance
359
363
  attrs[:class].split.each do |class_name|
364
+ fragment[:wj] = true if class_name == 'wj'
360
365
  next unless @theme_settings.key? class_name
361
366
  update_fragment fragment, @theme_settings[class_name]
362
367
  # NOTE: defer assignment of callback since we must look at combined styles of element and role
@@ -39,6 +39,7 @@ module Asciidoctor
39
39
 
40
40
  attr_reader :quality
41
41
  attr_reader :compatibility_level
42
+ attr_reader :compliance
42
43
 
43
44
  def initialize quality = 'default', compatibility_level = '1.4', compliance = 'PDF'
44
45
  @quality = QUALITY_NAMES[quality]
@@ -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
@@ -83,6 +83,7 @@ module Asciidoctor
83
83
  theme_data.base_text_align ||= 'left'
84
84
  theme_data.base_line_height ||= 1
85
85
  theme_data.base_font_color ||= '000000'
86
+ theme_data.base_font_size ||= 12
86
87
  theme_data.code_font_family ||= (theme_data.codespan_font_family || 'Courier')
87
88
  theme_data.conum_font_family ||= (theme_data.codespan_font_family || 'Courier')
88
89
  if (heading_font_family = theme_data.heading_font_family)
@@ -160,7 +161,7 @@ module Asciidoctor
160
161
  data[key] = {}.tap do |accum|
161
162
  val.each do |key2, val2|
162
163
  key2 = key2.tr '-', '_' if key2.include? '-'
163
- accum[key2.to_sym] = (key2.end_with? '_color') ? (to_color evaluate val2, data) : (evaluate val2, data)
164
+ accum[key2.to_sym] = (key2.end_with? '_color') ? (to_color evaluate val2, data, math: false) : (evaluate val2, data)
164
165
  end
165
166
  end if val
166
167
  elsif ::Hash === val
@@ -173,20 +174,18 @@ module Asciidoctor
173
174
  end
174
175
  elsif (rekey = DeprecatedKeys[key]) ||
175
176
  ((key.start_with? 'role_') && (key.end_with? '_align') && (rekey = key.sub RoleAlignKeyRx, '_text_align'))
176
- data[rekey] = evaluate val, data
177
+ data[rekey] = evaluate val, data, math: false
177
178
  elsif PaddingBottomHackKeys.include? key
178
179
  val = evaluate val, data
179
180
  # normalize padding hacks for themes designed before the converter had smart margins
180
181
  val[2] = val[0] if ::Array === val && val[0].to_f >= 0 && val[2].to_f <= 0
181
182
  data[key] = val
182
- # QUESTION: do we really need to evaluate_math in this case?
183
183
  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 }
184
+ # assume table_grid_color is a single color unless the value is a 2-element array for backwards compatibility
185
+ if key == 'table_border_color' ? ::Array === val : (key == 'table_grid_color' && ::Array === val && val.size == 2)
186
+ data[key] = val.map {|it| to_color evaluate it, data, math: false }
188
187
  else
189
- data[key] = to_color evaluate val, data
188
+ data[key] = to_color evaluate val, data, math: false
190
189
  end
191
190
  elsif key.end_with? '_content'
192
191
  data[key] = (expand_vars val.to_s, data).to_s
@@ -196,12 +195,12 @@ module Asciidoctor
196
195
  data
197
196
  end
198
197
 
199
- def evaluate expr, vars
198
+ def evaluate expr, vars, math: true
200
199
  case expr
201
200
  when ::String
202
- evaluate_math expand_vars expr, vars
201
+ math ? (evaluate_math expand_vars expr, vars) : (expand_vars expr, vars)
203
202
  when ::Array
204
- expr.map {|e| evaluate e, vars }
203
+ expr.map {|e| evaluate e, vars, math: math }
205
204
  else
206
205
  expr
207
206
  end
@@ -233,7 +232,6 @@ module Asciidoctor
233
232
  def evaluate_math expr
234
233
  return expr if !(::String === expr) || ColorValue === expr
235
234
  # resolve measurement values (e.g., 0.5in => 36)
236
- # QUESTION: should we round the value? perhaps leave that to the precision functions
237
235
  # NOTE: leave % as a string; handled by converter for now
238
236
  original, expr = expr, (resolve_measurement_values expr)
239
237
  while true
@@ -257,8 +255,7 @@ module Asciidoctor
257
255
  end
258
256
  end
259
257
  if (expr.end_with? ')') && expr =~ PrecisionFuncRx
260
- op = $1
261
- offset = op.length + 1
258
+ offset = (op = $1).length + 1
262
259
  expr = expr[offset...-1].to_f.send op.to_sym
263
260
  end
264
261
  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.1'
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.1
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-09-17 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: asciidoctor
@@ -193,8 +193,8 @@ dependencies:
193
193
  - - "~>"
194
194
  - !ruby/object:Gem::Version
195
195
  version: 1.4.0
196
- description: An extension for Asciidoctor that converts AsciiDoc documents to PDF
197
- using the Prawn PDF library.
196
+ description: An add-on converter for Asciidoctor that converts AsciiDoc documents
197
+ to PDF using the Prawn PDF generation library.
198
198
  email: dan@opendevise.com
199
199
  executables:
200
200
  - asciidoctor-pdf
@@ -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