asciidoctor-pdf 1.5.0.rc.2 → 1.5.0.rc.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.adoc +29 -0
  3. data/README.adoc +33 -6
  4. data/asciidoctor-pdf.gemspec +3 -7
  5. data/data/fonts/ABOUT-notoemoji-subset +3 -0
  6. data/data/fonts/ABOUT-notoserif-subset +1 -1
  7. data/data/fonts/mplus1mn-bold-ascii.ttf +0 -0
  8. data/data/fonts/mplus1mn-bold-subset.ttf +0 -0
  9. data/data/fonts/mplus1mn-bold_italic-ascii.ttf +0 -0
  10. data/data/fonts/mplus1mn-bold_italic-subset.ttf +0 -0
  11. data/data/fonts/mplus1mn-italic-ascii.ttf +0 -0
  12. data/data/fonts/mplus1mn-italic-subset.ttf +0 -0
  13. data/data/fonts/mplus1mn-regular-ascii-conums.ttf +0 -0
  14. data/data/fonts/mplus1mn-regular-subset.ttf +0 -0
  15. data/data/fonts/mplus1p-regular-fallback.ttf +0 -0
  16. data/data/fonts/notoemoji-subset.ttf +0 -0
  17. data/data/fonts/notoserif-bold-subset.ttf +0 -0
  18. data/data/fonts/notoserif-bold_italic-subset.ttf +0 -0
  19. data/data/fonts/notoserif-italic-subset.ttf +0 -0
  20. data/data/fonts/notoserif-regular-subset.ttf +0 -0
  21. data/data/themes/default-theme.yml +1 -1
  22. data/data/themes/default-with-fallback-font-theme.yml +4 -17
  23. data/docs/theming-guide.adoc +38 -13
  24. data/lib/asciidoctor/pdf.rb +0 -1
  25. data/lib/asciidoctor/pdf/converter.rb +154 -123
  26. data/lib/asciidoctor/pdf/ext/asciidoctor/logging_shim.rb +9 -1
  27. data/lib/asciidoctor/pdf/ext/core.rb +1 -0
  28. data/lib/asciidoctor/pdf/ext/core/file.rb +9 -0
  29. data/lib/asciidoctor/pdf/ext/prawn/extensions.rb +10 -0
  30. data/lib/asciidoctor/pdf/ext/prawn/font/afm.rb +2 -6
  31. data/lib/asciidoctor/pdf/ext/prawn/formatted_text/box.rb +40 -0
  32. data/lib/asciidoctor/pdf/formatted_text/formatter.rb +5 -6
  33. data/lib/asciidoctor/pdf/formatted_text/fragment_position_renderer.rb +2 -1
  34. data/lib/asciidoctor/pdf/formatted_text/inline_image_arranger.rb +7 -12
  35. data/lib/asciidoctor/pdf/formatted_text/inline_image_renderer.rb +1 -4
  36. data/lib/asciidoctor/pdf/formatted_text/transform.rb +1 -1
  37. data/lib/asciidoctor/pdf/theme_loader.rb +10 -9
  38. data/lib/asciidoctor/pdf/version.rb +1 -1
  39. metadata +10 -64
  40. data/lib/asciidoctor/pdf/temporary_path.rb +0 -15
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7290367ccc4d34f5fe1c5668c08de5a84e49af2d605b2ff30586620ab0eb6b58
4
- data.tar.gz: '0920410efe9b0f47ad3dd5ae10626e251898782e190d2e73cb232d4a46bf15f5'
3
+ metadata.gz: '09626023cb6a135adce58fb0340da45204eb411c5ee142f0b46fdb0224940f9e'
4
+ data.tar.gz: ee585d380fecf131696731998ef8f53f3c0c26ed7a7b50c10579d92c9eb941d6
5
5
  SHA512:
6
- metadata.gz: 411e2a27ea226772b264c9686a163dc2f5b932f160182df11df9bef3b3eac2424e50c96287539a21d3a646ad8cc9ad29964e3c11d7305d045a74671cc33024b8
7
- data.tar.gz: 227a016304d3fa406e4e1ae5a23ee5be0152d7bfacb94435a96d6735256bb4a100b853f1bae26489c74c9fb4648ff1ba191273594c657124a7188ae773c3618e
6
+ metadata.gz: 96cc29fcadcf79c8f7d4b051c39114b4c6f54f3e7d21951b0c16fab4f85a22a5ae32863907ebe72ea7a1ba851aaa5e8d56a8400d5b57688aca146f4f84cef534
7
+ data.tar.gz: 2ff61e20d2a6f4da7ef87b8735cf1ffcdbab2be73ff1dc05f62592c0d25ac5d069e2708058f134d5feee71e9df0c6b55bd02bb6c76a7e471faa8797feec11639
@@ -5,6 +5,34 @@
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/master[commit history] on GitHub.
7
7
 
8
+ == 1.5.0.rc.3 (2020-02-04) - @mojavelinux
9
+
10
+ * reserve space for inline image correctly so it doesn't mangle the character spacing in the line when the image wraps (#1516)
11
+ * allow custom theme to merge font catalog with theme being extended (#1505)
12
+ * allow font path to be declared once for all font styles (#1507)
13
+ * continue border, background, and column rule of admonition block on subsequent pages when block gets split (#1287)
14
+ * allow max-width on caption be specified as a percentage (of the container width) (#1484)
15
+ * add support for remote image in running content (if allow-uri-read attribute is set) (#1536)
16
+ * add support for remote background images specified by theme (if allow-uri-read attribute is set) (#1536)
17
+ * add support for remote title page logo image specified by theme (if allow-uri-read attribute is set) (#1536)
18
+ * place dots on correct page when section title in TOC wraps across a page boundary (#1533)
19
+ * add destination to top of imported PDF if ID is specified on image block
20
+ * log reason if theme file cannot be parsed or compiled (#1491)
21
+ * fix crash if background image in theme is not readable
22
+ * bundle emoji font and use as fallback in default-with-fallback-font theme (#1129)
23
+ * add dark theme for chronicles example
24
+ * allow vertical-align key for header and footer categories to accept numeric offset as second value (e.g., [top, 10]) (#1488)
25
+ * link font family for abstract and sidebar to heading font family if only latter is set
26
+ * if path of missing font is absolute, don't suggest that it was not found in the fontsdir
27
+ * allow use of style "regular" as alias for "normal" when defining font
28
+ * emit warning in verbose mode if glyph cannot be found in fallback font (#1529)
29
+ * don't crash if table is empty and emit warning (#607)
30
+ * only emit warning when non-WINANSI character is used with AFM font if verbose mode is enabled
31
+ * do not emit warning when non-WINANSI character is used with AFM font inside scratch document
32
+ * do not emit log messages from scratch document
33
+ * upgrade treetop to 1.6
34
+ * reenable tests on Windows (#1499) *@slonopotamus*
35
+
8
36
  == 1.5.0.rc.2 (2020-01-09) - @mojavelinux
9
37
 
10
38
  * patch Prawn to fix incompatibilty with Ruby 2.7 (to fix text wrapping)
@@ -284,6 +312,7 @@ For a detailed view of what has changed, refer to the {uri-repo}/commits/master[
284
312
  == 1.5.0.alpha.18 (2019-06-01) - @mojavelinux
285
313
 
286
314
  * restore compatibility with Asciidoctor back to 1.5.3 and add verification to test matrix (#1038)
315
+ * allow one theme to extend another theme using the top-level `extends` key (#367)
287
316
  * allow theme to set text indent for paragraphs using prose_text_indent (#191)
288
317
  * allow theme to set spacing between adjacent paragraphs using prose_margin_inner (#191)
289
318
  * show parts in toc when toclevels=0 (#783)
@@ -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
- v1.5.0.rc.2, 2020-01-09
3
+ v1.5.0.rc.3, 2020-02-04
4
4
  // Settings:
5
5
  :experimental:
6
6
  :idprefix:
@@ -23,7 +23,7 @@ endif::[]
23
23
  :project-name: Asciidoctor PDF
24
24
  :project-handle: asciidoctor-pdf
25
25
  // Variables:
26
- :release-version: 1.5.0.rc.2
26
+ :release-version: 1.5.0.rc.3
27
27
  // URIs:
28
28
  :url-asciidoctor: http://asciidoctor.org
29
29
  :url-gem: http://rubygems.org/gems/asciidoctor-pdf
@@ -90,6 +90,7 @@ But don't miss the <<Highlights>> and <<Known Limitations>> sections to understa
90
90
  * Auto-generated index
91
91
  * Automatic hyphenation (when enabled)
92
92
  * Compression / optimization of output file
93
+ * Permissive line breaking for CJK languages
93
94
 
94
95
  == Known Limitations
95
96
 
@@ -103,6 +104,7 @@ But don't miss the <<Highlights>> and <<Known Limitations>> sections to understa
103
104
  * AsciiDoc table cell leaves padding below last block (due to lack of margin collapsing)
104
105
  * Prawn does not support double-wide box drawing glyphs correctly, so box drawings aren't aligned properly in verbatim blocks (see https://github.com/prawnpdf/prawn/issues/1002[prawn#1002]
105
106
  * Orphan / widow support is limited; a page break can occur between a section title and its section content, a table caption and the caption, etc.; use a manual page break to avoid
107
+ * If a no-break hyphen is surrounded by formatted text on both sides (or is formatted individually), it will not prevent a line break
106
108
 
107
109
  == Prerequisites
108
110
 
@@ -218,9 +220,9 @@ gem 'prawn-table', github: 'prawnpdf/prawn-table'
218
220
 
219
221
  You can then install the gems into your project using the `bundle` command:
220
222
 
221
- $ bundle --path=.bundle/gems
223
+ $ bundle config set --local path .bundle/gems && bundle
222
224
 
223
- Since you are using Bundler to manage the gems, you'll need to prefix all commands with `bundle exec`.
225
+ Since you're using Bundler to manage the gems, you'll need to prefix all commands with `bundle exec`.
224
226
  For example:
225
227
 
226
228
  $ bundle exec asciidoctor-pdf -v
@@ -689,8 +691,9 @@ The prawn-gmagick gem uses native extensions to compile against GraphicsMagick.
689
691
  This system prerequisite limits installation to Linux and OSX.
690
692
  Please refer to the {url-prawn-gmagick}[README for prawn-gmagick] to learn how to install it.
691
693
 
692
- Once this gem is installed, Asciidoctor automatically switches over to it to handle embedding of all images.
693
- In addition to support for more additional image file formats, this gem also speeds up image processing considerably, so we highly recommend using it if you can.
694
+ Once this gem is installed, Asciidoctor automatically loads it, then delegates to it to handle all image embedding.
695
+ In addition to support for additional image file formats, this gem also speeds up image processing considerably.
696
+ We highly recommend using this gem if you're able to install it.
694
697
 
695
698
  == Importing PDF Pages
696
699
 
@@ -940,6 +943,30 @@ If you want to enable the autofit option globally, set the `autofit-option` docu
940
943
  :autofit-option:
941
944
  ----
942
945
 
946
+ == Autowidth Tables
947
+
948
+ Asciidoctor PDF does support autowidth tables.
949
+ However, the behavior differs from HTML when the content forces the table to the page boundary.
950
+ The behavior, which is handled by the prawn-table library, is explained in this section.
951
+
952
+ If the natural width of all columns (based on the width of the cell content) is less than the width of the page, it behaves as you'd expect.
953
+ Each column is assigned the width it needs to prevent the content from wrapping.
954
+
955
+ However, when the natural width of all columns exceeds the width of the page, the behavior may not be what you expect.
956
+ What prawn-table does is compute how to arrange the table on an infinite canvas, where each column can have a width no greater than the width of the page.
957
+ Then, it reduces the width of the table by reducing the width of each column proportionally.
958
+ As a result, columns which reported the width necessary to render without wrapping now no longer do.
959
+
960
+ The reason this compression is not performed like in HTML is because prawn-table has no awareness of words.
961
+ Thus, it doesn't know how to redistribute with width intelligently.
962
+
963
+ To protected against truncation or insufficient width errors, prawn-table wraps text by character.
964
+ That's why the last character in the cell can end up getting wrapped.
965
+ (There's a small amount of tolerance built in to prawn-table to address some edge cases, but it's not sufficient to handle all of them).
966
+
967
+ For the reason just explained, you should be extremely careful with relying on autowidth tables in Asciidoctor PDF, especially when the natural content of the cells forces the table to page boundary.
968
+ Let experience be your guide.
969
+
943
970
  == Printing Page Ranges
944
971
 
945
972
  The print dialog doesn't understand the page numbers labels (which appear in the running content).
@@ -37,6 +37,7 @@ Gem::Specification.new do |s|
37
37
  s.add_runtime_dependency 'prawn', '~> 2.2.0'
38
38
  # NOTE ttfunk 1.6 generates TT instructions ghostscript cannot process, so lock the version of ttfunk
39
39
  s.add_runtime_dependency 'ttfunk', ['~> 1.5.0'], ['>= 1.5.1']
40
+ # NOTE must use prawn-table from master branch (defined in Gemfile) for full functionality
40
41
  s.add_runtime_dependency 'prawn-table', '~> 0.2.0'
41
42
  s.add_runtime_dependency 'prawn-templates', '~> 0.1.0'
42
43
  s.add_runtime_dependency 'prawn-svg', '~> 0.30.0'
@@ -44,18 +45,13 @@ Gem::Specification.new do |s|
44
45
  s.add_runtime_dependency 'safe_yaml', '~> 1.0.0'
45
46
  s.add_runtime_dependency 'thread_safe', '~> 0.3.0'
46
47
  s.add_runtime_dependency 'concurrent-ruby', '~> 1.1.0'
47
- # For our usage, treetop 1.6 is slower than treetop 1.5
48
- s.add_runtime_dependency 'treetop', '~> 1.5.0'
48
+ s.add_runtime_dependency 'treetop', '~> 1.6.0'
49
49
 
50
50
  s.add_development_dependency 'rake', '~> 13.0.0'
51
- s.add_development_dependency 'deep-cover-core', '~> 0.7.0'
52
- s.add_development_dependency 'simplecov', '~> 0.17.0'
53
51
  s.add_development_dependency 'rspec', '~> 3.9.0'
54
52
  s.add_development_dependency 'pdf-inspector', '~> 1.3.0'
55
53
  # Asciidoctor PDF supports Rouge >= 2 (verified in CI build using 2.0.0)
56
- s.add_development_dependency 'rouge', '~> 3.14.0'
57
- s.add_development_dependency 'rubocop', '~> 0.78.0'
58
- s.add_development_dependency 'rubocop-rspec', '~> 1.37.0'
54
+ s.add_development_dependency 'rouge', '~> 3.0'
59
55
  s.add_development_dependency 'coderay', '~> 1.1.0'
60
56
  s.add_development_dependency 'chunky_png', '~> 1.3.0'
61
57
  end
@@ -0,0 +1,3 @@
1
+ Noto Emoji font is generated from the google-noto-emoji-fonts (commit 16151a2312a1f8a7d79e91789d3cfe24559d61f7).
2
+
3
+ Some glyphs have been removed that conflict with the other built-in fonts to produce the notoemoji-subset.ttf font.
@@ -1,4 +1,4 @@
1
- Noto Serif fonts are generated from the google-noto-serif-fonts-20161022 Fedora RPM package Noto Serif.
1
+ Noto Serif fonts are generated from the google-noto-serif-fonts-20161022 Fedora RPM package (commit 86b2e553c3e3e4d6614dadd1fa0a7a6dafd74552).
2
2
 
3
3
  The following changes were made using fontforge to produce the notoserif-*-subset.ttf fonts:
4
4
 
@@ -207,7 +207,7 @@ example:
207
207
  border_color: $base_border_color
208
208
  border_radius: $base_border_radius
209
209
  border_width: 0.75
210
- background_color: FFFFFF
210
+ background_color: $page-background-color
211
211
  # FIXME reenable padding bottom once margin collapsing is implemented
212
212
  padding: [$vertical_rhythm, $horizontal_rhythm, 0, $horizontal_rhythm]
213
213
  image:
@@ -1,22 +1,9 @@
1
1
  extends: default
2
2
  font:
3
3
  catalog:
4
- Noto Serif:
5
- normal: notoserif-regular-subset.ttf
6
- bold: notoserif-bold-subset.ttf
7
- italic: notoserif-italic-subset.ttf
8
- bold_italic: notoserif-bold_italic-subset.ttf
9
- M+ 1mn:
10
- normal: mplus1mn-regular-subset.ttf
11
- bold: mplus1mn-bold-subset.ttf
12
- italic: mplus1mn-italic-subset.ttf
13
- bold_italic: mplus1mn-bold_italic-subset.ttf
4
+ merge: true
14
5
  # M+ 1p supports Latin, Latin-1 Supplement, Latin Extended, Greek, Cyrillic, Vietnamese, Japanese & an assortment of symbols
15
6
  # It also provides arrows for ->, <-, => and <= replacements in case these glyphs are missing from font
16
- M+ 1p Fallback:
17
- normal: mplus1p-regular-fallback.ttf
18
- bold: mplus1p-regular-fallback.ttf
19
- italic: mplus1p-regular-fallback.ttf
20
- bold_italic: mplus1p-regular-fallback.ttf
21
- fallbacks:
22
- - M+ 1p Fallback
7
+ M+ 1p Fallback: mplus1p-regular-fallback.ttf
8
+ Noto Emoji: notoemoji-subset.ttf
9
+ fallbacks: [M+ 1p Fallback, Noto Emoji]
@@ -52,6 +52,11 @@ The Asciidoctor PDF theme language is described using the http://en.wikipedia.or
52
52
  Therefore, if you have a background in web design, the terminology should be immediately familiar to you.
53
53
  *Note, however, that the theming system isn't actually CSS.*
54
54
 
55
+ The theme file must be named _<name>-theme.yml_, where `<name>` is the name of the theme.
56
+ _We recommend *not* using the names *base* or *default* so you don't confuse it with one of the built-in themes._
57
+
58
+ === Selectors and Properties
59
+
55
60
  Like CSS, themes have both selectors and properties.
56
61
  Selectors are the component you want to style.
57
62
  The properties are the style elements of that component that can be styled.
@@ -74,8 +79,7 @@ YAML is a human-friendly data format that resembles CSS and helps to describe th
74
79
  The theme language adds some extra features to YAML, such as variables, basic math, measurements and color values.
75
80
  These enhancements will be explained in detail in later sections.
76
81
 
77
- The theme file must be named _<name>-theme.yml_, where `<name>` is the name of the theme.
78
- _We recommend *not* using the names *base* or *default* so you don't confuse it with one of the built-in themes._
82
+ === Basic Theme
79
83
 
80
84
  Here's an example of a basic theme file that extends the base theme:
81
85
 
@@ -118,6 +122,8 @@ When creating a new theme, you only have to define the keys you want to override
118
122
  All the available keys are documented in <<Keys>>.
119
123
  The converter uses the information from the theme map to help construct the PDF.
120
124
 
125
+ === Basic Extended Theme
126
+
121
127
  Instead of designing a theme from scratch, you can extend the default theme using the `extends` key as follows:
122
128
 
123
129
  [source,yaml]
@@ -144,6 +150,8 @@ Alternatively, you can snag the file from your local installation using the foll
144
150
  cp "$ASCIIDOCTOR_PDF_DIR/data/themes/default-theme.yml" custom-theme.yml
145
151
  ====
146
152
 
153
+ === Key Nesting
154
+
147
155
  Keys may be nested to an arbitrary depth to eliminate redundant prefixes (an approach inspired by SASS).
148
156
  Once the theme is loaded, all keys are flattened into a single map of qualified keys.
149
157
  Nesting is simply a shorthand way of organizing the keys.
@@ -665,6 +673,7 @@ However, once you convert to PDF, you have to meet the font requirements of PDF
665
673
  That means you need to provide a font (at least a fallback font) that contains glyphs for all the characters you want to use.
666
674
  If you don't, you may notice that characters are missing (usually replaced with a box).
667
675
  There's nothing Asciidoctor can do to convince PDF to work with extended characters without the right fonts in play.
676
+ To see which characters are missing from the font, enable verbose mode (`-v`) when running Asciidoctor PDF.
668
677
 
669
678
  === Built-In (AFM) Fonts
670
679
 
@@ -702,7 +711,7 @@ Even though the built-in fonts require the content to be encoded in WINANSI, _yo
702
711
  Asciidoctor PDF encodes the content into WINANSI when building the PDF.
703
712
 
704
713
  WARNING: Built-in (AFM) fonts do not use the <<fallback-fonts,fallback fonts>>.
705
- In order for the fallback font to kick in, you must be using a TrueType font.
714
+ In order for the fallback font to kick in, you must use a TrueType font as the primary font.
706
715
 
707
716
  .WINANSI Encoding Behavior
708
717
  ****
@@ -851,10 +860,10 @@ This will allow you to use the same font names (aka families) in both your graph
851
860
 
852
861
  === Fallback Fonts
853
862
 
854
- If a TrueType font is missing a character needed to render the document, such as a special symbol, you can have Asciidoctor PDF look for the character in a fallback font.
863
+ If a TrueType font is missing a character needed to render the document, such as a special symbol or emoji, you can have Asciidoctor PDF look for the character in a fallback font.
855
864
 
856
865
  You only need to specify a single fallback font, typically one that provides a full set of symbols.
857
- If the character isn't found in the fallback font, it will mostly likely be replaced by a box (which is guaranteed if you're using the bundled fallback font).
866
+ If the character isn't found in the fallback font, it will mostly likely be replaced by a box (i.e., the notdef glyph), which is guaranteed if you're using the bundled fallback font.
858
867
 
859
868
  IMPORTANT: When defining the fallback font, you *must specify all four variants* (normal, bold, italic, bold_italic), even if you use the same font file for each.
860
869
 
@@ -863,6 +872,8 @@ Any glyph missing from an AFM font is simply replaced with the "`not`" glyph (`&
863
872
 
864
873
  CAUTION: The `default` theme does not use a fallback font.
865
874
  However, the built-in `default-with-fallback-font` theme does.
875
+ In fact, it provides two.
876
+ One for general writing in non-Latin languages (M+ 1p) and another for emoji (Noto Emoji).
866
877
  Using the fallback font slows down PDF generation slightly because it has to analyze every single character.
867
878
  It's use is not recommended for large documents.
868
879
  Instead, it's best to select primary fonts that have all the characters you need.
@@ -927,6 +938,20 @@ Now you're covered.
927
938
  If your custom TTF font is missing a glyph, Asciidoctor PDF will look in your fallback font.
928
939
  You don't need to reference the fallback font anywhere else in your theme file.
929
940
 
941
+ Here's another example that shows how to use an alternative emoji font (Symbola):
942
+
943
+ [source,yaml]
944
+ ----
945
+ extends: default-with-fallback-font
946
+ font:
947
+ catalog:
948
+ merge: true
949
+ Symbola: /path/to/symbola.ttf
950
+ fallbacks: [ M+ 1p, Symbola ]
951
+ ----
952
+
953
+ Now Asciidoctor PDF will look for the emoji in the Symbola font instead of the Noto Emoji font.
954
+
930
955
  == Keys
931
956
 
932
957
  This section lists all the keys that are available when creating a custom theme.
@@ -966,8 +991,8 @@ If the filename is absolute, it's used as is.
966
991
  If the filename begins with `./`, it's resolved as a theme file relative to the current theme file.
967
992
  Otherwise, the filename is resolved as a theme file in the normal way (relative to the value of the `pdf-themesdir` attribute).
968
993
 
969
- CAUTION: If you define the <<Custom fonts,font catalog>> in a theme that extends from `default`, you *must* redeclare any built-in font that on which the combined theme depends.
970
- You can find those definitions in default theme.
994
+ CAUTION: If you define the <<Custom fonts,font catalog>> in a theme that extends from `default`, you either have to redeclare any built-in font that on which the combined theme depends, or you need to set `merge: true` above your font definitions.
995
+ You can find the built-in definitions in default theme.
971
996
  You'll then need to include `GEM_FONTS_DIR` in the value of the `pdf-fontsdir` attribute so that the converter can find and register them.
972
997
  To avoid having to do this, make sure you set the font family for any element that declares a font family in the default theme.
973
998
 
@@ -3350,7 +3375,7 @@ The keys in this category control the arrangement of block images.
3350
3375
  align: inherit
3351
3376
 
3352
3377
  |caption-max-width^[5]^
3353
- |fit-content {vbar} none +
3378
+ |fit-content {vbar} none {vbar} <<measurement-units,Measurement>> +
3354
3379
  (default: none)
3355
3380
  |image:
3356
3381
  caption:
@@ -3839,7 +3864,7 @@ The keys in this category control the arrangement and style of tables and table
3839
3864
  caption-side: bottom
3840
3865
 
3841
3866
  |caption-max-width
3842
- |fit-content {vbar} none +
3867
+ |fit-content {vbar} none {vbar} <<measurement-units,Measurement>> +
3843
3868
  (default: fit-content)
3844
3869
  |table:
3845
3870
  caption-max-width: none
@@ -4475,7 +4500,7 @@ To avoid this problem, reduce the height of the running content periphery or mak
4475
4500
  title-style: toc
4476
4501
 
4477
4502
  |vertical-align
4478
- |top {vbar} middle {vbar} bottom +
4503
+ |top {vbar} middle {vbar} bottom {vbar} [top {vbar} middle {vbar} bottom, <<measurement-units,Measurement>>] +
4479
4504
  (default: middle)
4480
4505
  |header:
4481
4506
  vertical-align: middle
@@ -4600,7 +4625,7 @@ To avoid this problem, reduce the height of the running content periphery or mak
4600
4625
  title-style: toc
4601
4626
 
4602
4627
  |vertical-align
4603
- |top {vbar} middle {vbar} bottom +
4628
+ |top {vbar} middle {vbar} bottom {vbar} [top {vbar} middle {vbar} bottom, <<measurement-units,Measurement>>] +
4604
4629
  (default: middle)
4605
4630
  |footer:
4606
4631
  vertical-align: top
@@ -4795,7 +4820,7 @@ header:
4795
4820
  center:
4796
4821
  content: $header-recto-center-content
4797
4822
  ----
4798
- <1> You can use the `footer-vertical-align` attribute to slighly nudge the image up or down.
4823
+ <1> You can use the `image-vertical-align` key to slighly nudge the image up or down.
4799
4824
 
4800
4825
  CAUTION: By default, the image must fit in the allotted space for the running header or footer.
4801
4826
  Otherwise, you will run into layout issues.
@@ -4940,7 +4965,7 @@ These settings override equivalent keys defined in the theme file, where applica
4940
4965
 
4941
4966
  |pdf-page-size
4942
4967
  |https://github.com/prawnpdf/pdf-core/blob/0.6.0/lib/pdf/core/page_geometry.rb#L16-L68[Named size^] {vbar} <<measurement-units,Measurement[width, height]>>
4943
- |:pdf-page-size: 6in x 9in
4968
+ |:pdf-page-size: [6in, 9in]
4944
4969
 
4945
4970
  |pdf-folio-placement
4946
4971
  |virtual {vbar} virtual-inverted {vbar} physical {vbar} physical-inverted
@@ -9,7 +9,6 @@ rescue LoadError
9
9
  end unless defined? GMagick::Image
10
10
  require_relative 'pdf/measurements'
11
11
  require_relative 'pdf/sanitizer'
12
- require_relative 'pdf/temporary_path'
13
12
  require_relative 'pdf/text_transformer'
14
13
  require_relative 'pdf/ext'
15
14
  require_relative 'pdf/theme_loader'
@@ -12,11 +12,7 @@ module Asciidoctor
12
12
  module PDF
13
13
  class Converter < ::Prawn::Document
14
14
  include ::Asciidoctor::Converter
15
- if defined? ::Asciidoctor::Logging
16
- include ::Asciidoctor::Logging
17
- else
18
- include ::Asciidoctor::LoggingShim
19
- end
15
+ include ::Asciidoctor::Logging
20
16
  include ::Asciidoctor::Writer
21
17
  include ::Asciidoctor::Prawn::Extensions
22
18
 
@@ -133,17 +129,18 @@ module Asciidoctor
133
129
  result = send method_name, node
134
130
  else
135
131
  # TODO: delegate to convert_method_missing
136
- logger.warn %(conversion missing in backend #{@backend} for #{name})
132
+ logger.warn %(conversion missing in backend #{@backend} for #{name}) unless scratch?
137
133
  end
138
134
  # NOTE: inline nodes generate pseudo-HTML strings; the remainder write directly to PDF object
139
135
  ::Asciidoctor::Inline === node ? result : self
140
136
  end
141
137
 
142
138
  def traverse node, opts = {}
143
- if self != (prev_converter = node.document.converter)
144
- node.document.instance_variable_set :@converter, self
145
- else
139
+ # NOTE converter instance in scratch document gets duplicated; must be rewired to this one
140
+ if self == (prev_converter = node.document.converter)
146
141
  prev_converter = nil
142
+ else
143
+ node.document.instance_variable_set :@converter, self
147
144
  end
148
145
  if node.blocks?
149
146
  node.content
@@ -294,6 +291,7 @@ module Asciidoctor
294
291
 
295
292
  stamp_foreground_image doc, has_front_cover
296
293
  layout_cover_page doc, :back
294
+ clean_up_tmp_files
297
295
  nil
298
296
  end
299
297
 
@@ -330,6 +328,7 @@ module Asciidoctor
330
328
  @fallback_fonts = [*theme.font_fallbacks]
331
329
  @allow_uri_read = doc.attr? 'allow-uri-read'
332
330
  @cache_uri = doc.attr? 'cache-uri'
331
+ @tmp_files = {}
333
332
  if (bg_image = resolve_background_image doc, theme, 'page-background-image') && bg_image[0]
334
333
  @page_bg_image = { verso: bg_image, recto: bg_image }
335
334
  else
@@ -385,11 +384,12 @@ module Asciidoctor
385
384
  theme
386
385
  rescue
387
386
  if user_themesdir
388
- message = %(could not locate or load the pdf theme `#{theme_name}' in #{user_themesdir}; reverting to default theme)
387
+ message = %(could not locate or load the pdf theme `#{theme_name}' in #{user_themesdir})
389
388
  else
390
- message = %(could not locate or load the built-in pdf theme `#{theme_name}'; reverting to default theme)
389
+ message = %(could not locate or load the built-in pdf theme `#{theme_name}')
391
390
  end
392
- logger.error message
391
+ message += %( because of #{$!.class} #{$!.message}) unless ::SystemCallError === $!
392
+ logger.error %(#{message}; reverting to default theme)
393
393
  @themesdir = (theme = ThemeLoader.load_theme).__dir__
394
394
  theme
395
395
  end
@@ -760,36 +760,35 @@ module Asciidoctor
760
760
  shift_bottom = (shift_base * 2) / 3.0
761
761
  keep_together do |box_height = nil|
762
762
  push_scratch doc if scratch?
763
- if box_height && (@theme.admonition_background_color ||
764
- ((@theme.admonition_border_width || 0) > 0 && @theme.admonition_border_color))
765
- float do
766
- bounding_box [0, cursor], width: bounds.width, height: box_height do
767
- theme_fill_and_stroke_bounds :admonition
768
- end
769
- end
770
- end
763
+ theme_fill_and_stroke_block :admonition, box_height if box_height
771
764
  pad_box [0, cpad[1], 0, lpad[3]] do
772
765
  if box_height
766
+ label_height = [box_height, cursor].min
773
767
  if (rule_color = @theme.admonition_column_rule_color) &&
774
768
  (rule_width = @theme.admonition_column_rule_width || @theme.base_border_width) && rule_width > 0
775
769
  float do
776
- bounding_box [0, cursor], width: label_width + lpad[1], height: box_height do
777
- stroke_vertical_rule rule_color,
778
- at: bounds.right,
779
- line_style: (@theme.admonition_column_rule_style || :solid).to_sym,
780
- line_width: rule_width
770
+ rule_height = box_height
771
+ while rule_height > 0
772
+ rule_segment_height = [rule_height, cursor].min
773
+ bounding_box [0, cursor], width: label_width + lpad[1], height: rule_segment_height do
774
+ stroke_vertical_rule rule_color,
775
+ at: bounds.right,
776
+ line_style: (@theme.admonition_column_rule_style || :solid).to_sym,
777
+ line_width: rule_width
778
+ end
779
+ advance_page if (rule_height -= rule_segment_height) > 0
781
780
  end
782
781
  end
783
782
  end
784
783
  float do
785
- bounding_box [0, cursor], width: label_width, height: box_height do
784
+ bounding_box [0, cursor], width: label_width, height: label_height do
786
785
  if icons == 'font'
787
786
  # FIXME: we assume icon is square
788
787
  icon_size = fit_icon_to_bounds icon_size
789
788
  # NOTE: Prawn's vertical center is not reliable, so calculate it manually
790
789
  if label_valign == :center
791
790
  label_valign = :top
792
- if (vcenter_pos = (box_height - icon_size) * 0.5) > 0
791
+ if (vcenter_pos = (label_height - icon_size) * 0.5) > 0
793
792
  move_down vcenter_pos
794
793
  end
795
794
  end
@@ -805,22 +804,22 @@ module Asciidoctor
805
804
  position: label_align,
806
805
  vposition: label_valign,
807
806
  width: label_width,
808
- height: box_height,
807
+ height: label_height,
809
808
  fallback_font_name: fallback_svg_font_name,
810
809
  enable_web_requests: allow_uri_read,
811
810
  enable_file_requests_with_root: (::File.dirname icon_path),
812
811
  cache_images: cache_uri
813
- if (icon_height = (svg_size = svg_obj.document.sizing).output_height) > box_height
814
- icon_width = (svg_obj.resize height: (icon_height = box_height)).output_width
812
+ if (icon_height = (svg_size = svg_obj.document.sizing).output_height) > label_height
813
+ icon_width = (svg_obj.resize height: (icon_height = label_height)).output_width
815
814
  else
816
815
  icon_width = svg_size.output_width
817
816
  end
818
817
  svg_obj.draw
819
818
  svg_obj.document.warnings.each do |icon_warning|
820
819
  logger.warn %(problem encountered in image: #{icon_path}; #{icon_warning})
821
- end
820
+ end unless scratch?
822
821
  rescue
823
- logger.warn %(could not embed admonition icon: #{icon_path}; #{$!.message})
822
+ logger.warn %(could not embed admonition icon: #{icon_path}; #{$!.message}) unless scratch?
824
823
  end
825
824
  else
826
825
  begin
@@ -828,14 +827,14 @@ module Asciidoctor
828
827
  icon_aspect_ratio = image_info.width.fdiv image_info.height
829
828
  # NOTE: don't scale image up if smaller than label_width
830
829
  icon_width = [(to_pt image_info.width, :px), label_width].min
831
- if (icon_height = icon_width * (1 / icon_aspect_ratio)) > box_height
832
- icon_width *= box_height / icon_height
833
- icon_height = box_height # rubocop:disable Lint/UselessAssignment
830
+ if (icon_height = icon_width * (1 / icon_aspect_ratio)) > label_height
831
+ icon_width *= label_height / icon_height
832
+ icon_height = label_height # rubocop:disable Lint/UselessAssignment
834
833
  end
835
834
  embed_image image_obj, image_info, width: icon_width, position: label_align, vposition: label_valign
836
835
  rescue
837
836
  # QUESTION should we show the label in this case?
838
- logger.warn %(could not embed admonition icon: #{icon_path}; #{$!.message})
837
+ logger.warn %(could not embed admonition icon: #{icon_path}; #{$!.message}) unless scratch?
839
838
  end
840
839
  end
841
840
  else
@@ -846,7 +845,7 @@ module Asciidoctor
846
845
  # NOTE: Prawn's vertical center is not reliable, so calculate it manually
847
846
  if label_valign == :center
848
847
  label_valign = :top
849
- if (vcenter_pos = (box_height - (height_of_typeset_text label_text, line_height: 1)) * 0.5) > 0
848
+ if (vcenter_pos = (label_height - (height_of_typeset_text label_text, line_height: 1)) * 0.5) > 0
850
849
  move_down vcenter_pos
851
850
  end
852
851
  end
@@ -902,10 +901,6 @@ module Asciidoctor
902
901
  def convert_open node
903
902
  if node.style == 'abstract'
904
903
  convert_abstract node
905
- elsif node.style == 'partintro' && node.blocks.size == 1 && node.blocks[0].style == 'abstract'
906
- # TODO: process block title and id
907
- # TODO: process abstract child even when partintro has multiple blocks
908
- convert_abstract node.blocks[0]
909
904
  else
910
905
  doc = node.document
911
906
  keep_together_if node.option? 'unbreakable' do
@@ -1015,13 +1010,9 @@ module Asciidoctor
1015
1010
  # HACK: undo the margin below previous listing or literal block
1016
1011
  # TODO: allow this to be set using colist_margin_top
1017
1012
  unless at_page_top?
1018
- # NOTE: this logic won't work for a colist nested inside a list item until Asciidoctor 1.5.3
1019
1013
  if (self_idx = node.parent.blocks.index node) && self_idx > 0 &&
1020
1014
  [:listing, :literal].include?(node.parent.blocks[self_idx - 1].context)
1021
- move_up @theme.block_margin_bottom / 2.0
1022
- # or we could do...
1023
- #move_up @theme.block_margin_bottom
1024
- #move_down @theme.caption_margin_inside * 2
1015
+ move_up @theme.block_margin_bottom - @theme.outline_list_item_spacing
1025
1016
  end
1026
1017
  end
1027
1018
  add_dest_for_block node if node.id
@@ -1097,7 +1088,7 @@ module Asciidoctor
1097
1088
  term_kerning = default_kerning?
1098
1089
  end
1099
1090
  node.items.each do |terms, desc|
1100
- term_text = [*terms].map(&:text).join ?\n
1091
+ term_text = terms.map(&:text).join ?\n
1101
1092
  if (term_width = width_of term_text, inline_format: term_inline_format, kerning: term_kerning) > max_term_width
1102
1093
  max_term_width = term_width
1103
1094
  end
@@ -1138,7 +1129,6 @@ module Asciidoctor
1138
1129
  term_line_height = @theme.description_list_term_line_height || @theme.base_line_height
1139
1130
  line_metrics = theme_font(:description_list_term) { calc_line_metrics term_line_height }
1140
1131
  node.items.each do |terms, desc|
1141
- terms = [*terms]
1142
1132
  # NOTE: don't orphan the terms (keep together terms and at least one line of content)
1143
1133
  allocate_space_for_list_item line_metrics, (terms.size + 1), ((@theme.description_list_term_spacing || 0) + 0.05)
1144
1134
  theme_font :description_list_term do
@@ -1212,7 +1202,7 @@ module Asciidoctor
1212
1202
  if Bullets.key? candidate
1213
1203
  bullet_type = candidate
1214
1204
  else
1215
- logger.warn %(unknown unordered list style: #{candidate})
1205
+ logger.warn %(unknown unordered list style: #{candidate}) unless scratch?
1216
1206
  bullet_type = :disc
1217
1207
  end
1218
1208
  end
@@ -1318,13 +1308,13 @@ module Asciidoctor
1318
1308
  marker = %(#{index}.)
1319
1309
  else
1320
1310
  complex = node.complex?
1321
- logger.warn %(unknown list type #{list_type.inspect})
1311
+ logger.warn %(unknown list type #{list_type.inspect}) unless scratch?
1322
1312
  marker = @theme.ulist_marker_disc_content || Bullets[:disc]
1323
1313
  end
1324
1314
 
1325
1315
  if marker
1326
1316
  if marker_style[:font_family] == 'fa'
1327
- logger.info { 'deprecated fa icon set found in theme; use fas, far, or fab instead' }
1317
+ logger.info 'deprecated fa icon set found in theme; use fas, far, or fab instead' unless scratch?
1328
1318
  marker_style[:font_family] = FontAwesomeIconSets.find {|candidate| (icon_font_data candidate).yaml[candidate].value? marker } || 'fas'
1329
1319
  end
1330
1320
  marker_gap = rendered_width_of_char 'x'
@@ -1359,7 +1349,7 @@ module Asciidoctor
1359
1349
  def traverse_list_item node, list_type, opts = {}
1360
1350
  if list_type == :dlist # qanda
1361
1351
  terms, desc = node
1362
- [*terms].each {|term| layout_prose %(<em>#{term.text}</em>), (opts.merge margin_top: 0, margin_bottom: @theme.description_list_term_spacing) }
1352
+ terms.each {|term| layout_prose %(<em>#{term.text}</em>), (opts.merge margin_top: 0, margin_bottom: @theme.description_list_term_spacing) }
1363
1353
  if desc
1364
1354
  layout_prose desc.text, (opts.merge hyphenate: true) if desc.text?
1365
1355
  traverse desc
@@ -1390,18 +1380,28 @@ module Asciidoctor
1390
1380
  elsif (image_path = resolve_image_path node, target, (opts.fetch :relative_to_imagesdir, true), image_format)
1391
1381
  if image_format == 'pdf'
1392
1382
  if ::File.readable? image_path
1383
+ if (id = node.id)
1384
+ add_dest_block = proc do
1385
+ node.set_attr 'pdf-destination', (node_dest = dest_top)
1386
+ add_dest id, node_dest
1387
+ end
1388
+ end
1393
1389
  # NOTE: import_page automatically advances to next page afterwards
1394
1390
  # QUESTION should we add destination to top of imported page?
1395
1391
  if (pgnums = node.attr 'pages', nil, false)
1396
1392
  (resolve_pagenums pgnums).each_with_index do |pgnum, idx|
1397
- import_page image_path, page: pgnum, replace: (idx == 0 ? page.empty? : true)
1393
+ if idx == 0
1394
+ import_page image_path, page: pgnum, replace: page.empty?, &add_dest_block
1395
+ else
1396
+ import_page image_path, page: pgnum, replace: true
1397
+ end
1398
1398
  end
1399
1399
  else
1400
- import_page image_path, page: [(node.attr 'page', nil, 1).to_i, 1].max, replace: page.empty?
1400
+ import_page image_path, page: [(node.attr 'page', nil, 1).to_i, 1].max, replace: page.empty?, &add_dest_block
1401
1401
  end
1402
1402
  else
1403
1403
  # QUESTION should we use alt text in this case?
1404
- logger.warn %(pdf to insert not found or not readable: #{image_path})
1404
+ logger.warn %(pdf to insert not found or not readable: #{image_path}) unless scratch?
1405
1405
  end
1406
1406
  return
1407
1407
  elsif !(::File.readable? image_path)
@@ -1473,7 +1473,7 @@ module Asciidoctor
1473
1473
  svg_obj.draw
1474
1474
  svg_obj.document.warnings.each do |img_warning|
1475
1475
  logger.warn %(problem encountered in image: #{image_path}; #{img_warning})
1476
- end
1476
+ end unless scratch?
1477
1477
  draw_image_border image_cursor, rendered_w, rendered_h, alignment unless node.role? && (node.has_role? 'noborder')
1478
1478
  if (link = node.attr 'link', nil, false)
1479
1479
  add_link_to_image link, { width: rendered_w, height: rendered_h }, position: alignment, y: image_y
@@ -1516,8 +1516,6 @@ module Asciidoctor
1516
1516
  rescue
1517
1517
  on_image_error :exception, node, target, (opts.merge message: %(could not embed image: #{image_path}; #{$!.message}#{::Prawn::Errors::UnsupportedImageType === $! ? '; install prawn-gmagick gem to add support' : ''}))
1518
1518
  end
1519
- ensure
1520
- unlink_tmp_file image_path if image_path
1521
1519
  end
1522
1520
 
1523
1521
  def draw_image_border top, w, h, alignment
@@ -1537,7 +1535,7 @@ module Asciidoctor
1537
1535
  end
1538
1536
 
1539
1537
  def on_image_error _reason, node, target, opts = {}
1540
- logger.warn opts[:message] if opts.key? :message
1538
+ logger.warn opts[:message] if (opts.key? :message) && !scratch?
1541
1539
  alt_text_vars = { alt: (node.attr 'alt'), target: target }
1542
1540
  alt_text_template = @theme.image_alt_content || '%{link}[%{alt}]%{/link} | <em>%{target}</em>'
1543
1541
  if (link = node.attr 'link', nil, false)
@@ -1688,8 +1686,6 @@ module Asciidoctor
1688
1686
  lexer_opts = { nowrap: true, noclasses: true, stripnl: false, style: style }
1689
1687
  lexer_opts[:startinline] = !(node.option? 'mixed') if lexer.name == 'PHP'
1690
1688
  source_string, conum_mapping = extract_conums source_string
1691
- # NOTE: pygments.rb strips trailing whitespace; preserve it in case there are conums on last line
1692
- num_trailing_spaces = source_string.length - (source_string = source_string.rstrip).length if conum_mapping
1693
1689
  # NOTE: highlight can return nil if something goes wrong; fallback to encoded source string if this happens
1694
1690
  result = (lexer.highlight source_string, options: lexer_opts) || (node.apply_subs source_string, [:specialcharacters])
1695
1691
  if node.attr? 'highlight', nil, false
@@ -1711,7 +1707,7 @@ module Asciidoctor
1711
1707
  postprocess = true
1712
1708
  end
1713
1709
  fragments = text_formatter.format result
1714
- fragments = restore_conums fragments, conum_mapping, num_trailing_spaces, linenums, highlight_lines if postprocess
1710
+ fragments = restore_conums fragments, conum_mapping, linenums, highlight_lines if postprocess
1715
1711
  source_chunks = guard_indentation_in_fragments fragments
1716
1712
  end
1717
1713
  when 'rouge'
@@ -1793,7 +1789,7 @@ module Asciidoctor
1793
1789
  string = string.split(LF).map.with_index {|line, line_num|
1794
1790
  # FIXME: we get extra spaces before numbers if more than one on a line
1795
1791
  if line.include? '<'
1796
- line.gsub CalloutExtractRx do
1792
+ line = line.gsub CalloutExtractRx do
1797
1793
  # honor the escape
1798
1794
  if $1 == ?\\
1799
1795
  $&.sub $1, ''
@@ -1802,9 +1798,14 @@ module Asciidoctor
1802
1798
  ''
1803
1799
  end
1804
1800
  end
1805
- else
1806
- line
1801
+ # NOTE use first position to store space that precedes conums
1802
+ if (conum_mapping.key? line_num) && (line.end_with? ' ')
1803
+ trimmed_line = line.rstrip
1804
+ conum_mapping[line_num].unshift line.slice trimmed_line.length, line.length
1805
+ line = trimmed_line
1806
+ end
1807
1807
  end
1808
+ line
1808
1809
  }.join LF
1809
1810
  conum_mapping = nil if conum_mapping.empty?
1810
1811
  [string, conum_mapping]
@@ -1814,7 +1815,7 @@ module Asciidoctor
1814
1815
  #--
1815
1816
  # QUESTION can this be done more efficiently?
1816
1817
  # QUESTION can we reuse arrange_fragments_by_line?
1817
- def restore_conums fragments, conum_mapping, num_trailing_spaces = 0, linenums = nil, highlight_lines = nil
1818
+ def restore_conums fragments, conum_mapping, linenums = nil, highlight_lines = nil
1818
1819
  lines = []
1819
1820
  line_num = 0
1820
1821
  # reorganize the fragments into an array of lines
@@ -1846,7 +1847,7 @@ module Asciidoctor
1846
1847
  end
1847
1848
  line.unshift text: %(#{visible_line_num.to_s.rjust pad_size} ), linenum: visible_line_num, color: linenum_color if linenums
1848
1849
  if conum_mapping && (conums = conum_mapping.delete cur_line_num)
1849
- line << { text: ' ' * num_trailing_spaces } if last_line && num_trailing_spaces > 0
1850
+ line << { text: conums.shift } if ::String === conums[0]
1850
1851
  conum_text = conums.map {|num| conum_glyph num }.join ' '
1851
1852
  line << (conum_color ? { text: conum_text, color: conum_color } : { text: conum_text })
1852
1853
  end
@@ -2038,7 +2039,10 @@ module Asciidoctor
2038
2039
  end
2039
2040
 
2040
2041
  # NOTE: Prawn aborts if table data is empty, so ensure there's at least one row
2041
- table_data = ::Array.new(node.columns.size) { { 'content' => '' } } if table_data.empty?
2042
+ if table_data.empty?
2043
+ logger.warn message_with_context 'no rows found in table', source_location: node.source_location
2044
+ table_data << ::Array.new([node.columns.size, 1].max) { { content: '' } }
2045
+ end
2042
2046
 
2043
2047
  border_width = {}
2044
2048
  table_border_color = theme.table_border_color || theme.table_grid_color || theme.base_border_color
@@ -2368,7 +2372,7 @@ module Asciidoctor
2368
2372
  end
2369
2373
  %(<a id="#{target || node.id}">#{DummyText}</a>#{reftext})
2370
2374
  else
2371
- logger.warn %(unknown anchor type: #{node.type.inspect})
2375
+ logger.warn %(unknown anchor type: #{node.type.inspect}) unless scratch?
2372
2376
  end
2373
2377
  end
2374
2378
 
@@ -2416,14 +2420,14 @@ module Asciidoctor
2416
2420
  requested_icon_name = icon_name
2417
2421
  icon_set, icon_name = remapped_icon_name.split '-', 2
2418
2422
  glyph = (icon_font_data icon_set).unicode icon_name
2419
- logger.info { %(#{requested_icon_name} icon found in deprecated fa icon set; using #{icon_name} from #{icon_set} icon set instead) }
2423
+ logger.info { %(#{requested_icon_name} icon found in deprecated fa icon set; using #{icon_name} from #{icon_set} icon set instead) } unless scratch?
2420
2424
  # new name in Font Awesome >= 5 (but document is configured to use fa icon set)
2421
2425
  else
2422
2426
  font_data = nil
2423
2427
  if (resolved_icon_set = FontAwesomeIconSets.find {|candidate| (font_data = icon_font_data candidate).unicode icon_name rescue nil })
2424
2428
  icon_set = resolved_icon_set
2425
2429
  glyph = font_data.unicode icon_name
2426
- logger.info { %(#{icon_name} icon not found in deprecated fa icon set; using match found in #{resolved_icon_set} icon set instead) }
2430
+ logger.info { %(#{icon_name} icon not found in deprecated fa icon set; using match found in #{resolved_icon_set} icon set instead) } unless scratch?
2427
2431
  end
2428
2432
  end
2429
2433
  else
@@ -2450,7 +2454,7 @@ module Asciidoctor
2450
2454
  # TODO: support rotate and flip attributes
2451
2455
  %(<font name="#{icon_set}"#{size_attr}#{class_attr}>#{glyph}</font>)
2452
2456
  else
2453
- logger.warn %(#{icon_name} is not a valid icon name in the #{icon_set} icon set)
2457
+ logger.warn %(#{icon_name} is not a valid icon name in the #{icon_set} icon set) unless scratch?
2454
2458
  %([#{node.attr 'alt'}])
2455
2459
  end
2456
2460
  else
@@ -2472,7 +2476,7 @@ module Asciidoctor
2472
2476
  if ::File.readable? image_path
2473
2477
  width_attr = (width = preresolve_explicit_width node.attributes) ? %( width="#{width}") : ''
2474
2478
  fit_attr = (fit = node.attr 'fit', nil, false) ? %( fit="#{fit}") : ''
2475
- img = %(<img src="#{image_path}" format="#{image_format}" alt="[#{encode_quotes node.attr 'alt'}]"#{width_attr}#{fit_attr} tmp="#{TemporaryPath === image_path}">)
2479
+ img = %(<img src="#{image_path}" format="#{image_format}" alt="[#{encode_quotes node.attr 'alt'}]"#{width_attr}#{fit_attr}>)
2476
2480
  else
2477
2481
  logger.warn %(image to embed not found or not readable: #{image_path}) unless scratch?
2478
2482
  img = %([#{node.attr 'alt'}])
@@ -2590,13 +2594,14 @@ module Asciidoctor
2590
2594
  # QUESTION allow alignment per element on title page?
2591
2595
  title_align = (@theme.title_page_align || @base_align).to_sym
2592
2596
 
2593
- # TODO: disallow .pdf as image type
2597
+ # FIXME: disallow .pdf as image type
2594
2598
  if @theme.title_page_logo_display != 'none' && (logo_image_path = (doc.attr 'title-logo-image') || (logo_image_from_theme = @theme.title_page_logo_image))
2595
2599
  if (logo_image_path.include? ':') && logo_image_path =~ ImageAttributeValueRx
2596
2600
  logo_image_attrs = (AttributeList.new $2).parse %w(alt width height)
2597
2601
  if logo_image_from_theme
2598
2602
  relative_to_imagesdir = false
2599
- logo_image_path = ThemeLoader.resolve_theme_asset (sub_attributes_discretely doc, $1), @themesdir
2603
+ logo_image_path = sub_attributes_discretely doc, $1
2604
+ logo_image_path = ThemeLoader.resolve_theme_asset logo_image_path, @themesdir unless doc.is_uri? logo_image_path
2600
2605
  else
2601
2606
  relative_to_imagesdir = true
2602
2607
  logo_image_path = $1
@@ -2604,7 +2609,10 @@ module Asciidoctor
2604
2609
  else
2605
2610
  logo_image_attrs = {}
2606
2611
  relative_to_imagesdir = false
2607
- logo_image_path = ThemeLoader.resolve_theme_asset (sub_attributes_discretely doc, logo_image_path), @themesdir if logo_image_from_theme
2612
+ if logo_image_from_theme
2613
+ logo_image_path = sub_attributes_discretely doc, logo_image_path
2614
+ logo_image_path = ThemeLoader.resolve_theme_asset logo_image_path, @themesdir unless doc.is_uri? logo_image_path
2615
+ end
2608
2616
  end
2609
2617
  logo_image_attrs['target'] = logo_image_path
2610
2618
  if (logo_align = [(logo_image_attrs.delete 'align'), @theme.title_page_logo_align, title_align.to_s].find {|val| (BlockAlignmentNames.include? val) })
@@ -2730,8 +2738,6 @@ module Asciidoctor
2730
2738
  image_page image_path, (image_opts.merge canvas: true)
2731
2739
  end
2732
2740
  end
2733
- ensure
2734
- unlink_tmp_file image_path if image_path
2735
2741
  end
2736
2742
 
2737
2743
  def stamp_foreground_image doc, has_front_cover
@@ -2866,6 +2872,7 @@ module Asciidoctor
2866
2872
  raise ArgumentError, 'invalid subject'
2867
2873
  end
2868
2874
  category_caption = (category = opts[:category]) ? %(#{category}_caption) : 'caption'
2875
+ container_width = bounds.width
2869
2876
  block_align = opts.delete :block_align
2870
2877
  if (align = @theme[%(#{category_caption}_align)] || @theme.caption_align)
2871
2878
  align = align == 'inherit' ? (block_align || @base_align) : align.to_sym
@@ -2873,16 +2880,21 @@ module Asciidoctor
2873
2880
  align = @base_align.to_sym
2874
2881
  end
2875
2882
  indent_by = [0, 0]
2876
- if block_align
2877
- block_width = opts.delete :block_width
2878
- if (max_width = opts.delete :max_width) && max_width != 'none' &&
2879
- (max_width != 'fit-content' || (max_width = block_width)) && (remainder = bounds.width - max_width) > 0
2883
+ block_width = opts.delete :block_width
2884
+ if (max_width = opts.delete :max_width) && max_width != 'none'
2885
+ if max_width == 'fit-content'
2886
+ max_width = block_width || container_width
2887
+ else
2888
+ max_width = [max_width.to_f / 100 * bounds.width, bounds.width].min if ::String === max_width && (max_width.end_with? '%')
2889
+ block_align = align
2890
+ end
2891
+ if (remainder = container_width - max_width) > 0
2880
2892
  case block_align
2881
2893
  when :right
2882
2894
  indent_by = [remainder, 0]
2883
2895
  when :center
2884
2896
  indent_by = [(side_margin = remainder * 0.5), side_margin]
2885
- else # :left
2897
+ else # :left, nil
2886
2898
  indent_by = [0, remainder]
2887
2899
  end
2888
2900
  end
@@ -3020,7 +3032,11 @@ module Asciidoctor
3020
3032
  start_dots = last_fragment_pos.right + hanging_indent
3021
3033
  last_fragment_cursor = last_fragment_pos.top + line_metrics.padding_top
3022
3034
  # NOTE this will be incorrect if wrapped line is all monospace
3023
- start_cursor = last_fragment_cursor if start_cursor - last_fragment_cursor > line_metrics.height
3035
+ if (last_fragment_page_number = last_fragment_pos.page_number) > start_page_number ||
3036
+ (start_cursor - last_fragment_cursor) > line_metrics.height
3037
+ start_page_number = last_fragment_page_number
3038
+ start_cursor = last_fragment_cursor
3039
+ end
3024
3040
  end
3025
3041
  end_page_number = page_number
3026
3042
  end_cursor = cursor
@@ -3066,7 +3082,7 @@ module Asciidoctor
3066
3082
  icon_data = (AdmonitionIcons[key] || {}).merge icon_data
3067
3083
  if (icon_name = icon_data[:name])
3068
3084
  unless icon_name.start_with?(*IconSetPrefixes)
3069
- logger.info { %(#{key} admonition in theme uses icon from deprecated fa icon set; use fas, far, or fab instead) }
3085
+ logger.info { %(#{key} admonition in theme uses icon from deprecated fa icon set; use fas, far, or fab instead) } unless scratch?
3070
3086
  icon_data[:name] = %(fa-#{icon_name}) unless icon_name.start_with? 'fa-'
3071
3087
  end
3072
3088
  end
@@ -3273,7 +3289,7 @@ module Asciidoctor
3273
3289
  content = transform_text content, @text_transform if @text_transform
3274
3290
  end
3275
3291
  formatted_text_box parse_text(content, color: @font_color, inline_format: [normalize: true]),
3276
- at: [left, bounds.top - trim_styles[:padding][0] - trim_styles[:content_offset] + (trim_styles[:valign] == :center ? font.descender * 0.5 : 0)],
3292
+ at: [left, bounds.top - trim_styles[:padding][0] - trim_styles[:content_offset] + ((Array trim_styles[:valign])[0] == :center ? font.descender * 0.5 : 0)],
3277
3293
  width: colwidth,
3278
3294
  height: trim_styles[:prose_content_height],
3279
3295
  align: colspec[:align],
@@ -3299,6 +3315,10 @@ module Asciidoctor
3299
3315
 
3300
3316
  def allocate_running_content_layout doc, page, periphery, cache
3301
3317
  cache[layout = page.layout] ||= begin
3318
+ valign, valign_offset = @theme[%(#{periphery}_vertical_align)]
3319
+ if (valign = (valign || :middle).to_sym) == :middle
3320
+ valign = :center
3321
+ end
3302
3322
  trim_styles = {
3303
3323
  line_metrics: (trim_line_metrics = calc_line_metrics @theme[%(#{periphery}_line_height)] || @theme.base_line_height),
3304
3324
  # NOTE we've already verified this property is set
@@ -3313,7 +3333,7 @@ module Asciidoctor
3313
3333
  column_rule_style: (@theme[%(#{periphery}_column_rule_style)] || :solid).to_sym,
3314
3334
  column_rule_width: (trim_column_rule_color ? @theme[%(#{periphery}_column_rule_width)] || 0 : 0),
3315
3335
  column_rule_spacing: (@theme[%(#{periphery}_column_rule_spacing)] || 0),
3316
- valign: (val = (@theme[%(#{periphery}_vertical_align)] || :middle).to_sym) == :middle ? :center : val,
3336
+ valign: valign_offset ? [valign, valign_offset] : valign,
3317
3337
  img_valign: @theme[%(#{periphery}_image_vertical_align)],
3318
3338
  left: {
3319
3339
  recto: (trim_left_recto = @page_margin_by_side[:recto][3]),
@@ -3338,7 +3358,7 @@ module Asciidoctor
3338
3358
  }
3339
3359
  case trim_styles[:img_valign]
3340
3360
  when nil
3341
- trim_styles[:img_valign] = trim_styles[:valign]
3361
+ trim_styles[:img_valign] = valign
3342
3362
  when 'middle'
3343
3363
  trim_styles[:img_valign] = :center
3344
3364
  when 'top', 'center', 'bottom'
@@ -3392,10 +3412,9 @@ module Asciidoctor
3392
3412
  ColumnPositions.each do |position|
3393
3413
  unless (val = @theme[%(#{periphery}_#{side}_#{position}_content)]).nil_or_empty?
3394
3414
  if (val.include? ':') && val =~ ImageAttributeValueRx
3395
- # TODO: support image URL
3396
- if ::File.readable? (image_path = (ThemeLoader.resolve_theme_asset $1, @themesdir))
3397
- image_attrs = (AttributeList.new $2).parse %w(alt width)
3398
- image_opts = resolve_image_options image_path, image_attrs, container_size: [colspec_dict[side][position][:width], trim_styles[:content_height]], format: image_attrs['format']
3415
+ image_attrs = (AttributeList.new $2).parse %w(alt width)
3416
+ if (image_path = resolve_image_path doc, $1, @themesdir, (image_format = image_attrs['format'])) && (::File.readable? image_path)
3417
+ image_opts = resolve_image_options image_path, image_attrs, container_size: [colspec_dict[side][position][:width], trim_styles[:content_height]], format: image_format
3399
3418
  side_content[position] = [image_path, image_opts, image_attrs['link']]
3400
3419
  else
3401
3420
  # NOTE allows inline image handler to report invalid reference and replace with alt text
@@ -3550,7 +3569,7 @@ module Asciidoctor
3550
3569
  true
3551
3570
  end
3552
3571
  end
3553
- raise ::Errno::ENOENT, %(#{path} not found in #{fonts_dir}) unless found
3572
+ raise ::Errno::ENOENT, ((File.absolute_path? path) ? %(#{path} not found) : %(#{path} not found in #{fonts_dir.gsub ValueSeparatorRx, ' or '})) unless found
3554
3573
  end
3555
3574
  register_font key => styles
3556
3575
  end
@@ -3620,7 +3639,7 @@ module Asciidoctor
3620
3639
  if (b_color = @theme[%(#{category}_border_color)]) == 'transparent'
3621
3640
  b_color = @page_bg_color
3622
3641
  end
3623
- b_radius = (@theme[%(#{category}_border_radius)] || 0) + b_width if b_width
3642
+ b_radius = (@theme[%(#{category}_border_radius)] || 0) + (b_width || 0)
3624
3643
  if b_width && b_color
3625
3644
  if b_color == @page_bg_color # let page background cut into block background
3626
3645
  b_gap_color, b_shift = @page_bg_color, b_width
@@ -4000,46 +4019,53 @@ module Asciidoctor
4000
4019
  # the temporary file. If the target is a URI and the allow-uri-read attribute
4001
4020
  # is not set, or the URI cannot be read, this method returns a nil value.
4002
4021
  #
4003
- # When a temporary file is used, the TemporaryPath type is mixed into the path string.
4004
- def resolve_image_path node, image_path = nil, relative_to_imagesdir = true, image_format = nil
4022
+ # When a temporary file is used, the file is stored in @tmp_files to be cleaned up after conversion.
4023
+ def resolve_image_path node, image_path = nil, relative_to = true, image_format = nil
4005
4024
  doc = node.document
4006
- imagesdir = relative_to_imagesdir ? (resolve_imagesdir doc) : nil
4025
+ imagesdir = relative_to == true ? (resolve_imagesdir doc) : relative_to
4007
4026
  image_path ||= node.attr 'target'
4008
4027
  image_format ||= ::Asciidoctor::Image.format image_path, (::Asciidoctor::Image === node ? node.attributes : nil)
4009
- # NOTE currently used for inline images
4028
+ # NOTE base64 logic currently used for inline images
4010
4029
  if ::Base64 === image_path
4011
- tmp_image = ::Tempfile.create ['image-', image_format && %(.#{image_format})]
4030
+ return (tmp_file = @tmp_files[image_path]) && tmp_file.path if @tmp_files.key? image_path
4031
+ @tmp_files[image_path] = tmp_image = ::Tempfile.create ['image-', image_format && %(.#{image_format})]
4012
4032
  tmp_image.binmode unless image_format == 'svg'
4013
4033
  begin
4014
4034
  tmp_image.write ::Base64.decode64 image_path
4015
- tmp_image.path.extend TemporaryPath
4035
+ tmp_image.close
4036
+ tmp_image.path
4016
4037
  rescue
4017
- nil
4018
- ensure
4038
+ @tmp_files[image_path] = nil
4019
4039
  tmp_image.close
4040
+ unlink_tmp_file tmp_image
4041
+ nil
4020
4042
  end
4021
4043
  # handle case when image is a URI
4022
- elsif (node.is_uri? image_path) || (imagesdir && (node.is_uri? imagesdir) &&
4023
- (image_path = (node.normalize_web_path image_path, imagesdir, false)))
4044
+ elsif (node.is_uri? image_path) ||
4045
+ (imagesdir && (node.is_uri? imagesdir) && (image_path = node.normalize_web_path image_path, imagesdir, false))
4024
4046
  unless allow_uri_read
4025
4047
  logger.warn %(allow-uri-read is not enabled; cannot embed remote image: #{image_path}) unless scratch?
4026
4048
  return
4027
4049
  end
4028
- if cache_uri
4050
+ if @tmp_files.key? image_path
4051
+ return (tmp_file = @tmp_files[image_path]) && tmp_file.path
4052
+ elsif cache_uri
4029
4053
  Helpers.require_library 'open-uri/cached', 'open-uri-cached' unless defined? ::OpenURI::Cache
4030
4054
  else
4031
4055
  ::OpenURI
4032
4056
  end
4033
- tmp_image = ::Tempfile.create ['image-', image_format && %(.#{image_format})]
4057
+ @tmp_files[image_path] = tmp_image = ::Tempfile.create ['image-', image_format && %(.#{image_format})]
4034
4058
  tmp_image.binmode if (binary = image_format != 'svg')
4035
4059
  begin
4036
4060
  ::OpenURI.open_uri(image_path, (binary ? 'rb' : 'r')) {|fd| tmp_image.write fd.read }
4037
- tmp_image.path.extend TemporaryPath
4061
+ tmp_image.close
4062
+ tmp_image.path
4038
4063
  rescue
4039
4064
  logger.warn %(could not retrieve remote image: #{image_path}; #{$!.message}) unless scratch?
4040
- nil
4041
- ensure
4065
+ @tmp_files[image_path] = nil
4042
4066
  tmp_image.close
4067
+ unlink_tmp_file tmp_image
4068
+ nil
4043
4069
  end
4044
4070
  # handle case when image is a local file
4045
4071
  else
@@ -4063,15 +4089,14 @@ module Asciidoctor
4063
4089
  return []
4064
4090
  elsif (image_path.include? ':') && image_path =~ ImageAttributeValueRx
4065
4091
  image_attrs = (AttributeList.new $2).parse %w(alt width)
4092
+ image_format = image_attrs['format']
4066
4093
  if from_theme
4067
- # TODO: support remote image when loaded from theme
4068
- image_path = ThemeLoader.resolve_theme_asset (sub_attributes_discretely doc, $1), @themesdir
4094
+ image_path = resolve_image_path doc, (sub_attributes_discretely doc, $1), @themesdir, image_format
4069
4095
  else
4070
- image_path = resolve_image_path doc, $1, true, (image_format = image_attrs['format'])
4096
+ image_path = resolve_image_path doc, $1, true, image_format
4071
4097
  end
4072
4098
  elsif from_theme
4073
- # TODO: support remote image when loaded from theme
4074
- image_path = ThemeLoader.resolve_theme_asset (sub_attributes_discretely doc, image_path), @themesdir
4099
+ image_path = resolve_image_path doc, (sub_attributes_discretely doc, image_path), @themesdir
4075
4100
  else
4076
4101
  image_path = resolve_image_path doc, image_path, false
4077
4102
  end
@@ -4079,7 +4104,7 @@ module Asciidoctor
4079
4104
  return unless image_path
4080
4105
 
4081
4106
  unless ::File.readable? image_path
4082
- logger.warn %(#{key.tr '-', ' '} not found or readable: #{image_path})
4107
+ logger.warn %(#{key.to_s.tr '-_', ' '} not found or readable: #{image_path})
4083
4108
  return
4084
4109
  end
4085
4110
 
@@ -4279,13 +4304,16 @@ module Asciidoctor
4279
4304
  link_annotation [image_x, (image_y - image_height), (image_x + image_width), image_y], Border: [0, 0, 0], A: { Type: :Action, S: :URI, URI: uri.as_pdf }
4280
4305
  end
4281
4306
 
4282
- # QUESTION is there a better way to do this?
4283
- # I suppose we could have @tmp_files as an instance variable on converter instead
4284
- # It might be sufficient to delete temporary files once per conversion
4285
- def unlink_tmp_file path
4286
- path.unlink if TemporaryPath === path && path.exist?
4307
+ def clean_up_tmp_files
4308
+ @tmp_files.values.each {|tmp_file| unlink_tmp_file tmp_file if tmp_file }
4309
+ @tmp_files.clear
4310
+ end
4311
+
4312
+ def unlink_tmp_file file
4313
+ path = file.path
4314
+ ::File.unlink path if ::File.exist? path
4287
4315
  rescue
4288
- logger.warn %(could not delete temporary image: #{path}; #{$!.message})
4316
+ logger.warn %(could not delete temporary file: #{path}; #{$!.message}) unless scratch?
4289
4317
  end
4290
4318
 
4291
4319
  def apply_subs_discretely doc, value, opts = {}
@@ -4308,7 +4336,7 @@ module Asciidoctor
4308
4336
 
4309
4337
  def sub_attributes_discretely doc, value
4310
4338
  doc.set_attr 'attribute-missing', 'skip' unless (attribute_missing = doc.attr 'attribute-missing') == 'skip'
4311
- value = doc.apply_subs value
4339
+ value = doc.apply_subs value, [:attributes]
4312
4340
  doc.set_attr 'attribute-missing', attribute_missing unless attribute_missing == 'skip'
4313
4341
  value
4314
4342
  end
@@ -4394,11 +4422,14 @@ module Asciidoctor
4394
4422
  def init_scratch_prototype
4395
4423
  @save_state = nil
4396
4424
  @scratch_depth = 0
4425
+ # NOTE can't marshall tmp_files, so clear them before creating prototype
4426
+ saved_tmp_files, @tmp_files = @tmp_files, {}
4397
4427
  # IMPORTANT don't set font before using Marshal, it causes serialization to fail
4398
4428
  @prototype = ::Marshal.load ::Marshal.dump self
4399
- @prototype.state.store.info.data[:Scratch] = true
4429
+ @prototype.state.store.info.data[:Scratch] = @prototype.text_formatter.scratch = true
4400
4430
  # NOTE we're now starting a new page each time, so no need to do it here
4401
4431
  #@prototype.start_new_page if @prototype.page_number == 0
4432
+ @tmp_files = saved_tmp_files
4402
4433
  end
4403
4434
 
4404
4435
  def push_scratch doc