asciidoctor-pdf 1.5.0.alpha.17 → 1.5.0.alpha.18

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: fcb6c79a944028551c1ec56650df511a0f4be9984d379de22c891031ce743fe2
4
- data.tar.gz: d75bce156557910b7aa82c703b97fd266a5035cd4b37707756ca2f3e29359363
3
+ metadata.gz: fed971e3655c6b460cdf90ba7205802ad7060710287123a36df56b1e75dd8257
4
+ data.tar.gz: 888b1ef50e7e690bab7661ee0b10d35671376e85352389ccc8f1bf6b11798cfc
5
5
  SHA512:
6
- metadata.gz: be77878203c03b479682a80f50baf8278599b6c9d5d24e3dba46e2ad798f26641f223a6b620cff240d6e893eed4d12804b0693f5dbbd926b79e65c40c704e4cc
7
- data.tar.gz: 2c3d80e93665b076bda8c1c6b4d6dd59225eb6437d9ad679ebed6995ad1523cadf215a0d6dbe3bab461a089ecf1433bab259cc8bdab519737029659acc175355
6
+ metadata.gz: e29597fcbbb5c7cc2b822ad5844f0d656fc5890a1b6fb062511f9fb88a68204d5cde0f98b897b9f448c24897c53a9b221328a53ec8ef2f922f769d9f707153ed
7
+ data.tar.gz: a0275c1a8cee51ccbcf69102d47508f892784637ed0727a4c507c7d3f538b632aa2e87430e23146c7b93a295a95e4057f794f3b7693a1891517106f16c20b563
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/master[commit history] on GitHub.
7
7
 
8
+ == 1.5.0.alpha.18 (2019-06-01) - @mojavelinux
9
+
10
+ * restore compatibility with Asciidoctor back to 1.5.3 and add verification to test matrix (#1038)
11
+ * allow theme to set text indent for paragraphs using prose_text_indent (#191)
12
+ * allow theme to set spacing between adjacent paragraphs using prose_margin_inner (#191)
13
+ * show parts in toc when toclevels=0 (#783)
14
+ * add support for autonumbered callouts in source blocks (#1076)
15
+ * fix duplication of footnotes in keep together regions (#1047)
16
+ * display standalone preamble in book normally (#1051)
17
+ * allow outline depth to be set using outlinelevels attribute independent of toclevels (#1054)
18
+ * fix compounding cell padding (#1053)
19
+ * add support for qanda list (#1013)
20
+ * fix parsing of bibref and link inside footnote text (#1061)
21
+ * restore square brackets around ID of bibliography entry with custom ID (#1065)
22
+ * add page_numbering_start_at key to theme to control start page for page numbering (#1041)
23
+ * don't allow running_content_start_at key to affect page numbering (#1041)
24
+ * substitute \{chapter-title} property on front matter pages (replace with doctitle and toc-title, respectively, when running content starts before first page of body) (#1040)
25
+ * allow side margins to be set on elements on title page (#824)
26
+ * don't promote preamble to preface if preface-title attribute is empty
27
+ * expand padding value for running content (header and footer) to array
28
+ * add support for unnumbered (and no-bullet) style on ordered list (#1073)
29
+ * add visual regression capability to test suite (@beatchristen)
30
+ * ensure index section doesn't get numbered when using Asciidoctor < 1.5.7
31
+ * add part signifier and part number to part title if partnums is set; allow signifier to be customized using part-signifier attribute (#597)
32
+ * add support for the chapter-signifier attribute as the prefered alternative to chapter-label
33
+ * warn if the image referenced in the running content cannot be found (#731)
34
+
8
35
  == 1.5.0.alpha.17 (2019-04-23) - @mojavelinux
9
36
 
10
37
  * drop support for Ruby < 2.3 (and installation will fail for Ruby < 2.1)
@@ -34,8 +61,8 @@ For a detailed view of what has changed, refer to the {uri-repo}/commits/master[
34
61
  * handle case when uri to make breakable is empty (#936)
35
62
  * add support for frame=ends as alternative to frame=topbot on table
36
63
  * allow table frame and grid to be set globally using the table-frame and table-grid attributes (#822)
37
- * disable table stripes by default
38
- * allow table stripes to be enabled globally using table-stripes attribute
64
+ * disable table stripes by default (#1049)
65
+ * allow table stripes to be enabled globally using table-stripes attribute (#1049)
39
66
  * use new logging subsystem, if available; otherwise, use shim (#905)
40
67
  * allow alignment of list text to be controlled using roles (#182)
41
68
  * allow text alignment to be set for abstract (#893)
@@ -56,6 +83,7 @@ For a detailed view of what has changed, refer to the {uri-repo}/commits/master[
56
83
  * guard against pygments returning nil (#884)
57
84
  * encode quotes in alt text of inline image (#977)
58
85
  * fix crash when menu macro is used in a section or block title (#934)
86
+ * remove duplicate message when syntax highlighter is unavailable; don't crash processor (#1078)
59
87
  * only look for the start attribute on the code block itself when highlighting with rouge
60
88
  * apply block styling to background for line-oriented tokens in rouge by default
61
89
  * detect pagenum ranges in index when media is print or prepress (#906)
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
- v1.5.0.alpha.17, 2019-04-23
3
+ v1.5.0.alpha.18, 2019-06-01
4
4
  // Settings:
5
5
  :experimental:
6
6
  :idprefix:
@@ -24,7 +24,7 @@ endif::[]
24
24
  :project-name: Asciidoctor PDF
25
25
  :project-handle: asciidoctor-pdf
26
26
  // Variables:
27
- :release-version: 1.5.0.alpha.17
27
+ :release-version: 1.5.0.alpha.18
28
28
  // URIs:
29
29
  :uri-asciidoctor: http://asciidoctor.org
30
30
  :uri-gem: http://rubygems.org/gems/asciidoctor-pdf
@@ -41,6 +41,7 @@ endif::[]
41
41
 
42
42
  ifdef::status[]
43
43
  image:https://img.shields.io/travis/asciidoctor/asciidoctor-pdf/master.svg[Build Status (Travis CI),link=https://travis-ci.org/asciidoctor/asciidoctor-pdf]
44
+ image:https://ci.appveyor.com/api/projects/status/524bhoms3j2dp1o3/branch/master?svg=true[Build Status (AppVeyor),link=https://ci.appveyor.com/project/asciidoctor/asciidoctor-pdf]
44
45
  image:https://img.shields.io/gem/v/asciidoctor-pdf.svg[Latest Release, link={uri-gem}]
45
46
  image:https://img.shields.io/badge/license-MIT-blue.svg[MIT License, link=#copyright]
46
47
  endif::[]
@@ -118,7 +119,7 @@ endif::[]
118
119
 
119
120
  == Prerequisites
120
121
 
121
- All that's needed is Ruby 2.3 or better and a few Ruby gems, which we explain how to install in the next section.
122
+ All that's needed is Ruby 2.3 or better and a few Ruby gems (including at least Asciidoctor 1.5.3), which we explain how to install in the next section.
122
123
 
123
124
  To check if you have Ruby available, use the `ruby` command to query the version installed:
124
125
 
@@ -756,7 +757,11 @@ To run the tests, simply invoke rspec via bundler.
756
757
 
757
758
  $ bundle exec rspec
758
759
 
759
- If you want to see the name of each test that is run, add the `-fd` option:
760
+ To disable the visual integration tests, pass the `` option:
761
+
762
+ $ bundle exec rspec -t ~integration
763
+
764
+ If you want to see the name of each test as it is run, add the `-fd` option:
760
765
 
761
766
  $ bundle exec rspec -fd
762
767
 
@@ -790,6 +795,28 @@ You can use the application to convert a document as follows:
790
795
 
791
796
  $ bundle exec asciidoctor-pdf /path/to/sample.adoc
792
797
 
798
+ === Install the Application (optional)
799
+
800
+ If you want to install the application globally so you can run it anywhere, use the following `rake` task:
801
+
802
+ $ bundle exec rake install
803
+
804
+ This task will package the gem and install it into your system gems.
805
+
806
+ If you want to install the gem using a separate command, first use the following `rake` task to build it:
807
+
808
+ $ rm -rf pkg
809
+ bundle exec rake build
810
+
811
+ This task packages the application as a gem and writes it to the [.path]_pkg_ directory.
812
+ A message will be printed to the console telling you the exact filename.
813
+ You can now use the `gem install` command to install it.
814
+
815
+ $ gem install pkg/*.gem
816
+
817
+ You'll want to pay attention to which Ruby installation you are installing the gem into.
818
+ If successful, the `asciidoctor-pdf` executable will be available on your PATH.
819
+
793
820
  endif::[]
794
821
 
795
822
  === Test a Pull Request
@@ -35,7 +35,7 @@ Gem::Specification.new do |s|
35
35
  s.require_paths = ['lib']
36
36
  #s.test_files = files.grep %r/^(?:test|spec|feature)\/.*$/
37
37
 
38
- s.add_runtime_dependency 'asciidoctor', '>= 1.5.0', '< 3.0.0'
38
+ s.add_runtime_dependency 'asciidoctor', '>= 1.5.3', '< 3.0.0'
39
39
  s.add_runtime_dependency 'prawn', '~> 2.2.0'
40
40
  s.add_runtime_dependency 'prawn-table', '~> 0.2.0'
41
41
  s.add_runtime_dependency 'prawn-templates', '~> 0.1.0'
@@ -50,4 +50,5 @@ Gem::Specification.new do |s|
50
50
  s.add_development_dependency 'rake', '~> 12.3.0'
51
51
  s.add_development_dependency 'rspec', '~> 3.8.0'
52
52
  s.add_development_dependency 'pdf-inspector', '~> 1.3.0'
53
+ s.add_development_dependency 'chunky_png', '~> 1.3.0'
53
54
  end
@@ -96,12 +96,33 @@ link:
96
96
  font_color: #002FA7
97
97
  outline_list:
98
98
  indent: $base_font_size * 1.5
99
+ footer:
100
+ height: $base_line_height_length * 2.5
101
+ line_height: 1
102
+ recto:
103
+ right:
104
+ content: '{page-number}'
105
+ verso:
106
+ left:
107
+ content: $footer_recto_right_content
99
108
  ----
100
109
 
101
110
  When creating a new theme, you only have to define the keys you want to override from the base theme, which is loaded prior to loading your custom theme.
102
111
  All the available keys are documented in <<Keys>>.
103
112
  The converter uses the information from the theme map to help construct the PDF.
104
113
 
114
+ Instead of writing a theme from scratch, you can extend the default theme using the `extends` key as follows:
115
+
116
+ [source,yaml]
117
+ ----
118
+ extends: default
119
+ base:
120
+ color: #ff0000
121
+ ----
122
+
123
+ You can also point the extends key at another custom theme to extend from it.
124
+ Currently, the base theme is always loaded first.
125
+
105
126
  WARNING: If you start a new theme from scratch, we strongly recommend defining TrueType fonts and specifying them in the `base` and `literal` categories.
106
127
  Otherwise, Asciidoctor PDF will use built-in AFM fonts, which can result in missing functionality and warnings.
107
128
 
@@ -876,6 +897,34 @@ When creating a theme, all keys are optional.
876
897
  Required keys are provided by the base theme.
877
898
  Therefore, you only have to declare keys that you want to override.
878
899
 
900
+ [#keys-extends]
901
+ === Extends
902
+
903
+ A theme can extend another theme using the `extends` key.
904
+ The extends key accepts either a single value or an array of values.
905
+ Each value is interpreted as a filename.
906
+ If the filename equals `default`, it resolves to the location of the default (built-in) theme.
907
+ If the filename is absolute, it's used as is.
908
+ If the filename begins with `./`, it's resolved as a theme file relative to the current theme file.
909
+ Otherwise, the filename is resolved as a theme file in the normal way (relative to the value of the `pdf-stylesdir` attribute).
910
+
911
+ Currently, the base theme is always loaded first.
912
+ Then, the files referenced by the extends key are loaded in order.
913
+ Finally, the keys in the current file are loaded.
914
+ Each time a theme is loaded, the keys are overlaid onto the keys from the previous theme.
915
+
916
+ [cols="3,4,5l"]
917
+ |===
918
+ |Key |Value Type |Example
919
+
920
+ |extends
921
+ |String or Array
922
+ (default: [])
923
+ |extends:
924
+ - default
925
+ - ./brand-theme.yml
926
+ |===
927
+
879
928
  [#keys-page]
880
929
  === Page
881
930
 
@@ -932,6 +981,12 @@ See <<Title Page>> for details.
932
981
  (default: A4)
933
982
  |page:
934
983
  size: Letter
984
+
985
+ |numbering_start_at
986
+ |title {vbar} toc {vbar} body +
987
+ (default: body)
988
+ |page:
989
+ numbering_start_at: toc
935
990
  |===
936
991
 
937
992
  . Page background images are automatically scaled to fit within the bounds of the page.
@@ -1615,8 +1670,23 @@ Typically, all the margin is placed on the bottom.
1615
1670
  (default: 12)
1616
1671
  |prose:
1617
1672
  margin_bottom: $vertical_spacing
1673
+
1674
+ |margin_inner^[1]^
1675
+ |<<measurement-units,Measurement>> +
1676
+ (default: $prose_margin_bottom)
1677
+ |prose:
1678
+ margin_inner: 0
1679
+
1680
+ |text_indent
1681
+ |<<measurement-units,Measurement>> +
1682
+ (default: _not set_)
1683
+ |prose:
1684
+ text_indent: 18
1618
1685
  |===
1619
1686
 
1687
+ . Controls the margin between adjacent paragraphs.
1688
+ Useful when using indented paragraphs.
1689
+
1620
1690
  [#keys-block]
1621
1691
  === Block
1622
1692
 
@@ -2955,6 +3025,56 @@ The keys in this category control the arrangement and style of tables and table
2955
3025
  . Applied to even rows by default; controlled using `stripes` attribute (even, odd, all, none) on table.
2956
3026
  //. `<parity>` can be `odd` (odd rows) or `even` (even rows).
2957
3027
 
3028
+ [#keys-footnotes]
3029
+ === Footnotes
3030
+
3031
+ The keys in this catagory control the style the list of footnotes at the end of the chapter (book) or document (otherwise).
3032
+ If the `footnotes-title` attribute is specified, it is styled as a block caption.
3033
+ The styling of the links is controlled by the global link styles.
3034
+
3035
+ [cols="3,4,5l"]
3036
+ |===
3037
+ |Key |Value Type |Example
3038
+
3039
+ 3+|[#key-prefix-footnotes]*Key Prefix:* <<key-prefix-footnotes,footnotes>>
3040
+
3041
+ |font_color
3042
+ |<<colors,Color>> +
3043
+ (default: $base_font_color)
3044
+ |footnotes:
3045
+ font_color: #cccccc
3046
+
3047
+ |font_size
3048
+ |<<values,Number>> +
3049
+ (default: 8)
3050
+ |footnotes:
3051
+ font_size: 6
3052
+
3053
+ |font_style
3054
+ |<<font-styles,Font style>> +
3055
+ (default: $base_font_style)
3056
+ |footnotes:
3057
+ font_style: italic
3058
+
3059
+ |item_spacing
3060
+ |<<measurement-units,Measurement>> +
3061
+ (default: 3)
3062
+ |footnotes:
3063
+ item_spacing: 5
3064
+
3065
+ |margin_top
3066
+ |<<measurement-units,Measurement>> +
3067
+ (default: 0)
3068
+ |footnotes:
3069
+ margin_top: 10
3070
+
3071
+ |text_transform
3072
+ |<<text-transforms,Text transform>> +
3073
+ (default: _inherit_)
3074
+ |footnotes:
3075
+ text_transform: lowercase
3076
+ |===
3077
+
2958
3078
  [#keys-table-of-contents]
2959
3079
  === Table of Contents (TOC)
2960
3080
 
@@ -3144,6 +3264,7 @@ If value is not specified, dot leaders are shown for all levels.
3144
3264
  === Running Content (Header & Footer)
3145
3265
 
3146
3266
  The keys in this category control the arrangement and style of running header and footer content.
3267
+ Please note that the running content will _not_ be used unless a) the periphery (header or footer) is configured and b) the height key for the periphery is assigned a value.
3147
3268
 
3148
3269
  [cols="3,4,5l"]
3149
3270
  |===
@@ -3347,7 +3468,7 @@ The keys in this category control the arrangement and style of running header an
3347
3468
  content: '\{page-number}'
3348
3469
  |===
3349
3470
  . The background color spans the width of the page, as does the border when a background color is specified.
3350
- . If the height is not set, the running content at this periphery is disabled.
3471
+ . *If the height is not set, the running content at this periphery is disabled.*
3351
3472
  . If the side padding is negative, the content will bleed into the margin of the page.
3352
3473
  . `<side>` can be `recto` (right-hand, odd-numbered pages) or `verso` (left-hand, even-numbered pages).
3353
3474
  Where the page sides fall in relation to the physical or printed page number is controlled using the `pdf-folio-placement` attribute (except when `media=prepress`, which implies `physical`).
@@ -3540,6 +3661,10 @@ These settings override equivalent keys defined in the theme file, where applica
3540
3661
  |screen {vbar} print {vbar} prepress
3541
3662
  |:media: prepress
3542
3663
 
3664
+ |outlinelevels
3665
+ |number (default: same as _toclevels_)
3666
+ |:outlinelevels: 2
3667
+
3543
3668
  |page-background-image^[4]^
3544
3669
  |path^[2]^ {vbar} image macro^[3]^
3545
3670
  |:page-background-image: image:bg.jpg[]
@@ -1,4 +1,6 @@
1
- # NOTE these are all candidates for inclusion in Asciidoctor core
1
+ # NOTE these are either candidates for inclusion in Asciidoctor core or backports
2
+ require_relative 'asciidoctor_ext/abstract_block'
3
+ require_relative 'asciidoctor_ext/document'
2
4
  require_relative 'asciidoctor_ext/section'
3
5
  require_relative 'asciidoctor_ext/list'
4
6
  require_relative 'asciidoctor_ext/list_item'
@@ -0,0 +1,5 @@
1
+ class Asciidoctor::AbstractBlock
2
+ def sections?
3
+ !sections.empty?
4
+ end unless method_defined? :sections?
5
+ end
@@ -0,0 +1,3 @@
1
+ class Asciidoctor::Document
2
+ alias catalog references unless method_defined? :catalog
3
+ end
@@ -1,11 +1,17 @@
1
1
  module Asciidoctor
2
2
  class StubLogger
3
3
  class << self
4
- def warn message
4
+ def info message = nil
5
+ # ignore since this isn't a real logger
6
+ end
7
+
8
+ def warn message = nil
9
+ message = block_given? ? yield : message unless message
5
10
  ::Kernel.warn %(asciidoctor: WARNING: #{message})
6
11
  end
7
12
 
8
- def error message
13
+ def error message = nil
14
+ message = block_given? ? yield : message unless message
9
15
  ::Kernel.warn %(asciidoctor: ERROR: #{message})
10
16
  end
11
17
  end
@@ -1,17 +1,25 @@
1
1
  class Asciidoctor::Section
2
2
  def numbered_title opts = {}
3
3
  unless (@cached_numbered_title ||= nil)
4
- if (slevel = (@level == 0 && @special ? 1 : @level)) == 0
5
- @is_numbered = false
6
- @cached_numbered_title = @cached_formal_numbered_title = title
7
- elsif @numbered && !@caption && slevel <= (@document.attr 'sectnumlevels', 3).to_i
4
+ slevel = @level == 0 && @special ? 1 : @level
5
+ if @numbered && !@caption && slevel <= (@document.attr 'sectnumlevels', 3).to_i
8
6
  @is_numbered = true
9
- @cached_numbered_title = %(#{sectnum} #{title})
10
- @cached_formal_numbered_title = if slevel == 1 && @document.doctype == 'book'
11
- %(#{@document.attr 'chapter-label', 'Chapter'} #{@cached_numbered_title}).lstrip
7
+ @cached_formal_numbered_title = if @document.doctype == 'book'
8
+ if slevel == 0
9
+ @cached_numbered_title = %(#{sectnum nil, ':'} #{title})
10
+ %(#{@document.attr 'part-signifier', 'Part'} #{@cached_numbered_title}).lstrip
11
+ elsif slevel == 1
12
+ @cached_numbered_title = %(#{sectnum} #{title})
13
+ %(#{@document.attr 'chapter-signifier', (@document.attr 'chapter-label', 'Chapter')} #{@cached_numbered_title}).lstrip
14
+ else
15
+ @cached_numbered_title = %(#{sectnum} #{title})
16
+ end
12
17
  else
13
- @cached_numbered_title
18
+ @cached_numbered_title = %(#{sectnum} #{title})
14
19
  end
20
+ elsif slevel == 0
21
+ @is_numbered = false
22
+ @cached_numbered_title = @cached_formal_numbered_title = title
15
23
  else
16
24
  @is_numbered = false
17
25
  @cached_numbered_title = @cached_formal_numbered_title = captioned_title
@@ -90,8 +90,7 @@ class Converter < ::Prawn::Document
90
90
  MeasurementRxt = '\\d+(?:\\.\\d+)?(?:in|cm|mm|p[txc])?'
91
91
  MeasurementPartsRx = /^(\d+(?:\.\d+)?)(in|mm|cm|p[txc])?$/
92
92
  PageSizeRx = /^(?:\[(#{MeasurementRxt}), ?(#{MeasurementRxt})\]|(#{MeasurementRxt})(?: x |x)(#{MeasurementRxt})|\S+)$/
93
- # CalloutExtractRx synced from /lib/asciidoctor.rb of Asciidoctor core
94
- CalloutExtractRx = /(?:(?:\/\/|#|--|;;) ?)?(\\)?<!?(--|)(\d+)\2> ?(?=(?:\\?<!?\2\d+\2> ?)*$)/
93
+ CalloutExtractRx = /(?:(?:\/\/|#|--|;;) ?)?(\\)?<!?(|--)(\d+|\.)\2> ?(?=(?:\\?<!?\2(?:\d+|\.)\2>)*$)/
95
94
  ImageAttributeValueRx = /^image:{1,2}(.*?)\[(.*?)\]$/
96
95
  UriBreakCharsRx = /(?:\/|\?|&amp;|#)(?!$)/
97
96
  UriBreakCharRepl = %(\\&#{ZeroWidthSpace})
@@ -113,10 +112,10 @@ class Converter < ::Prawn::Document
113
112
  # NOTE enabling data-uri forces Asciidoctor Diagram to produce absolute image paths
114
113
  doc.attributes['data-uri'] = ((doc.instance_variable_get :@attribute_overrides) || {})['data-uri'] = ''
115
114
  end
116
- @list_numbers = []
117
- @list_bullets = []
118
115
  @capabilities = {
119
- expands_tabs: (::Asciidoctor::VERSION.start_with? '1.5.3.') || AsciidoctorVersion >= (::Gem::Version.create '1.5.3')
116
+ expands_tabs: (::Asciidoctor::VERSION.start_with? '1.5.3.') || AsciidoctorVersion >= (::Gem::Version.create '1.5.3'),
117
+ special_sectnums: AsciidoctorVersion >= (::Gem::Version.create '1.5.7'),
118
+ syntax_highlighter: AsciidoctorVersion >= (::Gem::Version.create '2.0.0'),
120
119
  }
121
120
  end
122
121
 
@@ -155,16 +154,19 @@ class Converter < ::Prawn::Document
155
154
  unless (doc.attribute_locked? 'pagenums') || ((doc.instance_variable_get :@attributes_modified).include? 'pagenums')
156
155
  doc.attributes['pagenums'] = ''
157
156
  end
157
+ if (idx_sect = doc.sections.find {|candidate| candidate.sectname == 'index' }) && idx_sect.numbered
158
+ idx_sect.numbered = false
159
+ end unless @capabilities[:special_sectnums]
158
160
  #assign_missing_section_ids doc
159
161
 
160
162
  # promote anonymous preface (defined using preamble block) to preface section
161
163
  # FIXME this should be done in core
162
- if doc.doctype == 'book' && (blk_0 = doc.blocks[0]) && blk_0.context == :preamble &&
163
- blk_0.title? && blk_0.blocks[0].style != 'abstract' && (blk_1 = doc.blocks[1]) && blk_1.context == :section
164
+ if doc.doctype == 'book' && (blk_0 = doc.blocks[0]) && blk_0.context == :preamble && blk_0.title? &&
165
+ !blk_0.title.nil_or_empty? && blk_0.blocks[0].style != 'abstract' && (blk_1 = doc.blocks[1]) && blk_1.context == :section
164
166
  preface = Section.new doc, blk_1.level, false, attributes: { 1 => 'preface', 'style' => 'preface' }
165
167
  preface.special = true
166
168
  preface.sectname = 'preface'
167
- preface.title = doc.attr 'preface-title', 'Preface'
169
+ preface.title = blk_0.instance_variable_get :@title
168
170
  # QUESTION should ID be generated from raw or converted title? core is not clear about this
169
171
  preface.id = preface.generate_id
170
172
  preface.blocks.replace blk_0.blocks.map {|b| b.parent = preface; b }
@@ -233,20 +235,31 @@ class Converter < ::Prawn::Document
233
235
  start_new_page if @media == 'prepress' && verso_page?
234
236
 
235
237
  if insert_title_page
236
- body_start_page_number = page_number
238
+ body_offset = (body_start_page_number = page_number) - 1
239
+ front_matter_sig = [@theme.running_content_start_at || 'body', @theme.page_numbering_start_at || 'body', insert_toc]
237
240
  # NOTE start running content from title or toc, if specified (default: body)
238
- if @theme.running_content_start_at == 'title'
239
- num_front_matter_pages = 0
240
- elsif insert_toc && @theme.running_content_start_at == 'toc'
241
- num_front_matter_pages = 1
242
- else # body
243
- num_front_matter_pages = body_start_page_number - 1
244
- end
241
+ num_front_matter_pages = {
242
+ ['title', 'title', true] => [0, 0],
243
+ ['title', 'title', false] => [0, 0],
244
+ ['title', 'toc', true] => [0, 1],
245
+ ['title', 'toc', false] => [0, 1],
246
+ ['title', 'body', true] => [0, body_offset],
247
+ ['title', 'body', false] => [0, 1],
248
+ ['toc', 'title', true] => [1, 0],
249
+ ['toc', 'title', false] => [1, 0],
250
+ ['toc', 'toc', true] => [1, 1],
251
+ ['toc', 'toc', false] => [1, 1],
252
+ ['toc', 'body', true] => [1, body_offset],
253
+ ['body', 'title', true] => [body_offset, 0],
254
+ ['body', 'title', false] => [1, 0],
255
+ ['body', 'toc', true] => [body_offset, 1],
256
+ }[front_matter_sig] || [body_offset, body_offset]
245
257
  else
246
- num_front_matter_pages = body_start_page_number - 1
258
+ # Q: what if there's only a toc page, but not title?
259
+ num_front_matter_pages = [body_start_page_number - 1] * 2
247
260
  end
248
261
 
249
- @index.start_page_number = num_front_matter_pages + 1
262
+ @index.start_page_number = num_front_matter_pages[1] + 1
250
263
  doc.set_attr 'pdf-anchor', (doc_anchor = derive_anchor_from_id doc.id, 'top')
251
264
  add_dest_for_block doc, doc_anchor
252
265
 
@@ -261,18 +274,18 @@ class Converter < ::Prawn::Document
261
274
  # QUESTION should we delete page if document is empty? (leaving no pages?)
262
275
  delete_page if page_is_empty? && page_count > 1
263
276
 
264
- toc_page_nums = insert_toc ? (layout_toc doc, num_toc_levels, toc_page_nums.first, num_front_matter_pages, toc_start) : []
277
+ toc_page_nums = insert_toc ? (layout_toc doc, num_toc_levels, toc_page_nums.first, num_front_matter_pages[1], toc_start) : []
265
278
 
266
279
  unless page_count < body_start_page_number
267
280
  unless doc.noheader || @theme.header_height.to_f.zero?
268
- layout_running_content :header, doc, skip: num_front_matter_pages
281
+ layout_running_content :header, doc, skip: num_front_matter_pages, body_start_page_number: body_start_page_number
269
282
  end
270
283
  unless doc.nofooter || @theme.footer_height.to_f.zero?
271
- layout_running_content :footer, doc, skip: num_front_matter_pages
284
+ layout_running_content :footer, doc, skip: num_front_matter_pages, body_start_page_number: body_start_page_number
272
285
  end
273
286
  end
274
287
 
275
- add_outline doc, num_toc_levels, toc_page_nums, num_front_matter_pages
288
+ add_outline doc, (doc.attr 'outlinelevels', num_toc_levels).to_i, toc_page_nums, num_front_matter_pages[1]
276
289
  # TODO allow document (or theme) to override initial view magnification
277
290
  # NOTE add 1 to page height to force initial scroll to 0; a nil value also seems to work
278
291
  catalog.data[:OpenAction] = dest_fit_horizontally((page_height + 1), state.pages[0]) if state.pages.size > 0
@@ -321,6 +334,8 @@ class Converter < ::Prawn::Document
321
334
  @font_color = theme.base_font_color
322
335
  @base_align = (align = doc.attr 'text-alignment') && (TextAlignmentNames.include? align) ? align : theme.base_align
323
336
  @text_transform = nil
337
+ @list_numerals = []
338
+ @list_bullets = []
324
339
  @footnotes = []
325
340
  @index = IndexCatalog.new
326
341
  # NOTE we have to init Pdfmark class here while we have reference to the doc
@@ -359,19 +374,19 @@ class Converter < ::Prawn::Document
359
374
  page_margin = nil
360
375
  end
361
376
 
362
- page_size = if (doc.attr? 'pdf-page-size') && (m = PageSizeRx.match(doc.attr 'pdf-page-size'))
377
+ if (doc.attr? 'pdf-page-size') && PageSizeRx =~ (doc.attr 'pdf-page-size')
363
378
  # e.g, [8.5in, 11in]
364
- if m[1]
365
- [m[1], m[2]]
379
+ if $1
380
+ page_size = [$1, $2]
366
381
  # e.g, 8.5in x 11in
367
- elsif m[3]
368
- [m[3], m[4]]
382
+ elsif $3
383
+ page_size = [$3, $4]
369
384
  # e.g, A4
370
385
  else
371
- m[0]
386
+ page_size = $&
372
387
  end
373
388
  else
374
- theme.page_size
389
+ page_size = theme.page_size
375
390
  end
376
391
 
377
392
  page_size = case page_size
@@ -388,9 +403,9 @@ class Converter < ::Prawn::Document
388
403
  if ::Numeric === dim
389
404
  # dimension cannot be less than 0
390
405
  dim > 0 ? dim : break
391
- elsif ::String === dim && (m = (MeasurementPartsRx.match dim))
406
+ elsif ::String === dim && MeasurementPartsRx =~ dim
392
407
  # NOTE truncate to max precision retained by PDF::Core
393
- (to_pt m[1].to_f, m[2]).truncate 4
408
+ (to_pt $1.to_f, $2).truncate 4
394
409
  else
395
410
  break
396
411
  end
@@ -519,6 +534,9 @@ class Converter < ::Prawn::Document
519
534
  end
520
535
  theme_font :abstract do
521
536
  prose_opts = { line_height: @theme.abstract_line_height, align: (@theme.abstract_align || @base_align).to_sym }
537
+ if (text_indent = @theme.prose_text_indent)
538
+ prose_opts[:indent_paragraphs] = text_indent
539
+ end
522
540
  # FIXME control more first_line_options using theme
523
541
  if (line1_font_style = @theme.abstract_first_line_font_style) && line1_font_style.to_sym != font_style
524
542
  prose_opts[:first_line_options] = { styles: [font_style, line1_font_style.to_sym] }
@@ -547,21 +565,25 @@ class Converter < ::Prawn::Document
547
565
 
548
566
  def convert_preamble node
549
567
  # TODO find_by needs to support a depth argument
550
- if (first_p = (node.find_by context: :paragraph)[0]) && first_p.parent == node
568
+ # FIXME core should not be promoting paragraph to preamble if there are no sections
569
+ if (first_p = (node.find_by context: :paragraph)[0]) && first_p.parent == node && node.document.sections?
551
570
  first_p.add_role 'lead'
552
571
  end
553
572
  convert_content_for_block node
554
573
  end
555
574
 
556
- # TODO add prose around image logic (use role to add special logic for headshot)
557
575
  def convert_paragraph node
558
576
  add_dest_for_block node if node.id
559
- prose_opts = {}
577
+ prose_opts = { margin_bottom: 0 }
560
578
  lead = (roles = node.roles).include? 'lead'
561
579
  if (align = resolve_alignment_from_role roles)
562
580
  prose_opts[:align] = align
563
581
  end
564
582
 
583
+ if (text_indent = @theme.prose_text_indent)
584
+ prose_opts[:indent_paragraphs] = text_indent
585
+ end
586
+
565
587
  # TODO check if we're within one line of the bottom of the page
566
588
  # and advance to the next page if so (similar to logic for section titles)
567
589
  layout_caption node.title if node.title?
@@ -573,6 +595,14 @@ class Converter < ::Prawn::Document
573
595
  else
574
596
  layout_prose node.content, prose_opts
575
597
  end
598
+
599
+ if (margin_inner_val = @theme.prose_margin_inner) &&
600
+ (next_block = (siblings = node.parent.blocks)[(siblings.index node) + 1]) && next_block.context == :paragraph
601
+ margin_bottom_val = margin_inner_val
602
+ else
603
+ margin_bottom_val = @theme.prose_margin_bottom
604
+ end
605
+ margin_bottom margin_bottom_val
576
606
  end
577
607
 
578
608
  def convert_admonition node
@@ -627,6 +657,7 @@ class Converter < ::Prawn::Document
627
657
  shift_top = shift_base / 3.0
628
658
  shift_bottom = (shift_base * 2) / 3.0
629
659
  keep_together do |box_height = nil|
660
+ push_scratch doc if scratch?
630
661
  pad_box [0, cpad[1], 0, lpad[3]] do
631
662
  if box_height
632
663
  if (rule_color = @theme.admonition_column_rule_color) &&
@@ -634,7 +665,7 @@ class Converter < ::Prawn::Document
634
665
  float do
635
666
  bounding_box [0, cursor], width: label_width + lpad[1], height: box_height do
636
667
  stroke_vertical_rule rule_color,
637
- at: bounds.width,
668
+ at: bounds.right,
638
669
  line_style: (@theme.admonition_column_rule_style || :solid).to_sym,
639
670
  line_width: rule_width
640
671
  end
@@ -728,6 +759,7 @@ class Converter < ::Prawn::Document
728
759
  move_up shift_bottom unless at_page_top?
729
760
  end
730
761
  end
762
+ pop_scratch doc if scratch?
731
763
  end
732
764
  theme_margin :block, :bottom
733
765
  end
@@ -736,6 +768,7 @@ class Converter < ::Prawn::Document
736
768
  add_dest_for_block node if node.id
737
769
  theme_margin :block, :top
738
770
  keep_together do |box_height = nil|
771
+ push_scratch node.document if scratch?
739
772
  caption_height = node.title? ? (layout_caption node) : 0
740
773
  if box_height
741
774
  float do
@@ -749,6 +782,7 @@ class Converter < ::Prawn::Document
749
782
  convert_content_for_block node
750
783
  end
751
784
  end
785
+ pop_scratch node.document if scratch?
752
786
  end
753
787
  theme_margin :block, :bottom
754
788
  end
@@ -773,6 +807,7 @@ class Converter < ::Prawn::Document
773
807
  b_width = @theme.blockquote_border_width
774
808
  b_color = @theme.blockquote_border_color
775
809
  keep_together do |box_height = nil|
810
+ push_scratch node.document if scratch?
776
811
  start_page_number = page_number
777
812
  start_cursor = cursor
778
813
  caption_height = node.title? ? (layout_caption node) : 0
@@ -823,6 +858,7 @@ class Converter < ::Prawn::Document
823
858
  end unless b_height == 0
824
859
  end
825
860
  end
861
+ pop_scratch node.document if scratch?
826
862
  end
827
863
  theme_margin :block, :bottom
828
864
  end
@@ -834,6 +870,7 @@ class Converter < ::Prawn::Document
834
870
  add_dest_for_block node if node.id
835
871
  theme_margin :block, :top
836
872
  keep_together do |box_height = nil|
873
+ push_scratch node.document if scratch?
837
874
  if box_height
838
875
  # FIXME due to the calculation error logged in #789, we must advance page even when content is split across pages
839
876
  advance_page if box_height > cursor && !at_page_top?
@@ -890,6 +927,7 @@ class Converter < ::Prawn::Document
890
927
  convert_content_for_block node
891
928
  end
892
929
  end
930
+ pop_scratch node.document if scratch?
893
931
  end
894
932
  theme_margin :block, :bottom
895
933
  end
@@ -908,10 +946,10 @@ class Converter < ::Prawn::Document
908
946
  end
909
947
  end
910
948
  add_dest_for_block node if node.id
911
- @list_numbers ||= []
949
+ @list_numerals ||= []
912
950
  # FIXME move \u2460 to constant (or theme setting)
913
951
  # \u2460 = circled one, \u24f5 = double circled one, \u278b = negative circled one
914
- @list_numbers << %(\u2460)
952
+ @list_numerals << %(\u2460)
915
953
  #stroke_horizontal_rule @theme.caption_border_bottom_color
916
954
  line_metrics = calc_line_metrics @theme.base_line_height
917
955
  node.items.each_with_index do |item, idx|
@@ -919,7 +957,7 @@ class Converter < ::Prawn::Document
919
957
  advance_page if cursor < (line_metrics.height + line_metrics.leading + line_metrics.padding_top) + 1
920
958
  convert_colist_item item
921
959
  end
922
- @list_numbers.pop
960
+ @list_numerals.pop
923
961
  # correct bottom margin of last item
924
962
  list_margin_bottom = @theme.prose_margin_bottom
925
963
  margin_bottom list_margin_bottom - @theme.outline_list_item_spacing
@@ -931,7 +969,7 @@ class Converter < ::Prawn::Document
931
969
  marker_width = rendered_width_of_string %(#{conum_glyph 1}x)
932
970
  float do
933
971
  bounding_box [0, cursor], width: marker_width do
934
- @list_numbers << (index = @list_numbers.pop).next
972
+ @list_numerals << (index = @list_numerals.pop).next
935
973
  theme_font :conum do
936
974
  layout_prose index, align: :center, line_height: @theme.conum_line_height, inline_format: false, margin: 0
937
975
  end
@@ -940,39 +978,46 @@ class Converter < ::Prawn::Document
940
978
  end
941
979
 
942
980
  indent marker_width do
943
- convert_content_for_list_item node, margin_bottom: @theme.outline_list_item_spacing
981
+ convert_content_for_list_item node, :colist, margin_bottom: @theme.outline_list_item_spacing
944
982
  end
945
983
  end
946
984
 
947
985
  def convert_dlist node
948
986
  add_dest_for_block node if node.id
949
987
 
950
- # TODO check if we're within one line of the bottom of the page
951
- # and advance to the next page if so (similar to logic for section titles)
952
- layout_caption node.title if node.title?
988
+ case node.style
989
+ when 'qanda'
990
+ (@list_numerals ||= []) << '1'
991
+ convert_outline_list node
992
+ @list_numerals.pop
993
+ else
994
+ # TODO check if we're within one line of the bottom of the page
995
+ # and advance to the next page if so (similar to logic for section titles)
996
+ layout_caption node.title if node.title?
953
997
 
954
- node.items.each do |terms, desc|
955
- terms = [*terms]
956
- # NOTE don't orphan the terms, allow for at least one line of content
957
- # FIXME extract ensure_space (or similar) method
958
- advance_page if cursor < @theme.base_line_height_length * (terms.size + 1)
959
- terms.each do |term|
960
- # FIXME layout_prose should pass style downward when parsing formatted text
961
- #layout_prose term.text, style: @theme.description_list_term_font_style.to_sym, margin_top: 0, margin_bottom: @theme.description_list_term_spacing, align: :left
962
- term_text = term.text
963
- case @theme.description_list_term_font_style.to_sym
964
- when :bold
965
- term_text = %(<strong>#{term_text}</strong>)
966
- when :italic
967
- term_text = %(<em>#{term_text}</em>)
968
- when :bold_italic
969
- term_text = %(<strong><em>#{term_text}</em></strong>)
970
- end
971
- layout_prose term_text, margin_top: 0, margin_bottom: @theme.description_list_term_spacing, align: :left
972
- end
973
- if desc
974
- indent @theme.description_list_description_indent do
975
- convert_content_for_list_item desc
998
+ node.items.each do |terms, desc|
999
+ terms = [*terms]
1000
+ # NOTE don't orphan the terms, allow for at least one line of content
1001
+ # FIXME extract ensure_space (or similar) method
1002
+ advance_page if cursor < @theme.base_line_height_length * (terms.size + 1)
1003
+ terms.each do |term|
1004
+ # FIXME layout_prose should pass style downward when parsing formatted text
1005
+ #layout_prose term.text, style: @theme.description_list_term_font_style.to_sym, margin_top: 0, margin_bottom: @theme.description_list_term_spacing, align: :left
1006
+ term_text = term.text
1007
+ case @theme.description_list_term_font_style.to_sym
1008
+ when :bold
1009
+ term_text = %(<strong>#{term_text}</strong>)
1010
+ when :italic
1011
+ term_text = %(<em>#{term_text}</em>)
1012
+ when :bold_italic
1013
+ term_text = %(<strong><em>#{term_text}</em></strong>)
1014
+ end
1015
+ layout_prose term_text, margin_top: 0, margin_bottom: @theme.description_list_term_spacing, align: :left
1016
+ end
1017
+ if desc
1018
+ indent @theme.description_list_description_indent do
1019
+ convert_content_for_list_item desc, :dlist_desc
1020
+ end
976
1021
  end
977
1022
  end
978
1023
  end
@@ -980,7 +1025,7 @@ class Converter < ::Prawn::Document
980
1025
 
981
1026
  def convert_olist node
982
1027
  add_dest_for_block node if node.id
983
- @list_numbers ||= []
1028
+ @list_numerals ||= []
984
1029
  # TODO move list_number resolve to a method
985
1030
  list_number = case node.style
986
1031
  when 'arabic'
@@ -997,16 +1042,20 @@ class Converter < ::Prawn::Document
997
1042
  RomanNumeral.new 'I'
998
1043
  when 'lowergreek'
999
1044
  LowercaseGreekA
1045
+ when 'unstyled', 'unnumbered', 'no-bullet'
1046
+ nil
1047
+ when 'none'
1048
+ ''
1000
1049
  else
1001
1050
  '1'
1002
1051
  end
1003
1052
  # TODO support start values < 1 (issue #498)
1004
1053
  if (start = ((node.attr 'start', nil, false) || ((node.option? 'reversed') ? node.items.size : 1)).to_i) > 1
1005
- (start - 1).times { list_number = list_number.next }
1054
+ (start - 1).times { list_number = list_number.next }
1006
1055
  end
1007
- @list_numbers << list_number
1056
+ @list_numerals << list_number
1008
1057
  convert_outline_list node
1009
- @list_numbers.pop
1058
+ @list_numerals.pop
1010
1059
  end
1011
1060
 
1012
1061
  def convert_ulist node
@@ -1064,13 +1113,13 @@ class Converter < ::Prawn::Document
1064
1113
  complex = false
1065
1114
  # ...or if we want to give all items in the list the same treatment
1066
1115
  #complex = node.items.find(&:complex?) ? true : false
1067
- if node.context == :ulist && !@list_bullets[-1]
1116
+ if (node.context == :ulist && !@list_bullets[-1]) || (node.context == :olist && !@list_numerals[-1])
1068
1117
  if node.style == 'unstyled'
1069
1118
  # unstyled takes away all indentation
1070
1119
  list_indent = 0
1071
1120
  elsif (list_indent = @theme.outline_list_indent) > 0
1072
1121
  # no-bullet aligns text with left-hand side of bullet position (as though there's no bullet)
1073
- list_indent = [list_indent - (rendered_width_of_string %(\u2022x)), 0].max
1122
+ list_indent = [list_indent - (rendered_width_of_string %(#{node.context == :ulist ? "\u2022" : '1.'}x)), 0].max
1074
1123
  end
1075
1124
  else
1076
1125
  list_indent = @theme.outline_list_indent
@@ -1079,7 +1128,7 @@ class Converter < ::Prawn::Document
1079
1128
  node.items.each do |item|
1080
1129
  # FIXME extract to an ensure_space (or similar) method; simplify
1081
1130
  advance_page if cursor < (line_metrics.height + line_metrics.leading + line_metrics.padding_top)
1082
- convert_outline_list_item item, item.complex?, opts
1131
+ convert_outline_list_item item, node, opts
1083
1132
  end
1084
1133
  end
1085
1134
  # NOTE Children will provide the necessary bottom margin if last item is complex.
@@ -1091,33 +1140,48 @@ class Converter < ::Prawn::Document
1091
1140
  end
1092
1141
  end
1093
1142
 
1094
- def convert_outline_list_item node, complex = false, opts = {}
1143
+ def convert_outline_list_item node, list, opts = {}
1095
1144
  # TODO move this to a draw_bullet (or draw_marker) method
1096
1145
  marker_style = {}
1097
1146
  marker_style[:font_color] = @theme.outline_list_marker_font_color || @font_color
1098
1147
  marker_style[:font_family] = font_family
1099
1148
  marker_style[:font_size] = font_size
1100
1149
  marker_style[:line_height] = @theme.base_line_height
1101
- case (list_type = node.parent.context)
1150
+ case (list_type = list.context)
1102
1151
  when :ulist
1103
- marker_type = @list_bullets[-1]
1104
- if marker_type == :checkbox
1105
- # QUESTION should we remove marker indent if not a checkbox?
1106
- if node.attr? 'checkbox', nil, false
1107
- marker_type = (node.attr? 'checked', nil, false) ? :checked : :unchecked
1108
- marker = @theme[%(ulist_marker_#{marker_type}_content)] || BallotBox[marker_type]
1152
+ complex = node.complex?
1153
+ if (marker_type = @list_bullets[-1])
1154
+ if marker_type == :checkbox
1155
+ # QUESTION should we remove marker indent if not a checkbox?
1156
+ if node.attr? 'checkbox', nil, false
1157
+ marker_type = (node.attr? 'checked', nil, false) ? :checked : :unchecked
1158
+ marker = @theme[%(ulist_marker_#{marker_type}_content)] || BallotBox[marker_type]
1159
+ end
1160
+ else
1161
+ marker = @theme[%(ulist_marker_#{marker_type}_content)] || Bullets[marker_type]
1109
1162
  end
1110
- else
1111
- marker = @theme[%(ulist_marker_#{marker_type}_content)] || Bullets[marker_type]
1163
+ [:font_color, :font_family, :font_size, :line_height].each do |prop|
1164
+ marker_style[prop] = @theme[%(ulist_marker_#{marker_type}_#{prop})] || @theme[%(ulist_marker_#{prop})] || marker_style[prop]
1165
+ end if marker
1112
1166
  end
1113
- [:font_color, :font_family, :font_size, :line_height].each do |prop|
1114
- marker_style[prop] = @theme[%(ulist_marker_#{marker_type}_#{prop})] || @theme[%(ulist_marker_#{prop})] || marker_style[prop]
1115
- end if marker
1116
1167
  when :olist
1117
- dir = (node.parent.option? 'reversed') ? :pred : :next
1118
- @list_numbers << ((index = @list_numbers.pop).public_send dir)
1168
+ complex = node.complex?
1169
+ if (index = @list_numerals.pop)
1170
+ if index.empty?
1171
+ marker = ''
1172
+ else
1173
+ marker = %(#{index}.)
1174
+ dir = (node.parent.option? 'reversed') ? :pred : :next
1175
+ @list_numerals << (index = index.public_send dir)
1176
+ end
1177
+ end
1178
+ when :dlist
1179
+ # NOTE list.style is 'qanda'
1180
+ complex = node[1] && node[1].complex?
1181
+ @list_numerals << (index = @list_numerals.pop).next
1119
1182
  marker = %(#{index}.)
1120
1183
  else
1184
+ complex = node.complex?
1121
1185
  logger.warn %(unknown list type #{list_type.inspect})
1122
1186
  marker = @theme.ulist_marker_disc_content || Bullets[:disc]
1123
1187
  end
@@ -1148,15 +1212,24 @@ class Converter < ::Prawn::Document
1148
1212
  end
1149
1213
 
1150
1214
  if complex
1151
- convert_content_for_list_item node, opts
1215
+ convert_content_for_list_item node, list_type, opts
1152
1216
  else
1153
- convert_content_for_list_item node, (opts.merge margin_bottom: @theme.outline_list_item_spacing)
1217
+ convert_content_for_list_item node, list_type, (opts.merge margin_bottom: @theme.outline_list_item_spacing)
1154
1218
  end
1155
1219
  end
1156
1220
 
1157
- def convert_content_for_list_item node, opts = {}
1158
- layout_prose node.text, opts if node.text?
1159
- convert_content_for_block node
1221
+ def convert_content_for_list_item node, list_type, opts = {}
1222
+ if list_type == :dlist # qanda
1223
+ terms, desc = node
1224
+ [*terms].each {|term| layout_prose %(<em>#{term.text}</em>), opts }
1225
+ if desc
1226
+ layout_prose desc.text, opts if desc.text?
1227
+ convert_content_for_block desc
1228
+ end
1229
+ else
1230
+ layout_prose node.text, opts if node.text?
1231
+ convert_content_for_block node
1232
+ end
1160
1233
  end
1161
1234
 
1162
1235
  def convert_image node, opts = {}
@@ -1330,10 +1403,8 @@ class Converter < ::Prawn::Document
1330
1403
  ::OpenURI
1331
1404
  end
1332
1405
  poster = open(%(http://vimeo.com/api/v2/video/#{video_id}.xml), 'r') do |f|
1333
- (/<thumbnail_large>(.*?)<\/thumbnail_large>/.match f.read)[1]
1406
+ /<thumbnail_large>(.*?)<\/thumbnail_large>/ =~ f.read && $1
1334
1407
  end
1335
- else
1336
- poster = nil
1337
1408
  end
1338
1409
  type = 'Vimeo video'
1339
1410
  else
@@ -1366,13 +1437,27 @@ class Converter < ::Prawn::Document
1366
1437
 
1367
1438
  # HACK disable built-in syntax highlighter; must be done before calling node.content!
1368
1439
  if node.style == 'source' && node.attributes['language'] &&
1369
- (highlighter = node.document.attributes['source-highlighter']) &&
1370
- (SourceHighlighters.include? highlighter)
1440
+ (highlighter = node.document.attributes['source-highlighter']) && (SourceHighlighters.include? highlighter) &&
1441
+ (@capabilities[:syntax_highlighter] ? node.document.syntax_highlighter.highlight? : true)
1442
+ case highlighter
1443
+ when 'coderay'
1444
+ unless defined? ::Asciidoctor::Prawn::CodeRayEncoder
1445
+ highlighter = nil if (Helpers.require_library CodeRayRequirePath, 'coderay', :warn).nil?
1446
+ end
1447
+ when 'pygments'
1448
+ unless defined? ::Pygments
1449
+ highlighter = nil if (Helpers.require_library 'pygments', 'pygments.rb', :warn).nil?
1450
+ end
1451
+ when 'rouge'
1452
+ unless defined? ::Rouge::Formatters::Prawn
1453
+ highlighter = nil if (Helpers.require_library RougeRequirePath, 'rouge', :warn).nil?
1454
+ end
1455
+ end
1371
1456
  prev_subs = (subs = node.subs).dup
1372
- # NOTE the highlight sub is only set for coderay and pygments atm
1457
+ # NOTE the highlight sub is only set for coderay, rouge, and pygments atm
1373
1458
  highlight_idx = subs.index :highlight
1374
1459
  # NOTE scratch? here only applies if listing block is nested inside another block
1375
- if scratch?
1460
+ if !highlighter || scratch?
1376
1461
  highlighter = nil
1377
1462
  if highlight_idx
1378
1463
  # switch the :highlight sub back to :specialcharacters
@@ -1401,7 +1486,6 @@ class Converter < ::Prawn::Document
1401
1486
 
1402
1487
  source_chunks = case highlighter
1403
1488
  when 'coderay'
1404
- Helpers.require_library CodeRayRequirePath, 'coderay' unless defined? ::Asciidoctor::Prawn::CodeRayEncoder
1405
1489
  source_string, conum_mapping = extract_conums source_string
1406
1490
  srclang = node.attr 'language', 'text', false
1407
1491
  begin
@@ -1412,7 +1496,6 @@ class Converter < ::Prawn::Document
1412
1496
  fragments = (::CodeRay.scan source_string, srclang).to_prawn
1413
1497
  conum_mapping ? (restore_conums fragments, conum_mapping) : fragments
1414
1498
  when 'pygments'
1415
- Helpers.require_library 'pygments', 'pygments.rb' unless defined? ::Pygments
1416
1499
  lexer = ::Pygments::Lexer.find_by_alias(node.attr 'language', 'text', false) || ::Pygments::Lexer.find_by_mimetype('text/plain')
1417
1500
  lexer_opts = {
1418
1501
  nowrap: true,
@@ -1451,7 +1534,6 @@ class Converter < ::Prawn::Document
1451
1534
  fragments = restore_conums fragments, conum_mapping, num_trailing_spaces, linenums if conum_mapping
1452
1535
  fragments = guard_indentation fragments
1453
1536
  when 'rouge'
1454
- Helpers.require_library RougeRequirePath, 'rouge' unless defined? ::Rouge::Formatters::Prawn
1455
1537
  lexer = ::Rouge::Lexer.find(node.attr 'language', 'text', false) || ::Rouge::Lexers::PlainText
1456
1538
  lexer_opts = lexer.tag == 'php' ? { start_inline: !(node.option? 'mixed') } : {}
1457
1539
  formatter = (@rouge_formatter ||= ::Rouge::Formatters::Prawn.new theme: (node.document.attr 'rouge-style'), line_gap: @theme.code_line_gap)
@@ -1533,15 +1615,16 @@ class Converter < ::Prawn::Document
1533
1615
  # and the mapping of lines to conums as the second.
1534
1616
  def extract_conums string
1535
1617
  conum_mapping = {}
1618
+ auto_num = 0
1536
1619
  string = string.split(LF).map.with_index {|line, line_num|
1537
1620
  # FIXME we get extra spaces before numbers if more than one on a line
1538
1621
  if line.include? '<'
1539
1622
  line.gsub(CalloutExtractRx) {
1540
1623
  # honor the escape
1541
- if $1 == '\\'
1542
- $&.sub '\\', ''
1624
+ if $1 == ?\\
1625
+ $&.sub $1, ''
1543
1626
  else
1544
- (conum_mapping[line_num] ||= []) << $3.to_i
1627
+ (conum_mapping[line_num] ||= []) << ($3 == '.' ? (auto_num += 1) : $3.to_i)
1545
1628
  ''
1546
1629
  end
1547
1630
  }
@@ -1748,7 +1831,9 @@ class Converter < ::Prawn::Document
1748
1831
  cell_line_metrics = calc_line_metrics theme.base_line_height
1749
1832
  end
1750
1833
  if cell_line_metrics
1751
- unless ::Array === (cell_padding = cell_data[:padding]) && cell_padding.size == 4
1834
+ if ::Array === (cell_padding = cell_data[:padding]) && cell_padding.size == 4
1835
+ cell_padding = cell_padding.dup
1836
+ else
1752
1837
  cell_padding = cell_data[:padding] = inflate_padding cell_padding
1753
1838
  end
1754
1839
  cell_padding[0] += cell_line_metrics.padding_top
@@ -2026,7 +2111,7 @@ class Converter < ::Prawn::Document
2026
2111
  elsif (@media ||= node.document.attr 'media', 'screen') != 'screen' || (node.document.attr? 'show-link-uri')
2027
2112
  # QUESTION should we insert breakable chars into URI when building fragment instead?
2028
2113
  # TODO allow style of printed link to be controlled by theme
2029
- %(<a href="#{target = node.target}"#{attrs.join}>#{node.text}</a> [<font size="0.85em">#{breakable_uri target}</font>])
2114
+ %(<a href="#{target = node.target}"#{attrs.join}>#{node.text}</a> [<font size="0.85em">#{breakable_uri target}</font>&#93;)
2030
2115
  else
2031
2116
  %(<a href="#{node.target}"#{attrs.join}>#{node.text}</a>)
2032
2117
  end
@@ -2038,18 +2123,18 @@ class Converter < ::Prawn::Document
2038
2123
  %(<a href="#{node.target}">#{node.text || path}</a>)
2039
2124
  elsif (refid = node.attributes['refid'])
2040
2125
  unless (text = node.text)
2041
- if (refs = node.document.references[:refs])
2126
+ if (refs = node.document.catalog[:refs])
2042
2127
  if ::Asciidoctor::AbstractNode === (ref = refs[refid])
2043
2128
  text = ref.xreftext((@xrefstyle ||= (node.document.attr 'xrefstyle')))
2044
2129
  end
2045
2130
  else
2046
2131
  # Asciidoctor < 1.5.6
2047
- text = node.document.references[:ids][refid]
2132
+ text = node.document.catalog[:ids][refid]
2048
2133
  end
2049
2134
  end
2050
- %(<a anchor="#{derive_anchor_from_id refid}">#{text || "[#{refid}]"}</a>)
2135
+ %(<a anchor="#{derive_anchor_from_id refid}">#{text || "[#{refid}]"}</a>).gsub ']', '&#93;'
2051
2136
  else
2052
- %(<a anchor="#{node.document.attr 'pdf-anchor'}">#{node.text || '[^top]'}</a>)
2137
+ %(<a anchor="#{node.document.attr 'pdf-anchor'}">#{node.text || '[^top&#93;'}</a>)
2053
2138
  end
2054
2139
  when :ref
2055
2140
  # NOTE destination is created inside callback registered by FormattedTextTransform#build_fragment
@@ -2058,9 +2143,14 @@ class Converter < ::Prawn::Document
2058
2143
  when :bibref
2059
2144
  # NOTE destination is created inside callback registered by FormattedTextTransform#build_fragment
2060
2145
  # NOTE technically node.text should be node.reftext, but subs have already been applied to text
2061
- # NOTE check reftext? for compatibility with Asciidoctor <= 1.5.5
2146
+ # NOTE reftext is no longer enclosed in [] starting in Asciidoctor 2.0.0
2062
2147
  # NOTE id is used instead of target starting in Asciidoctor 2.0.0
2063
- %(<a name="#{node.target || node.id}">#{DummyText}</a>#{(reftext = node.reftext) ? reftext : "[#{node.target || node.id}]"})
2148
+ if (reftext = node.reftext)
2149
+ reftext = %([#{reftext}]) unless reftext.start_with? '['
2150
+ else
2151
+ reftext = %([#{node.target || node.id}])
2152
+ end
2153
+ %(<a name="#{node.target || node.id}">#{DummyText}</a>#{reftext})
2064
2154
  else
2065
2155
  logger.warn %(unknown anchor type: #{node.type.inspect})
2066
2156
  end
@@ -2166,7 +2256,7 @@ class Converter < ::Prawn::Document
2166
2256
  node.type == :visible ? node.text : ''
2167
2257
  else
2168
2258
  dest = {
2169
- anchor: (anchor_name = %(__indexterm-#{node.object_id}))
2259
+ anchor: (anchor_name = @index.next_anchor_name)
2170
2260
  # NOTE page number is added in InlineDestinationMarker
2171
2261
  }
2172
2262
  anchor = %(<a name="#{anchor_name}" type="indexterm">#{DummyText}</a>)
@@ -2270,9 +2360,7 @@ class Converter < ::Prawn::Document
2270
2360
  relative_to_imagesdir = false
2271
2361
  end
2272
2362
  # HACK quick fix to resolve image path relative to theme
2273
- unless doc.attr? 'title-logo-image'
2274
- logo_image_path = ThemeLoader.resolve_theme_asset logo_image_path, (doc.attr 'pdf-stylesdir')
2275
- end
2363
+ logo_image_path = ThemeLoader.resolve_theme_asset logo_image_path, (doc.attr 'pdf-stylesdir') unless doc.attr? 'title-logo-image'
2276
2364
  logo_image_attrs['target'] = logo_image_path
2277
2365
  logo_image_attrs['align'] ||= (@theme.title_page_logo_align || title_align.to_s)
2278
2366
  # QUESTION should we allow theme to turn logo image off?
@@ -2287,7 +2375,9 @@ class Converter < ::Prawn::Document
2287
2375
  # FIXME add API to Asciidoctor for creating blocks like this (extract from extensions module?)
2288
2376
  image_block = ::Asciidoctor::Block.new doc, :image, content_model: :empty, attributes: logo_image_attrs
2289
2377
  # NOTE pinned option keeps image on same page
2290
- convert_image image_block, relative_to_imagesdir: relative_to_imagesdir, pinned: true
2378
+ indent (@theme.title_page_logo_margin_left || 0), (@theme.title_page_logo_margin_right || 0) do
2379
+ convert_image image_block, relative_to_imagesdir: relative_to_imagesdir, pinned: true
2380
+ end
2291
2381
  @y = initial_y
2292
2382
  end
2293
2383
 
@@ -2304,34 +2394,40 @@ class Converter < ::Prawn::Document
2304
2394
  @y = title_top
2305
2395
  end
2306
2396
  move_down(@theme.title_page_title_margin_top || 0)
2307
- theme_font :title_page_title do
2308
- layout_heading doctitle.main,
2309
- align: title_align,
2310
- margin: 0,
2311
- line_height: @theme.title_page_title_line_height
2397
+ indent (@theme.title_page_title_margin_left || 0), (@theme.title_page_title_margin_right || 0) do
2398
+ theme_font :title_page_title do
2399
+ layout_heading doctitle.main,
2400
+ align: title_align,
2401
+ margin: 0,
2402
+ line_height: @theme.title_page_title_line_height
2403
+ end
2312
2404
  end
2313
2405
  move_down(@theme.title_page_title_margin_bottom || 0)
2314
2406
  if doctitle.subtitle
2315
2407
  move_down(@theme.title_page_subtitle_margin_top || 0)
2316
- theme_font :title_page_subtitle do
2317
- layout_heading doctitle.subtitle,
2318
- align: title_align,
2319
- margin: 0,
2320
- line_height: @theme.title_page_subtitle_line_height
2408
+ indent (@theme.title_page_subtitle_margin_left || 0), (@theme.title_page_subtitle_margin_right || 0) do
2409
+ theme_font :title_page_subtitle do
2410
+ layout_heading doctitle.subtitle,
2411
+ align: title_align,
2412
+ margin: 0,
2413
+ line_height: @theme.title_page_subtitle_line_height
2414
+ end
2321
2415
  end
2322
2416
  move_down(@theme.title_page_subtitle_margin_bottom || 0)
2323
2417
  end
2324
2418
  if doc.attr? 'authors'
2325
2419
  move_down(@theme.title_page_authors_margin_top || 0)
2326
- # TODO provide an API in core to get authors as an array
2327
- authors = (1..(doc.attr 'authorcount', 1).to_i).map {|idx|
2328
- doc.attr(idx == 1 ? 'author' : %(author_#{idx}))
2329
- } * (@theme.title_page_authors_delimiter || ', ')
2330
- theme_font :title_page_authors do
2331
- layout_prose authors,
2332
- align: title_align,
2333
- margin: 0,
2334
- normalize: false
2420
+ indent (@theme.title_page_authors_margin_left || 0), (@theme.title_page_authors_margin_right || 0) do
2421
+ # TODO provide an API in core to get authors as an array
2422
+ authors = (1..(doc.attr 'authorcount', 1).to_i).map {|idx|
2423
+ doc.attr(idx == 1 ? 'author' : %(author_#{idx}))
2424
+ } * (@theme.title_page_authors_delimiter || ', ')
2425
+ theme_font :title_page_authors do
2426
+ layout_prose authors,
2427
+ align: title_align,
2428
+ margin: 0,
2429
+ normalize: false
2430
+ end
2335
2431
  end
2336
2432
  move_down(@theme.title_page_authors_margin_bottom || 0)
2337
2433
  end
@@ -2339,11 +2435,13 @@ class Converter < ::Prawn::Document
2339
2435
  unless revision_info.empty?
2340
2436
  move_down(@theme.title_page_revision_margin_top || 0)
2341
2437
  revision_text = revision_info * (@theme.title_page_revision_delimiter || ', ')
2342
- theme_font :title_page_revision do
2343
- layout_prose revision_text,
2344
- align: title_align,
2345
- margin: 0,
2346
- normalize: false
2438
+ indent (@theme.title_page_revision_margin_left || 0), (@theme.title_page_revision_margin_right || 0) do
2439
+ theme_font :title_page_revision do
2440
+ layout_prose revision_text,
2441
+ align: title_align,
2442
+ margin: 0,
2443
+ normalize: false
2444
+ end
2347
2445
  end
2348
2446
  move_down(@theme.title_page_revision_margin_bottom || 0)
2349
2447
  end
@@ -2505,8 +2603,8 @@ class Converter < ::Prawn::Document
2505
2603
  layout_heading((doc.attr 'toc-title'), align: toc_title_align)
2506
2604
  end
2507
2605
  end
2508
- # QUESTION should we skip this whole method if num_levels == 0?
2509
- if num_levels > 0
2606
+ # QUESTION should we skip this whole method if num_levels < 0?
2607
+ unless num_levels < 0
2510
2608
  dot_leader = theme_font :toc do
2511
2609
  # TODO we could simplify by using nested theme_font :toc_dot_leader
2512
2610
  if (dot_leader_font_style = (@theme.toc_dot_leader_font_style || :normal).to_sym) != font_style
@@ -2618,7 +2716,8 @@ class Converter < ::Prawn::Document
2618
2716
 
2619
2717
  # TODO delegate to layout_page_header and layout_page_footer per page
2620
2718
  def layout_running_content periphery, doc, opts = {}
2621
- skip = opts[:skip] || 1
2719
+ skip, skip_pagenums, body_start_page_number = opts[:skip] || [1, 1]
2720
+ body_start_page_number = opts[:body_start_page_number] || 1
2622
2721
  # NOTE find and advance to first non-imported content page to use as model page
2623
2722
  return unless (content_start_page = state.pages[skip..-1].index {|p| !p.imported_page? })
2624
2723
  content_start_page += (skip + 1)
@@ -2640,7 +2739,7 @@ class Converter < ::Prawn::Document
2640
2739
  section_start_pages = {}
2641
2740
  trailing_section_start_pages = {}
2642
2741
  sections.each do |sect|
2643
- page_num = (sect.attr 'pdf-page-start').to_i - skip
2742
+ page_num = (sect.attr 'pdf-page-start').to_i - skip_pagenums
2644
2743
  if is_book && ((sect_is_part = sect.part?) || sect.chapter?)
2645
2744
  if sect_is_part
2646
2745
  part_start_pages[page_num] ||= (sect.numbered_title formal: true)
@@ -2663,8 +2762,8 @@ class Converter < ::Prawn::Document
2663
2762
  sections_by_page = {}
2664
2763
  # QUESTION should the default part be the doctitle?
2665
2764
  last_part = nil
2666
- # QUESTION should we enforce that the preamble is preface?
2667
- last_chap = is_book ? (doc.attr 'preface-title', 'Preface') : nil
2765
+ # QUESTION should we enforce that the preamble is a preface?
2766
+ last_chap = is_book ? :pre : nil
2668
2767
  last_sect = nil
2669
2768
  sect_search_threshold = 1
2670
2769
  (1..num_pages).each do |num|
@@ -2691,7 +2790,17 @@ class Converter < ::Prawn::Document
2691
2790
  end
2692
2791
  end
2693
2792
  parts_by_page[num] = last_part
2694
- chapters_by_page[num] = last_chap
2793
+ if last_chap == :pre
2794
+ if num == 1
2795
+ chapters_by_page[num] = doc.doctitle
2796
+ elsif num >= body_start_page_number
2797
+ chapters_by_page[num] = is_book ? (doc.attr 'preface-title', 'Preface') : nil
2798
+ else
2799
+ chapters_by_page[num] = doc.attr 'toc-title'
2800
+ end
2801
+ else
2802
+ chapters_by_page[num] = last_chap
2803
+ end
2695
2804
  sections_by_page[num] = last_sect
2696
2805
  end
2697
2806
 
@@ -2709,7 +2818,7 @@ class Converter < ::Prawn::Document
2709
2818
  trim_top = page_height
2710
2819
  # NOTE height is required atm
2711
2820
  trim_height = @theme.header_height || page_margin_top
2712
- trim_padding = @theme.header_padding || [0, 0, 0, 0]
2821
+ trim_padding = inflate_padding @theme.header_padding || 0
2713
2822
  trim_bg_color = resolve_theme_color :header_background_color
2714
2823
  trim_border_width = @theme.header_border_width || @theme.base_border_width
2715
2824
  trim_border_style = (@theme.header_border_style || :solid).to_sym
@@ -2720,7 +2829,7 @@ class Converter < ::Prawn::Document
2720
2829
  trim_line_metrics = calc_line_metrics(@theme.footer_line_height || @theme.base_line_height)
2721
2830
  # NOTE height is required atm
2722
2831
  trim_top = trim_height = @theme.footer_height || page_margin_bottom
2723
- trim_padding = @theme.footer_padding || [0, 0, 0, 0]
2832
+ trim_padding = inflate_padding @theme.footer_padding || 0
2724
2833
  trim_bg_color = resolve_theme_color :footer_background_color
2725
2834
  trim_border_width = @theme.footer_border_width || @theme.base_border_width
2726
2835
  trim_border_style = (@theme.footer_border_style || :solid).to_sym
@@ -2804,21 +2913,25 @@ class Converter < ::Prawn::Document
2804
2913
  ColumnPositions.each do |position|
2805
2914
  unless (val = @theme[%(#{periphery}_#{side}_#{position}_content)]).nil_or_empty?
2806
2915
  # TODO support image URL (using resolve_image_path)
2807
- if (val.include? ':') && val =~ ImageAttributeValueRx &&
2808
- ::File.readable?(path = (ThemeLoader.resolve_theme_asset $1, (doc.attr 'pdf-stylesdir')))
2809
- attrs = (AttributeList.new $2).parse
2810
- col_width = colspec_dict[side][position][:width]
2811
- if (fit = attrs['fit']) == 'contain'
2812
- width = col_width
2813
- else
2814
- unless (width = resolve_explicit_width attrs, col_width)
2815
- # QUESTION should we lookup and scale intrinsic width if explicit width is not given?
2816
- # NOTE failure message will be reported later when image is rendered
2817
- width = (to_pt intrinsic_image_dimensions(path)[:width], :px) rescue 0
2916
+ if (val.include? ':') && val =~ ImageAttributeValueRx
2917
+ if ::File.readable?(path = (ThemeLoader.resolve_theme_asset $1, (doc.attr 'pdf-stylesdir')))
2918
+ attrs = (AttributeList.new $2).parse
2919
+ col_width = colspec_dict[side][position][:width]
2920
+ if (fit = attrs['fit']) == 'contain'
2921
+ width = col_width
2922
+ else
2923
+ unless (width = resolve_explicit_width attrs, col_width)
2924
+ # QUESTION should we lookup and scale intrinsic width if explicit width is not given?
2925
+ # NOTE failure message will be reported later when image is rendered
2926
+ width = (to_pt intrinsic_image_dimensions(path)[:width], :px) rescue 0
2927
+ end
2928
+ width = col_width if fit == 'scale-down' && width > col_width
2818
2929
  end
2819
- width = col_width if fit == 'scale-down' && width > col_width
2930
+ side_content[position] = { path: path, width: width, fit: !!fit }
2931
+ else
2932
+ logger.warn %(image to embed not found or not readable: #{path})
2933
+ side_content[position] = val
2820
2934
  end
2821
- side_content[position] = { path: path, width: width, fit: !!fit }
2822
2935
  else
2823
2936
  side_content[position] = val
2824
2937
  end
@@ -2875,7 +2988,8 @@ class Converter < ::Prawn::Document
2875
2988
  repeat((content_start_page..page_count), dynamic: true) do
2876
2989
  # NOTE don't write on pages which are imported / inserts (otherwise we can get a corrupt PDF)
2877
2990
  next if page.imported_page?
2878
- pgnum_label = page_number - skip
2991
+ pgnum_label = page_number - skip_pagenums
2992
+ pgnum_label = (RomanNumeral.new page_number, :lower) if pgnum_label < 1
2879
2993
  side = page_side((folio_basis == :physical ? page_number : pgnum_label), invert_folio)
2880
2994
  # FIXME we need to have a content setting for chapter pages
2881
2995
  content_by_position, colspec_by_position = content_dict[side], colspec_dict[side]
@@ -2943,13 +3057,13 @@ class Converter < ::Prawn::Document
2943
3057
  doc.set_attr 'attribute-missing', 'skip' unless attribute_missing_doc == 'skip'
2944
3058
  if (content = doc.apply_subs content).include? '{'
2945
3059
  # NOTE must use &#123; in place of {, not \{, to escape attribute reference
2946
- content = content.split(LF).delete_if {|line| SimpleAttributeRefRx =~ line } * LF
3060
+ content = content.split(LF).delete_if {|line| SimpleAttributeRefRx.match? line } * LF
2947
3061
  end
2948
3062
  doc.set_attr 'attribute-missing', attribute_missing_doc unless attribute_missing_doc == 'skip'
2949
3063
  end
2950
3064
  theme_font %(#{periphery}_#{side}_#{position}) do
2951
3065
  formatted_text_box parse_text(content, color: @font_color, inline_format: [normalize: true]),
2952
- at: [colspec[:x], trim_content_height + trim_padding[2] + trim_line_metrics.padding_bottom],
3066
+ at: [colspec[:x], trim_top - trim_padding[0] + (trim_valign == :center ? font.descender * 0.5 : 0)],
2953
3067
  width: colspec[:width],
2954
3068
  height: trim_content_height,
2955
3069
  align: colspec[:align],
@@ -3540,6 +3654,8 @@ class Converter < ::Prawn::Document
3540
3654
 
3541
3655
  # QUESTION move to prawn/extensions.rb?
3542
3656
  def init_scratch_prototype
3657
+ @save_state = nil
3658
+ @scratch_depth = 0
3543
3659
  # IMPORTANT don't set font before using Marshal, it causes serialization to fail
3544
3660
  @prototype = ::Marshal.load ::Marshal.dump self
3545
3661
  @prototype.state.store.info.data[:Scratch] = true
@@ -3547,6 +3663,23 @@ class Converter < ::Prawn::Document
3547
3663
  #@prototype.start_new_page if @prototype.page_number == 0
3548
3664
  end
3549
3665
 
3666
+ def push_scratch doc
3667
+ if (@scratch_depth += 1) == 1
3668
+ @save_state = {
3669
+ catalog: {}.tap {|accum| doc.catalog.each {|k, v| accum[k] = v.dup } },
3670
+ attributes: doc.attributes.dup,
3671
+ }
3672
+ end
3673
+ end
3674
+
3675
+ def pop_scratch doc
3676
+ if (@scratch_depth -= 1) == 0
3677
+ doc.catalog.replace @save_state[:catalog]
3678
+ doc.attributes.replace @save_state[:attributes]
3679
+ @save_state = nil
3680
+ end
3681
+ end
3682
+
3550
3683
  =begin
3551
3684
  # TODO could assign pdf-anchor attributes here too
3552
3685
  def assign_missing_section_ids doc