asciidoctor-pdf 2.0.0.alpha.3 → 2.0.0.beta.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: 7688a2448dace024b0e6057e878d556ec99ee1ac97bf4c91803d59346a136741
4
- data.tar.gz: 13807213320754c03cce6304300745ec31a349a8e95441e1495ddc6e6401a99a
3
+ metadata.gz: f3c96a0b756ccd22c3aec10c571e5ef0280a0003398f48a86287037c52d13a85
4
+ data.tar.gz: c611aec1ecafd132c9415babfc193929bbe52879b6282cc752d6def346ce9d56
5
5
  SHA512:
6
- metadata.gz: f3207de1db655ec85350df8b37530306e1ac13d1c1108b091886a8b45fcd5ce42fdc417fc759ef40ba77945925518e76e31341d12ec951ee648772c5b81364da
7
- data.tar.gz: ab345b122e4137e33ed8ab0956270ce067f20b8b8b4f6aba3737f54f002b60b0653d5e9fbc356778d3787f9601b2f4ed9a6e0a5694bab5c9f376365297ae0626
6
+ metadata.gz: 517791afccee6a976f59244b08ffdd062f09d2153d04917a61b969ef525752eee70d8118586932757e3f6fd175880f899a60d768cfc4b8e8ef6cabdad5f5f239
7
+ data.tar.gz: 3ae8abd3251302def2b2305a4c9aebe940235735d0c0204891c909e383ac8622eb21b158d3d844a49b60dab5f1c5d82e8252961c1f2247b6158a7975d9d7c50b
data/CHANGELOG.adoc CHANGED
@@ -5,6 +5,33 @@
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 {uri-repo}/commits/main[commit history] on GitHub.
7
7
 
8
+ == 2.0.0.beta.1 (2022-05-04) - @mojavelinux
9
+
10
+ Enhancements::
11
+
12
+ * introduce `index-pagenum-sequence-style` document attribute to control style of sequential page numbers in index when media=screen (#1656)
13
+ * allow entry for document in outline to be controlled using `outline-title` attribute (#1789)
14
+ * allow extended converter to insert or filter toc entries by overriding `get_entries_for_toc` method (#2097)
15
+ * add `asciidoctor/pdf/nopngmagick` script to unregister Gmagick handler for PNG images only (#1687)
16
+ * allow theme to configure which end the caption is placed for a block image (#2115)
17
+ * add `Page#imported` method to mark page as imported (which suppresses running contennt)
18
+ * add support for `smallcaps` text transform by replacing lowercase letters with small capital variants (#1192)
19
+ * use `base-border-color` as default border color; control appearance of border using `border-width` value alone (#2134)
20
+ * remove border colors in base theme so all border colors can be controlled using `base-border-color` when extending theme
21
+ * enable running footer when using base theme
22
+
23
+ Bug Fixes::
24
+
25
+ * allow border width of block image to be specified as an array (1, 2, or 4 values) (#2119)
26
+ * rename `delete_page` extension method to `delete_current_page` to avoid conflict with incompatible method on `Prawn::Document`
27
+ * remap `table-caption-side` theme key to `table-caption-end` (#2125)
28
+ * add missing glyph for `ÿ` in built-in fonts
29
+ * remove use of deprecated keys in chronicles-dark-theme.yml
30
+
31
+ === Details
32
+
33
+ {url-repo}/releases/tag/v2.0.0.beta.1[git tag] | {url-repo}/compare/v2.0.0.alpha.3\...v2.0.0.beta.1[full diff]
34
+
8
35
  == 2.0.0.alpha.3 (2022-05-01) - @mojavelinux
9
36
 
10
37
  Enhancements::
@@ -44,7 +71,7 @@ Enhancements::
44
71
  * pass the `part` and `chapterlike` options to the `arrange_section` method for convenience
45
72
  * add support for `background-color` property on caption (#1995)
46
73
  * add support for image-based icons, resolved from `iconsdir` and having the `icontype` file extension (#1770)
47
- * add `asciidoctor/pdf/nogmagick` script to prevent Gmagick from handling PNG images (#1687)
74
+ * add `asciidoctor/pdf/nogmagick` script to prevent loading prawn-gmagick gem (#1687)
48
75
  * change name of `untitled` option on special section to `notitle`
49
76
  * allow the title of any section to be hidden using the `notitle` option
50
77
  * allow imported PDF page to be referenced in TOC by enclosing in parent section with `notitle` option (#1213)
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.0.0.alpha.3, 2022-05-01
3
+ v2.0.0.beta.1, 2022-05-04
4
4
  // Settings:
5
5
  :experimental:
6
6
  :idprefix:
@@ -24,7 +24,7 @@ endif::[]
24
24
  :project-handle: asciidoctor-pdf
25
25
  // Variables:
26
26
  :release-line: 2.0.x
27
- :release-version: 2.0.0.alpha.3
27
+ :release-version: 2.0.0.beta.1
28
28
  // URIs:
29
29
  :url-asciidoctor: http://asciidoctor.org
30
30
  :url-gem: http://rubygems.org/gems/asciidoctor-pdf
@@ -713,23 +713,41 @@ If you're using fonts in your SVG, and you want those fonts to be preserved, tho
713
713
 
714
714
  == Supporting Additional Image File Formats
715
715
 
716
- In order to embed an image into a PDF, Asciidoctor PDF must understand how to read it.
716
+ In order to embed an image into a PDF, Asciidoctor PDF must understand how to decode it.
717
717
  To perform this work, Asciidoctor delegates to the underlying libraries.
718
- {url-prawn}[Prawn] provides support for reading JPG and PNG images.
719
- {url-prawn-svg}[prawn-svg] brings support for SVG images.
720
- Without any additional libraries, those are the only supported image file formats.
718
+ {url-prawn}[Prawn] provides support for decoding JPG and PNG images.
719
+ {url-prawn-svg}[prawn-svg] brings support for translating SVG images to PDF commands.
720
+ Without any additional libraries, those are the only image file formats supported by Asciidoctor PDF.
721
721
 
722
- If you need support for additional image formats, such as GIF, TIFF, or interlaced PNG--and you don't want to convert those images to a supported format--you must install the {url-prawn-gmagick}[prawn-gmagick] (>= 0.0.9) Ruby gem.
723
- prawn-gmagick is an extension for Prawn based on {url-graphicsmagick}[GraphicsMagick] that adds support for all the image formats recognized by that library.
724
- prawn-gmagick has the added benefit of significantly reducing the time it takes to generate a PDF containing a lot of images.
722
+ If you need support for additional image formats, such as GIF, TIFF, WebP, or interlaced PNG--and you don't want to convert those images to a supported format like JPG--you must install the {url-prawn-gmagick}[prawn-gmagick] (>= 0.0.9) Ruby gem.
723
+ prawn-gmagick is an extension for Prawn that delegates image decoding to {url-graphicsmagick}[GraphicsMagick] to add support for all image formats recognized by that library.
724
+
725
+ prawn-gmagick has the additional benefit of *significantly* reducing the processing time, power, and memory necessary to generate a PDF that contains a lot of PNG images.
726
+ For large books (such as Pro Git), you might see the conversion time drop by as much as half.
727
+ Decoding PNG images requires a lot of mathematical computation, a task Ruby is not particularly efficient at performing.
728
+ That's why adding the prawn-gmagick gem to the converter makes such a substantial difference.
729
+
730
+ As an alternative to using prawn-gmagick, you could optimize the images you pass into Asciidoctor PDF, either by scaling them down or converting them to an uncompressed format like JPG.
725
731
 
726
732
  The prawn-gmagick gem uses native extensions to compile against GraphicsMagick.
727
- This system prerequisite limits installation to Linux and OSX.
733
+ This system prerequisite limits installation to C Ruby running on Linux and macOS.
728
734
  Please refer to the {url-prawn-gmagick}[README for prawn-gmagick] to learn how to install it.
729
735
 
730
- Once this gem is installed, Asciidoctor automatically loads it, then delegates to it to handle all image embedding.
731
- In addition to support for additional image file formats, this gem also speeds up image processing considerably.
732
- We highly recommend using this gem if you're able to install it.
736
+ $ gem install prawn-gmagick
737
+
738
+ When this gem is installed, Asciidoctor automatically detects and loads it, then delegates all image decoding to GraphicsMagick by way of the bridge it provides.
739
+ We highly recommend using this gem with Asciidoctor PDF if you're able to install it.
740
+
741
+ The one downside of delegating to GraphicsMagick is that it can mangle certain PNG images.
742
+ If this happens, you can instruct Asciidoctor PDF to not delegate to GraphicsMagick to load PNG images by requiring `asciidoctor/pdf/nopngmagick` when calling Asciidoctor PDF, as follows:
743
+
744
+ $ asciidoctor-pdf -r asciidoctor/pdf/nopngmagick doc.adoc
745
+
746
+ You can also tell Asciidoctor PDF not to use prawn-gmagick at all by requiring `asciidoctor/pdf/nogmagick` when calling Asciidoctor PDF, as follows:
747
+
748
+ $ asciidoctor-pdf -r asciidoctor/pdf/nogmagick doc.adoc
749
+
750
+ Granted, bypassing prawn-gmagick means you no longer get support for additional image formats that Prawn cannot handle or the PNG acceleration.
733
751
 
734
752
  == Importing PDF Pages
735
753
 
@@ -17,6 +17,7 @@ The following changes were made using fontforge to produce mplus1p-regular-fallb
17
17
  ** General Punctuation (U+2000–U203a)
18
18
  ** Geometric Shapes (U+25a0–U25ff)
19
19
  ** Assorted Symbols (U+20ac, U+2122, U+21d0–U+21d5, U+2190–U+2195, U+2610–U+2611, U+2713–U+2714)
20
+ ** Latin Small Capital Letters (U+1D00, U+0299, U+1D04, U+1D05, U+1D07, U+A730, U+0262, U+029C, U+026A, U+1D0A, U+1D0B, U+029F, U+1D0D, U+0274, U+1D0F, U+1D18, U+A7AF (missing), U+0280, U+A731, U+1D1B, U+1D1C, U+1D20, U+1D21, U+028F, U+1D22)
20
21
  ** .notdef glyph
21
22
  * Added BOM (U+feff), hair space (U+200a), zero-width space (U+200b) and line feed (U+000a) characters (from blank)
22
23
  * Manually added non-breaking hyphen (U+2011) from hyphen (U+002d)
@@ -17,6 +17,7 @@ The following changes were made using fontforge to produce the notosans-*-subset
17
17
  ** General Punctuation (U+2000–U203a)
18
18
  ** Geometric Shapes (U+25a0–U25ff)
19
19
  ** Assorted Symbols (U+20ac, U+2122, U+21d0, U+21d2, U+2190, U+2192)
20
+ ** Latin Small Capital Letters (U+1D00, U+0299, U+1D04, U+1D05, U+1D07, U+A730 (missing), U+0262, U+029C, U+026A, U+1D0A, U+1D0B, U+029F, U+1D0D, U+0274, U+1D0F, U+1D18, U+A7AF (missing), U+0280, U+A731 (missing), U+1D1B, U+1D1C, U+1D20, U+1D21, U+028F, U+1D22, U+01EB)
20
21
  ** .notdef glyph
21
22
  * Imported ballot boxes from Font Awesome (U+2610, U+2611) (Noto Serif Regular only)
22
23
  * Added line feed character (U+000a)
@@ -17,6 +17,7 @@ The following changes were made using fontforge to produce the notoserif-*-subse
17
17
  ** General Punctuation (U+2000–U203a)
18
18
  ** Geometric Shapes (U+25a0–U25ff)
19
19
  ** Assorted Symbols (U+20ac, U+2122, U+21d0, U+21d2, U+2190, U+2192)
20
+ ** Latin Small Capital Letters (U+1D00, U+0299, U+1D04, U+1D05, U+1D07, U+A730 (missing), U+0262, U+029C, U+026A, U+1D0A, U+1D0B, U+029F, U+1D0D, U+0274, U+1D0F, U+1D18, U+A7AF (missing), U+0280, U+A731 (missing), U+1D1B, U+1D1C, U+1D20, U+1D21, U+028F, U+1D22, U+01EB)
20
21
  ** .notdef glyph
21
22
  * Imported ballot boxes from Font Awesome (U+2610, U+2611) (Noto Serif Regular only)
22
23
  * Added line feed character (U+000a)
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
@@ -1,24 +1,19 @@
1
- # NOTE file is read "as is"; variables are not parsed; key layout must be flat
1
+ # NOTE: file is read "as is"; variables are not parsed; key layout must be flat
2
2
  page_background_color: 'FFFFFF'
3
3
  page_layout: portrait
4
4
  page_initial_zoom: FitH
5
- # 36 is equivalent to 0.5in
6
- page_margin: 36
5
+ page_margin: 36 # 36 is 0.5in
7
6
  page_margin_inner: 48
8
7
  page_margin_outer: 24
9
8
  page_size: A4
10
9
  base_text_align: left
11
- #base_font_color: '333333'
12
10
  base_font_color: '000000'
13
- #base_font_family: Times-Roman
14
11
  base_font_family: Helvetica
15
12
  base_font_size: 12
16
- # QUESTION should we rename to min_font_size?
17
13
  base_font_size_min: 6
18
14
  base_font_style: normal
19
15
  base_line_height: 1.15
20
- base_border_color: 'EEEEEE'
21
- base_border_width: 0.5
16
+ base_border_color: '000000'
22
17
  role_lead_font_size: 13.5
23
18
  role_line-through_text_decoration: line-through
24
19
  role_underline_text_decoration: underline
@@ -47,7 +42,6 @@ heading_margin_top: 4
47
42
  heading_margin_bottom: 12
48
43
  heading_min_height_after: 20
49
44
  title_page_text_align: center
50
- title_page_line_height: 1.15
51
45
  title_page_logo_top: 10%
52
46
  title_page_title_top: 40%
53
47
  title_page_title_font_size: 18
@@ -69,43 +63,37 @@ abstract_line_height: 1.4
69
63
  abstract_padding: 0
70
64
  abstract_title_align: center
71
65
  abstract_title_font_style: bold
72
- admonition_column_rule_color: 'EEEEEE'
73
66
  admonition_column_rule_width: 0.5
74
67
  admonition_padding: [4, 12, 4, 12]
75
68
  admonition_label_font_style: bold
76
69
  admonition_label_text_transform: uppercase
77
- quote_border_color: 'EEEEEE'
78
70
  quote_border_left_width: 4
79
71
  quote_padding: [3, 12, 3, 14]
80
- verse_border_color: 'EEEEEE'
81
72
  verse_border_left_width: 4
82
73
  verse_padding: [3, 12, 3, 14]
83
74
  code_font_family: Courier
84
75
  code_font_size: 10.8
85
76
  code_line_height: 1.2
86
77
  code_padding: 9
87
- code_border_color: 'EEEEEE'
88
78
  code_border_width: 0.5
89
79
  conum_line_height: 1.15
90
80
  conum_glyphs: circled
91
81
  example_background_color: 'FFFFFF'
92
- example_border_color: 'EEEEEE'
93
82
  example_border_width: 0.5
94
83
  example_padding: 12
95
84
  image_align: left
96
85
  prose_margin_bottom: 12
97
86
  sidebar_background_color: 'EEEEEE'
87
+ sidebar_border_width: 0.5
98
88
  sidebar_padding: 12
99
89
  sidebar_title_align: center
100
90
  sidebar_title_font_style: bold
101
- table_border_color: '000000'
102
91
  table_border_style: solid
103
92
  table_border_width: 0.5
104
93
  table_cell_padding: 2
105
94
  table_head_font_style: bold
106
95
  table_head_border_bottom_width: 1.25
107
96
  table_body_stripe_background_color: 'EEEEEE'
108
- thematic_break_border_color: 'EEEEEE'
109
97
  thematic_break_border_style: solid
110
98
  thematic_break_border_width: 0.5
111
99
  thematic_break_margin_bottom: 12
@@ -113,5 +101,8 @@ toc_indent: 15
113
101
  toc_line_height: 1.4
114
102
  footnotes_font_size: 9
115
103
  footnotes_item_spacing: 3
104
+ footer_border_width: 0.5
105
+ footer_font_size: 10
106
+ footer_height: 30
116
107
  footer_recto_right_content: '{page-number}'
117
108
  footer_verso_left_content: '{page-number}'
@@ -4223,11 +4223,12 @@ The keys in this category control the arrangement and style of tables and table
4223
4223
  caption:
4224
4224
  text-align: left
4225
4225
 
4226
- |caption-side
4226
+ |caption-end
4227
4227
  |top {vbar} bottom +
4228
4228
  (default: top)
4229
4229
  |table:
4230
- caption-side: bottom
4230
+ caption:
4231
+ end: bottom
4231
4232
 
4232
4233
  |caption-max-width
4233
4234
  |fit-content {vbar} fit-content(percentage) {vbar} none {vbar} <<measurement-units,Measurement>> +
@@ -5921,7 +5922,7 @@ def ink_part_title node, title, opts = {}
5921
5922
  fill_absolute_bounds 'E64C3D'
5922
5923
  move_down 20
5923
5924
  typeset_text title, (calc_line_metrics 1.5), color: 'FFFFFF', inline_format: true, align: :center, size: 42
5924
- page.instance_variable_set :@imported_page, true
5925
+ page.imported
5925
5926
  end
5926
5927
  ----
5927
5928
 
@@ -139,6 +139,7 @@ module Asciidoctor
139
139
  # NOTE: enabling data-uri forces Asciidoctor Diagram to produce absolute image paths
140
140
  doc.attributes['data-uri'] = (doc.instance_variable_get :@attribute_overrides)['data-uri'] = ''
141
141
  end
142
+ @label = :primary
142
143
  @initial_instance_variables = [:@initial_instance_variables] + instance_variables
143
144
  end
144
145
 
@@ -181,8 +182,9 @@ module Asciidoctor
181
182
  def convert_document doc
182
183
  doc.promote_preface_block
183
184
  init_pdf doc
184
- # set default value for outline, pagenums, and show-link-uri if not otherwise set
185
+ # set default value for outline, outline-title, and pagenums attributes if not otherwise set
185
186
  doc.attributes['outline'] = '' unless (doc.attribute_locked? 'outline') || ((doc.instance_variable_get :@attributes_modified).include? 'outline')
187
+ doc.attributes['outline-title'] = '' unless (doc.attribute_locked? 'outline-title') || ((doc.instance_variable_get :@attributes_modified).include? 'outline-title')
186
188
  doc.attributes['pagenums'] = '' unless (doc.attribute_locked? 'pagenums') || ((doc.instance_variable_get :@attributes_modified).include? 'pagenums')
187
189
  #assign_missing_section_ids doc
188
190
 
@@ -216,7 +218,7 @@ module Asciidoctor
216
218
  if (insert_toc = (doc.attr? 'toc') && !((toc_placement = doc.attr 'toc-placement') == 'macro' || toc_placement == 'preamble') && doc.sections?)
217
219
  start_new_page if @ppbook && verso_page?
218
220
  add_dest_for_block doc, id: 'toc', y: (at_page_top? ? page_height : nil)
219
- allocate_toc doc, toc_num_levels, cursor, title_page_on
221
+ @toc_extent = allocate_toc doc, toc_num_levels, cursor, title_page_on
220
222
  else
221
223
  @toc_extent = nil
222
224
  end
@@ -303,19 +305,19 @@ module Asciidoctor
303
305
  # NOTE: for a book, these are leftover footnotes; for an article this is everything
304
306
  outdent_section { ink_footnotes doc }
305
307
 
306
- if @toc_extent
308
+ if (toc_extent = @toc_extent)
307
309
  if title_page_on && !insert_toc
308
- num_front_matter_pages[0] = @toc_extent.to.page if @theme.running_content_start_at == 'after-toc'
309
- num_front_matter_pages[1] = @toc_extent.to.page if @theme.page_numbering_start_at == 'after-toc'
310
+ num_front_matter_pages[0] = toc_extent.to.page if @theme.running_content_start_at == 'after-toc'
311
+ num_front_matter_pages[1] = toc_extent.to.page if @theme.page_numbering_start_at == 'after-toc'
310
312
  end
311
- toc_page_nums = ink_toc doc, toc_num_levels, @toc_extent.from.page, @toc_extent.from.cursor, num_front_matter_pages[1]
313
+ toc_page_nums = ink_toc doc, toc_num_levels, toc_extent.from.page, toc_extent.from.cursor, num_front_matter_pages[1]
312
314
  else
313
315
  toc_page_nums = []
314
316
  end
315
317
 
316
318
  # NOTE: delete orphaned page (a page was created but there was no additional content)
317
319
  # QUESTION: should we delete page if document is empty? (leaving no pages?)
318
- delete_page if page_count > 1 && page.empty?
320
+ delete_current_page if page_count > 1 && page.empty?
319
321
  end
320
322
 
321
323
  unless page_count < body_start_page_number
@@ -461,8 +463,6 @@ module Asciidoctor
461
463
  end
462
464
 
463
465
  def prepare_theme theme
464
- theme.base_border_color ||= '000000'
465
- theme.base_border_width ||= 0
466
466
  theme.base_font_color ||= '000000'
467
467
  theme.base_font_size ||= 12
468
468
  theme.base_font_style = theme.base_font_style&.to_sym || :normal
@@ -479,7 +479,9 @@ module Asciidoctor
479
479
  theme.list_item_spacing ||= 0
480
480
  theme.description_list_term_spacing ||= 0
481
481
  theme.description_list_description_indent ||= 0
482
+ theme.table_border_color ||= (theme.base_border_color || '000000')
482
483
  theme.table_border_width ||= 0.5
484
+ theme.thematic_break_border_color ||= (theme.base_border_color || '000000')
483
485
  theme.image_border_width ||= 0
484
486
  theme.code_linenum_font_color ||= '999999'
485
487
  theme.callout_list_margin_top_after_code ||= 0
@@ -902,8 +904,8 @@ module Asciidoctor
902
904
  pad_box [0, cpad[1], 0, lpad[3]] do
903
905
  if extent
904
906
  label_height = extent.single_page_height || cursor
905
- if (rule_color = @theme.admonition_column_rule_color) &&
906
- (rule_width = @theme.admonition_column_rule_width || @theme.base_border_width) > 0
907
+ if (rule_width = @theme.admonition_column_rule_width || 0) > 0 &&
908
+ (rule_color = @theme.admonition_column_rule_color || @theme.base_border_color)
907
909
  rule_style = @theme.admonition_column_rule_style&.to_sym || :solid
908
910
  float do
909
911
  extent.each_page do |first_page, last_page|
@@ -1057,7 +1059,7 @@ module Asciidoctor
1057
1059
  category = node.context == :quote ? :quote : :verse
1058
1060
  # NOTE: b_width and b_left_width are mutually exclusive
1059
1061
  if (b_left_width = @theme[%(#{category}_border_left_width)]) && b_left_width > 0
1060
- b_color = @theme[%(#{category}_border_color)]
1062
+ b_color = @theme[%(#{category}_border_color)] || @theme.base_border_color
1061
1063
  else
1062
1064
  b_left_width = nil
1063
1065
  b_width = nil if (b_width = @theme[%(#{category}_border_width)]) == 0
@@ -1559,8 +1561,10 @@ module Asciidoctor
1559
1561
  # TODO: add `to_pt page_width` method to ViewportWidth type
1560
1562
  width = (width.to_f / 100) * page_width if ViewportWidth === width
1561
1563
 
1564
+ caption_end = @theme.image_caption_end&.to_sym || :bottom
1565
+ caption_max_width = @theme.image_caption_max_width
1562
1566
  # NOTE: if width is not set explicitly and max-width is fit-content, caption height may not be accurate
1563
- caption_h = node.title? ? (ink_caption node, category: :image, side: :bottom, block_align: alignment, block_width: width, max_width: @theme.image_caption_max_width, dry_run: true, force_top_margin: true) : 0
1567
+ caption_h = node.title? ? (ink_caption node, category: :image, end: caption_end, block_align: alignment, block_width: width, max_width: caption_max_width, dry_run: true, force_top_margin: caption_end == :bottom) : 0
1564
1568
 
1565
1569
  align_to_page = node.option? 'align-to-page'
1566
1570
  pinned = opts[:pinned]
@@ -1596,14 +1600,14 @@ module Asciidoctor
1596
1600
  end
1597
1601
  rendered_w = (svg_obj.resize height: (rendered_h = available_h)).output_width if rendered_h > available_h
1598
1602
  end
1599
- image_y = y
1600
- image_cursor = cursor
1601
1603
  add_dest_for_block node if node.id
1602
1604
  # NOTE: workaround to fix Prawn not adding fill and stroke commands on page that only has an image;
1603
1605
  # breakage occurs when running content (stamps) are added to page
1604
1606
  update_colors if graphic_state.color_space.empty?
1605
- # NOTE: cursor advances automatically
1606
- svg_obj.draw
1607
+ ink_caption node, category: :image, end: :top, block_align: alignment, block_width: rendered_w, max_width: caption_max_width if caption_end == :top && node.title?
1608
+ image_y = y
1609
+ image_cursor = cursor
1610
+ svg_obj.draw # NOTE: cursor advances automatically
1607
1611
  svg_obj.document.warnings.each do |img_warning|
1608
1612
  log :warn, %(problem encountered in image: #{image_path}; #{img_warning})
1609
1613
  end unless scratch?
@@ -1627,12 +1631,13 @@ module Asciidoctor
1627
1631
  end
1628
1632
  rendered_w, rendered_h = image_info.calc_image_dimensions height: available_h if rendered_h > available_h
1629
1633
  end
1630
- image_y = y
1631
- image_cursor = cursor
1632
1634
  add_dest_for_block node if node.id
1633
1635
  # NOTE: workaround to fix Prawn not adding fill and stroke commands on page that only has an image;
1634
1636
  # breakage occurs when running content (stamps) are added to page
1635
1637
  update_colors if graphic_state.color_space.empty?
1638
+ ink_caption node, category: :image, end: :top, block_align: alignment, block_width: rendered_w, max_width: caption_max_width if caption_end == :top && node.title?
1639
+ image_y = y
1640
+ image_cursor = cursor
1636
1641
  # NOTE: specify both width and height to avoid recalculation
1637
1642
  embed_image image_obj, image_info, width: rendered_w, height: rendered_h, position: alignment
1638
1643
  draw_image_border image_cursor, rendered_w, rendered_h, alignment unless node.role? && (node.has_role? 'noborder')
@@ -1643,7 +1648,7 @@ module Asciidoctor
1643
1648
  move_down rendered_h if y == image_y
1644
1649
  end
1645
1650
  end
1646
- ink_caption node, category: :image, side: :bottom, block_align: alignment, block_width: rendered_w, max_width: @theme.image_caption_max_width if node.title?
1651
+ ink_caption node, category: :image, end: :bottom, block_align: alignment, block_width: rendered_w, max_width: caption_max_width if caption_end == :bottom && node.title?
1647
1652
  theme_margin :block, :bottom, (next_enclosed_block node) unless pinned
1648
1653
  rescue => e
1649
1654
  raise if ::StopIteration === e
@@ -1652,7 +1657,7 @@ module Asciidoctor
1652
1657
  end
1653
1658
 
1654
1659
  def draw_image_border top, w, h, alignment
1655
- if @theme.image_border_width > 0 && @theme.image_border_color
1660
+ if (Array @theme.image_border_width).any? {|it| it&.> 0 } && (@theme.image_border_color || @theme.base_border_color)
1656
1661
  if (@theme.image_border_fit || 'content') == 'auto'
1657
1662
  bb_width = bounds.width
1658
1663
  elsif alignment == :center
@@ -1685,7 +1690,7 @@ module Asciidoctor
1685
1690
  (resolve_alignment_from_role node.roles) || (@theme.image_align&.to_sym || :left)
1686
1691
  ink_prose alt_text_template % alt_text_vars, align: alignment, margin: 0, normalize: false, single_line: true
1687
1692
  end
1688
- ink_caption node, category: :image, side: :bottom if node.title?
1693
+ ink_caption node, category: :image, end: :bottom if node.title?
1689
1694
  theme_margin :block, :bottom, (next_enclosed_block node) unless opts[:pinned]
1690
1695
  nil
1691
1696
  end
@@ -1695,7 +1700,7 @@ module Asciidoctor
1695
1700
  audio_path = node.media_uri node.attr 'target'
1696
1701
  play_symbol = (node.document.attr? 'icons', 'font') ? %(<font name="fas">#{(icon_font_data 'fas').unicode 'play'}</font>) : RightPointer
1697
1702
  ink_prose %(#{play_symbol}#{NoBreakSpace}<a href="#{audio_path}">#{audio_path}</a> <em>(audio)</em>), normalize: false, margin: 0, single_line: true
1698
- ink_caption node, labeled: false, side: :bottom if node.title?
1703
+ ink_caption node, labeled: false, end: :bottom if node.title?
1699
1704
  theme_margin :block, :bottom, (next_enclosed_block node)
1700
1705
  end
1701
1706
 
@@ -1723,7 +1728,7 @@ module Asciidoctor
1723
1728
  add_dest_for_block node if node.id
1724
1729
  play_symbol = (node.document.attr? 'icons', 'font') ? %(<font name="fas">#{(icon_font_data 'fas').unicode 'play'}</font>) : RightPointer
1725
1730
  ink_prose %(#{play_symbol}#{NoBreakSpace}<a href="#{video_path}">#{video_path}</a> <em>(#{type})</em>), normalize: false, margin: 0, single_line: true
1726
- ink_caption node, labeled: false, side: :bottom if node.title?
1731
+ ink_caption node, labeled: false, end: :bottom if node.title?
1727
1732
  theme_margin :block, :bottom, (next_enclosed_block node)
1728
1733
  else
1729
1734
  original_attributes = node.attributes.dup
@@ -2188,12 +2193,12 @@ module Asciidoctor
2188
2193
 
2189
2194
  rect_side_names = [:top, :right, :bottom, :left]
2190
2195
  grid_axis_names = [:rows, :cols]
2191
- border_color = (rect_side_names.zip expand_rect_values theme.table_border_color, theme.base_border_color).to_h
2196
+ border_color = (rect_side_names.zip expand_rect_values theme.table_border_color, 'transparent').to_h
2192
2197
  border_style = (rect_side_names.zip (expand_rect_values theme.table_border_style, :solid).map(&:to_sym)).to_h
2193
- border_width = (rect_side_names.zip expand_rect_values theme.table_border_width, theme.base_border_width).to_h
2194
- grid_color = (grid_axis_names.zip expand_grid_values (theme.table_grid_color || [border_color[:top], border_color[:left]]), theme.base_border_color).to_h
2198
+ border_width = (rect_side_names.zip expand_rect_values theme.table_border_width, 0).to_h
2199
+ grid_color = (grid_axis_names.zip expand_grid_values (theme.table_grid_color || [border_color[:top], border_color[:left]]), 'transparent').to_h
2195
2200
  grid_style = (grid_axis_names.zip (expand_grid_values (theme.table_grid_style || [border_style[:top], border_style[:left]]), :solid).map(&:to_sym)).to_h
2196
- grid_width = (grid_axis_names.zip expand_grid_values (theme.table_grid_width || [border_width[:top], border_width[:left]]), theme.base_border_width).to_h
2201
+ grid_width = (grid_axis_names.zip expand_grid_values (theme.table_grid_width || [border_width[:top], border_width[:left]]), 0).to_h
2197
2202
 
2198
2203
  if table_header_size
2199
2204
  head_border_bottom_color = theme.table_head_border_bottom_color || grid_color[:rows]
@@ -2239,7 +2244,7 @@ module Asciidoctor
2239
2244
  alignment = theme.table_align&.to_sym || :left
2240
2245
  end
2241
2246
 
2242
- caption_side = theme.table_caption_side&.to_sym || :top
2247
+ caption_end = theme.table_caption_end&.to_sym || :top
2243
2248
  caption_max_width = theme.table_caption_max_width || 'fit-content'
2244
2249
 
2245
2250
  table_settings = {
@@ -2268,7 +2273,7 @@ module Asciidoctor
2268
2273
  table table_data, table_settings do
2269
2274
  # NOTE: call width to capture resolved table width
2270
2275
  table_width = width
2271
- @pdf.ink_table_caption node, alignment, table_width, caption_max_width if node.title? && caption_side == :top
2276
+ @pdf.ink_table_caption node, alignment, table_width, caption_max_width if node.title? && caption_end == :top
2272
2277
  # NOTE: align using padding instead of bounding_box as prawn-table does
2273
2278
  # using a bounding_box across pages mangles the margin box of subsequent pages
2274
2279
  if alignment != :left && table_width != (this_bounds = @pdf.bounds).width
@@ -2341,7 +2346,7 @@ module Asciidoctor
2341
2346
  bounds.subtract_left_padding left_padding
2342
2347
  bounds.subtract_right_padding right_padding if right_padding
2343
2348
  end
2344
- ink_table_caption node, alignment, table_width, caption_max_width, caption_side if node.title? && caption_side == :bottom
2349
+ ink_table_caption node, alignment, table_width, caption_max_width, caption_end if node.title? && caption_end == :bottom
2345
2350
  theme_margin :block, :bottom, (next_enclosed_block node)
2346
2351
  rescue ::Prawn::Errors::CannotFit
2347
2352
  log :error, (message_with_context 'cannot fit contents of table cell into specified column width', source_location: node.source_location)
@@ -2349,12 +2354,14 @@ module Asciidoctor
2349
2354
 
2350
2355
  def convert_thematic_break node
2351
2356
  theme_margin :thematic_break, :top
2352
- stroke_horizontal_rule @theme.thematic_break_border_color, line_width: @theme.thematic_break_border_width, line_style: (@theme.thematic_break_border_style&.to_sym || :solid)
2357
+ stroke_horizontal_rule @theme.thematic_break_border_color,
2358
+ line_width: @theme.thematic_break_border_width,
2359
+ line_style: (@theme.thematic_break_border_style&.to_sym || :solid)
2353
2360
  theme_margin :thematic_break, ((block_next = next_enclosed_block node) ? :bottom : :top), block_next || true
2354
2361
  end
2355
2362
 
2356
2363
  def convert_toc node, opts = {}
2357
- # NOTE: only allow document to have a single toc
2364
+ # NOTE: only allow document to have a single managed toc
2358
2365
  return if @toc_extent
2359
2366
  is_macro = (placement = opts[:placement] || 'macro') == 'macro'
2360
2367
  if ((doc = node.document).attr? 'toc-placement', placement) && (doc.attr? 'toc') && doc.sections?
@@ -2363,11 +2370,11 @@ module Asciidoctor
2363
2370
  start_new_page if @ppbook && verso_page? && !(is_macro && (node.option? 'nonfacing'))
2364
2371
  end
2365
2372
  add_dest_for_block node, id: (node.id || 'toc') if is_macro
2366
- allocate_toc doc, (doc.attr 'toclevels', 2).to_i, cursor, (title_page_on = is_book || (doc.attr? 'title-page'))
2367
- @index.start_page_number = @toc_extent.to.page + 1 if title_page_on && @theme.page_numbering_start_at == 'after-toc'
2373
+ toc_extent = @toc_extent = allocate_toc doc, (doc.attr 'toclevels', 2).to_i, cursor, (title_page_on = is_book || (doc.attr? 'title-page'))
2374
+ @index.start_page_number = toc_extent.to.page + 1 if title_page_on && @theme.page_numbering_start_at == 'after-toc'
2368
2375
  if is_macro
2369
- @disable_running_content[:header] += @toc_extent.page_range if node.option? 'noheader'
2370
- @disable_running_content[:footer] += @toc_extent.page_range if node.option? 'nofooter'
2376
+ @disable_running_content[:header] += toc_extent.page_range if node.option? 'noheader'
2377
+ @disable_running_content[:footer] += toc_extent.page_range if node.option? 'nofooter'
2371
2378
  end
2372
2379
  end
2373
2380
  nil
@@ -2385,7 +2392,7 @@ module Asciidoctor
2385
2392
 
2386
2393
  if at_page_top?
2387
2394
  if page_layout && page_layout != page.layout && page.empty?
2388
- delete_page
2395
+ delete_current_page
2389
2396
  advance_page layout: page_layout
2390
2397
  end
2391
2398
  elsif page_layout
@@ -2395,8 +2402,9 @@ module Asciidoctor
2395
2402
  end
2396
2403
  end
2397
2404
 
2398
- def convert_index_section _node
2405
+ def convert_index_section node
2399
2406
  space_needed_for_category = @theme.description_list_term_spacing + (2 * (height_of_typeset_text 'A'))
2407
+ pagenum_sequence_style = node.document.attr 'index-pagenum-sequence-style'
2400
2408
  column_box [0, cursor], columns: @theme.index_columns, width: bounds.width, reflow_margins: true do
2401
2409
  def @bounding_box.move_past_bottom *args # rubocop:disable Lint/NestedMethodDefinition
2402
2410
  super(*args)
@@ -2409,8 +2417,8 @@ module Asciidoctor
2409
2417
  align: :left,
2410
2418
  inline_format: false,
2411
2419
  margin_bottom: @theme.description_list_term_spacing,
2412
- style: @theme.description_list_term_font_style.to_sym
2413
- category.terms.each {|term| convert_index_list_item term }
2420
+ style: @theme.description_list_term_font_style&.to_sym
2421
+ category.terms.each {|term| convert_index_list_item term, pagenum_sequence_style }
2414
2422
  # NOTE: see previous note for why we can't use margin_bottom method
2415
2423
  if @theme.prose_margin_bottom > y - reference_bounds.absolute_bottom
2416
2424
  bounds.move_past_bottom
@@ -2422,13 +2430,24 @@ module Asciidoctor
2422
2430
  nil
2423
2431
  end
2424
2432
 
2425
- def convert_index_list_item term
2433
+ def convert_index_list_item term, pagenum_sequence_style = nil
2426
2434
  text = escape_xml term.name
2427
2435
  unless term.container?
2428
2436
  if @media == 'screen'
2429
- pagenums = term.dests.map {|dest| %(<a anchor="#{dest[:anchor]}">#{dest[:page]}</a>) }
2437
+ case pagenum_sequence_style
2438
+ when 'page'
2439
+ pagenums = term.dests.uniq {|dest| dest[:page] }.map {|dest| %(<a anchor="#{dest[:anchor]}">#{dest[:page]}</a>) }
2440
+ when 'range'
2441
+ first_anchor_per_page = term.dests.each_with_object({}) {|dest, accum| accum[dest[:page]] ||= dest[:anchor] }
2442
+ pagenums = (consolidate_ranges first_anchor_per_page.keys).map do |range|
2443
+ anchor = first_anchor_per_page[(range.include? '-') ? (range.partition '-')[0] : range]
2444
+ %(<a anchor="#{anchor}">#{range}</a>)
2445
+ end
2446
+ else # term
2447
+ pagenums = term.dests.map {|dest| %(<a anchor="#{dest[:anchor]}">#{dest[:page]}</a>) }
2448
+ end
2430
2449
  else
2431
- pagenums = consolidate_ranges term.dests.uniq {|dest| dest[:page] }.map {|dest| dest[:page] } # rubocop:disable Lint/AmbiguousBlockAssociation
2450
+ pagenums = consolidate_ranges term.dests.map {|dest| dest[:page] }.uniq
2432
2451
  end
2433
2452
  text = %(#{text}, #{pagenums.join ', '})
2434
2453
  end
@@ -2436,7 +2455,7 @@ module Asciidoctor
2436
2455
  ink_prose text, align: :left, margin: 0, hanging_indent: subterm_indent * 2
2437
2456
  indent subterm_indent do
2438
2457
  term.subterms.each do |subterm|
2439
- convert_index_list_item subterm
2458
+ convert_index_list_item subterm, pagenum_sequence_style
2440
2459
  end
2441
2460
  end unless term.leaf?
2442
2461
  end
@@ -2991,7 +3010,7 @@ module Asciidoctor
2991
3010
  }.merge(opts)
2992
3011
  end
2993
3012
  if h_category && @theme[%(#{h_category}_border_width)] &&
2994
- @theme[%(#{h_category}_border_color)] && page_number == start_page_number
3013
+ (@theme[%(#{h_category}_border_color)] || @theme.base_border_color) && page_number == start_page_number
2995
3014
  float do
2996
3015
  bounding_box [bounds.left, start_cursor], width: bounds.width, height: start_cursor - cursor do
2997
3016
  theme_fill_and_stroke_bounds h_category
@@ -3112,7 +3131,7 @@ module Asciidoctor
3112
3131
  end
3113
3132
  end
3114
3133
  theme_font_cascade [:caption, category_caption] do
3115
- if ((opts.delete :side) || :top) == :top
3134
+ if ((opts.delete :end) || (opts.delete :side) || :top) == :top
3116
3135
  margin = { top: caption_margin_outside, bottom: caption_margin_inside }
3117
3136
  else
3118
3137
  margin = { top: caption_margin_inside, bottom: caption_margin_outside }
@@ -3140,8 +3159,8 @@ module Asciidoctor
3140
3159
  end
3141
3160
 
3142
3161
  # Render the caption for a table and return the height of the rendered content
3143
- def ink_table_caption node, table_alignment = :left, table_width = nil, max_width = nil, side = :top
3144
- ink_caption node, category: :table, side: side, block_align: table_alignment, block_width: table_width, max_width: max_width
3162
+ def ink_table_caption node, table_alignment = :left, table_width = nil, max_width = nil, end_ = :top
3163
+ ink_caption node, category: :table, end: end_, block_align: table_alignment, block_width: table_width, max_width: max_width
3145
3164
  end
3146
3165
 
3147
3166
  def allocate_toc doc, toc_num_levels, toc_start_cursor, title_page_on
@@ -3163,7 +3182,11 @@ module Asciidoctor
3163
3182
  extent.each_page {|first_page| start_new_page unless first_page }
3164
3183
  move_cursor_to extent.to.cursor
3165
3184
  end
3166
- @toc_extent = extent
3185
+ extent
3186
+ end
3187
+
3188
+ def get_entries_for_toc node
3189
+ node.sections
3167
3190
  end
3168
3191
 
3169
3192
  # NOTE: num_front_matter_pages not used during a dry run
@@ -3199,7 +3222,7 @@ module Asciidoctor
3199
3222
  }
3200
3223
  end
3201
3224
  theme_margin :toc, :top
3202
- ink_toc_level doc.sections, num_levels, dot_leader, num_front_matter_pages
3225
+ ink_toc_level (get_entries_for_toc doc), num_levels, dot_leader, num_front_matter_pages
3203
3226
  end
3204
3227
  # NOTE: range must be calculated relative to toc_page_number; absolute page number in scratch document is arbitrary
3205
3228
  toc_page_numbers = (toc_page_number..(toc_page_number + (page_number - start_page_number)))
@@ -3207,38 +3230,48 @@ module Asciidoctor
3207
3230
  toc_page_numbers
3208
3231
  end
3209
3232
 
3210
- def ink_toc_level sections, num_levels, dot_leader, num_front_matter_pages
3233
+ def ink_toc_level entries, num_levels, dot_leader, num_front_matter_pages
3211
3234
  # NOTE: font options aren't always reliable, so store size separately
3212
3235
  toc_font_info = theme_font :toc do
3213
3236
  { font: font, size: @font_size }
3214
3237
  end
3215
3238
  hanging_indent = @theme.toc_hanging_indent
3216
- sections.each do |sect|
3217
- next if (num_levels_for_sect = (sect.attr 'toclevels', num_levels).to_i) < sect.level
3218
- theme_font :toc, level: (sect.level + 1) do
3219
- sect_title = @text_transform ? (transform_text sect.numbered_title, @text_transform) : sect.numbered_title
3220
- next if sect_title.empty?
3239
+ entries.each do |entry|
3240
+ next if (num_levels_for_entry = (entry.attr 'toclevels', num_levels).to_i) < (entry_level = entry.level + 1).pred
3241
+ theme_font :toc, level: entry_level do
3242
+ next unless (entry_anchor = (entry.attr 'pdf-anchor') || entry.id)
3243
+ entry_title = entry.context == :section ? entry.numbered_title : (entry.title? ? entry.title : (entry.xreftext 'basic'))
3244
+ next if entry_title.empty?
3245
+ entry_title = transform_text entry_title, @text_transform if @text_transform
3221
3246
  pgnum_label_placeholder_width = rendered_width_of_string '0' * @toc_max_pagenum_digits
3222
- # NOTE: only write section title (excluding dots and page number) if this is a dry run
3247
+ # NOTE: only write title (excluding dots and page number) if this is a dry run
3223
3248
  if scratch?
3224
3249
  indent 0, pgnum_label_placeholder_width do
3225
3250
  # NOTE: must wrap title in empty anchor element in case links are styled with different font family / size
3226
- ink_prose sect_title, anchor: true, normalize: false, hanging_indent: hanging_indent, normalize_line_height: true, margin: 0
3251
+ ink_prose entry_title, anchor: true, normalize: false, hanging_indent: hanging_indent, normalize_line_height: true, margin: 0
3227
3252
  end
3228
3253
  else
3229
- physical_pgnum = sect.attr 'pdf-page-start'
3230
- virtual_pgnum = physical_pgnum - num_front_matter_pages
3231
- pgnum_label = (virtual_pgnum < 1 ? (RomanNumeral.new physical_pgnum, :lower) : virtual_pgnum).to_s
3254
+ if !(physical_pgnum = entry.attr 'pdf-page-start') &&
3255
+ (target_page_ref = (get_dest entry_anchor)&.first) &&
3256
+ (target_page_idx = state.pages.index {|candidate| candidate.dictionary == target_page_ref })
3257
+ physical_pgnum = target_page_idx + 1
3258
+ end
3259
+ if physical_pgnum
3260
+ virtual_pgnum = physical_pgnum - num_front_matter_pages
3261
+ pgnum_label = (virtual_pgnum < 1 ? (RomanNumeral.new physical_pgnum, :lower) : virtual_pgnum).to_s
3262
+ else
3263
+ pgnum_label = '?'
3264
+ end
3232
3265
  start_page_number = page_number
3233
3266
  start_cursor = cursor
3234
3267
  start_dots = nil
3235
- sect_title_inherited = (apply_text_decoration ::Set.new, :toc, sect.level.next).merge anchor: (sect_anchor = sect.attr 'pdf-anchor'), color: @font_color
3268
+ entry_title_inherited = (apply_text_decoration ::Set.new, :toc, entry_level).merge anchor: entry_anchor, color: @font_color
3236
3269
  # NOTE: use text formatter to add anchor overlay to avoid using inline format with synthetic anchor tag
3237
- sect_title_fragments = text_formatter.format sect_title, inherited: sect_title_inherited
3270
+ entry_title_fragments = text_formatter.format entry_title, inherited: entry_title_inherited
3238
3271
  line_metrics = calc_line_metrics @base_line_height
3239
3272
  indent 0, pgnum_label_placeholder_width do
3240
- (sect_title_fragments[-1][:callback] ||= []) << (last_fragment_pos = ::Asciidoctor::PDF::FormattedText::FragmentPositionRenderer.new)
3241
- typeset_formatted_text sect_title_fragments, line_metrics, hanging_indent: hanging_indent, normalize_line_height: true
3273
+ (entry_title_fragments[-1][:callback] ||= []) << (last_fragment_pos = ::Asciidoctor::PDF::FormattedText::FragmentPositionRenderer.new)
3274
+ typeset_formatted_text entry_title_fragments, line_metrics, hanging_indent: hanging_indent, normalize_line_height: true
3242
3275
  start_dots = last_fragment_pos.right + hanging_indent
3243
3276
  last_fragment_cursor = last_fragment_pos.top + line_metrics.padding_top
3244
3277
  start_cursor = last_fragment_cursor if last_fragment_pos.page_number > start_page_number || (start_cursor - last_fragment_cursor) > line_metrics.height
@@ -3246,7 +3279,7 @@ module Asciidoctor
3246
3279
  end_cursor = cursor
3247
3280
  move_cursor_to start_cursor
3248
3281
  # NOTE: we're guaranteed to be on the same page as the final line of the entry
3249
- if dot_leader[:width] > 0 && (dot_leader[:levels].include? sect.level)
3282
+ if dot_leader[:width] > 0 && (dot_leader[:levels].include? entry_level.pred)
3250
3283
  pgnum_label_width = rendered_width_of_string pgnum_label
3251
3284
  pgnum_label_font_settings = { color: @font_color, font: font_family, size: @font_size, styles: font_styles }
3252
3285
  save_font do
@@ -3258,18 +3291,18 @@ module Asciidoctor
3258
3291
  typeset_formatted_text [
3259
3292
  { text: dot_leader[:text] * num_dots, color: dot_leader[:font_color] },
3260
3293
  dot_leader[:spacer],
3261
- ({ text: pgnum_label, anchor: sect_anchor }.merge pgnum_label_font_settings),
3294
+ ({ text: pgnum_label, anchor: entry_anchor }.merge pgnum_label_font_settings),
3262
3295
  ], line_metrics, align: :right
3263
3296
  end
3264
3297
  else
3265
- typeset_formatted_text [{ text: pgnum_label, color: @font_color, anchor: sect_anchor }], line_metrics, align: :right
3298
+ typeset_formatted_text [{ text: pgnum_label, color: @font_color, anchor: entry_anchor }], line_metrics, align: :right
3266
3299
  end
3267
3300
  move_cursor_to end_cursor
3268
3301
  end
3269
3302
  end
3270
3303
  indent @theme.toc_indent do
3271
- ink_toc_level sect.sections, num_levels_for_sect, dot_leader, num_front_matter_pages
3272
- end if num_levels_for_sect > sect.level
3304
+ ink_toc_level (get_entries_for_toc entry), num_levels_for_entry, dot_leader, num_front_matter_pages
3305
+ end if num_levels_for_entry >= entry_level
3273
3306
  end
3274
3307
  end
3275
3308
 
@@ -3437,7 +3470,7 @@ module Asciidoctor
3437
3470
  theme_font periphery do
3438
3471
  canvas do
3439
3472
  bounding_box [trim_styles[:content_left][side], trim_styles[:top][side]], width: trim_styles[:content_width][side], height: trim_styles[:height] do
3440
- if (trim_column_rule_width = trim_styles[:column_rule_width]) > 0
3473
+ if trim_styles[:column_rule_color] && (trim_column_rule_width = trim_styles[:column_rule_width]) > 0
3441
3474
  trim_column_rule_spacing = trim_styles[:column_rule_spacing]
3442
3475
  else
3443
3476
  trim_column_rule_width = nil
@@ -3545,12 +3578,12 @@ module Asciidoctor
3545
3578
  # NOTE: we've already verified this property is set
3546
3579
  height: (trim_height = @theme[%(#{periphery}_height)]),
3547
3580
  bg_color: (resolve_theme_color %(#{periphery}_background_color).to_sym),
3548
- border_color: (trim_border_color = resolve_theme_color %(#{periphery}_border_color).to_sym),
3581
+ border_width: (trim_border_width = @theme[%(#{periphery}_border_width)] || 0),
3582
+ border_color: trim_border_width > 0 ? (resolve_theme_color %(#{periphery}_border_color).to_sym, @theme.base_border_color) : nil,
3549
3583
  border_style: (@theme[%(#{periphery}_border_style)]&.to_sym || :solid),
3550
- border_width: (trim_border_width = trim_border_color ? @theme[%(#{periphery}_border_width)] || @theme.base_border_width : 0),
3551
- column_rule_color: (trim_column_rule_color = resolve_theme_color %(#{periphery}_column_rule_color).to_sym),
3584
+ column_rule_width: (trim_column_rule_width = @theme[%(#{periphery}_column_rule_width)] || 0),
3585
+ column_rule_color: trim_column_rule_width > 0 ? (resolve_theme_color %(#{periphery}_column_rule_color).to_sym) : nil,
3552
3586
  column_rule_style: (@theme[%(#{periphery}_column_rule_style)]&.to_sym || :solid),
3553
- column_rule_width: (trim_column_rule_color ? @theme[%(#{periphery}_column_rule_width)] || 0 : 0),
3554
3587
  column_rule_spacing: (@theme[%(#{periphery}_column_rule_spacing)] || 0),
3555
3588
  valign: valign_offset ? [valign, valign_offset] : valign,
3556
3589
  img_valign: @theme[%(#{periphery}_image_vertical_align)],
@@ -3718,8 +3751,9 @@ module Asciidoctor
3718
3751
  outline.define do
3719
3752
  initial_pagenum = has_front_cover ? 2 : 1
3720
3753
  # FIXME: use sanitize: :plain_text on Document#doctitle once available
3721
- if document.page_count >= initial_pagenum && (doctitle = document.resolve_doctitle doc)
3722
- page title: (document.sanitize doctitle), destination: (document.dest_top initial_pagenum)
3754
+ if document.page_count >= initial_pagenum && (outline_title = doc.attr 'outline-title') &&
3755
+ (outline_title.empty? ? (outline_title = document.resolve_doctitle doc) : outline_title)
3756
+ page title: (document.sanitize outline_title), destination: (document.dest_top initial_pagenum)
3723
3757
  end
3724
3758
  # QUESTION: is there any way to get add_outline_level to invoke in the context of the outline?
3725
3759
  document.add_outline_level self, doc.sections, num_levels, expand_levels
@@ -3781,7 +3815,14 @@ module Asciidoctor
3781
3815
  pdf_doc.render_file target
3782
3816
  # QUESTION: restore attributes first?
3783
3817
  @pdfmark&.generate_file target
3784
- (Optimizer.new @optimize, pdf_doc.min_version).optimize_file target if @optimize
3818
+ if (quality = @optimize)
3819
+ if quality.include? ','
3820
+ quality, compliance = quality.split ',', 2
3821
+ elsif quality.include? '/'
3822
+ quality, compliance = nil, quality
3823
+ end
3824
+ (Optimizer.new quality, pdf_doc.min_version, compliance).optimize_file target
3825
+ end
3785
3826
  to_file = true
3786
3827
  end
3787
3828
  if !ENV['KEEP_ARTIFACTS']
@@ -3833,7 +3874,7 @@ module Asciidoctor
3833
3874
  end
3834
3875
 
3835
3876
  def resolve_text_transform key, use_fallback = true
3836
- if (transform = ::Hash === key ? (key.delete :text_transform) : @theme[key.to_s])
3877
+ if (transform = ::Hash === key ? (key.delete :text_transform) : @theme[key])
3837
3878
  transform == 'none' ? nil : transform
3838
3879
  elsif use_fallback
3839
3880
  @text_transform
@@ -3842,9 +3883,9 @@ module Asciidoctor
3842
3883
 
3843
3884
  # QUESTION: should we pass a category as an argument?
3844
3885
  # QUESTION: should we make this a method on the theme ostruct? (e.g., @theme.resolve_color key, fallback)
3845
- def resolve_theme_color key, fallback_color = nil
3846
- if (color = @theme[key.to_s]) && color != 'transparent'
3847
- color
3886
+ def resolve_theme_color key, fallback_color = nil, transparent_color = fallback_color
3887
+ if (color = @theme[key])
3888
+ color == 'transparent' ? transparent_color : color
3848
3889
  else
3849
3890
  fallback_color
3850
3891
  end
@@ -3855,7 +3896,7 @@ module Asciidoctor
3855
3896
  end
3856
3897
 
3857
3898
  def theme_fill_and_stroke_bounds category, opts = {}
3858
- fill_and_stroke_bounds opts[:background_color], @theme[%(#{category}_border_color)],
3899
+ fill_and_stroke_bounds opts[:background_color], @theme[%(#{category}_border_color)] || @theme.base_border_color,
3859
3900
  line_width: @theme[%(#{category}_border_width)],
3860
3901
  line_style: (@theme[%(#{category}_border_style)]&.to_sym || :solid),
3861
3902
  radius: @theme[%(#{category}_border_radius)]
@@ -3881,11 +3922,9 @@ module Asciidoctor
3881
3922
  ink_caption node_with_caption, category: category if node_with_caption
3882
3923
  return
3883
3924
  end
3884
- if (b_color = @theme[%(#{category}_border_color)]) == 'transparent'
3885
- b_color = @page_bg_color
3886
- end
3925
+ b_color = resolve_theme_color %(#{category}_border_color).to_sym, @theme.base_border_color, @page_bg_color
3887
3926
  b_radius ||= (@theme[%(#{category}_border_radius)] || 0) + (b_width || 0)
3888
- if b_width && b_color
3927
+ if b_width
3889
3928
  if b_color == @page_bg_color # let page background cut into block background
3890
3929
  b_gap_color, b_shift = @page_bg_color, (b_width * 0.5)
3891
3930
  elsif (b_gap_color = bg_color) && b_gap_color != b_color
@@ -14,6 +14,14 @@ class PDF::Core::Page
14
14
  content.stream.filtered_stream == (@tare_content_stream ||= InitialPageContent) && document.page_number > 0
15
15
  end
16
16
 
17
+ # Flags this page as imported.
18
+ #
19
+ def imported
20
+ @imported_page = true
21
+ end
22
+
23
+ alias imported_page imported
24
+
17
25
  # Reset the content of the page.
18
26
  # Note that this method may leave behind an orphaned background image.
19
27
  def reset_content
@@ -103,7 +103,7 @@ module Asciidoctor
103
103
  NewPageRequiredError = ::Class.new ::StopIteration
104
104
 
105
105
  InhibitNewPageProc = proc do |pdf|
106
- pdf.delete_page
106
+ pdf.delete_current_page
107
107
  raise NewPageRequiredError
108
108
  end
109
109
 
@@ -111,7 +111,7 @@ module Asciidoctor
111
111
 
112
112
  DetectEmptyFirstPageProc = proc do |delegate, pdf|
113
113
  if pdf.state.pages[pdf.page_number - 2].empty?
114
- pdf.delete_page
114
+ pdf.delete_current_page
115
115
  raise NewPageRequiredError
116
116
  end
117
117
  delegate.call pdf if (pdf.state.on_page_create_callback = delegate)
@@ -269,6 +269,20 @@ module Asciidoctor
269
269
  dest_xyz 0, page_height, nil, (page_num ? state.pages[page_num - 1] : page)
270
270
  end
271
271
 
272
+ # Gets the destination registered for the specified name. The return value
273
+ # matches that which was passed to the add_dest method.
274
+ #
275
+ def get_dest name, node = dests.data
276
+ node.children.each do |child|
277
+ if ::PDF::Core::NameTree::Value === child
278
+ return child.value.data if child.name == name
279
+ elsif (found = get_dest name, child)
280
+ return found
281
+ end
282
+ end
283
+ nil
284
+ end
285
+
272
286
  # Fonts
273
287
 
274
288
  # Registers a new custom font described in the data parameter
@@ -495,6 +509,8 @@ module Asciidoctor
495
509
  lowercase_pcdata text
496
510
  when :capitalize, 'capitalize'
497
511
  capitalize_words_pcdata text
512
+ when :smallcaps, 'smallcaps'
513
+ smallcaps_pcdata text
498
514
  else
499
515
  text
500
516
  end
@@ -823,7 +839,7 @@ module Asciidoctor
823
839
 
824
840
  # Deletes the current page and move the cursor
825
841
  # to the previous page.
826
- def delete_page
842
+ def delete_current_page
827
843
  pg = page_number
828
844
  pdf_store = state.store
829
845
  content_id = page.content.identifier
@@ -857,7 +873,7 @@ module Asciidoctor
857
873
  prev_page_size = page.size
858
874
  state.compress = false if state.compress # can't use compression if using template
859
875
  prev_text_rendering_mode = (defined? @text_rendering_mode) ? @text_rendering_mode : nil
860
- delete_page if options[:replace]
876
+ delete_current_page if options[:replace]
861
877
  # NOTE: use functionality provided by prawn-templates
862
878
  start_new_page_discretely template: file, template_page: options[:page]
863
879
  # prawn-templates sets text_rendering_mode to :unknown, which breaks running content; revert
@@ -869,11 +885,11 @@ module Asciidoctor
869
885
  # way atm to prevent the size & layout of the imported page from affecting subsequent pages
870
886
  advance_page size: prev_page_size, layout: prev_page_layout if options.fetch :advance, true
871
887
  elsif options.fetch :advance_if_missing, true
872
- delete_page
888
+ delete_current_page
873
889
  # NOTE: see previous comment
874
890
  advance_page size: prev_page_size, layout: prev_page_layout
875
891
  else
876
- delete_page
892
+ delete_current_page
877
893
  end
878
894
  nil
879
895
  end
@@ -99,7 +99,7 @@ module Prawn
99
99
  apply_font_properties { pdf.traverse content }
100
100
  if (extra_pages = pdf.page_number - start_page) > 0
101
101
  logger.error %(the table cell on page #{start_page} has been truncated; Asciidoctor PDF does not support table cell content that exceeds the height of a single page) unless extra_pages == 1 && pdf.page.empty?
102
- extra_pages.times { pdf.delete_page }
102
+ extra_pages.times { pdf.delete_current_page }
103
103
  end
104
104
  nil
105
105
  end
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+
3
+ Prawn.image_handler.register! Prawn::Images::PNG if defined? GMagick::Image
@@ -40,9 +40,10 @@ module Asciidoctor
40
40
  attr_reader :quality
41
41
  attr_reader :compatibility_level
42
42
 
43
- def initialize quality = 'default', compatibility_level = '1.4'
43
+ def initialize quality = 'default', compatibility_level = '1.4', compliance = 'PDF'
44
44
  @quality = QUALITY_NAMES[quality]
45
45
  @compatibility_level = compatibility_level
46
+ @compliance = compliance
46
47
  if (gs_path = ::ENV['GS'])
47
48
  ::RGhost::Config::GS[:path] = gs_path
48
49
  end
@@ -57,10 +58,16 @@ module Asciidoctor
57
58
  else
58
59
  inputs = target
59
60
  end
60
- (::RGhost::Convert.new inputs).to :pdf,
61
- filename: filename_tmp.to_s,
62
- quality: @quality,
63
- d: { Printed: false, CannotEmbedFontPolicy: '/Warning', CompatibilityLevel: @compatibility_level }
61
+ d = { Printed: false, CannotEmbedFontPolicy: '/Warning', CompatibilityLevel: @compatibility_level }
62
+ case @compliance
63
+ when 'PDF/A', 'PDF/A-1', 'PDF/A-2', 'PDF/A-3'
64
+ d[:PDFA] = ((@compliance.split '-', 2)[1] || 1).to_i
65
+ d[:ShowAnnots] = false
66
+ when 'PDF/X', 'PDF/X-1', 'PDF/X-3'
67
+ d[:PDFX] = true
68
+ d[:ShowAnnots] = false
69
+ end
70
+ (::RGhost::Convert.new inputs).to :pdf, filename: filename_tmp.to_s, quality: @quality, d: d
64
71
  filename_o.binwrite filename_tmp.binread
65
72
  end
66
73
  nil
@@ -10,6 +10,12 @@ module Asciidoctor
10
10
  WordRx = /\p{Word}+/
11
11
  Hyphen = '-'
12
12
  SoftHyphen = ?\u00ad
13
+ LowerAlphaChars = 'a-z'
14
+ # NOTE: use more widely-supported ғ instead of ꜰ as replacement for F
15
+ # NOTE: use more widely-supported ǫ instead of ꞯ as replacement for Q
16
+ # NOTE: use more widely-supported s (lowercase latin "s") instead of ꜱ as replacement for S
17
+ # NOTE: in small caps, x (lowercase latin "x") remains unchanged
18
+ SmallCapsChars = 'ᴀʙᴄᴅᴇғɢʜɪᴊᴋʟᴍɴoᴘǫʀsᴛᴜᴠᴡxʏᴢ'
13
19
 
14
20
  def capitalize_words_pcdata string
15
21
  if XMLMarkupRx.match? string
@@ -50,6 +56,14 @@ module Asciidoctor
50
56
  string.upcase
51
57
  end
52
58
  end
59
+
60
+ def smallcaps_pcdata string
61
+ if XMLMarkupRx.match? string
62
+ string.gsub(PCDATAFilterRx) { $2 ? ($2.tr LowerAlphaChars, SmallCapsChars) : $1 }
63
+ else
64
+ string.tr LowerAlphaChars, SmallCapsChars
65
+ end
66
+ end
53
67
  end
54
68
  end
55
69
  end
@@ -15,7 +15,7 @@ module Asciidoctor
15
15
  BaseThemePath = ::File.join ThemesDir, 'base-theme.yml'
16
16
  BundledThemeNames = (::Dir.children ThemesDir).map {|it| it.slice 0, it.length - 10 }
17
17
  DeprecatedCategoryKeys = { 'blockquote' => 'quote', 'key' => 'kbd', 'literal' => 'codespan', 'outline_list' => 'list' }
18
- AmbiguousAlignKeys = %w(base heading heading_h1 heading_h2 heading_h3 heading_h4 heading_h5 heading_h6 title_page abstract abstract_title admonition_label sidebar_title toc_title).each_with_object({}) do |prefix, accum|
18
+ DeprecatedKeys = %w(base heading heading_h1 heading_h2 heading_h3 heading_h4 heading_h5 heading_h6 title_page abstract abstract_title admonition_label sidebar_title toc_title).each_with_object({ 'table_caption_side' => 'table_caption_end' }) do |prefix, accum|
19
19
  accum[%(#{prefix}_align)] = %(#{prefix}_text_align)
20
20
  end
21
21
  PaddingBottomHackKeys = %w(example_padding quote_padding sidebar_padding verse_padding)
@@ -171,7 +171,7 @@ module Asciidoctor
171
171
  val.each do |subkey, subval|
172
172
  process_entry %(#{key}_#{key == 'role' || !(subkey.include? '-') ? subkey : (subkey.tr '-', '_')}), subval, data
173
173
  end
174
- elsif (rekey = AmbiguousAlignKeys[key]) ||
174
+ elsif (rekey = DeprecatedKeys[key]) ||
175
175
  ((key.start_with? 'role_') && (key.end_with? '_align') && (rekey = key.sub RoleAlignKeyRx, '_text_align'))
176
176
  data[rekey] = evaluate val, data
177
177
  elsif PaddingBottomHackKeys.include? key
@@ -222,7 +222,7 @@ module Asciidoctor
222
222
  var = var.tr '-', '_' if var.include? '-'
223
223
  if (vars.respond_to? var) ||
224
224
  DeprecatedCategoryKeys.any? {|old, new| (var.start_with? old + '_') && (vars.respond_to? (replace = new + (var.slice old.length, var.length))) && (var = replace) } ||
225
- ((replace = AmbiguousAlignKeys[var]) && (vars.respond_to? replace) && (var = replace))
225
+ ((replace = DeprecatedKeys[var]) && (vars.respond_to? replace) && (var = replace))
226
226
  vars[var]
227
227
  else
228
228
  logger.warn %(unknown variable reference in PDF theme: #{ref})
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Asciidoctor
4
4
  module PDF
5
- VERSION = '2.0.0.alpha.3'
5
+ VERSION = '2.0.0.beta.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.0.0.alpha.3
4
+ version: 2.0.0.beta.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-05-01 00:00:00.000000000 Z
12
+ date: 2022-05-04 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: asciidoctor
@@ -291,6 +291,7 @@ files:
291
291
  - lib/asciidoctor/pdf/index_catalog.rb
292
292
  - lib/asciidoctor/pdf/measurements.rb
293
293
  - lib/asciidoctor/pdf/nogmagick.rb
294
+ - lib/asciidoctor/pdf/nopngmagick.rb
294
295
  - lib/asciidoctor/pdf/optimizer.rb
295
296
  - lib/asciidoctor/pdf/pdfmark.rb
296
297
  - lib/asciidoctor/pdf/roman_numeral.rb